Recycleview在电视端聚焦问题

需求

项目需求:在电视端加载视频列表并播放视频,用遥控器操作每一个menu和子项目的ietm。

问题

    1. recycleview如何在电视端聚焦
    1. recycleview聚焦混乱
    1. 第一个recycleview(菜单栏menu)切换到第二个recycleview(子项Item)
      TV开发和手机开发有个不同的就是焦点问题。
      在手机端,手指只要一划就可以到后面了,而在TV端,需要用遥控器左右键控制焦点移动。

RecyclerView(简称RV)在TV端的应用可以借鉴的案例不多,相比于ListView,RV的很多功能都需要自己实现。诸如,wrap_content自适应问题,选中的背景问题,setSelection()方法等。

menu列表

本项目中的menu采用重写recycleview:
主要解决Recycleview切换到子项item时,返回记住当前选择的menu。如图所示:


左侧切换到子项item时,记住当前的menu
  • TvRecyclerView属性介绍 :
        <attr name="scrollMode"/>
        <attr name="focusDrawable" format="reference" />
        <attr name="isAutoProcessFocus" format="boolean" />
        <attr name="focusScale" format="float" />

(1) scrollMode控制TvRecyclerView的滑动模式, TvRecyclerView的滑动模式有两种:

  • ① normalScroll: 默认情况下是这种模式, 当TvRecyclerView是这种模式时, 当焦点滑动到的view是没有全部显示出来的, TvRecyclerView将会向按键的方向滑动屏幕一半的距离.
  • ② followScroll: 当TvRecyclerView是这种模式时, 当焦点滑动到view在屏幕当中就一直滑动, 效果与android Tv上的HorizontalGridView差不多.

(2) focusDrawable 设置选择的Drawable, 如图一的白色选择框, 默认是没有设置, 想要这种效果需要设置此属性或在代码中设置.

(3) isAutoProcessFocus 控制焦点处理是由谁来做, 默认焦点由TvRecyclerView来处理.
当isAutoProcessFocus 为false, 子view是可以获得焦点的, 当isAutoProcessFocus为true, 子view获取
不到焦点, 焦点由TvRecyclerView来处理.

(4)focusScale 设置选中view时, view的放大系数. 大于1.0f才生效.

主要代码如下:

public class TvRecyclerView extends RecyclerView {

    public static final String TAG = "TvRecyclerView";
    private static final float DEFAULT_SELECT_SCALE = 1.04f;

    private static final int SCROLL_NORMAL = 0;
    private static final int SCROLL_FOLLOW = 1;

    private FocusBorderView mFocusBorderView;

    private Drawable mDrawableFocus;
    public boolean mIsDrawFocusMoveAnim;
    private float mSelectedScaleValue;
    private float mFocusMoveAnimScale;

    private int mSelectedPosition;
    private View mNextFocused;
    private boolean mInLayout;

    private int mFocusFrameLeft;
    private int mFocusFrameTop;
    private int mFocusFrameRight;
    private int mFocusFrameBottom;

    private boolean mReceivedInvokeKeyDown;
    protected View mSelectedItem;
    private OnItemStateListener mItemStateListener;
    private Scroller mScrollerFocusMoveAnim;
    private boolean mIsFollowScroll;

    private int mScreenWidth;
    private int mScreenHeight;
    private boolean mIsAutoProcessFocus;
    private int mOrientation;
    private boolean mIsSetItemSelected = false;
    private boolean mIsNeedMoveForSelect = false;

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

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

    public TvRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
        setAttributeSet(attrs);
        // 解决问题: 当需要选择的item没有显示在屏幕上, 需要滑动让item显示出来.
        // 这时需要调整item的位置, 并且item获取焦点
        addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if (mIsNeedMoveForSelect) {
                    mIsNeedMoveForSelect = false;

                    int firstVisiblePos = getFirstVisiblePosition();
                    View selectView = getChildAt(mSelectedPosition - firstVisiblePos);
                    if (selectView != null) {
                        mSelectedItem = selectView;
                        adjustSelectOffset(selectView);
                    }
                }
            }
        });
    }

    private void init() {
        mScrollerFocusMoveAnim = new Scroller(getContext());
        mIsDrawFocusMoveAnim = false;
        mReceivedInvokeKeyDown = false;
        mSelectedPosition = 0;
        mNextFocused = null;
        mInLayout = false;
        mIsFollowScroll = false;
        mSelectedScaleValue = DEFAULT_SELECT_SCALE;
        mIsAutoProcessFocus = true;

        mFocusFrameLeft = 22;
        mFocusFrameTop = 22;
        mFocusFrameRight = 22;
        mFocusFrameBottom = 22;
        mOrientation = HORIZONTAL;
        mScreenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
        mScreenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
    }

    private void setAttributeSet(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.TvRecyclerView);
            int type = typeArray.getInteger(R.styleable.TvRecyclerView_scrollMode, 0);
            if (type == 1) {
                mIsFollowScroll = true;
            }

            final Drawable drawable = typeArray.getDrawable(R.styleable.TvRecyclerView_focusDrawable);
            if (drawable != null) {
                setFocusDrawable(drawable);
            }

            mSelectedScaleValue = typeArray.getFloat(R.styleable.TvRecyclerView_focusScale, DEFAULT_SELECT_SCALE);
            mIsAutoProcessFocus = typeArray.getBoolean(R.styleable.TvRecyclerView_isAutoProcessFocus, true);
            if (!mIsAutoProcessFocus) {
                mSelectedScaleValue = 1.0f;
                setChildrenDrawingOrderEnabled(true);
            }
            typeArray.recycle();
        }
        if (mIsAutoProcessFocus) {
            // set TvRecyclerView process Focus
            setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
        }
    }

    private void addFlyBorderView(Context context) {
        if (mFocusBorderView == null) {
            mFocusBorderView = new FocusBorderView(context);
            ((Activity) context).getWindow().addContentView(mFocusBorderView,
                    new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            mFocusBorderView.setSelectPadding(mFocusFrameLeft, mFocusFrameTop,
                    mFocusFrameRight, mFocusFrameBottom);
        }
    }

    public int getFirstVisiblePosition() {
        int firstVisiblePos = -1;
        LayoutManager layoutManager = getLayoutManager();
        if (layoutManager != null) {
            if (layoutManager instanceof LinearLayoutManager) {
                firstVisiblePos = ((LinearLayoutManager) layoutManager)
                        .findFirstVisibleItemPosition();
            } else if (layoutManager instanceof ModuleLayoutManager) {
                firstVisiblePos = ((ModuleLayoutManager) layoutManager)
                        .findFirstVisibleItemPosition();
            }
        }
        return firstVisiblePos;
    }

    public int getLastVisiblePosition() {
        int lastVisiblePos = -1;
        LayoutManager layoutManager = getLayoutManager();
        if (layoutManager != null) {
            if (layoutManager instanceof LinearLayoutManager) {
                lastVisiblePos = ((LinearLayoutManager) layoutManager)
                        .findLastVisibleItemPosition();
            } else if (layoutManager instanceof ModuleLayoutManager) {
                lastVisiblePos = ((ModuleLayoutManager) layoutManager)
                        .findLastVisibleItemPosition();
            }
        }
        return lastVisiblePos;
    }

    @Override
    public void setLayoutManager(LayoutManager layoutManager) {
        if (layoutManager instanceof LinearLayoutManager) {
            mOrientation = ((LinearLayoutManager) layoutManager).getOrientation();
        } else if (layoutManager instanceof ModuleLayoutManager) {
            mOrientation = ((ModuleLayoutManager) layoutManager).getOrientation();
        }
        Log.i(TAG, "setLayoutManager: ====orientation==" + mOrientation);
        super.setLayoutManager(layoutManager);
    }

    /**
     * note: if you set the property of isAutoProcessFocus is false, the listener will be invalid
     *
     * @param listener itemStateListener
     */
    public void setOnItemStateListener(OnItemStateListener listener) {
        mItemStateListener = listener;
    }

    public void setSelectedScale(float scale) {
        if (scale >= 1.0f) {
            mSelectedScaleValue = scale;
        }
    }

    public void setIsAutoProcessFocus(boolean isAuto) {
        mIsAutoProcessFocus = isAuto;
        if (!isAuto) {
            mSelectedScaleValue = 1.0f;
            setChildrenDrawingOrderEnabled(true);
        } else {
            if (mSelectedScaleValue == 1.0f) {
                mSelectedScaleValue = DEFAULT_SELECT_SCALE;
            }
        }
    }

    public void setFocusDrawable(Drawable focusDrawable) {
        mDrawableFocus = focusDrawable;
    }

    public void setScrollMode(int mode) {
        mIsFollowScroll = mode == SCROLL_FOLLOW;
    }

    /**
     * When call this method, you must ensure that the location of the view has been inflate
     *
     * @param position selected item position
     */
    public void setItemSelected(int position) {
        if (mSelectedPosition == position) {
            return;
        }

        mIsSetItemSelected = true;
        if (position >= getAdapter().getItemCount()) {
            position = getAdapter().getItemCount() - 1;
        }
        mSelectedPosition = position;
        requestLayout();
    }

    /**
     * the selected item, there are two cases:
     * 1. item is displayed on the screen
     * 2. item is not displayed on the screen
     */
    private void adjustSelectMode() {
        int childCount = getChildCount();
        if (mSelectedPosition < childCount) {
            mSelectedItem = getChildAt(mSelectedPosition);
            adjustSelectOffset(mSelectedItem);
        } else {
            mIsNeedMoveForSelect = true;
            scrollToPosition(mSelectedPosition);
        }
    }

    /**
     * adjust the selected item position to half screen location
     */
    private void adjustSelectOffset(View selectView) {
        if (mIsAutoProcessFocus) {
            scrollOffset(selectView);
        } else {
            scrollOffset(selectView);
            selectView.requestFocus();
        }
        if (mItemStateListener != null) {
            mItemStateListener.onItemViewFocusChanged(true, selectView,
                    mSelectedPosition);
        }
    }

    private void scrollOffset(View selectView) {
        int dx;
        if (mOrientation == HORIZONTAL) {
            dx = selectView.getLeft() + selectView.getWidth() / 2 - mScreenWidth / 2;
            scrollBy(dx, 0);
        } else {
            dx = selectView.getTop() + selectView.getHeight() / 2 - mScreenHeight / 2;
            scrollBy(0, dx);
        }
    }

    @Override
    public boolean isInTouchMode() {
        boolean result = super.isInTouchMode();
        // 解决4.4版本抢焦点的问题
        if (Build.VERSION.SDK_INT == 19) {
            return !(hasFocus() && !result);
        } else {
            return result;
        }
    }

    /**
     * fix issue: not have focus box when change focus
     *
     * @param child   child view
     * @param focused the focused view
     */
    @Override
    public void requestChildFocus(View child, View focused) {
        if (mSelectedPosition < 0) {
            mSelectedPosition = getChildAdapterPosition(focused);
        }
        super.requestChildFocus(child, focused);
        if (mIsAutoProcessFocus) {
            requestFocus();
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (mIsAutoProcessFocus) {
            addFlyBorderView(getContext());
        }
    }

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
        if (mItemStateListener != null) {
            if (mSelectedItem == null) {
                mSelectedItem = getChildAt(mSelectedPosition - getFirstVisiblePosition());
            }
            mItemStateListener.onItemViewFocusChanged(gainFocus, mSelectedItem,
                    mSelectedPosition);
        }
        if (mFocusBorderView == null) {
            return;
        }
        mFocusBorderView.setTvRecyclerView(this);
        if (gainFocus) {
            mFocusBorderView.bringToFront();
        }
        if (mSelectedItem != null) {
            if (gainFocus) {
                mSelectedItem.setSelected(true);
            } else {
                mSelectedItem.setSelected(false);
            }
            if (gainFocus && !mInLayout) {
                mFocusBorderView.startFocusAnim();
            }
        }
        if (!gainFocus) {
            mFocusBorderView.dismissFocus();
        }
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        int focusIndex = indexOfChild(mSelectedItem);
        if (focusIndex < 0) {
            return i;
        }
        if (i < focusIndex) {
            return i;
        } else if (i < childCount - 1) {
            return focusIndex + childCount - 1 - i;
        } else {
            return focusIndex;
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mInLayout = true;
        super.onLayout(changed, l, t, r, b);
        if (mIsSetItemSelected) {
            adjustSelectMode();
            mIsSetItemSelected = false;
        }

        // fix issue: when start anim the FocusView location error in AutoProcessFocus mode
        Adapter adapter = getAdapter();
        if (adapter != null && mSelectedPosition >= adapter.getItemCount()) {
            mSelectedPosition = adapter.getItemCount() - 1;
        }
        mSelectedItem = getChildAt(mSelectedPosition - getFirstVisiblePosition());
        mInLayout = false;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mFocusBorderView != null && mFocusBorderView.getTvRecyclerView() != null) {
            mFocusBorderView.invalidate();
        }
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        Bundle bundle = (Bundle) state;
        Parcelable superData = bundle.getParcelable("super_data");
        super.onRestoreInstanceState(superData);
        setItemSelected(bundle.getInt("select_pos", 0));
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        Parcelable superData = super.onSaveInstanceState();
        bundle.putParcelable("super_data", superData);
        bundle.putInt("select_pos", mSelectedPosition);
        return bundle;
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            int keyCode = event.getKeyCode();
            if (mSelectedItem == null) {
                mSelectedItem = getChildAt(mSelectedPosition);
            }
            try {
                if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                    mNextFocused = FocusFinder.getInstance().findNextFocus(this, mSelectedItem, View.FOCUS_LEFT);
                } else if (keyCode == KEYCODE_DPAD_RIGHT) {
                    mNextFocused = FocusFinder.getInstance().findNextFocus(this, mSelectedItem, View.FOCUS_RIGHT);
                } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                    mNextFocused = FocusFinder.getInstance().findNextFocus(this, mSelectedItem, View.FOCUS_UP);
                } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                    mNextFocused = FocusFinder.getInstance().findNextFocus(this, mSelectedItem, View.FOCUS_DOWN);
                }
            } catch (Exception e) {
                Log.i(TAG, "dispatchKeyEvent: get next focus item error: " + e.getMessage());
                mNextFocused = null;
            }

            if (!mIsAutoProcessFocus) {
                processMoves(event.getKeyCode());
                if (mNextFocused != null) {
                    mSelectedItem = mNextFocused;
                } else {
                    mSelectedItem = getFocusedChild();
                }
                mSelectedPosition = getChildAdapterPosition(mSelectedItem);
            }
        }
        return super.dispatchKeyEvent(event);
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_LEFT:
            case KeyEvent.KEYCODE_DPAD_UP:
            case KEYCODE_DPAD_RIGHT:
            case KeyEvent.KEYCODE_DPAD_DOWN:
                if (processMoves(keyCode)) {
                    return true;
                }
                break;
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
                mReceivedInvokeKeyDown = true;
                break;
            default:
                break;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER: {
                if (mReceivedInvokeKeyDown) {
                    if ((getAdapter() != null) && (mSelectedItem != null)) {
                        if (mItemStateListener != null) {
                            if (mFocusBorderView != null) {
                                mFocusBorderView.startClickAnim();
                            }
                            mItemStateListener.onItemViewClick(mSelectedItem,
                                    getChildAdapterPosition(mSelectedItem));
                        }
                    }
                }
                mReceivedInvokeKeyDown = false;
                return true;
            }
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    public void computeScroll() {
        if (mScrollerFocusMoveAnim.computeScrollOffset()) {
            if (mIsDrawFocusMoveAnim) {
                mFocusMoveAnimScale = ((float) (mScrollerFocusMoveAnim.getCurrX())) / 100;
            }
            postInvalidate();
        } else {
            if (mIsDrawFocusMoveAnim) {
                if (mNextFocused != null) {
                    mSelectedItem = mNextFocused;
                    mSelectedPosition = getChildAdapterPosition(mSelectedItem);
                }
                mIsDrawFocusMoveAnim = false;
                setLayerType(View.LAYER_TYPE_SOFTWARE, null);
                postInvalidate();
                if (mItemStateListener != null) {
                    mItemStateListener.onItemViewFocusChanged(true, mSelectedItem,
                            mSelectedPosition);
                }
            }
        }
    }

    private boolean processMoves(int keycode) {
        if (mNextFocused == null || !hasFocus()) {
            return false;
        } else {
            if (mIsDrawFocusMoveAnim) {
                return true;
            }

            if (!mIsFollowScroll) {
                boolean isVisible = isVisibleChild(mNextFocused);
                boolean isHalfVisible = isHalfVisibleChild(mNextFocused);
                if (isHalfVisible || !isVisible) {
                    smoothScrollView(keycode);
                }
            } else {
                boolean isOver = isOverHalfScreen(mNextFocused, keycode);
                if (isOver) {
                    smoothScrollView(keycode);
                }
            }
            if (mIsAutoProcessFocus) {
                startFocusMoveAnim();
            } else {
                invalidate();
            }
            return true;
        }
    }

    private void smoothScrollView(int keycode) {
        int scrollDistance = getScrollDistance(keycode);
        if ((keycode == KEYCODE_DPAD_RIGHT || keycode == KeyEvent.KEYCODE_DPAD_LEFT)
                && mOrientation == HORIZONTAL) {
            smoothScrollBy(scrollDistance, 0);
        } else if ((keycode == KeyEvent.KEYCODE_DPAD_UP || keycode == KeyEvent.KEYCODE_DPAD_DOWN)
                && mOrientation == VERTICAL) {
            smoothScrollBy(0, scrollDistance);
        }
    }

    private int getScrollDistance(int keyCode) {
        int distance = 0;
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_RIGHT:
                distance = mNextFocused.getLeft() +
                        mNextFocused.getWidth() / 2 - mScreenWidth / 2;
                break;
            case KeyEvent.KEYCODE_DPAD_LEFT:
                distance = mNextFocused.getLeft()
                        - mScreenWidth / 2 + mNextFocused.getWidth() / 2;
                break;
            case KeyEvent.KEYCODE_DPAD_UP:
                distance = mNextFocused.getBottom() -
                        mNextFocused.getHeight() / 2 - mScreenHeight / 2;
                break;
            case KeyEvent.KEYCODE_DPAD_DOWN:
                distance = mNextFocused.getTop() +
                        mNextFocused.getHeight() / 2 - mScreenHeight / 2;
                break;
            default:
                break;
        }
        return distance;
    }

    private boolean isHalfVisibleChild(View child) {
        if (child != null) {
            Rect ret = new Rect();
            boolean isVisible = child.getLocalVisibleRect(ret);
            if (mOrientation == HORIZONTAL) {
                return isVisible && (ret.width() < child.getWidth());
            } else {
                return isVisible && (ret.height() < child.getHeight());
            }
        }
        return false;
    }

    private boolean isVisibleChild(View child) {
        if (child != null) {
            Rect ret = new Rect();
            return child.getLocalVisibleRect(ret);
        }
        return false;
    }

    private boolean isOverHalfScreen(View child, int keycode) {
        Rect ret = new Rect();
        boolean visibleRect = child.getGlobalVisibleRect(ret);
        if (visibleRect && keycode == KEYCODE_DPAD_RIGHT) {
            if (ret.right > mScreenWidth / 2) {
                return true;
            }
        } else if (visibleRect && keycode == KeyEvent.KEYCODE_DPAD_LEFT) {
            if (ret.left < mScreenWidth / 2) {
                return true;
            }
        } else if (visibleRect && keycode == KeyEvent.KEYCODE_DPAD_UP) {
            if (ret.top < mScreenHeight / 2) {
                return true;
            }
        } else if (visibleRect && keycode == KeyEvent.KEYCODE_DPAD_DOWN) {
            if (ret.bottom > mScreenHeight / 2) {
                return true;
            }
        }
        return false;
    }

    private void startFocusMoveAnim() {
        setLayerType(View.LAYER_TYPE_NONE, null);
        mIsDrawFocusMoveAnim = true;
        if (mItemStateListener != null) {
            mItemStateListener.onItemViewFocusChanged(false, mSelectedItem,
                    mSelectedPosition);
        }
        mScrollerFocusMoveAnim.startScroll(0, 0, 100, 100, 200);
        invalidate();
    }

    /**
     * When the TvRecyclerView width is determined, the returned position is correct
     *
     * @return selected view position
     */
    public int getSelectedPosition() {
        return mSelectedPosition;
    }

    View getSelectedView() {
        return mSelectedItem;
    }

    public float getSelectedScaleValue() {
        return mSelectedScaleValue;
    }

    public Drawable getDrawableFocus() {
        return mDrawableFocus;
    }

    public View getNextFocusView() {
        return mNextFocused;
    }

    public float getFocusMoveAnimScale() {
        return mFocusMoveAnimScale;
    }

    public void setSelectPadding(int left, int top, int right, int bottom) {
        mFocusFrameLeft = left;
        mFocusFrameTop = top;
        mFocusFrameRight = right;
        mFocusFrameBottom = bottom;

        if (mFocusBorderView != null) {
            mFocusBorderView.setSelectPadding(mFocusFrameLeft, mFocusFrameTop,
                    mFocusFrameRight, mFocusFrameBottom);
        }
    }

    public interface OnItemStateListener {
        void onItemViewClick(View view, int position);

        void onItemViewFocusChanged(boolean gainFocus, View view, int position);
    }
}

xml.文件如下:

    <RecyclerViewEx.TvRecyclerView
                android:id="@+id/title_list"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:divider="@null"
                android:fastScrollEnabled="false"
                android:footerDividersEnabled="false"
                android:headerDividersEnabled="false"
                app:focusDrawable="@drawable/bg_item_focused" />

注: app:focusDrawable="@drawable/bg_item_focused" 主要是当前menu中的item聚焦的效果。

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@color/bg_btn"></solid>

    <stroke
        android:width="1dp"
        android:color="@color/darkgray" />

    <corners android:radius="@dimen/x1" />

</shape>

问题 三

本项目中的右侧item采用原生的recycleview,

image.png

项目中最大的问题是左侧menu按下遥控器右键切换到右侧子项item时,(如果当前右侧的数据源不低于4个)会默认选择第二列的第一个数据源,如图标注所示:
image.png

项目需求: 当menu切换的时候,按下遥控器右键,切换到右侧子item的时候默认选择第一列的第一个数据源。
解决方案:在xml文件布局中,recycleview的外边套着一层布局,让当前布局可聚焦,具体如下:

       <LinearLayout
            android:id="@+id/ll_content"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="8"
            android:background="@color/white"
            android:focusable="true"
            android:orientation="vertical"
            android:padding="@dimen/x2">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/content_list"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:divider="@null"
                android:fastScrollEnabled="false"
                android:focusable="false"
                android:footerDividersEnabled="false"
                android:headerDividersEnabled="false" />

        </LinearLayout>

设置外层的LinearLayout的android:focusable="true",RecyclerView的android:focusable="false"
最后在acticity的设置LinearLayout的聚焦监听器:

 mLayoutContent.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (hasFocus) {
                    if (mListViewContent.getChildCount() > 0) {
                        mListViewContent.getChildAt(0).requestFocus();
                    }
                }
            }
        });

mListViewContent为RecycleView。

最后每次切换menu的时候,自动让右侧的items滚动到最上面,附上方法:

mListViewMenu.setOnItemStateListener(new TvRecyclerView.OnItemStateListener() {
            @Override
            public void onItemViewClick(View view, int position) {
//                mAdapterMenu.setSelectPosition(position);
//                mAdapterContent.setData(mVideoBeans.get(position).getList(), false);
            }

            @Override
            public void onItemViewFocusChanged(boolean gainFocus, View view, int position) {
                if (position < 0) return;
                if (gainFocus) {
                    mAdapterMenu.setSelectPosition(position);

                    //为右侧recycleview设置数据源
                    mAdapterContent.setData(mVideoBeans.get(position).getList(), false);
                    //
                    if (mCurPosition != position) {
                        if (mListViewContent.getAdapter().getItemCount() > 0) {
                            //设置右侧的recycleview滚动到最上面
                            mListViewContent.scrollToPosition(0);
                            mCurPosition = position;
                        }
                    }
                }
            }
        });

mListViewMenu为重写的TvRecycleView。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容