• 14_自定义ItemDecoration实现qq好友列表分组效果


    14_自定义ItemDecoration实现qq好友列表分组效果

    一.先上效果

    在这里插入图片描述

    二.RecyclerView实现简单分组

    RecyclerView比较常规的用法,显示多item布局,直接上代码:

    public class SectionListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        private List<SectionItem> datas;
        private Context context;
        public static final int ITEM_TYPE_SECTION = 1000;
        public static final int ITEM_TYPE_NORMAL = 1001;
    
        public SectionListAdapter(Context context, List<SectionItem> datas) {
            this.context = context;
            this.datas = datas;
        }
    
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            RecyclerView.ViewHolder holder = null;
            if (viewType == ITEM_TYPE_SECTION) {
                View itemView = LayoutInflater.from(context).inflate(R.layout.section_item, parent, false);
                holder = new SectionItemHolder(itemView);
            } else {
                View itemView = LayoutInflater.from(context).inflate(R.layout.group_item, parent, false);
                holder = new NormalItemHolder(itemView);
            }
            return holder;
        }
    
        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            SectionItem sectionItem = datas.get(position);
            if(getItemViewType(position) == ITEM_TYPE_SECTION) {
                SectionItemHolder sectionItemHolder = (SectionItemHolder) holder;
                sectionItemHolder.tvSectionName.setText(sectionItem.name);
                sectionItemHolder.tvButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(context, sectionItem.name, Toast.LENGTH_SHORT).show();
                    }
                });
            } else {
                NormalItemHolder normalItemHolder = (NormalItemHolder) holder;
                normalItemHolder.tvGroupName.setText(sectionItem.name);
            }
        }
    
        @Override
        public int getItemCount() {
            return datas != null ? datas.size():0;
        }
    
        @Override
        public int getItemViewType(int position) {
            if(datas != null && datas.size() > position) {
                if(datas.get(position).isSection) {
                    return ITEM_TYPE_SECTION;
                }
            }
            return ITEM_TYPE_NORMAL;
        }
    
        protected static class SectionItemHolder extends RecyclerView.ViewHolder {
    
            private Button tvButton;
            private TextView tvSectionName;
    
            public SectionItemHolder(@NonNull View itemView) {
                super(itemView);
                tvSectionName = itemView.findViewById(R.id.tv_section_item_name);
                tvButton = itemView.findViewById(R.id.tv_button);
            }
    
        }
    
        protected static class NormalItemHolder extends RecyclerView.ViewHolder {
    
            TextView tvGroupName;
    
            public NormalItemHolder(@NonNull View itemView) {
                super(itemView);
                tvGroupName = itemView.findViewById(R.id.tv_group_item_name);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    public class MainActivity extends AppCompatActivity {
    
        private ActivityMainBinding dataBinding;
        private List<SectionItem> datas = new ArrayList<>();
        private SectionListAdapter mAdapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            dataBinding =  DataBindingUtil.setContentView(this, R.layout.activity_main);
            dataBinding.groupList.setLayoutManager(new LinearLayoutManager(this));
    
            datas.add(new SectionItem("group1", "group1", true));
            for(int i=0; i < 10; i++) {
                datas.add(new SectionItem("item" + i, "group1", false));
            }
    
            datas.add(new SectionItem("group2", "group2", true));
            for(int i=0; i < 5; i++) {
                datas.add(new SectionItem("item" + i, "group2", false));
            }
    
            datas.add(new SectionItem("group3", "group3", true));
            for(int i=0; i < 20; i++) {
                datas.add(new SectionItem("item" + i, "group3", false));
            }
    
            mAdapter = new SectionListAdapter(this, datas);
            dataBinding.groupList.setAdapter(mAdapter);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    三.自定义ItemDecoration实现吸顶

    在ItemDecoration的实现方法中,有两个方法是用于绘制的,一个是onDraw,一个是onDrawOver,onDraw方法中绘制的内容如果和RecyclerView的item有重叠,重叠区域会被item覆盖,常用于在RecyclerView的item周围绘制分割,而在onDrawOver方法中绘制的内容会覆盖在RecyclerView的item之上,因此我们需要在onDrawOver中进行绘制,才能实现吸顶。具体实现步骤如下:

    • 重写ItemDecoration的getItemOffsets方法,在getItemOffsets方法中直接调用adapter的createViewHolder创建一个新的分组的ViewHolder,后续会提供给onDrawOver进行绘制,并缓存RecyclerView中所有已经创建的分组ViewHolder
    public class SectionDecoration extends RecyclerView.ItemDecoration {
    
        Map<Integer, RecyclerView.ViewHolder> sections = new LinkedHashMap<>();
        private RecyclerView.ViewHolder overViewHolder = null;
    
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            RecyclerView.Adapter adapter = parent.getAdapter();
            int position = parent.getChildLayoutPosition(view);
            if(adapter != null) {
                if(overViewHolder == null) {
                    overViewHolder = adapter.createViewHolder(null, SectionListAdapter.ITEM_TYPE_SECTION);
                }
                if(adapter.getItemViewType(position) == SectionListAdapter.ITEM_TYPE_SECTION) {
                    if(sections.get(position) == null) {
                        sections.put(position, parent.getChildViewHolder(view));
                    }
                }
            }
        }
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 在绘制吸顶之前,我们需要先找到需要吸顶的目标ViewHolder,目标ViewHolder有多大,overViewHolder就有多大,而overViewHolder的位置始终是在RecyclerView的最顶部,因此问题的关键在于怎么寻找目标ViewHolder的position,我们通过一张图来说明怎么寻找目标ViewHolder的position,找到目标ViewHolder的position之后,从缓存中取出Viewholder即为目标ViewHolder。

    在这里插入图片描述

    public class SectionDecoration extends RecyclerView.ItemDecoration {
    
        Map<Integer, RecyclerView.ViewHolder> sections = new LinkedHashMap<>();
        private RecyclerView.ViewHolder overViewHolder = null;
    
        ...
    
        @Override
        public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            RecyclerView.Adapter adapter = parent.getAdapter();
            if(adapter != null) {
                int count = parent.getChildCount();
                int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
                int position = firstVisibleItemPosition;
                if (position > 0) {
                    for (int i = 0; i < count; i++) {
                        View child = parent.getChildAt(i);
                        position = parent.getChildLayoutPosition(child);
                        if (child.getBottom() >= (parent.getPaddingTop()) && adapter.getItemViewType(position + 1) == SectionListAdapter.ITEM_TYPE_SECTION) {
                            break;
                        }
                    }
                }
    
                int sectionPosition = -1;
                for (int i = 0; i <= position; i++) {
                    if(adapter.getItemViewType(i) == SectionListAdapter.ITEM_TYPE_SECTION) {
                        sectionPosition = i;
                    }
                }
    
                RecyclerView.ViewHolder targetViewHolder = null;
                if(sectionPosition != -1) {
                    targetViewHolder = sections.get(sectionPosition);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 绘制overViewHolder
    @Override
    public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        if(adapter != null) {
            int count = parent.getChildCount();
            int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
            int position = firstVisibleItemPosition;
            if (position > 0) {
                for (int i = 0; i < count; i++) {
                    View child = parent.getChildAt(i);
                    position = parent.getChildLayoutPosition(child);
                    if (adapter.getItemViewType(position + 1) == SectionListAdapter.ITEM_TYPE_SECTION) {
                        break;
                    }
                }
            }
    
            int sectionPosition = -1;
            for (int i = 0; i <= position; i++) {
                if(adapter.getItemViewType(i) == SectionListAdapter.ITEM_TYPE_SECTION) {
                  	sectionPosition = i;
                }
            }
    
            RecyclerView.ViewHolder targetViewHolder = null;
            if(sectionPosition != -1) {
              	targetViewHolder = sections.get(sectionPosition);
            }
    
            if(targetViewHolder != null) {
                int width = targetViewHolder.itemView.getMeasuredWidth();
                int height = targetViewHolder.itemView.getMeasuredHeight();
                overViewHolder.itemView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
    
                int top = parent.getPaddingTop();
                int left = parent.getPaddingLeft();
                overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
    
                drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
            }
        }
    }
    
    private void drawOverViewHolder(Canvas canvas, View overSectionView, RecyclerView parent, int left, int top) {
        canvas.save();
        canvas.clipRect(left, parent.getPaddingTop(), left + overSectionView.getMeasuredWidth(), top + overSectionView.getMeasuredHeight());
        canvas.translate(left, top);
        overSectionView.draw(canvas);
        canvas.restore();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 我们先把它添加到RecyclerView中看下效果:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dataBinding =  DataBindingUtil.setContentView(this, R.layout.activity_main);
        dataBinding.groupList.setLayoutManager(new LinearLayoutManager(this));
        dataBinding.groupList.addItemDecoration(new SectionDecoration());
    
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    可以看到吸顶的ViewHolder已经被绘制出来了,但是吸顶item的数据没有动态变化,并且下一个需要吸顶的ViewHolder滑动到当前吸顶的ViewHolder位置时,不会往上推,并且吸顶ViewHolder上的按钮是不可点击状态。

    • 动态改变吸顶ViewHolder的数据
    adapter.onBindViewHolder(overViewHolder, sectionPosition);
    
    int width = targetViewHolder.itemView.getMeasuredWidth();
    int height = targetViewHolder.itemView.getMeasuredHeight();
    overViewHolder.itemView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    • 什么时候触发上推

    找到下一个需要吸顶的ViewHolder的前一个ViewHolder的itemView,如果没找到,那么说明不需要上推,如果找到了:

    if(itemView.getBottom() - recyclerView.getPaddingTop() <= 当前吸顶item.getMeasuredHeight()) {

    ​ 开始上推

    }

    在这里插入图片描述

    int top = parent.getPaddingTop();
    int left = parent.getPaddingLeft();
    
    RecyclerView.ViewHolder sectionTailHolder = null;
    if(position > 0) {
      	sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
    } else {
        if(adapter.getItemViewType(1) == SectionListAdapter.ITEM_TYPE_SECTION) {
            sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
        }
    }
    if(sectionTailHolder != null && (sectionTailHolder.itemView.getBottom() - top) <= height) {
      	//上推
    }
    
    overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
    drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 实现吸顶ViewHolder上推

    在这里插入图片描述

    int top = parent.getPaddingTop();
    int left = parent.getPaddingLeft();
    
    RecyclerView.ViewHolder sectionTailHolder = null;
    if(position > 0) {
      	sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
    } else {
        if(adapter.getItemViewType(1) == SectionListAdapter.ITEM_TYPE_SECTION) {
            sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
        }
    }
    if(sectionTailHolder != null && (sectionTailHolder.itemView.getBottom() - top) <= height) {
      	//上推
      	top = sectionTailHolder.itemView.getBottom() - overViewHolder.itemView.getMeasuredHeight();
    }
    
    overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
    drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    • 处理点击事件

    吸顶的itemView中的button不能点击的原因在于,吸顶的itemView只是被摆放和绘制到了屏幕上,并没有attachToWindow,而View的点击事件是从window开始分发的,所以我们可以考虑把吸顶的itemView添加到一个已经attachToWindow的ViewGroup中,我们把它添加到RecyclerView的父View中即可。

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        RecyclerView.Adapter adapter = parent.getAdapter();
        int position = parent.getChildLayoutPosition(view);
        if(adapter != null) {
            if(overViewHolder == null) {
                ViewGroup recyclerViewParent = (ViewGroup) parent.getParent();
                overViewHolder = adapter.createViewHolder(recyclerViewParent, SectionListAdapter.ITEM_TYPE_SECTION);
                overViewHolder.itemView.setClipBounds(new Rect(0, 0, 0, 0));
                recyclerViewParent.addView(overViewHolder.itemView);
            }
            if(adapter.getItemViewType(position) == SectionListAdapter.ITEM_TYPE_SECTION) {
                if(sections.get(position) == null) {
                    sections.put(position, parent.getChildViewHolder(view));
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    四.完整代码

    public class SectionDecoration extends RecyclerView.ItemDecoration {
    
        Map<Integer, RecyclerView.ViewHolder> sections = new LinkedHashMap<>();
        private RecyclerView.ViewHolder overViewHolder = null;
    
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            RecyclerView.Adapter adapter = parent.getAdapter();
            int position = parent.getChildLayoutPosition(view);
            if(adapter != null) {
                if(overViewHolder == null) {
                    ViewGroup recyclerViewParent = (ViewGroup) parent.getParent();
                    overViewHolder = adapter.createViewHolder(recyclerViewParent, SectionListAdapter.ITEM_TYPE_SECTION);
                    overViewHolder.itemView.setClipBounds(new Rect(0, 0, 0, 0));
                    recyclerViewParent.addView(overViewHolder.itemView);
                }
                if(adapter.getItemViewType(position) == SectionListAdapter.ITEM_TYPE_SECTION) {
                    if(sections.get(position) == null) {
                        sections.put(position, parent.getChildViewHolder(view));
                    }
                }
            }
        }
    
        @Override
        public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.onDrawOver(canvas, parent, state);
            RecyclerView.Adapter adapter = parent.getAdapter();
            if(adapter != null) {
                int count = parent.getChildCount();
                int firstVisibleItemPosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
                int position = firstVisibleItemPosition;
                if (position > 0) {
                    for (int i = 0; i < count; i++) {
                        View child = parent.getChildAt(i);
                        position = parent.getChildLayoutPosition(child);
                        if (child.getBottom() >= (parent.getPaddingTop()) && adapter.getItemViewType(position + 1) == SectionListAdapter.ITEM_TYPE_SECTION) {
                            break;
                        }
                    }
                }
    
                int sectionPosition = -1;
                for (int i = 0; i <= position; i++) {
                    if(adapter.getItemViewType(i) == SectionListAdapter.ITEM_TYPE_SECTION) {
                        sectionPosition = i;
                    }
                }
    
                RecyclerView.ViewHolder targetViewHolder = null;
                if(sectionPosition != -1) {
                    targetViewHolder = sections.get(sectionPosition);
                }
    
                if(targetViewHolder != null) {
                    adapter.onBindViewHolder(overViewHolder, sectionPosition);
                    int width = targetViewHolder.itemView.getMeasuredWidth();
                    int height = targetViewHolder.itemView.getMeasuredHeight();
                    overViewHolder.itemView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
    
                    int top = parent.getPaddingTop();
                    int left = parent.getPaddingLeft();
    
                    RecyclerView.ViewHolder sectionTailHolder = null;
                    if(position > 0) {
                        sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
                    } else {
    				    if(adapter.getItemViewType(1) == SectionListAdapter.ITEM_TYPE_SECTION) {
    				        sectionTailHolder = parent.findViewHolderForLayoutPosition(position);
    				    }
    				}
                    if(sectionTailHolder != null && (sectionTailHolder.itemView.getBottom() - top) <= height) {
                        //上推
                        top = sectionTailHolder.itemView.getBottom() - overViewHolder.itemView.getMeasuredHeight();
                    }
    
                    overViewHolder.itemView.layout(left + parent.getLeft(), top + parent.getTop(), left + parent.getLeft() + width, top + parent.getTop() + height);
                    drawOverViewHolder(canvas, overViewHolder.itemView, parent, left, top);
                }
            }
        }
    
        private void drawOverViewHolder(Canvas canvas, View overSectionView, RecyclerView parent, int left, int top) {
            canvas.save();
            canvas.clipRect(left, parent.getPaddingTop(), left + overSectionView.getMeasuredWidth(), top + overSectionView.getMeasuredHeight());
            canvas.translate(left, top);
            overSectionView.draw(canvas);
            canvas.restore();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    五.总结

    RecyclerView实现item吸顶的突破口在于ItemDecoration的onDrawOver方法,至于怎么去绘制吸顶item以及处理点击事件,就是仁者见仁、智者见智了。

  • 相关阅读:
    CMake error “include could not find load file: FetchContent“
    Go 语言中的异常处理简单实践 panic、recover【GO 基础】
    一文教会你如何在内网搭建一套属于自己小组的在线 API 文档?
    Kafka配置SSL信道加密
    Linux基础:文件权限详细说明(全)
    设计模式:解释器模式
    【Selenium & Other】一键杀死进程 & 进程清理大师
    【汇编】Debug的使用
    Spark开源REST服务——Apache Livy(Spark 客户端)
    【CSDN|每日一练】小艺读书
  • 原文地址:https://blog.csdn.net/gzx110304/article/details/126306499