RecyclerView 在tv端 焦点问题

转自:http://blog.csdn.net/u010072711/article/details/51039967 (收藏)



我们把RecyclerView写成GridView样式,并把RecyclerView的item写成focusable并且有焦点框的时候,我们用焦点滚动RecyclerView的时候会发现RecyclerView的焦点跳转有bug,跟我们想要的焦点跳转规则不一致,会出现的BUG如下图:

RecyclerView 在tv端 焦点问题_第1张图片黑色方框代表屏幕,我们从左上角的一个item往下按焦点的时候,当需要加载新的一行的时候焦点却跑到了新的一行的最后一个item上面了,(如图,本来是item1获得焦点的,结果跑到item2上面了)。

   这是RecyclerView的一个BUG,记得RecyclerView刚出来的时候滚动都还有点卡顿,到了现在滚动起来还是非常流畅的,比较一个全新的艺术般的空间是需要时间来沉淀的,这个BUG我们可以重写GridLayoutManger来解决。直接看代码:

[java]  view plain  copy
 
  1. package com.wasu.cs.widget;  
  2.   
  3. import android.content.Context;  
  4. import android.support.v7.widget.GridLayoutManager;  
  5. import android.support.v7.widget.RecyclerView;  
  6. import android.util.AttributeSet;  
  7. import android.view.View;  
  8.   
  9. /** 
  10.  * 自定义GridLayoutManager,修改RecyelerView焦点乱跳的BUG 
  11.  * Created by Danxingxi on 2016/4/1. 
  12.  */  
  13. public class FocusGridLayoutManager extends GridLayoutManager {  
  14.   
  15.   
  16.     /** 
  17.      * Constructor used when layout manager is set in XML by RecyclerView attribute 
  18.      * "layoutManager". If spanCount is not specified in the XML, it defaults to a 
  19.      * single column. 
  20.      * 
  21.      * @param context 
  22.      * @param attrs 
  23.      * @param defStyleAttr 
  24.      * @param defStyleRes 
  25.      * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount 
  26.      */  
  27.     public FocusGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {  
  28.         super(context, attrs, defStyleAttr, defStyleRes);  
  29.     }  
  30.   
  31.     /** 
  32.      * Creates a vertical GridLayoutManager 
  33.      * 
  34.      * @param context   Current context, will be used to access resources. 
  35.      * @param spanCount The number of columns in the grid 
  36.      */  
  37.     public FocusGridLayoutManager(Context context, int spanCount) {  
  38.         super(context, spanCount);  
  39.     }  
  40.   
  41.     /** 
  42.      * @param context       Current context, will be used to access resources. 
  43.      * @param spanCount     The number of columns or rows in the grid 
  44.      * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link 
  45.      *                      #VERTICAL}. 
  46.      * @param reverseLayout When set to true, layouts from end to start. 
  47.      */  
  48.     public FocusGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {  
  49.         super(context, spanCount, orientation, reverseLayout);  
  50.     }  
  51.   
  52.     /** 
  53.      * Return the current number of child views attached to the parent RecyclerView. 
  54.      * This does not include child views that were temporarily detached and/or scrapped. 
  55.      * 
  56.      * @return Number of attached children 
  57.      */  
  58.     @Override  
  59.     public int getChildCount() {  
  60.         return super.getChildCount();  
  61.     }  
  62.   
  63.     /** 
  64.      * Return the child view at the given index 
  65.      * 
  66.      * @param index Index of child to return 
  67.      * @return Child view at index 
  68.      */  
  69.     @Override  
  70.     public View getChildAt(int index) {  
  71.         return super.getChildAt(index);  
  72.     }  
  73.   
  74.     /** 
  75.      * Returns the number of items in the adapter bound to the parent RecyclerView. 
  76.      * @return The number of items in the bound adapter 
  77.      */  
  78.     @Override  
  79.     public int getItemCount() {  
  80.         return super.getItemCount();  
  81.     }  
  82.   
  83.     /** 
  84.      * Returns the item View which has or contains focus. 
  85.      * 
  86.      * @return A direct child of RecyclerView which has focus or contains the focused child. 
  87.      */  
  88.     @Override  
  89.     public View getFocusedChild() {  
  90.         return super.getFocusedChild();  
  91.     }  
  92.   
  93.     /** 
  94.      * Returns the adapter position of the item represented by the given View. This does not 
  95.      * contain any adapter changes that might have happened after the last layout. 
  96.      * 
  97.      * @param view The view to query 
  98.      * @return The adapter position of the item which is rendered by this View. 
  99.      */  
  100.     @Override  
  101.     public int getPosition(View view) {  
  102.         return super.getPosition(view);  
  103.     }  
  104.   
  105.     /** 
  106.      * 获取列数 
  107.      * @return 
  108.      */  
  109.     @Override  
  110.     public int getSpanCount() {  
  111.         return super.getSpanCount();  
  112.     }  
  113.   
  114.     /** 
  115.      * Called when searching for a focusable view in the given direction has failed for the current content of the RecyclerView. 
  116.      * This is the LayoutManager's opportunity to populate views in the given direction to fulfill the request if it can. 
  117.      * The LayoutManager should attach and return the view to be focused. The default implementation returns null. 
  118.      * 防止当recyclerview上下滚动的时候焦点乱跳 
  119.      * @param focused 
  120.      * @param focusDirection 
  121.      * @param recycler 
  122.      * @param state 
  123.      * @return 
  124.      */  
  125.     @Override  
  126.     public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {  
  127.   
  128.         // Need to be called in order to layout new row/column  
  129.         View nextFocus = super.onFocusSearchFailed(focused, focusDirection, recycler, state);  
  130.   
  131.         if (nextFocus == null) {  
  132.             return null;  
  133.         }  
  134.         /** 
  135.          * 获取当前焦点的位置 
  136.          */  
  137.         int fromPos = getPosition(focused);  
  138.         /** 
  139.          * 获取我们希望的下一个焦点的位置 
  140.          */  
  141.         int nextPos = getNextViewPos(fromPos, focusDirection);  
  142.   
  143.         return findViewByPosition(nextPos);  
  144.   
  145.     }  
  146.   
  147.     /** 
  148.      * Manually detect next view to focus. 
  149.      * 
  150.      * @param fromPos from what position start to seek. 
  151.      * @param direction in what direction start to seek. Your regular {@code View.FOCUS_*}. 
  152.      * @return adapter position of next view to focus. May be equal to {@code fromPos}. 
  153.      */  
  154.     protected int getNextViewPos(int fromPos, int direction) {  
  155.         int offset = calcOffsetToNextView(direction);  
  156.   
  157.         if (hitBorder(fromPos, offset)) {  
  158.             return fromPos;  
  159.         }  
  160.   
  161.         return fromPos + offset;  
  162.     }  
  163.   
  164.     /** 
  165.      * Calculates position offset. 
  166.      * 
  167.      * @param direction regular {@code View.FOCUS_*}. 
  168.      * @return position offset according to {@code direction}. 
  169.      */  
  170.     protected int calcOffsetToNextView(int direction) {  
  171.         int spanCount = getSpanCount();  
  172.         int orientation = getOrientation();  
  173.   
  174.         if (orientation == VERTICAL) {  
  175.             switch (direction) {  
  176.                 case View.FOCUS_DOWN:  
  177.                     return spanCount;  
  178.                 case View.FOCUS_UP:  
  179.                     return -spanCount;  
  180.                 case View.FOCUS_RIGHT:  
  181.                     return 1;  
  182.                 case View.FOCUS_LEFT:  
  183.                     return -1;  
  184.             }  
  185.         } else if (orientation == HORIZONTAL) {  
  186.             switch (direction) {  
  187.                 case View.FOCUS_DOWN:  
  188.                     return 1;  
  189.                 case View.FOCUS_UP:  
  190.                     return -1;  
  191.                 case View.FOCUS_RIGHT:  
  192.                     return spanCount;  
  193.                 case View.FOCUS_LEFT:  
  194.                     return -spanCount;  
  195.             }  
  196.         }  
  197.   
  198.         return 0;  
  199.     }  
  200.   
  201.     /** 
  202.      * Checks if we hit borders. 
  203.      * 
  204.      * @param from from what position. 
  205.      * @param offset offset to new position. 
  206.      * @return {@code true} if we hit border. 
  207.      */  
  208.     private boolean hitBorder(int from, int offset) {  
  209.         int spanCount = getSpanCount();  
  210.   
  211.         if (Math.abs(offset) == 1) {  
  212.             int spanIndex = from % spanCount;  
  213.             int newSpanIndex = spanIndex + offset;  
  214.             return newSpanIndex < 0 || newSpanIndex >= spanCount;  
  215.         } else {  
  216.             int newPos = from + offset;  
  217.             return newPos < 0 && newPos >= spanCount;  
  218.         }  
  219.     }  
  220. }  

   分析:在我们从第五行往下按的时候,第六行的view是重新加载的,当新的一行的item还没有加载出来的时候,去找焦点是找不到的,找不到焦点就会调用mLayout.onFocusSearchFailed()方法,RecyclerView 在tv端 焦点问题_第2张图片

onFocusSearchFailed方法是LayoutManager的方法,默认是返回null的,我们在自定义GridLayoutManager的时候重写此方法即可,具体的处理步骤请看到代码。在RecyclerView源代码中,onFocusSearchFailed是内部抽象类LayoutManager的一个成员方法,默认返回null。

RecyclerView 在tv端 焦点问题_第3张图片

你可能感兴趣的:(android)