原生RecyclerView 在Tv中的焦点处理很不好,经常找不到焦点或者焦点丢失。原因是因为当item未显示时即未加载时时不能获取焦点的。所以当我们按上下键时经常丢失焦点或者焦点乱跳。要解决这个问题我们必须要手动控制RecyclerView 的按键和焦点移动。

package com.phicomm.ottbox.view;import android.content.Context;import android.graphics.Rect;import android.os.Build;import android.support.v4.view.ViewCompat;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.util.Log;import android.view.FocusFinder;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import com.phicomm.ottbox.base.BaseFragment;/** * Created by root on 17-9-7. */public class TvRecyclerView extends RecyclerView {    private static final String TAG = "TvRecyclerView";    private int mPosition;    private BaseFragment mBindFragment;    public TvRecyclerView(Context context) {        this(context, null);    }    public TvRecyclerView(Context context, AttributeSet attrs) {        this(context, attrs, -1);    }    public TvRecyclerView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        init(context, attrs, defStyle);    }    private void init(Context context, AttributeSet attrs, int defStyle) {        initView();        //initAttr(context, attrs);    }    public void setBindFragment(BaseFragment fragment) {        this.mBindFragment = fragment;    }    private void initView() {        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);        setHasFixedSize(true);        setWillNotDraw(true);        setOverScrollMode(View.OVER_SCROLL_NEVER);        setChildrenDrawingOrderEnabled(true);        setClipChildren(false);        setClipToPadding(false);        setClickable(false);        setFocusable(true);        setFocusableInTouchMode(true);        /**         防止RecyclerView刷新时焦点不错乱bug的步骤如下:         (1)adapter执行setHasStableIds(true)方法         (2)重写getItemId()方法,让每个view都有各自的id         (3)RecyclerView的动画必须去掉         */        setItemAnimator(null);    }    private int getFreeWidth() {        return getWidth() - getPaddingLeft() - getPaddingRight();    }    private int getFreeHeight() {        return getHeight() - getPaddingTop() - getPaddingBottom();    }    @Override    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);    }    @Override    public boolean hasFocus() {        return super.hasFocus();    }    @Override    public boolean isInTouchMode() {        // 解决4.4版本抢焦点的问题        if (Build.VERSION.SDK_INT == 19) {            return !(hasFocus() && !super.isInTouchMode());        } else {            return super.isInTouchMode();        }    }    @Override    public void requestChildFocus(View child, View focused) {        super.requestChildFocus(child, focused);    }    @Override    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {        final int parentLeft = getPaddingLeft();        final int parentRight = getWidth() - getPaddingRight();        final int parentTop = getPaddingTop();        final int parentBottom = getHeight() - getPaddingBottom();        final int childLeft = child.getLeft() + rect.left;        final int childTop = child.getTop() + rect.top;        final int childRight = childLeft + rect.width();        final int childBottom = childTop + rect.height();        final int offScreenLeft = Math.min(0, childLeft - parentLeft);        final int offScreenRight = Math.max(0, childRight - parentRight);        final int offScreenTop = Math.min(0, childTop - parentTop);        final int offScreenBottom = Math.max(0, childBottom - parentBottom);        final boolean canScrollHorizontal = getLayoutManager().canScrollHorizontally();        final boolean canScrollVertical = getLayoutManager().canScrollVertically();        // Favor the "start" layout direction over the end when bringing one side or the other        // of a large rect into view. If we decide to bring in end because start is already        // visible, limit the scroll such that start won't go out of bounds.        final int dx;        if (canScrollHorizontal) {            if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL) {                dx = offScreenRight != 0 ? offScreenRight                        : Math.max(offScreenLeft, childRight - parentRight);            } else {                dx = offScreenLeft != 0 ? offScreenLeft                        : Math.min(childLeft - parentLeft, offScreenRight);            }        } else {            dx = 0;        }        // Favor bringing the top into view over the bottom. If top is already visible and        // we should scroll to make bottom visible, make sure top does not go out of bounds.        final int dy;        if (canScrollVertical) {            dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom);        } else {            dy = 0;        }        if (dx != 0 || dy != 0) {            if (immediate) {                scrollBy(dx, dy);            } else {                smoothScrollBy(dx, dy);            }            postInvalidate();            return true;        }        return false;    }    @Override    public int getBaseline() {        return -1;    }    @Override    public void setLayoutManager(LayoutManager layout) {        super.setLayoutManager(layout);    }    /**     * 判断是垂直,还是横向.     */    private boolean isVertical() {        LayoutManager manager = getLayoutManager();        if (manager != null) {            LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();            return layout.getOrientation() == LinearLayoutManager.VERTICAL;        }        return false;    }    @Override    protected int getChildDrawingOrder(int childCount, int i) {        View view = getFocusedChild();        if (null != view) {            mPosition = getChildAdapterPosition(view) - getFirstVisiblePosition();            if (mPosition < 0) {                return i;            } else {                if (i == childCount - 1) {                    if (mPosition > i) {                        mPosition = i;                    }                    return mPosition;                }                if (i == mPosition) {                    return childCount - 1;                }            }        }        return i;    }    public int getFirstVisiblePosition() {        if (getChildCount() == 0)            return 0;        else            return getChildAdapterPosition(getChildAt(0));    }    public int getLastVisiblePosition() {        final int childCount = getChildCount();        if (childCount == 0)            return 0;        else            return getChildAdapterPosition(getChildAt(childCount - 1));    }    /**     * 设置为0,这样可以防止View获取焦点的时候,ScrollView自动滚动到焦点View的位置     */    protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {        return 0;    }    @Override    public boolean dispatchKeyEvent(KeyEvent event) {        boolean result = super.dispatchKeyEvent(event);        View focusView = this.getFocusedChild();        if (mBindFragment != null) {            mBindFragment.setCacheViewFromContent(focusView);        }        if (focusView == null) {            return result;        } else {            if (event.getAction() == KeyEvent.ACTION_UP) {                return true;            } else {                switch (event.getKeyCode()) {                    case KeyEvent.KEYCODE_DPAD_RIGHT:                        View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);                        if (rightView != null) {                            rightView.requestFocus();                            return true;                        } else {                            return false;                        }                    case KeyEvent.KEYCODE_DPAD_LEFT:                        View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);                        Log.i(TAG, "leftView is null:" + (leftView == null));                        if (leftView != null) {                            leftView.requestFocus();                            return true;                        } else {                            return false;                        }                    case KeyEvent.KEYCODE_DPAD_DOWN:                        if (isVisBottom(this)) {                            this.smoothScrollToPosition(getLastVisiblePosition());                            return result;                        }                        View downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);                        Log.i(TAG, " downView is null:" + (downView == null));                        if (downView != null) {                            downView.requestFocus();                            int downOffset = downView.getTop() + downView.getHeight() / 2 - getHeight() / 2;                            this.smoothScrollBy(0, downOffset);                            return true;                        } else {                            return true;                        }                    case KeyEvent.KEYCODE_DPAD_UP:                        View upView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP);                        Log.i(TAG, "upView is null:" + (upView == null));                        if (upView != null) {                            upView.requestFocus();                            int upOffset = getHeight() / 2 - (upView.getBottom() - upView.getHeight() / 2);                            this.smoothScrollBy(0, -upOffset);                            return true;                        } else {                            Log.i(TAG, "tab cache view");                            if (mBindFragment != null) {                                mBindFragment.setmCacheViewFromTab(focusView);                            }                            return result;//返回false,否则第一行按上键回不到导航栏                        }                }            }        }        return result;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent e) {        return super.onInterceptTouchEvent(e);    }    //防止Activity时,RecyclerView崩溃    @Override    protected void onDetachedFromWindow() {        if (getLayoutManager() != null) {            super.onDetachedFromWindow();        }    }    /**     * 是否是最右边的item,如果是竖向,表示右边,如果是横向表示下边     *     * @param childPosition     * @return     */    public boolean isRightEdge(int childPosition) {        LayoutManager layoutManager = getLayoutManager();        if (layoutManager instanceof GridLayoutManager) {            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;            GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();            int totalSpanCount = gridLayoutManager.getSpanCount();            int totalItemCount = gridLayoutManager.getItemCount();            int childSpanCount = 0;            for (int i = 0; i <= childPosition; i++) {                childSpanCount += spanSizeLookUp.getSpanSize(i);            }            if (isVertical()) {                if (childSpanCount % gridLayoutManager.getSpanCount() == 0) {                    return true;                }            } else {                int lastColumnSize = totalItemCount % totalSpanCount;                if (lastColumnSize == 0) {                    lastColumnSize = totalSpanCount;                }                if (childSpanCount > totalItemCount - lastColumnSize) {                    return true;                }            }        } else if (layoutManager instanceof LinearLayoutManager) {            if (isVertical()) {                return true;            } else {                return childPosition == getLayoutManager().getItemCount() - 1;            }        }        return false;    }    /**     * 是否是最左边的item,如果是竖向,表示左方,如果是横向,表示上边     *     * @param childPosition     * @return     */    public boolean isLeftEdge(int childPosition) {        LayoutManager layoutManager = getLayoutManager();        if (layoutManager instanceof GridLayoutManager) {            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;            GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();            int totalSpanCount = gridLayoutManager.getSpanCount();            int childSpanCount = 0;            for (int i = 0; i <= childPosition; i++) {                childSpanCount += spanSizeLookUp.getSpanSize(i);            }            if (isVertical()) {                if (childSpanCount % gridLayoutManager.getSpanCount() == 1) {                    return true;                }            } else {                if (childSpanCount <= totalSpanCount) {                    return true;                }            }        } else if (layoutManager instanceof LinearLayoutManager) {            if (isVertical()) {                return true;            } else {                return childPosition == 0;            }        }        return false;    }    /**     * 是否是最上边的item,以recyclerview的方向做参考     *     * @param childPosition     * @return     */    public boolean isTopEdge(int childPosition) {        LayoutManager layoutManager = getLayoutManager();        if (layoutManager instanceof GridLayoutManager) {            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;            GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();            int totalSpanCount = gridLayoutManager.getSpanCount();            int childSpanCount = 0;            for (int i = 0; i <= childPosition; i++) {                childSpanCount += spanSizeLookUp.getSpanSize(i);            }            if (isVertical()) {                if (childSpanCount <= totalSpanCount) {                    return true;                }            } else {                if (childSpanCount % totalSpanCount == 1) {                    return true;                }            }        } else if (layoutManager instanceof LinearLayoutManager) {            if (isVertical()) {                return childPosition == 0;            } else {                return true;            }        }        return false;    }    /**     * 是否是最下边的item,以recyclerview的方向做参考     *     * @param childPosition     * @return     */    public boolean isBottomEdge(int childPosition) {        LayoutManager layoutManager = getLayoutManager();        if (layoutManager instanceof GridLayoutManager) {            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;            GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();            int itemCount = gridLayoutManager.getItemCount();            int childSpanCount = 0;            int totalSpanCount = gridLayoutManager.getSpanCount();            for (int i = 0; i <= childPosition; i++) {                childSpanCount += spanSizeLookUp.getSpanSize(i);            }            if (isVertical()) {                //最后一行item的个数                int lastRowCount = itemCount % totalSpanCount;                if (lastRowCount == 0) {                    lastRowCount = gridLayoutManager.getSpanCount();                }                if (childSpanCount > itemCount - lastRowCount) {                    return true;                }            } else {                if (childSpanCount % totalSpanCount == 0) {                    return true;                }            }        } else if (layoutManager instanceof LinearLayoutManager) {            if (isVertical()) {                return childPosition == getLayoutManager().getItemCount() - 1;            } else {                return true;            }        }        return false;    }    public interface OnInterceptListener {        boolean onIntercept(KeyEvent event);    }    /**     * 判断是否已经滑动到底部     *     * @param recyclerView     * @return     */    private boolean isVisBottom(RecyclerView recyclerView) {        LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();        int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();        int visibleItemCount = layoutManager.getChildCount();        int totalItemCount = layoutManager.getItemCount();        if (visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1) {            return true;        } else {            return false;        }    }}

重点是在dispatchKeyEvent中,让我们下一个item直接获取焦点。不再依赖系统处理。

BaseFragment 为与之绑定的fragment,作用是当按上键离开fragment到导航栏时的焦点位置。实现记录焦点功能。

更多相关文章

  1. Android(安卓)RecyclerView 的简单使用
  2. android去掉EditView的默认焦点问题
  3. 安卓学习笔记-----我所不熟悉的TexetView的一些属性
  4. android:focusable和android:focusableInTouchMode的区别
  5. 关于Android软键盘弹出的问题
  6. EditText控件设置只读,动态控制EditText状态
  7. Android(安卓)UI控件之 焦点问题
  8. Android特色开发之传感器
  9. Android(安卓)列表中设置Button后setOnItemClickListener失效问

随机推荐

  1. Android(安卓)Studio 友盟多渠道打包
  2. 对Android(安卓)Activity添加容错处理
  3. Android原生(Native)C开发之一:环境搭建篇
  4. Android的Bitmap和缓存机制
  5. Android(安卓)用Mediacodec硬解码视频包A
  6. [导入]2010-03-03 传智播客—Android(五)数
  7. android事件分发机制
  8. 最简单的基于FFmpeg的移动端例子:Android(
  9. Android中SQLite数据库存储方式
  10. Android启动过程详解(3)——Zygote