Focus Memory Encapsulation of Android RecyclerView

  • 2021-08-31 09:15:09
  • OfStack

The list focus implementation in TV development was introduced in the previous article

android tv list focus memory implementation, is to use external code control way to achieve, more cumbersome, now introduce the use of custom RecyclerView way to achieve, and added other functions: limit vertical and horizontal out of focus, moving in and out of focus event monitoring, etc.

The code is implemented as follows:


import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
public class FocusKeepRecyclerView extends RecyclerView {
  private static final String TAG = FocusKeepRecyclerView.class.getSimpleName();
  // Can I move out vertically 
  private boolean mCanFocusOutVertical = true;
  // Can I move out laterally 
  private boolean mCanFocusOutHorizontal = true;
  // Focus shift out recyclerview Event listening of 
  private FocusLostListener mFocusLostListener;
  // Focus shift in recyclerview Event listening of 
  private FocusGainListener mFocusGainListener;
  // Default number 1 The second selected number 1 Position 
  private int mCurrentFocusPosition = 0;

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

  public FocusKeepRecyclerView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public FocusKeepRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    setChildrenDrawingOrderEnabled(true);
    setItemAnimator(null);
    this.setFocusable(true);
  }

  public boolean isCanFocusOutVertical() {
    return mCanFocusOutVertical;
  }

  public void setCanFocusOutVertical(boolean canFocusOutVertical) {
    mCanFocusOutVertical = canFocusOutVertical;
  }

  public boolean isCanFocusOutHorizontal() {
    return mCanFocusOutHorizontal;
  }

  public void setCanFocusOutHorizontal(boolean canFocusOutHorizontal) {
    mCanFocusOutHorizontal = canFocusOutHorizontal;
  }

  @Override
  public View focusSearch(int direction) {
    return super.focusSearch(direction);
  }

  // Overwrite focusSearch Focus finding strategy 
  @Override
  public View focusSearch(View focused, int direction) {
    Log.i(TAG, "focusSearch " + focused + ",direction= " + direction);
    View view = super.focusSearch(focused, direction);
    if (focused == null) {
      return view;
    }
    if (view != null) {
  // This method returns the focus view Where the father is view, If it is in recyclerview Outside, it will be null. So according to whether it is null, To judge whether it was moved out recyclerview
      View nextFocusItemView = findContainingItemView(view);
      if (nextFocusItemView == null) {
        if (!mCanFocusOutVertical && (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP)) {
          // Shielding focus moves out longitudinally recyclerview
          return focused;
        }
        if (!mCanFocusOutHorizontal && (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
          // Transverse shift of shielding focus recyclerview
          return focused;
        }
       // Invoke removed listening 
        if (mFocusLostListener != null) {
          mFocusLostListener.onFocusLost(focused, direction);
        }
        return view;
      }
    }
    return view;
  }

  public void setFocusLostListener(FocusLostListener focusLostListener) {
    this.mFocusLostListener = focusLostListener;
  }

  public interface FocusLostListener {
    void onFocusLost(View lastFocusChild, int direction);
  }

  public void setGainFocusListener(FocusGainListener focusListener) {
    this.mFocusGainListener = focusListener;
  }

  public interface FocusGainListener {
    void onFocusGain(View child, View focued);
  }

  @Override
  public void requestChildFocus(View child, View focused) {
    Log.i(TAG, "nextchild= " + child + ",focused = " + focused);
    if (!hasFocus()) {
      //recyclerview  Sub view  Recover the focus and call the event listening that moved into the focus 
      if (mFocusGainListener != null) {
        mFocusGainListener.onFocusGain(child, focused);
      }
    }
    super.requestChildFocus(child, focused);// Executed super.requestChildFocus After hasFocus Will become true
    mCurrentFocusPosition = getChildViewHolder(child).getAdapterPosition();
    Log.i(TAG,"focusPos = "+mCurrentFocusPosition);
  }

 // Key codes to realize focus memory 
  @Override
  public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
    View view = null;
    if (this.hasFocus() || mCurrentFocusPosition < 0 || (view = getLayoutManager().findViewByPosition(mCurrentFocusPosition)) == null) {
      super.addFocusables(views,direction,focusableMode);
    }else if(view.isFocusable()){
// Will the current view Put Focusable views List, when you move into focus again, you will get the view, Realize the focus memory function 
      views.add(view);
    }else{
      super.addFocusables(views,direction,focusableMode);
    }
  }
  
  /**
   *  Control the final drawing of the current focus to prevent the focus from being blocked after being enlarged 
   *  Original sequence 123456789 , when 4 Yes focus The drawing order becomes 123567894
   * @param childCount
   * @param i
   * @return
   */
  @Override
  protected int getChildDrawingOrder(int childCount, int i) {
    View focusedChild = getFocusedChild();
    Log.i(TAG,"focusedChild ="+focusedChild);
    if(focusedChild== null){
      return super.getChildDrawingOrder(childCount, i);
    }else{
      int index = indexOfChild(focusedChild);
      Log.i(TAG, " index = " + index + ",i=" + i + ",count=" + childCount);
      if(i == childCount-1){
        return index;
      }
      if(i<index){
        return i;
      }
      return i+1;
    }
  }
}

Code implementation and comments are described above.

Can be directly used as an recyclerview, already has the focus memory function, do not need to add additional code in the outer layer; To increase the ability to limit vertical and horizontal out-of-focus, in-out-of-focus event listening, you can call the above setXXXListener and other methods.


Related articles: