一、RecyclerView的焦点记忆封装的学习

https://www.jianshu.com/p/75465a7fdd0f

二、整体代码压缩包

CustomRecyclerView.zip

三、实现步骤:

1.将聚焦封装,继承Recyclerview即可

image.png

2.将原有的适配器的recyclerview改成FocusKeepRecyclerView

  1. 1)在activity_main.xml中将RecyclerView改成FocusKeepRecyclerView<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572125057-9b20b5eb-a55f-455a-b05f-c236f1b18372.png#align=left&display=inline&height=246&margin=%5Bobject%20Object%5D&name=image.png&originHeight=246&originWidth=623&size=30950&status=done&style=none&width=623)<br /> (2)在RecyclerViewAdapterDemo中将RecyclerView改成FocusKeepRecyclerView<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572180807-58fccbbf-9750-4ed9-9e4d-e9e15c1ff8bd.png#align=left&display=inline&height=190&margin=%5Bobject%20Object%5D&name=image.png&originHeight=190&originWidth=590&size=19998&status=done&style=none&width=590)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572239772-86c91ccc-19ae-494a-b524-486c583699a4.png#align=left&display=inline&height=175&margin=%5Bobject%20Object%5D&name=image.png&originHeight=175&originWidth=613&size=19829&status=done&style=none&width=613)<br /> (3)在MainActivity中将RecyclerView改成FocusKeepRecyclerView<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572305098-ea4b3c5a-c02c-4939-93d8-36e2d59ccf8e.png#align=left&display=inline&height=131&margin=%5Bobject%20Object%5D&name=image.png&originHeight=131&originWidth=471&size=15106&status=done&style=none&width=471)

3.添加item点击,以获取焦点

  1. 1)定义一个接口,创建item点击方法<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572492609-037272d7-0054-4314-a437-b55a240021f8.png#align=left&display=inline&height=151&margin=%5Bobject%20Object%5D&name=image.png&originHeight=151&originWidth=546&size=13421&status=done&style=none&width=546)<br /> (2)创建点击事件,当它不为空的时候,获取点击的位置<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572545145-543416de-48b7-4081-a09e-ad7756b3a6d8.png#align=left&display=inline&height=251&margin=%5Bobject%20Object%5D&name=image.png&originHeight=251&originWidth=619&size=22499&status=done&style=none&width=619)<br /> (3)获取到的点击位置,按确定时,弹窗显示当前获取的位置数<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572607640-7dfdc562-b21c-496d-a5a2-d2abe05217f9.png#align=left&display=inline&height=140&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=644&size=13599&status=done&style=none&width=644)

4.创建item_seletor.xml布局文件,选中和未选中的背景颜色的变化

image.png

四、整体代码

  1. 1.activity_main.xml布局文件
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity">
  8. <com.example.customrecyclerview.FocusKeepRecyclerView
  9. android:id="@+id/rv_demo"
  10. android:layout_width="match_parent"
  11. android:focusable="true"
  12. android:layout_height="match_parent"/>
  13. </LinearLayout>
  1. 2.item_recycler_view.xml布局文件
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="wrap_content"
  6. android:layout_height="wrap_content"
  7. android:layout_margin="8dp"
  8. android:background="@drawable/item_seletor"
  9. tools:context=".MainActivity">
  10. <TextView
  11. android:id="@+id/tv_view"
  12. android:layout_width="100dp"
  13. android:layout_height="100dp"
  14. android:gravity="center"
  15. android:text="测试"
  16. android:textSize="20sp"
  17. android:textColor="#E2583F"
  18. android:focusable="true"/>
  19. </LinearLayout>
  1. 3.item_seletor.xml布局文件
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item android:state_focused="true" android:drawable="@color/teal_200"/>
  4. <item android:state_focused="false" android:drawable="@color/teal_700"/>
  5. </selector>
  1. 4.FocusKeepRecyclerView文件代码
  1. package com.example.customrecyclerview;
  2. import android.content.Context;
  3. import android.util.AttributeSet;
  4. import android.util.Log;
  5. import android.view.View;
  6. import android.view.ViewGroup;
  7. import androidx.annotation.NonNull;
  8. import androidx.annotation.Nullable;
  9. import androidx.recyclerview.widget.RecyclerView;
  10. import java.util.ArrayList;
  11. public class FocusKeepRecyclerView extends RecyclerView {
  12. private static final String TAG = FocusKeepRecyclerView.class.getSimpleName();
  13. //是否可以纵向移出
  14. private boolean mCanFocusOutVertical = true;
  15. //是否可以横向移出
  16. private boolean mCanFocusOutHorizontal = true;
  17. //焦点移出recyclerview的事件监听
  18. private FocusLostListener mFocusLostListener;
  19. //焦点移入recyclerview的事件监听
  20. private FocusGainListener mFocusGainListener;
  21. //默认第一次选中第一个位置
  22. private int mCurrentFocusPosition;
  23. public FocusKeepRecyclerView(@NonNull Context context) {
  24. super(context, null);
  25. }
  26. public FocusKeepRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
  27. super(context, attrs, 0);
  28. }
  29. public FocusKeepRecyclerView(@NonNull Context context,
  30. @Nullable AttributeSet attrs, int defStyleAttr) {
  31. super(context, attrs, defStyleAttr);
  32. setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  33. setChildrenDrawingOrderEnabled(true);
  34. setItemAnimator(null);
  35. this.setFocusable(true);
  36. }
  37. public boolean isCanFocusOutVertical() {
  38. return mCanFocusOutVertical;
  39. }
  40. public void setCanFocusOutVertical(boolean canFocusOutVertical) {
  41. mCanFocusOutVertical = canFocusOutVertical;
  42. }
  43. public boolean isCanFocusOutHorizontal() {
  44. return mCanFocusOutHorizontal;
  45. }
  46. public void setCanFocusOutHorizontal(boolean canFocusOutHorizontal) {
  47. this.mCanFocusOutHorizontal = canFocusOutHorizontal;
  48. }
  49. public View focusSearch(View focused, int direction) {
  50. Log.i(TAG, "focusSearch" + focused + ",direction=" + direction);
  51. View view = super.focusSearch(focused, direction);
  52. if (focused == null) {
  53. return view;
  54. }
  55. if (view != null) {
  56. View nextFocusItemView = findContainingItemView(view);
  57. if (nextFocusItemView == null) {
  58. if (!mCanFocusOutVertical && (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP)) {
  59. //屏蔽焦点纵向移出recyclerview
  60. return focused;
  61. }
  62. if (!mCanFocusOutHorizontal && (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT)) {
  63. //屏蔽焦点横向移出recyclerview
  64. return focused;
  65. }
  66. //调用移出的监听
  67. if (mFocusLostListener != null) {
  68. mFocusLostListener.onFocusLost(focused, direction);
  69. }
  70. return view;
  71. }
  72. }
  73. return view;
  74. }
  75. public void setFocusLostListener(FocusLostListener focusLostListener) {
  76. this.mFocusLostListener = focusLostListener;
  77. }
  78. public interface FocusLostListener {
  79. void onFocusLost(View lastFocusChild, int direction);
  80. }
  81. public void setFocusGainListener(FocusGainListener focusGainListener) {
  82. this.mFocusGainListener = focusGainListener;
  83. }
  84. public interface FocusGainListener {
  85. void onFocusGain(View child, View focused);
  86. }
  87. @Override
  88. public void requestChildFocus(View child, View focused) {
  89. mCurrentFocusPosition = getChildViewHolder(child).getAdapterPosition();
  90. Log.i(TAG, "focusPos=" + focused);
  91. if (!hasFocus()) {
  92. //recyclerview 子view重新获取焦点,调用移入焦点的事件监听
  93. if (mFocusGainListener != null) {
  94. mFocusGainListener.onFocusGain(child, focused);
  95. }
  96. }
  97. //执行过super.requestChildFocus之后hasFocus会变成true
  98. super.requestChildFocus(child, focused);
  99. mCurrentFocusPosition = getChildViewHolder(child).getAdapterPosition();
  100. Log.i(TAG, "focusPos=" + mCurrentFocusPosition);
  101. }
  102. //实现焦点记忆的关键代码
  103. @Override
  104. public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
  105. View view = null;
  106. if (this.hasFocus() || mCurrentFocusPosition < 0 || (view = getLayoutManager().findViewByPosition(mCurrentFocusPosition)) == null) {
  107. super.addFocusables(views, direction, focusableMode);
  108. } else if (view.isFocusable()) {
  109. //将当前的view放到Focusable views列表中,再次移入焦点是会取到该view,实现焦点记忆功能
  110. views.add(view);
  111. } else {
  112. super.addFocusables(views, direction, focusableMode);
  113. }
  114. }
  115. //控制当前焦点最后绘制,防止焦点放大后被遮挡
  116. //原顺序123456789,当4是focus时,绘制顺序变为123567894
  117. @Override
  118. protected int getChildDrawingOrder(int childCount, int i) {
  119. View focusedChild = getFocusedChild();
  120. Log.i(TAG, "focusedChild=" + focusedChild);
  121. if (focusedChild == null) {
  122. return super.getChildDrawingOrder(childCount, i);
  123. } else {
  124. int index = indexOfChild(focusedChild);
  125. Log.i(TAG, "index=" + index + ",i=" + i + "count=" + childCount);
  126. if (i == childCount - 1) {
  127. return index;
  128. }
  129. if (i < index) {
  130. return i;
  131. }
  132. return i + 1;
  133. }
  134. }
  135. }
  1. 5.RecyclerViewAdapterDemo文件代码
  1. package com.example.customrecyclerview;
  2. import android.content.Context;
  3. import android.view.LayoutInflater;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import android.widget.TextView;
  7. import androidx.annotation.NonNull;
  8. //import androidx.recyclerview.widget.RecyclerView;
  9. import java.util.List;
  10. // 1.创建适配器类继承自RecyclerView.Adapter,泛型传入RecyclerView.ViewHolder类。
  11. public class RecyclerViewAdapterDemo extends
  12. FocusKeepRecyclerView.Adapter<RecyclerViewAdapterDemo.MyViewHolder> {
  13. private Context context;
  14. private List<String> list;
  15. private View inflater;
  16. public RecyclerViewAdapterDemo(Context context, List<String> list){
  17. this.context=context;
  18. this.list=list;
  19. }
  20. private OnItemClickListener onItemClickListener;
  21. public interface OnItemClickListener{
  22. void onClick(int position);
  23. }
  24. public void setOnItemClickListener(OnItemClickListener listener){
  25. this.onItemClickListener = listener;
  26. }
  27. //3.重写RecyclerView.Adapter类的相关方法。
  28. @NonNull
  29. @Override
  30. public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
  31. inflater= LayoutInflater.from(context).inflate(R.layout.item_recycler_view,parent,false);
  32. MyViewHolder viewHolder=new MyViewHolder(inflater);
  33. return viewHolder;
  34. }
  35. @Override
  36. public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
  37. holder.tv_view.setText(list.get(position));
  38. holder.itemView.setOnClickListener(new View.OnClickListener() {
  39. @Override
  40. public void onClick(View v) {
  41. if(onItemClickListener!=null){
  42. onItemClickListener.onClick(position);
  43. }
  44. }
  45. });
  46. }
  47. @Override
  48. public int getItemCount() {
  49. return list.size();
  50. }
  51. // 2.创建内部类即RecyclerView.ViewHolder类的子类MyViewHolder,并初始化item的控件。
  52. class MyViewHolder extends FocusKeepRecyclerView.ViewHolder{
  53. private TextView tv_view;
  54. public MyViewHolder(@NonNull View itemView) {
  55. super(itemView);
  56. tv_view=itemView.findViewById(R.id.tv_view);
  57. }
  58. }
  59. }
  1. 6.MainActivity文件代码
  1. package com.example.customrecyclerview;
  2. import androidx.appcompat.app.AppCompatActivity;
  3. import androidx.recyclerview.widget.GridLayoutManager;
  4. import androidx.recyclerview.widget.LinearLayoutManager;
  5. import androidx.recyclerview.widget.RecyclerView;
  6. import android.content.Context;
  7. import android.os.Bundle;
  8. import android.widget.Toast;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. public class MainActivity extends AppCompatActivity {
  12. // 1.获取RecyclerView对象
  13. private FocusKeepRecyclerView recyclerView;
  14. private RecyclerViewAdapterDemo adapterDemo;
  15. private Context context;
  16. private List<String> list;
  17. @Override
  18. protected void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. setContentView(R.layout.activity_main);
  21. context=this;
  22. // 2.初始化数据
  23. recyclerView=findViewById(R.id.rv_demo);
  24. //3.适配器实例化
  25. list=new ArrayList<>();
  26. for (int i=0;i<9;i++){
  27. list.add("第"+i+"个测试");
  28. }
  29. adapterDemo=new RecyclerViewAdapterDemo(context,list);
  30. // 4.设置LayoutManager
  31. GridLayoutManager manager=new GridLayoutManager(context,3);
  32. manager.setOrientation(GridLayoutManager.VERTICAL);
  33. // LinearLayoutManager manager=new LinearLayoutManager(context);
  34. // manager.setOrientation(LinearLayoutManager.VERTICAL);
  35. recyclerView.setLayoutManager(manager);
  36. // 5. 设置Adapter
  37. recyclerView.setAdapter(adapterDemo);
  38. adapterDemo.setOnItemClickListener(new RecyclerViewAdapterDemo.OnItemClickListener() {
  39. @Override
  40. public void onClick(int position) {
  41. Toast.makeText(MainActivity.this,""+position,Toast.LENGTH_SHORT).show();
  42. }
  43. });
  44. }
  45. }
  1. 7.结果图:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1574587/1616572868577-fd5a756b-a88f-4099-ab99-6547f2c27c69.png#align=left&display=inline&height=887&margin=%5Bobject%20Object%5D&name=image.png&originHeight=887&originWidth=1845&size=230901&status=done&style=none&width=1845)