Android TV端的(RecyclerView)水平滚动焦点错乱问题

package com.hhzt.iptv.ui.customview;

import android.content.Context;import android.content.res.TypedArray;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.hhzt.iptv.R;import com.hhzt.iptv.lvb_w.interfaces.OnInterceptListener;import com.hhzt.iptv.lvb_w.utils.VirturlKeyPadCtr;

/** * Created by Administrator on 2017/6/7. */

public class TvRecyclerView extends RecyclerView {    private static final String TAG = "TvRecyclerView";

private int position;

//焦点是否居中    private boolean mSelectedItemCentered;

private int mSelectedItemOffsetStart;

private int mSelectedItemOffsetEnd;

//分页的时候使用    private int mLoadMoreBeforehandCount = 0;

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

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 void initAttr(Context context, AttributeSet attrs) {        if (attrs != null) {            final TypedArray a = context.obtainStyledAttributes(attrs,                    R.styleable.TvRecyclerView);            /**             * 如果是towWayView的layoutManager             */            final String name = a.getString(R.styleable.TvRecyclerView_tv_layoutManager);

mSelectedItemCentered = a.getBoolean(R.styleable.TvRecyclerView_tv_selectedItemCentered, false);

mLoadMoreBeforehandCount = a.getInteger(R.styleable.TvRecyclerView_tv_loadMoreBeforehandCount, 0);

mSelectedItemOffsetStart = a.getDimensionPixelSize(R.styleable.TvRecyclerView_tv_selectedItemOffsetStart, 0);

mSelectedItemOffsetEnd = a.getDimensionPixelSize(R.styleable.TvRecyclerView_tv_selectedItemOffsetEnd, 0);

a.recycle();        }    }

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

//return super.isInTouchMode();    }

@Override    public void requestChildFocus(View child, View focused) {

if (null != child) {            if (mSelectedItemCentered) {                mSelectedItemOffsetStart = !isVertical() ? (getFreeWidth() - child.getWidth()) : (getFreeHeight() - child.getHeight());                mSelectedItemOffsetStart /= 2;                mSelectedItemOffsetEnd = mSelectedItemOffsetStart;            }        }        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 - mSelectedItemOffsetStart);        final int offScreenRight = Math.max(0, childRight - parentRight + mSelectedItemOffsetEnd);

final int offScreenTop = Math.min(0, childTop - parentTop - mSelectedItemOffsetStart);        final int offScreenBottom = Math.max(0, childBottom - parentBottom + mSelectedItemOffsetEnd);

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);            }            // 重绘是为了选中item置顶,具体请参考getChildDrawingOrder方法            postInvalidate();            return true;        }

return false;    }

@Override    public int getBaseline() {        return -1;    }

public int getSelectedItemOffsetStart() {        return mSelectedItemOffsetStart;    }

public int getSelectedItemOffsetEnd() {        return mSelectedItemOffsetEnd;    }

@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;    }

/**     * 设置选中的Item距离开始或结束的偏移量;     * 与滚动方向有关;     * 与setSelectedItemAtCentered()方法二选一     *     * @param offsetStart     * @param offsetEnd   从结尾到你移动的位置.     */    public void setSelectedItemOffset(int offsetStart, int offsetEnd) {        setSelectedItemAtCentered(false);        mSelectedItemOffsetStart = offsetStart;        mSelectedItemOffsetEnd = offsetEnd;    }

/**     * 设置选中的Item居中;     * 与setSelectedItemOffset()方法二选一     *     * @param isCentered     */    public void setSelectedItemAtCentered(boolean isCentered) {        this.mSelectedItemCentered = isCentered;    }

@Override    protected int getChildDrawingOrder(int childCount, int i) {        View view = getFocusedChild();        if (null != view) {

position = getChildAdapterPosition(view) - getFirstVisiblePosition();            if (position < 0) {                return i;            } else {                if (i == childCount - 1) {//这是最后一个需要刷新的item                    if (position > i) {                        position = i;                    }                    return position;                }                if (i == position) {//这是原本要在最后一个刷新的item                    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));    }

/***********     * 按键加载更多 start     **********/

private OnLoadMoreListener mOnLoadMoreListener;

public interface OnLoadMoreListener {        void onLoadMore();    }

public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {        this.mOnLoadMoreListener = onLoadMoreListener;    }

private OnInterceptListener mInterceptLister;

public void setOnInterceptListener(OnInterceptListener listener) {        this.mInterceptLister = listener;    }

/**     * 设置为0,这样可以防止View获取焦点的时候,ScrollView自动滚动到焦点View的位置     */

protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {        return 0;    }

@Override    public void onScrollStateChanged(int state) {        if (state == SCROLL_STATE_IDLE) {            // 加载更多回调            if (null != mOnLoadMoreListener) {                if (getLastVisiblePosition() >= getAdapter().getItemCount() - (1 + mLoadMoreBeforehandCount)) {                    mOnLoadMoreListener.onLoadMore();                }            }        }        super.onScrollStateChanged(state);    }

@Override    public boolean dispatchKeyEvent(KeyEvent event) {        if (mInterceptLister != null && mInterceptLister.onIntercept(event)) {            return true;        }

boolean result = super.dispatchKeyEvent(event);        View focusView = this.getFocusedChild();        if (focusView == null) {            return result;        } else {

int dy = 0;            int dx = 0;            if (getChildCount() > 0) {                View firstView = this.getChildAt(0);                dy = firstView.getHeight();                dx = firstView.getWidth();            }            if (event.getAction() == KeyEvent.ACTION_UP) {                return true;            } else {                switch (event.getKeyCode()) {                    case KeyEvent.KEYCODE_DPAD_RIGHT:                        if(VirturlKeyPadCtr.isFastDoubleClick()){                            return true;                        }else {                            View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);                            Log.i(TAG, "rightView is null:" + (rightView == null));                            if (rightView != null) {                                rightView.requestFocus();                                return true;                            } else {                                this.smoothScrollBy(dx, 0);                                return true;                            }                        }                    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 {                            this.smoothScrollBy(-dx, 0);                            return true;                        }                    case KeyEvent.KEYCODE_DPAD_DOWN:                        View downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);                        Log.i(TAG, " downView is null:" + (downView == null));                        if (downView != null) {                            downView.requestFocus();                            return true;                        } else {                            this.smoothScrollBy(0, dy);                            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 (event.getAction() == KeyEvent.ACTION_UP) {                            return true;                        } else {                            if (upView != null) {                                upView.requestFocus();                                return true;                            } else {                                this.smoothScrollBy(0, -dy);                                return true;                            }

}                }

}

}        return result;    }

@Override    public boolean onInterceptTouchEvent(MotionEvent e) {        return super.onInterceptTouchEvent(e);    }

/**     * 设置默认选中.     */

public void setSelectedPosition(int pos) {        this.smoothScrollToPosition(pos);    }

//防止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 static boolean isFastDoubleClick() {   long time = System.currentTimeMillis();   long timeD = time - lastClickTime;   if ( 0 < timeD && timeD < 200) {      return true;   }   lastClickTime = time;   return false;}
				
时间: 2024-10-27 12:24:45

Android TV端的(RecyclerView)水平滚动焦点错乱问题的相关文章

为TV端开发助力(转载)

作者地址http://www.jianshu.com/u/63915ef020e2 针对Android Tv的自定义RecyclerView 作者 wenju_song 关注 2016.12.09 19:52* 字数 814 阅读 1368评论 17喜欢 22 前言:Android TV Launcher页在RecyclerView出来之前大家用GridView去实现.TV开发有五向键的监听,遥控器hover监听,点击事件等.用GridView去处理焦点是有一定挑战性的,往往会出现不可预料焦点错

Android TV界面聚焦效果---凸显效果

Android TV端应用程序的开发时,在聚焦效果上往往是将图标发大并且凸显到最上层来显示,如下图的效果. 该聚焦凸显效果有两种可行的实现方案,不过太麻烦了. 1)全两层布局,所谓全两层布局,就是图标显示层和聚焦凸显层分开,他们是完全重叠的两层.这种方式在程序上是非常不好办的,既要实现焦点转换又要实现上层凸显控件的消失或不消失状态,状态和逻辑都非常复杂. 2)部分两层布局,所谓部分两层布局,就是底层放需要呈现的图标,上层就只有2个可移动的图标,底层哪个图标获取了焦点,那么上层的焦点发大图标就马上

Android为TV端助力 listview与recyclerview上下联动

首先是主布局fragment里面的xml文件 <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/a

android tv焦点特效实现浅析

Android TV上的焦点凸显特效相信大家都看到过,那么我们就来实现它吧,首先上张效果图. 先说一下实现原理,主要通过重写RelativeLayout实现item,之后在其中加入scalanimation动画效果.刚开始处理时,还是发现了一些问题,比如item放大后会被其他item遮挡,如何添加选中边框等等,以及动画的实现等等.下面放上实现细节. 首先是item的代码: <view xmlns:android="http://schemas.android.com/apk/res/and

Android学习——HorizontalScollview水平滚动控件

HorizatalScollView控件只是支持水平滚动,而且它只能包含一个控件,通常是在<HorizatalScollView>标签中定义一个<LinearLayout> 标签并且在<LinearLayout>标签中android:orientation属性值设置为horization.然后在<LinearLayout>标签中放置多个控件,如果<LinearLayout>标签中的控件所占的总宽度超出屏幕的宽度,就会出现滚动效果. <Lin

android tv 实现颜色条滚动效果

直接贴代码: ColorView.java package com.xxx.demo; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.V

Android学习笔记技巧之垂直和水平滚动视图

[java] view plain copy <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_conte

Android中如何实现多行、水平滚动的分页的Gridview?

功能要求: (1)比如每页显示2X2,总共2XN,每个item显示图片+文字(点击有链接). 如果单行水平滚动,可以用Horizontalscrollview实现. 如果是多行水平滚动,则结合Gridview(一般是垂直滚动的)和Horizontalscrollview实现. (2)水平滚动翻页,下面有显示当前页的icon. 1.      实现自定义的HorizontalScrollView(HorizontalScrollView.java): 因为要翻页时需要传当前页给调用者,所以flin

Android TV APPs 的介绍与创建

最近开始接触到Andorid TV编程,发现目前这方便的资料与比较还比较少,但现在随着产品与用户的升级,涉及到Android TV与 Android Watch的编程离我们越来越近了,本篇博客开始,将以笔记的方式记录我Android TV从零到有的一个过程. 本博客创建步骤主要参考Android官方文档. Get Started with TV Apps TV应用程序使用相同的结构与手机和平板电脑.这种相似性意味着你也可以修改现有的应用程序运行在TV设备或创建新的应用程序基于你现在已经掌握的An