public class

GuidedActionAdapter

extends RecyclerView.Adapter

 java.lang.Object

androidx.recyclerview.widget.RecyclerView.Adapter

↳androidx.leanback.widget.GuidedActionAdapter

Gradle dependencies

compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'

  • groupId: androidx.leanback
  • artifactId: leanback
  • version: 1.2.0-alpha04

Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.leanback:leanback com.android.support:leanback-v17

Androidx class mapping:

androidx.leanback.widget.GuidedActionAdapter android.support.v17.leanback.widget.GuidedActionAdapter

Overview

GuidedActionAdapter instantiates views for guided actions, and manages their interactions. Presentation (view creation and state animation) is delegated to a GuidedActionsStylist, while clients are notified of interactions via GuidedActionAdapter.ClickListener and GuidedActionAdapter.FocusListener.

Summary

Constructors
publicGuidedActionAdapter(java.util.List<GuidedAction> actions, GuidedActionAdapter.ClickListener clickListener, GuidedActionAdapter.FocusListener focusListener, GuidedActionsStylist presenter, boolean isSubAdapter)

Constructs a GuidedActionAdapter with the given list of guided actions, the given click and focus listeners, and the given presenter.

Methods
public GuidedActionsStylist.ViewHolderfindSubChildViewHolder(View v)

public java.util.List<GuidedAction>getActions()

Used for serialization only.

public intgetCount()

Returns the count of actions managed by this adapter.

public GuidedActionsStylistgetGuidedActionsStylist()

public GuidedActiongetItem(int position)

Returns the GuidedAction at the given position in the managed list.

public intgetItemCount()

public intgetItemViewType(int position)

public voidhandleCheckedActions(GuidedActionsStylist.ViewHolder avh)

public intindexOf(GuidedAction action)

Return index of action in array

public voidonBindViewHolder(RecyclerView.ViewHolder holder, int position)

public RecyclerView.ViewHolderonCreateViewHolder(ViewGroup parent, int viewType)

public voidperformOnActionClick(GuidedActionsStylist.ViewHolder avh)

public voidsetActions(java.util.List<GuidedAction> actions)

Sets the list of actions managed by this adapter.

public voidsetClickListener(GuidedActionAdapter.ClickListener clickListener)

Sets the click listener for items managed by this adapter.

public voidsetDiffCallback(DiffCallback<GuidedAction> diffCallback)

Change DiffCallback used in GuidedActionAdapter.setActions(List).

public voidsetFocusListener(GuidedActionAdapter.FocusListener focusListener)

Sets the focus listener for items managed by this adapter.

from RecyclerView.Adapter<VH>bindViewHolder, createViewHolder, findRelativeAdapterPositionIn, getItemId, getStateRestorationPolicy, hasObservers, hasStableIds, notifyDataSetChanged, notifyItemChanged, notifyItemChanged, notifyItemInserted, notifyItemMoved, notifyItemRangeChanged, notifyItemRangeChanged, notifyItemRangeInserted, notifyItemRangeRemoved, notifyItemRemoved, onAttachedToRecyclerView, onBindViewHolder, onBindViewHolder, onDetachedFromRecyclerView, onFailedToRecycleView, onViewAttachedToWindow, onViewDetachedFromWindow, onViewRecycled, registerAdapterDataObserver, setHasStableIds, setStateRestorationPolicy, unregisterAdapterDataObserver
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public GuidedActionAdapter(java.util.List<GuidedAction> actions, GuidedActionAdapter.ClickListener clickListener, GuidedActionAdapter.FocusListener focusListener, GuidedActionsStylist presenter, boolean isSubAdapter)

Constructs a GuidedActionAdapter with the given list of guided actions, the given click and focus listeners, and the given presenter.

Parameters:

actions: The list of guided actions this adapter will manage.
focusListener: The focus listener for items in this adapter.
presenter: The presenter that will manage the display of items in this adapter.

Methods

public void setDiffCallback(DiffCallback<GuidedAction> diffCallback)

Change DiffCallback used in GuidedActionAdapter.setActions(List). Set to null for firing a general RecyclerView.Adapter.notifyDataSetChanged().

Parameters:

diffCallback:

public void setActions(java.util.List<GuidedAction> actions)

Sets the list of actions managed by this adapter. Use GuidedActionAdapter.setDiffCallback(DiffCallback) to change DiffCallback.

Parameters:

actions: The list of actions to be managed.

public int getCount()

Returns the count of actions managed by this adapter.

Returns:

The count of actions managed by this adapter.

public GuidedAction getItem(int position)

Returns the GuidedAction at the given position in the managed list.

Parameters:

position: The position of the desired GuidedAction.

Returns:

The GuidedAction at the given position.

public int indexOf(GuidedAction action)

Return index of action in array

Parameters:

action: Action to search index.

Returns:

Index of Action in array.

public GuidedActionsStylist getGuidedActionsStylist()

Returns:

GuidedActionsStylist used to build the actions list UI.

public void setClickListener(GuidedActionAdapter.ClickListener clickListener)

Sets the click listener for items managed by this adapter.

Parameters:

clickListener: The click listener for this adapter.

public void setFocusListener(GuidedActionAdapter.FocusListener focusListener)

Sets the focus listener for items managed by this adapter.

Parameters:

focusListener: The focus listener for this adapter.

public java.util.List<GuidedAction> getActions()

Used for serialization only.

public int getItemViewType(int position)

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)

public int getItemCount()

public GuidedActionsStylist.ViewHolder findSubChildViewHolder(View v)

public void handleCheckedActions(GuidedActionsStylist.ViewHolder avh)

public void performOnActionClick(GuidedActionsStylist.ViewHolder avh)

Source

/*
 * Copyright (C) 2015 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.leanback.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;

import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

import java.util.ArrayList;
import java.util.List;

/**
 * GuidedActionAdapter instantiates views for guided actions, and manages their interactions.
 * Presentation (view creation and state animation) is delegated to a {@link
 * GuidedActionsStylist}, while clients are notified of interactions via
 * {@link GuidedActionAdapter.ClickListener} and {@link GuidedActionAdapter.FocusListener}.
 */
@RestrictTo(LIBRARY_GROUP_PREFIX)
public class GuidedActionAdapter extends RecyclerView.Adapter {
    static final String TAG = "GuidedActionAdapter";
    static final boolean DEBUG = false;

    static final String TAG_EDIT = "EditableAction";
    static final boolean DEBUG_EDIT = false;

    /**
     * Object listening for click events within a {@link GuidedActionAdapter}.
     */
    public interface ClickListener {

        /**
         * Called when the user clicks on an action.
         */
        void onGuidedActionClicked(GuidedAction action);

    }

    /**
     * Object listening for focus events within a {@link GuidedActionAdapter}.
     */
    public interface FocusListener {

        /**
         * Called when the user focuses on an action.
         */
        void onGuidedActionFocused(@NonNull GuidedAction action);
    }

    /**
     * Object listening for edit events within a {@link GuidedActionAdapter}.
     */
    public interface EditListener {

        /**
         * Called when the user exits edit mode on an action.
         */
        void onGuidedActionEditCanceled(@NonNull GuidedAction action);

        /**
         * Called when the user exits edit mode on an action and process confirm button in IME.
         */
        long onGuidedActionEditedAndProceed(@NonNull GuidedAction action);

        /**
         * Called when Ime Open
         */
        void onImeOpen();

        /**
         * Called when Ime Close
         */
        void onImeClose();
    }

    final RecyclerView mRecyclerView;
    private final boolean mIsSubAdapter;
    private final ActionOnKeyListener mActionOnKeyListener;
    private final ActionOnFocusListener mActionOnFocusListener;
    private final ActionEditListener mActionEditListener;
    private final ActionAutofillListener mActionAutofillListener;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final List<GuidedAction> mActions;
    private ClickListener mClickListener;
    final GuidedActionsStylist mStylist;
    GuidedActionAdapterGroup mGroup;
    DiffCallback<GuidedAction> mDiffCallback;

    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (v != null && v.getWindowToken() != null && mRecyclerView.isAttachedToWindow()) {
                GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
                        mRecyclerView.getChildViewHolder(v);
                GuidedAction action = avh.getAction();
                if (action.hasTextEditable()) {
                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
                    mGroup.openIme(GuidedActionAdapter.this, avh);
                } else if (action.hasEditableActivatorView()) {
                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "toggle editing mode by click");
                    performOnActionClick(avh);
                } else {
                    handleCheckedActions(avh);
                    if (action.isEnabled() && !action.infoOnly()) {
                        performOnActionClick(avh);
                    }
                }
            }
        }
    };

    /**
     * Constructs a GuidedActionAdapter with the given list of guided actions, the given click and
     * focus listeners, and the given presenter.
     * @param actions The list of guided actions this adapter will manage.
     * @param focusListener The focus listener for items in this adapter.
     * @param presenter The presenter that will manage the display of items in this adapter.
     */
    public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
            FocusListener focusListener, GuidedActionsStylist presenter, boolean isSubAdapter) {
        super();
        mActions = actions == null ? new ArrayList<GuidedAction>() :
                new ArrayList<GuidedAction>(actions);
        mClickListener = clickListener;
        mStylist = presenter;
        mActionOnKeyListener = new ActionOnKeyListener();
        mActionOnFocusListener = new ActionOnFocusListener(focusListener);
        mActionEditListener = new ActionEditListener();
        mActionAutofillListener = new ActionAutofillListener();
        mIsSubAdapter = isSubAdapter;
        if (!isSubAdapter) {
            mDiffCallback = GuidedActionDiffCallback.getInstance();
        }
        mRecyclerView = isSubAdapter ? mStylist.getSubActionsGridView() :
                mStylist.getActionsGridView();
    }

    /**
     * Change DiffCallback used in {@link #setActions(List)}. Set to null for firing a
     * general {@link #notifyDataSetChanged()}.
     *
     * @param diffCallback
     */
    public void setDiffCallback(DiffCallback<GuidedAction> diffCallback) {
        mDiffCallback = diffCallback;
    }

    /**
     * Sets the list of actions managed by this adapter. Use {@link #setDiffCallback(DiffCallback)}
     * to change DiffCallback.
     * @param actions The list of actions to be managed.
     */
    public void setActions(final List<GuidedAction> actions) {
        if (!mIsSubAdapter) {
            mStylist.collapseAction(false);
        }
        mActionOnFocusListener.unFocus();
        if (mDiffCallback != null) {
            // temporary variable used for DiffCallback
            final List<GuidedAction> oldActions = new ArrayList<>();
            oldActions.addAll(mActions);

            // update items.
            mActions.clear();
            mActions.addAll(actions);

            DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                @Override
                public int getOldListSize() {
                    return oldActions.size();
                }

                @Override
                public int getNewListSize() {
                    return mActions.size();
                }

                @Override
                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                    return mDiffCallback.areItemsTheSame(oldActions.get(oldItemPosition),
                            mActions.get(newItemPosition));
                }

                @Override
                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                    return mDiffCallback.areContentsTheSame(oldActions.get(oldItemPosition),
                            mActions.get(newItemPosition));
                }

                @Nullable
                @Override
                public Object getChangePayload(int oldItemPosition, int newItemPosition) {
                    return mDiffCallback.getChangePayload(oldActions.get(oldItemPosition),
                            mActions.get(newItemPosition));
                }
            });

            // dispatch diff result
            diffResult.dispatchUpdatesTo(this);
        } else {
            mActions.clear();
            mActions.addAll(actions);
            notifyDataSetChanged();
        }
    }

    /**
     * Returns the count of actions managed by this adapter.
     * @return The count of actions managed by this adapter.
     */
    public int getCount() {
        return mActions.size();
    }

    /**
     * Returns the GuidedAction at the given position in the managed list.
     * @param position The position of the desired GuidedAction.
     * @return The GuidedAction at the given position.
     */
    public GuidedAction getItem(int position) {
        return mActions.get(position);
    }

    /**
     * Return index of action in array
     * @param action Action to search index.
     * @return Index of Action in array.
     */
    public int indexOf(GuidedAction action) {
        return mActions.indexOf(action);
    }

    /**
     * @return GuidedActionsStylist used to build the actions list UI.
     */
    public GuidedActionsStylist getGuidedActionsStylist() {
        return mStylist;
    }

    /**
     * Sets the click listener for items managed by this adapter.
     * @param clickListener The click listener for this adapter.
     */
    public void setClickListener(ClickListener clickListener) {
        mClickListener = clickListener;
    }

    /**
     * Sets the focus listener for items managed by this adapter.
     * @param focusListener The focus listener for this adapter.
     */
    public void setFocusListener(FocusListener focusListener) {
        mActionOnFocusListener.setFocusListener(focusListener);
    }

    /**
     * Used for serialization only.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public List<GuidedAction> getActions() {
        return new ArrayList<GuidedAction>(mActions);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getItemViewType(int position) {
        return mStylist.getItemViewType(mActions.get(position));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        GuidedActionsStylist.ViewHolder vh = mStylist.onCreateViewHolder(parent, viewType);
        View v = vh.itemView;
        v.setOnKeyListener(mActionOnKeyListener);
        v.setOnClickListener(mOnClickListener);
        v.setOnFocusChangeListener(mActionOnFocusListener);

        setupListeners(vh.getEditableTitleView());
        setupListeners(vh.getEditableDescriptionView());

        return vh;
    }

    private void setupListeners(EditText edit) {
        if (edit != null) {
            edit.setPrivateImeOptions("escapeNorth");
            edit.setOnEditorActionListener(mActionEditListener);
            if (edit instanceof ImeKeyMonitor) {
                ImeKeyMonitor monitor = (ImeKeyMonitor)edit;
                monitor.setImeKeyListener(mActionEditListener);
            }
            if (edit instanceof GuidedActionAutofillSupport) {
                ((GuidedActionAutofillSupport) edit).setOnAutofillListener(mActionAutofillListener);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        if (position >= mActions.size()) {
            return;
        }
        final GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)holder;
        GuidedAction action = mActions.get(position);
        mStylist.onBindViewHolder(avh, action);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getItemCount() {
        return mActions.size();
    }

    private class ActionOnFocusListener implements View.OnFocusChangeListener {

        private FocusListener mFocusListener;
        private View mSelectedView;

        ActionOnFocusListener(FocusListener focusListener) {
            mFocusListener = focusListener;
        }

        public void setFocusListener(FocusListener focusListener) {
            mFocusListener = focusListener;
        }

        public void unFocus() {
            if (mSelectedView != null && mRecyclerView.isAttachedToWindow()) {
                ViewHolder vh = mRecyclerView.getChildViewHolder(mSelectedView);
                if (vh != null) {
                    GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)vh;
                    mStylist.onAnimateItemFocused(avh, false);
                } else {
                    Log.w(TAG, "RecyclerView returned null view holder",
                            new Throwable());
                }
            }
        }

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (!mRecyclerView.isAttachedToWindow()) {
                return;
            }
            GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
                    mRecyclerView.getChildViewHolder(v);
            if (hasFocus) {
                mSelectedView = v;
                if (mFocusListener != null) {
                    // We still call onGuidedActionFocused so that listeners can clear
                    // state if they want.
                    mFocusListener.onGuidedActionFocused(avh.getAction());
                }
            } else {
                if (mSelectedView == v) {
                    mStylist.onAnimateItemPressedCancelled(avh);
                    mSelectedView = null;
                }
            }
            mStylist.onAnimateItemFocused(avh, hasFocus);
        }
    }

    public GuidedActionsStylist.ViewHolder findSubChildViewHolder(View v) {
        if (!mRecyclerView.isAttachedToWindow()) {
            return null;
        }
        GuidedActionsStylist.ViewHolder result = null;
        ViewParent parent = v.getParent();
        while (parent != mRecyclerView && parent != null && v != null) {
            v = (View)parent;
            parent = parent.getParent();
        }
        if (parent != null && v != null) {
            result = (GuidedActionsStylist.ViewHolder) mRecyclerView.getChildViewHolder(v);
        }
        return result;
    }

    public void handleCheckedActions(GuidedActionsStylist.ViewHolder avh) {
        GuidedAction action = avh.getAction();
        int actionCheckSetId = action.getCheckSetId();
        if (mRecyclerView.isAttachedToWindow() && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
            // Find any actions that are checked and are in the same group
            // as the selected action. Fade their checkmarks out.
            if (actionCheckSetId != GuidedAction.CHECKBOX_CHECK_SET_ID) {
                for (int i = 0, size = mActions.size(); i < size; i++) {
                    GuidedAction a = mActions.get(i);
                    if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
                        a.setChecked(false);
                        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder)
                                mRecyclerView.findViewHolderForPosition(i);
                        if (vh != null) {
                            mStylist.onAnimateItemChecked(vh, false);
                        }
                    }
                }
            }

            // If we we'ren't already checked, fade our checkmark in.
            if (!action.isChecked()) {
                action.setChecked(true);
                mStylist.onAnimateItemChecked(avh, true);
            } else {
                if (actionCheckSetId == GuidedAction.CHECKBOX_CHECK_SET_ID) {
                    action.setChecked(false);
                    mStylist.onAnimateItemChecked(avh, false);
                }
            }
        }
    }

    public void performOnActionClick(GuidedActionsStylist.ViewHolder avh) {
        if (mClickListener != null) {
            mClickListener.onGuidedActionClicked(avh.getAction());
        }
    }

    private class ActionOnKeyListener implements View.OnKeyListener {

        private boolean mKeyPressed = false;

        ActionOnKeyListener() {
        }

        /**
         * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
         */
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (v == null || event == null || !mRecyclerView.isAttachedToWindow()) {
                return false;
            }
            boolean handled = false;
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_CENTER:
                case KeyEvent.KEYCODE_NUMPAD_ENTER:
                case KeyEvent.KEYCODE_BUTTON_X:
                case KeyEvent.KEYCODE_BUTTON_Y:
                case KeyEvent.KEYCODE_ENTER:

                    GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
                            mRecyclerView.getChildViewHolder(v);
                    GuidedAction action = avh.getAction();

                    if (!action.isEnabled() || action.infoOnly()) {
                        if (event.getAction() == KeyEvent.ACTION_DOWN) {
                            // TODO: requires API 19
                            //playSound(v, AudioManager.FX_KEYPRESS_INVALID);
                        }
                        return true;
                    }

                    switch (event.getAction()) {
                        case KeyEvent.ACTION_DOWN:
                            if (DEBUG) {
                                Log.d(TAG, "Enter Key down");
                            }
                            if (!mKeyPressed) {
                                mKeyPressed = true;
                                mStylist.onAnimateItemPressed(avh, mKeyPressed);
                            }
                            break;
                        case KeyEvent.ACTION_UP:
                            if (DEBUG) {
                                Log.d(TAG, "Enter Key up");
                            }
                            // Sometimes we are losing ACTION_DOWN for the first ENTER after pressed
                            // Escape in IME.
                            if (mKeyPressed) {
                                mKeyPressed = false;
                                mStylist.onAnimateItemPressed(avh, mKeyPressed);
                            }
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
            return handled;
        }

    }

    private class ActionEditListener implements OnEditorActionListener,
            ImeKeyMonitor.ImeKeyListener {

        ActionEditListener() {
        }

        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME action: " + actionId);
            boolean handled = false;
            if (actionId == EditorInfo.IME_ACTION_NEXT
                    || actionId == EditorInfo.IME_ACTION_DONE) {
                mGroup.fillAndGoNext(GuidedActionAdapter.this, v);
                handled = true;
            } else if (actionId == EditorInfo.IME_ACTION_NONE) {
                if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
                // Escape north handling: stay on current item, but close editor
                handled = true;
                mGroup.fillAndStay(GuidedActionAdapter.this, v);
            }
            return handled;
        }

        @Override
        public boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event) {
            if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME key: " + keyCode);
            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
                mGroup.fillAndStay(GuidedActionAdapter.this, editText);
                return true;
            } else if (keyCode == KeyEvent.KEYCODE_ENTER
                    && event.getAction() == KeyEvent.ACTION_UP) {
                mGroup.fillAndGoNext(GuidedActionAdapter.this, editText);
                return true;
            }
            return false;
        }
    }

    private class ActionAutofillListener implements GuidedActionAutofillSupport.OnAutofillListener {
        ActionAutofillListener() {
        }

        @Override
        public void onAutofill(View view) {
            mGroup.fillAndGoNext(GuidedActionAdapter.this, (EditText) view);
        }
    }
}