public class

WearableActionDrawerView

extends WearableDrawerView

 java.lang.Object

↳FrameLayout

androidx.wear.widget.drawer.WearableDrawerView

↳androidx.wear.widget.drawer.WearableActionDrawerView

Gradle dependencies

compile group: 'androidx.wear', name: 'wear', version: '1.3.0-alpha02'

  • groupId: androidx.wear
  • artifactId: wear
  • version: 1.3.0-alpha02

Artifact androidx.wear:wear:1.3.0-alpha02 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.wear:wear com.android.support:wear

Androidx class mapping:

androidx.wear.widget.drawer.WearableActionDrawerView android.support.wear.widget.drawer.WearableActionDrawerView

Overview

Ease of use class for creating a Wearable action drawer. This can be used with WearableDrawerLayout to create a drawer for users to easily pull up contextual actions. These contextual actions may be specified by using a , which may be populated by either:

  • Specifying the app:actionMenu attribute in the XML layout file. Example:
     <androidx.wear.widget.drawer.WearableActionDrawerView
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width=”match_parent”
         android:layout_height=”match_parent”
         app:actionMenu="@menu/action_drawer" />
  • Getting the menu with WearableActionDrawerView.getMenu(), and then inflating it with . Example:
     Menu menu = actionDrawer.getMenu();
     getMenuInflater().inflate(R.menu.action_drawer, menu);

The full and MenuItem APIs are not implemented. The following methods are guaranteed to work:

For , the add methods, , , , , and are implemented.

For MenuItem, setting and getting the title and icon, MenuItem, and MenuItem are implemented.

Summary

Fields
from WearableDrawerViewSTATE_DRAGGING, STATE_IDLE, STATE_SETTLING
Constructors
publicWearableActionDrawerView(Context context)

publicWearableActionDrawerView(Context context, AttributeSet attrs)

publicWearableActionDrawerView(Context context, AttributeSet attrs, int defStyleAttr)

publicWearableActionDrawerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Methods
public booleancanScrollHorizontally(int direction)

public MenugetMenu()

Returns the Menu object that this WearableActionDrawer represents.

public voidonDrawerOpened()

Called when the drawer has settled in a completely open state.

public voidonPeekContainerClicked(View v)

Called when anything within the peek container is clicked.

public voidsetOnMenuItemClickListener(OnMenuItemClickListener listener)

Set a for this action drawer.

public voidsetTitle(java.lang.CharSequence title)

Sets the title for this action drawer.

from WearableDrawerViewaddView, getController, getDrawerContent, getDrawerState, isAutoPeekEnabled, isClosed, isLocked, isLockedWhenClosed, isOpened, isOpenOnlyAtTopEnabled, isPeeking, isPeekOnScrollDownEnabled, onAttachedToWindow, onDrawerClosed, onDrawerStateChanged, onFinishInflate, setDrawerContent, setIsAutoPeekEnabled, setIsLocked, setLockedWhenClosed, setOpenOnlyAtTopEnabled, setPeekContent, setPeekOnScrollDownEnabled
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public WearableActionDrawerView(Context context)

public WearableActionDrawerView(Context context, AttributeSet attrs)

public WearableActionDrawerView(Context context, AttributeSet attrs, int defStyleAttr)

public WearableActionDrawerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Methods

public void onDrawerOpened()

Called when the drawer has settled in a completely open state. The drawer is interactive at this point. This is analogous to WearableDrawerLayout.DrawerStateCallback.onDrawerOpened(WearableDrawerLayout, WearableDrawerView).

public boolean canScrollHorizontally(int direction)

public void onPeekContainerClicked(View v)

Called when anything within the peek container is clicked. However, if a custom peek view is supplied and it handles the click, then this may not be called. The default behavior is to open the drawer.

public void setOnMenuItemClickListener(OnMenuItemClickListener listener)

Set a for this action drawer.

public void setTitle(java.lang.CharSequence title)

Sets the title for this action drawer. If title is null, then the title will be removed.

public Menu getMenu()

Returns the Menu object that this WearableActionDrawer represents.

Applications should use this method to obtain the WearableActionDrawers's Menu object and inflate or add content to it as necessary.

Returns:

the Menu presented by this view

Source

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.wear.widget.drawer;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.wear.R;
import androidx.wear.internal.widget.ResourcesUtil;
import androidx.wear.widget.drawer.WearableActionDrawerMenu.WearableActionDrawerMenuItem;

/**
 * Ease of use class for creating a Wearable action drawer. This can be used with {@link
 * WearableDrawerLayout} to create a drawer for users to easily pull up contextual actions. These
 * contextual actions may be specified by using a {@link Menu}, which may be populated by either:
 *
 * <ul> <li>Specifying the {@code app:actionMenu} attribute in the XML layout file. Example:
 * <pre>
 * &lt;androidx.wear.widget.drawer.WearableActionDrawerView
 *     xmlns:app="http://schemas.android.com/apk/res-auto"
 *     android:layout_width=”match_parent”
 *     android:layout_height=”match_parent”
 *     app:actionMenu="@menu/action_drawer" /&gt;</pre>
 *
 * <li>Getting the menu with {@link #getMenu}, and then inflating it with {@link
 * MenuInflater#inflate}. Example:
 * <pre>
 * Menu menu = actionDrawer.getMenu();
 * getMenuInflater().inflate(R.menu.action_drawer, menu);</pre>
 *
 * </ul>
 *
 * <p><b>The full {@link Menu} and {@link MenuItem} APIs are not implemented.</b> The following
 * methods are guaranteed to work:
 *
 * <p>For {@link Menu}, the add methods, {@link Menu#clear}, {@link Menu#removeItem}, {@link
 * Menu#findItem}, {@link Menu#size}, and {@link Menu#getItem} are implemented.
 *
 * <p>For {@link MenuItem}, setting and getting the title and icon, {@link MenuItem#getItemId}, and
 * {@link MenuItem#setOnMenuItemClickListener} are implemented.
 */
public class WearableActionDrawerView extends WearableDrawerView {

    private static final String TAG = "WearableActionDrawer";

    final RecyclerView mActionList;
    final int mTopPadding;
    final int mBottomPadding;
    final int mLeftPadding;
    final int mRightPadding;
    final int mFirstItemTopPadding;
    final int mLastItemBottomPadding;
    final int mIconRightMargin;
    private final boolean mShowOverflowInPeek;
    @Nullable private final ImageView mPeekActionIcon;
    @Nullable private final ImageView mPeekExpandIcon;
    final RecyclerView.Adapter<RecyclerView.ViewHolder> mActionListAdapter;
    private OnMenuItemClickListener mOnMenuItemClickListener;
    private Menu mMenu;
    @Nullable CharSequence mTitle;

    public WearableActionDrawerView(Context context) {
        this(context, null);
    }

    public WearableActionDrawerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WearableActionDrawerView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public WearableActionDrawerView(
            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        setLockedWhenClosed(true);

        boolean showOverflowInPeek = false;
        int menuRes = 0;
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(
                    attrs, R.styleable.WearableActionDrawerView, defStyleAttr, 0 /* defStyleRes */);

            ViewCompat.saveAttributeDataForStyleable(
                    this, context, R.styleable.WearableActionDrawerView, attrs, typedArray,
                        defStyleAttr, 0);

            try {
                mTitle = typedArray.getString(R.styleable.WearableActionDrawerView_drawerTitle);
                showOverflowInPeek = typedArray.getBoolean(
                        R.styleable.WearableActionDrawerView_showOverflowInPeek, false);
                menuRes = typedArray
                        .getResourceId(R.styleable.WearableActionDrawerView_actionMenu, 0);
            } finally {
                typedArray.recycle();
            }
        }

        AccessibilityManager accessibilityManager =
                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
        mShowOverflowInPeek = showOverflowInPeek || accessibilityManager.isEnabled();

        if (!mShowOverflowInPeek) {
            LayoutInflater layoutInflater = LayoutInflater.from(context);
            View peekView = layoutInflater.inflate(R.layout.ws_action_drawer_peek_view,
                    getPeekContainer(), false /* attachToRoot */);
            setPeekContent(peekView);
            mPeekActionIcon = peekView.findViewById(R.id.ws_action_drawer_peek_action_icon);
            mPeekExpandIcon = peekView.findViewById(R.id.ws_action_drawer_expand_icon);
        } else {
            mPeekActionIcon = null;
            mPeekExpandIcon = null;
            getPeekContainer().setContentDescription(
                    context.getString(R.string.ws_action_drawer_content_description));
        }

        if (menuRes != 0) {
            // This must occur after initializing mPeekActionIcon, otherwise updatePeekIcons will
            // exit early.
            MenuInflater inflater = new MenuInflater(context);
            inflater.inflate(menuRes, getMenu());
        }

        int screenWidthPx = ResourcesUtil.getScreenWidthPx(context);
        int screenHeightPx = ResourcesUtil.getScreenHeightPx(context);

        Resources res = getResources();
        mTopPadding = res.getDimensionPixelOffset(R.dimen.ws_action_drawer_item_top_padding);
        mBottomPadding = res.getDimensionPixelOffset(R.dimen.ws_action_drawer_item_bottom_padding);
        mLeftPadding =
                ResourcesUtil.getFractionOfScreenPx(
                        context, screenWidthPx, R.fraction.ws_action_drawer_item_left_padding);
        mRightPadding =
                ResourcesUtil.getFractionOfScreenPx(
                        context, screenWidthPx, R.fraction.ws_action_drawer_item_right_padding);

        mFirstItemTopPadding =
                ResourcesUtil.getFractionOfScreenPx(
                        context, screenHeightPx,
                        R.fraction.ws_action_drawer_item_first_item_top_padding);
        mLastItemBottomPadding =
                ResourcesUtil.getFractionOfScreenPx(
                        context, screenHeightPx,
                        R.fraction.ws_action_drawer_item_last_item_bottom_padding);

        mIconRightMargin = res
                .getDimensionPixelOffset(R.dimen.ws_action_drawer_item_icon_right_margin);

        mActionList = new RecyclerView(context);
        mActionList.setId(R.id.action_list);
        mActionList.setLayoutManager(new LinearLayoutManager(context));
        mActionListAdapter = new ActionListAdapter(getMenu());
        // Do not bind the mActionListAdapter to the action list here. We will bind it when/if the
        // drawer is first opened to avoid the inflation cost in the case that the drawer is never
        // used
        setDrawerContent(mActionList);
    }

    @Override
    public void onDrawerOpened() {
        setContentIfFirstCall();
        if (mActionListAdapter.getItemCount() > 0) {
            RecyclerView.ViewHolder holder = mActionList.findViewHolderForAdapterPosition(0);
            if (holder != null && holder.itemView != null) {
                holder.itemView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
            }
        }
    }

    private void setContentIfFirstCall() {
        if (mActionList.getAdapter() == null) {
            mActionList.setAdapter(mActionListAdapter);
        }
    }

    @Override
    public boolean canScrollHorizontally(int direction) {
        // Prevent the window from being swiped closed while it is open by saying that it can scroll
        // horizontally.
        return isOpened();
    }

    @Override
    public void onPeekContainerClicked(View v) {
        if (mShowOverflowInPeek) {
            super.onPeekContainerClicked(v);
        } else {
            onMenuItemClicked(0);
        }
    }

    @Override
  /* package */ int preferGravity() {
        return Gravity.BOTTOM;
    }

    /**
     * Set a {@link OnMenuItemClickListener} for this action drawer.
     */
    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
        mOnMenuItemClickListener = listener;
    }

    /**
     * Sets the title for this action drawer. If {@code title} is {@code null}, then the title will
     * be removed.
     */
    public void setTitle(@Nullable CharSequence title) {
        if (TextUtils.equals(title, mTitle)) {
            return;
        }

        CharSequence oldTitle = mTitle;
        mTitle = title;
        if (oldTitle == null) {
            mActionListAdapter.notifyItemInserted(0);
        } else if (title == null) {
            mActionListAdapter.notifyItemRemoved(0);
        } else {
            mActionListAdapter.notifyItemChanged(0);
        }
    }

    boolean hasTitle() {
        return mTitle != null;
    }

    void onMenuItemClicked(int position) {
        if (position >= 0 && position < getMenu().size()) { // Sanity check.
            WearableActionDrawerMenuItem menuItem =
                    (WearableActionDrawerMenuItem) getMenu().getItem(position);
            if (menuItem.invoke()) {
                return;
            }

            if (mOnMenuItemClickListener != null) {
                mOnMenuItemClickListener.onMenuItemClick(menuItem);
            }
        }
    }

    void updatePeekIcons() {
        if (mPeekActionIcon == null || mPeekExpandIcon == null) {
            return;
        }

        Menu menu = getMenu();
        int numberOfActions = menu.size();

        // Only show drawer content (and allow it to be opened) when there's more than one action.
        if (numberOfActions > 1) {
            setDrawerContent(mActionList);
            mPeekExpandIcon.setVisibility(VISIBLE);
        } else {
            setDrawerContent(null);
            mPeekExpandIcon.setVisibility(GONE);
        }

        if (numberOfActions >= 1) {
            Drawable firstActionDrawable = menu.getItem(0).getIcon();
            // Because the ImageView will tint the Drawable white, attempt to get a mutable copy of
            // it. If a copy isn't made, the icon will be white in the expanded state, rendering it
            // invisible.
            if (firstActionDrawable != null) {
                firstActionDrawable = firstActionDrawable.getConstantState().newDrawable().mutate();
                firstActionDrawable.clearColorFilter();
            }

            mPeekActionIcon.setImageDrawable(firstActionDrawable);
            mPeekActionIcon.setContentDescription(menu.getItem(0).getTitle());
        }
    }

    /**
     * Returns the Menu object that this WearableActionDrawer represents.
     *
     * <p>Applications should use this method to obtain the WearableActionDrawers's Menu object and
     * inflate or add content to it as necessary.
     *
     * @return the Menu presented by this view
     */
    public Menu getMenu() {
        if (mMenu == null) {
            mMenu = new WearableActionDrawerMenu(
                    getContext(),
                    new WearableActionDrawerMenu.WearableActionDrawerMenuListener() {
                        @Override
                        public void menuItemChanged(int position) {
                            if (mActionListAdapter != null) {
                                int listPosition = hasTitle() ? position + 1 : position;
                                mActionListAdapter.notifyItemChanged(listPosition);
                            }
                            if (position == 0) {
                                updatePeekIcons();
                            }
                        }

                        @Override
                        public void menuItemAdded(int position) {
                            if (mActionListAdapter != null) {
                                int listPosition = hasTitle() ? position + 1 : position;
                                mActionListAdapter.notifyItemInserted(listPosition);
                            }
                            // Handle transitioning from 0->1 items (set peek icon) and
                            // 1->2 (switch to ellipsis.)
                            if (position <= 1) {
                                updatePeekIcons();
                            }
                        }

                        @Override
                        public void menuItemRemoved(int position) {
                            if (mActionListAdapter != null) {
                                int listPosition = hasTitle() ? position + 1 : position;
                                mActionListAdapter.notifyItemRemoved(listPosition);
                            }
                            // Handle transitioning from 2->1 items (remove ellipsis), and
                            // also the removal of item 1, which could cause the peek icon
                            // to change.
                            if (position <= 1) {
                                updatePeekIcons();
                            }
                        }

                        @Override
                        public void menuChanged() {
                            if (mActionListAdapter != null) {
                                mActionListAdapter.notifyDataSetChanged();
                            }
                            updatePeekIcons();
                        }
                    });
        }

        return mMenu;
    }

    private static final class TitleViewHolder extends RecyclerView.ViewHolder {

        public final View view;
        public final TextView textView;

        TitleViewHolder(View view) {
            super(view);
            this.view = view;
            textView = (TextView) view.findViewById(R.id.ws_action_drawer_title);
        }
    }

    private final class ActionListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        public static final int TYPE_ACTION = 0;
        public static final int TYPE_TITLE = 1;
        private final Menu mActionMenu;
        private final View.OnClickListener mItemClickListener =
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        int childPos =
                                mActionList.getChildAdapterPosition(v) - (hasTitle() ? 1 : 0);
                        if (childPos == RecyclerView.NO_POSITION) {
                            Log.w(TAG, "invalid child position");
                            return;
                        }
                        onMenuItemClicked(childPos);
                    }
                };

        ActionListAdapter(Menu menu) {
            mActionMenu = getMenu();
        }

        @Override
        public int getItemCount() {
            return mActionMenu.size() + (hasTitle() ? 1 : 0);
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
            int titleAwarePosition = hasTitle() ? position - 1 : position;
            if (viewHolder instanceof ActionItemViewHolder) {
                ActionItemViewHolder holder = (ActionItemViewHolder) viewHolder;
                holder.view.setPadding(
                        mLeftPadding,
                        position == 0 ? mFirstItemTopPadding : mTopPadding,
                        mRightPadding,
                        position == getItemCount() - 1 ? mLastItemBottomPadding : mBottomPadding);

                Drawable icon = mActionMenu.getItem(titleAwarePosition).getIcon();
                if (icon != null) {
                    icon = icon.getConstantState().newDrawable().mutate();
                }
                CharSequence title = mActionMenu.getItem(titleAwarePosition).getTitle();
                holder.textView.setText(title);
                holder.textView.setContentDescription(title);
                holder.iconView.setImageDrawable(icon);
            } else if (viewHolder instanceof TitleViewHolder) {
                TitleViewHolder holder = (TitleViewHolder) viewHolder;
                holder.textView.setPadding(0, mFirstItemTopPadding, 0, mBottomPadding);
                holder.textView.setText(mTitle);
            }
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            switch (viewType) {
                case TYPE_TITLE:
                    View titleView =
                            LayoutInflater.from(parent.getContext())
                                    .inflate(R.layout.ws_action_drawer_title_view, parent, false);
                    return new TitleViewHolder(titleView);

                case TYPE_ACTION:
                default:
                    View actionView =
                            LayoutInflater.from(parent.getContext())
                                    .inflate(R.layout.ws_action_drawer_item_view, parent, false);
                    actionView.setOnClickListener(mItemClickListener);
                    return new ActionItemViewHolder(actionView);
            }
        }

        @Override
        public int getItemViewType(int position) {
            return hasTitle() && position == 0 ? TYPE_TITLE : TYPE_ACTION;
        }
    }

    private final class ActionItemViewHolder extends RecyclerView.ViewHolder {

        public final View view;
        public final ImageView iconView;
        public final TextView textView;

        ActionItemViewHolder(View view) {
            super(view);
            this.view = view;
            iconView = (ImageView) view.findViewById(R.id.ws_action_drawer_item_icon);
            ((LinearLayout.LayoutParams) iconView.getLayoutParams()).setMarginEnd(mIconRightMargin);
            textView = (TextView) view.findViewById(R.id.ws_action_drawer_item_text);
        }
    }
}