Recycleview在電視端聚焦問題

需求

項(xiàng)目需求:在電視端加載視頻列表并播放視頻,用遙控器操作每一個(gè)menu和子項(xiàng)目的ietm。

問題

    1. recycleview如何在電視端聚焦
    1. recycleview聚焦混亂
    1. 第一個(gè)recycleview(菜單欄menu)切換到第二個(gè)recycleview(子項(xiàng)Item)
      TV開發(fā)和手機(jī)開發(fā)有個(gè)不同的就是焦點(diǎn)問題。
      在手機(jī)端,手指只要一劃就可以到后面了,而在TV端,需要用遙控器左右鍵控制焦點(diǎn)移動(dòng)。

RecyclerView(簡(jiǎn)稱RV)在TV端的應(yīng)用可以借鑒的案例不多,相比于ListView,RV的很多功能都需要自己實(shí)現(xiàn)。諸如,wrap_content自適應(yīng)問題,選中的背景問題,setSelection()方法等。

menu列表

本項(xiàng)目中的menu采用重寫recycleview:
主要解決Recycleview切換到子項(xiàng)item時(shí),返回記住當(dāng)前選擇的menu。如圖所示:


左側(cè)切換到子項(xiàng)item時(shí),記住當(dāng)前的menu
  • TvRecyclerView屬性介紹 :
        <attr name="scrollMode"/>
        <attr name="focusDrawable" format="reference" />
        <attr name="isAutoProcessFocus" format="boolean" />
        <attr name="focusScale" format="float" />

(1) scrollMode控制TvRecyclerView的滑動(dòng)模式, TvRecyclerView的滑動(dòng)模式有兩種:

  • ① normalScroll: 默認(rèn)情況下是這種模式, 當(dāng)TvRecyclerView是這種模式時(shí), 當(dāng)焦點(diǎn)滑動(dòng)到的view是沒有全部顯示出來(lái)的, TvRecyclerView將會(huì)向按鍵的方向滑動(dòng)屏幕一半的距離.
  • ② followScroll: 當(dāng)TvRecyclerView是這種模式時(shí), 當(dāng)焦點(diǎn)滑動(dòng)到view在屏幕當(dāng)中就一直滑動(dòng), 效果與android Tv上的HorizontalGridView差不多.

(2) focusDrawable 設(shè)置選擇的Drawable, 如圖一的白色選擇框, 默認(rèn)是沒有設(shè)置, 想要這種效果需要設(shè)置此屬性或在代碼中設(shè)置.

(3) isAutoProcessFocus 控制焦點(diǎn)處理是由誰(shuí)來(lái)做, 默認(rèn)焦點(diǎn)由TvRecyclerView來(lái)處理.
當(dāng)isAutoProcessFocus 為false, 子view是可以獲得焦點(diǎn)的, 當(dāng)isAutoProcessFocus為true, 子view獲取
不到焦點(diǎn), 焦點(diǎn)由TvRecyclerView來(lái)處理.

(4)focusScale 設(shè)置選中view時(shí), view的放大系數(shù). 大于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);
        // 解決問題: 當(dāng)需要選擇的item沒有顯示在屏幕上, 需要滑動(dòng)讓item顯示出來(lái).
        // 這時(shí)需要調(diào)整item的位置, 并且item獲取焦點(diǎn)
        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版本搶焦點(diǎn)的問題
        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" 主要是當(dāng)前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>

問題 三

本項(xiàng)目中的右側(cè)item采用原生的recycleview,

image.png

項(xiàng)目中最大的問題是左側(cè)menu按下遙控器右鍵切換到右側(cè)子項(xiàng)item時(shí),(如果當(dāng)前右側(cè)的數(shù)據(jù)源不低于4個(gè))會(huì)默認(rèn)選擇第二列的第一個(gè)數(shù)據(jù)源,如圖標(biāo)注所示:
image.png

項(xiàng)目需求: 當(dāng)menu切換的時(shí)候,按下遙控器右鍵,切換到右側(cè)子item的時(shí)候默認(rèn)選擇第一列的第一個(gè)數(shù)據(jù)源。
解決方案:在xml文件布局中,recycleview的外邊套著一層布局,讓當(dāng)前布局可聚焦,具體如下:

       <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>

設(shè)置外層的LinearLayout的android:focusable="true",RecyclerView的android:focusable="false"。
最后在acticity的設(shè)置LinearLayout的聚焦監(jiān)聽器:

 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的時(shí)候,自動(dòng)讓右側(cè)的items滾動(dòng)到最上面,附上方法:

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);

                    //為右側(cè)recycleview設(shè)置數(shù)據(jù)源
                    mAdapterContent.setData(mVideoBeans.get(position).getList(), false);
                    //
                    if (mCurPosition != position) {
                        if (mListViewContent.getAdapter().getItemCount() > 0) {
                            //設(shè)置右側(cè)的recycleview滾動(dòng)到最上面
                            mListViewContent.scrollToPosition(0);
                            mCurPosition = position;
                        }
                    }
                }
            }
        });

mListViewMenu為重寫的TvRecycleView。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,023評(píng)論 25 709
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 7,328評(píng)論 0 17
  • 最近做了一個(gè)Android UI相關(guān)開源項(xiàng)目庫(kù)匯總,里面集合了OpenDigg 上的優(yōu)質(zhì)的Android開源項(xiàng)目庫(kù)...
    OpenDigg閱讀 17,613評(píng)論 6 222
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,156評(píng)論 22 665
  • 我經(jīng)常和人講,在這個(gè)時(shí)代如果你想賺到十萬(wàn)塊錢就必須創(chuàng)造一百萬(wàn)的價(jià)值,而要賺一百萬(wàn),創(chuàng)造的價(jià)值可能就不止一千萬(wàn)才可以...
    國(guó)璽同學(xué)閱讀 190評(píng)論 0 0

友情鏈接更多精彩內(nèi)容