自定义Adapter实现RecyclerView的可展开二级列表expand效果
来源:互联网 发布:unity3d 布料插件 编辑:程序博客网 时间:2024/06/01 18:24
网上实现可展开效果的RecyclerView做法很多,但转发党太多,几乎找不到比较符合效率的做法,其中坑也不少。
想着RecyclerView这么强大,决定自己研究一个,并基于以下四个原则:
1、作为一个有强迫症的人,我只想仅用一个RecyclerView搞定这个效果,不想任何RecyclerView嵌套GridView或者ListView之类的想着就蛋疼的做法,代码也不优美。
2、RecyclerView显示什么,它的数据列表应该也跟显示一致,这样比较好维护。
3、对item或者是item里面的一些控件的点击处理,希望能在Activity中监听处理,Adapter中仅对数据进行显示处理,不涉及复杂的修改数据信息的操作。
4、设想是通过RecyclerView的添加和移除item方式来做展开和收起的效果,这样可以利用RecyclerVIewinsert和remove item的动画效果,还可以自由设定展开的信息类似GridView的形式。大致效果如下图:
正式开始实现这个效果:
一、首先定义数据类CourseInfo、ChapterInfo、SectionInfo,这三个都继承同一个BaseInfo。
public class CourseInfo extends BaseInfo { public int id; public String name; public List<ChapterInfo> chapterInfos = new ArrayList<>();}public class ChapterInfo extends BaseInfo { public String name; public int chapterIndex; public List<SectionInfo> sectionInfos = new ArrayList<>();}public class SectionInfo extends BaseInfo { public String name; public int chapterIndex; public int sectionIndex;}public class BaseInfo implements Serializable {}
二、重要的adapter实现方法如下:
之所以前面的数据类型都继承同一个BaseInfo,就是为了能把不同数据都装进一个list中。
原理主要是传入的数据和显示的数据分开,维护一个显示数据列表,展开就添加item,收起就移除item,这样添加和移除都可以利用RecyclerView自身的动画效果。
当然,如果想更改动画效果貌似还可以自定义自己的ItemAnimator,这个有空可以研究研究。
package com.ldw.testwork.expandrecyclerview;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.GridView;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.TextView;import com.ldw.testwork.R;import java.util.ArrayList;import java.util.List;import timber.log.Timber;/** * 一个可展开和收起的RecyclerView数据处理,传进的数据和显示的数据分开,展开添加item,收起则删除item。 * Created by ldw on 2017/12/1. */public class ChapterAdapter extends RecyclerView.Adapter implements View.OnClickListener { public static final int VIEW_TYPE_CHAPTER = 1; public static final int VIEW_TYPE_SECTION = 2; //传进来的课程信息 private CourseInfo courseInfo; //显示的数据集 private List<BaseInfo> dataInfos = new ArrayList<>(); //当前展开的课时,-1代表没有任何展开 private int curExpandChapterIndex = -1; public ChapterAdapter(CourseInfo _courseInfo) { this.courseInfo = _courseInfo; for(BaseInfo info : courseInfo.chapterInfos){ dataInfos.add(info); } } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView; if(viewType == VIEW_TYPE_CHAPTER){ itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_chapter, parent, false); return new ItemHolder(itemView); }else{ itemView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_section, parent, false); return new ItemSectionHolder(itemView); } } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { //Timber.v("---onBindViewHolder---position = "+position); if(getItemViewType(position) == VIEW_TYPE_CHAPTER){ ItemHolder itemHolder = (ItemHolder) holder; itemHolder.itemView.setTag(position); itemHolder.tvPractise.setTag(position); ChapterInfo chapterInfo = (ChapterInfo) dataInfos.get(position); itemHolder.tvName.setText(chapterInfo.name); if(chapterInfo.sectionInfos.size() > 0){ itemHolder.ivArrow.setVisibility(View.VISIBLE); if(curExpandChapterIndex == position){ itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_up); }else{ itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_down); } }else{ itemHolder.ivArrow.setVisibility(View.INVISIBLE); } }else{ ItemSectionHolder itemSectionHolder = (ItemSectionHolder) holder; itemSectionHolder.tvName.setTag(position); SectionInfo sectionInfo = (SectionInfo) dataInfos.get(position); itemSectionHolder.tvName.setText(sectionInfo.name); } } //该方法只更改itemView的部分信息,不全部刷新 @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads) { //Timber.v("---onBindViewHolder---payloads = "+payloads + ", "+position); if(payloads.isEmpty()){ super.onBindViewHolder(holder, position, payloads); }else{ String str = (String) payloads.get(0); //更改view的tag if(str.equals("change_position")){ if(getItemViewType(position) == VIEW_TYPE_CHAPTER){ ItemHolder itemHolder = (ItemHolder) holder; itemHolder.itemView.setTag(position); itemHolder.tvPractise.setTag(position); //改变箭头方向 if(curExpandChapterIndex == position){ itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_up); }else{ itemHolder.ivArrow.setBackgroundResource(R.drawable.arrow_down); } }else{ ItemSectionHolder itemSectionHolder = (ItemSectionHolder) holder; itemSectionHolder.tvName.setTag(position); } } } } @Override public long getItemId(int i) { return i; } @Override public int getItemCount() { if(dataInfos == null){ return 0; }else{ return dataInfos.size(); } } @Override public int getItemViewType(int position) { if(dataInfos.get(position) instanceof ChapterInfo){ return VIEW_TYPE_CHAPTER; }else if(dataInfos.get(position) instanceof SectionInfo){ return VIEW_TYPE_SECTION; } return super.getItemViewType(position); } public class ItemHolder extends RecyclerView.ViewHolder { public LinearLayout llBg; public ImageView ivArrow; public TextView tvName; public TextView tvPractise; public LinearLayout llSection; public GridView gvSection; public ItemHolder(View itemView) { super(itemView); ivArrow = (ImageView) itemView.findViewById(R.id.iv_item_chapter_arrow); tvName = (TextView) itemView.findViewById(R.id.tv_item_chapter_name); tvPractise = (TextView) itemView.findViewById(R.id.tv_item_chapter_practise); //将创建的View注册点击事件 itemView.setOnClickListener(ChapterAdapter.this); tvPractise.setOnClickListener(ChapterAdapter.this); } } public class ItemSectionHolder extends RecyclerView.ViewHolder { public TextView tvName; public ItemSectionHolder(View itemView) { super(itemView); tvName = (TextView) itemView.findViewById(R.id.tv_item_section_name); //将创建的View注册点击事件 tvName.setOnClickListener(ChapterAdapter.this); } } ////////////////////////////以下为item点击处理/////////////////////////////// private OnRecyclerViewItemClickListener mOnItemClickListener = null; public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) { this.mOnItemClickListener = listener; } /** item里面有多个控件可以点击 */ public enum ViewName { CHAPTER_ITEM, CHAPTER_ITEM_PRACTISE, SECTION_ITEM } public interface OnRecyclerViewItemClickListener { void onClick(View view, ViewName viewName, int chapterIndex, int sectionIndex); } @Override public void onClick(View v) { if (mOnItemClickListener != null) { //注意这里使用getTag方法获取数据 int position = (int) v.getTag(); ViewName viewName = ViewName.CHAPTER_ITEM; int chapterIndex = -1; int sectionIndex = -1; if(getItemViewType(position) == VIEW_TYPE_CHAPTER){ ChapterInfo chapterInfo = (ChapterInfo) dataInfos.get(position); chapterIndex = chapterInfo.chapterIndex; sectionIndex = -1; if(v.getId() == R.id.tv_item_chapter_practise){ viewName = ViewName.CHAPTER_ITEM_PRACTISE; }else{ viewName = ViewName.CHAPTER_ITEM; if(chapterInfo.sectionInfos.size() > 0){ if(chapterIndex == curExpandChapterIndex){ narrow(curExpandChapterIndex); }else{ narrow(curExpandChapterIndex); expand(chapterIndex); } } } }else if(getItemViewType(position) == VIEW_TYPE_SECTION){ SectionInfo sectionInfo = (SectionInfo) dataInfos.get(position); viewName = ViewName.SECTION_ITEM; chapterIndex = sectionInfo.chapterIndex; sectionIndex = sectionInfo.sectionIndex; } mOnItemClickListener.onClick(v, viewName, chapterIndex, sectionIndex); } } /** * 展开某个item * @param chapterIndex */ private void expand(int chapterIndex){ dataInfos.addAll(chapterIndex+1, courseInfo.chapterInfos.get(chapterIndex).sectionInfos); curExpandChapterIndex = chapterIndex; Timber.v("---expand---"+(chapterIndex+1)+", "+courseInfo.chapterInfos.get(chapterIndex).sectionInfos.size()); notifyItemRangeInserted(chapterIndex+1, courseInfo.chapterInfos.get(chapterIndex).sectionInfos.size()); /*notifyItemRangeChanged(chapterIndex + 1 + courseInfo.chapterInfos.get(chapterIndex).sectionInfos.size(), getItemCount() - chapterIndex - 1, "change_position");*/ notifyItemRangeChanged(0, getItemCount(), "change_position"); } /** * 收起某个item * @param chapterIndex */ private void narrow(int chapterIndex){ if(chapterIndex != -1){ int removeStart = chapterIndex + 1; int removeCount = 0; for(int i=removeStart; i<dataInfos.size() && getItemViewType(i) == VIEW_TYPE_SECTION; i++){ removeCount++; } dataInfos.removeAll(courseInfo.chapterInfos.get(chapterIndex).sectionInfos); curExpandChapterIndex = -1; Timber.v("---narrow---"+removeStart+", "+removeCount); notifyItemRangeRemoved(removeStart, removeCount); //notifyItemRangeChanged(removeStart, getItemCount() - removeStart, "change_position"); notifyItemRangeChanged(0, getItemCount(), "change_position"); } }}
三、布局文件,activity只是一个RecyclerView,这里不贴出来了。
以下是item_chapter.xml文件:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:background="#eff7e9" android:orientation="horizontal"> <ImageView android:id="@+id/iv_item_chapter_arrow" android:layout_width="20dp" android:layout_height="20dp" android:layout_gravity="center_vertical" android:background="@drawable/arrow_down"/> <TextView android:id="@+id/tv_item_chapter_name" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:textSize="12sp" android:paddingStart="5dp" android:paddingEnd="30dp" android:maxLines="1" android:ellipsize="end" android:gravity="start|center_vertical" android:text="item_chapter_name"/> <TextView android:id="@+id/tv_item_chapter_practise" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dp" android:layout_gravity="center_vertical" android:gravity="center" android:text="practise"/></LinearLayout>以下是item_section.xml文件:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="35dp" android:layout_gravity="center" android:gravity="center"> <TextView android:id="@+id/tv_item_section_name" android:layout_width="80dp" android:layout_height="35dp" android:layout_gravity="center" android:gravity="center" android:text="section"/></RelativeLayout>
四、Activity中的代码:
package com.ldw.testwork;import android.os.Bundle;import android.support.annotation.Nullable;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.View;import com.ldw.testwork.expandrecyclerview.ChapterAdapter;import com.ldw.testwork.expandrecyclerview.ChapterInfo;import com.ldw.testwork.expandrecyclerview.CourseInfo;import com.ldw.testwork.expandrecyclerview.SectionInfo;import com.ldw.testwork.utils.ToastUtil;import timber.log.Timber;public class ExpandRecyclerViewActivity extends AppCompatActivity { RecyclerView mRecyclerView; CourseInfo mCourseInfo; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_expandrecyclerview); initData(); initViews(); } private void initData(){ //假数据 mCourseInfo = new CourseInfo(); mCourseInfo.name = "假装是课程的名称"; for(int i=0; i<31; i++){ ChapterInfo chapterInfo = new ChapterInfo(); chapterInfo.name = "假装是课时名称"+(i+1); chapterInfo.chapterIndex = i; if(i==0){ for(int j=0; j<2; j++){ SectionInfo sectionInfo = new SectionInfo(); sectionInfo.name = "第"+(j+1)+"节"; sectionInfo.chapterIndex = i; sectionInfo.sectionIndex = j; chapterInfo.sectionInfos.add(sectionInfo); } }else if(i==1){ for(int j=0; j<3; j++){ SectionInfo sectionInfo = new SectionInfo(); sectionInfo.name = "第"+(j+1)+"节"; sectionInfo.chapterIndex = i; sectionInfo.sectionIndex = j; chapterInfo.sectionInfos.add(sectionInfo); } }else if(i==2){ }else{ for (int j = 0; j < 4; j++) { SectionInfo sectionInfo = new SectionInfo(); sectionInfo.name = "第" + (j + 1) + "节"; sectionInfo.chapterIndex = i; sectionInfo.sectionIndex = j; chapterInfo.sectionInfos.add(sectionInfo); } } mCourseInfo.chapterInfos.add(chapterInfo); } } private void initViews(){ mRecyclerView = (RecyclerView) findViewById(R.id.rv_expand); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); final ChapterAdapter chapterAdapter = new ChapterAdapter(mCourseInfo); mRecyclerView.setAdapter(chapterAdapter); chapterAdapter.setOnItemClickListener(new ChapterAdapter.OnRecyclerViewItemClickListener() { @Override public void onClick(View view, ChapterAdapter.ViewName viewName, int chapterIndex, int sectionIndex) { //Timber.v("---onClick---"+viewName+", "+chapterIndex+", "+sectionIndex); switch (viewName){ case CHAPTER_ITEM: if(mCourseInfo.chapterInfos.get(chapterIndex).sectionInfos.size() > 0){ Timber.v("---onClick---just expand or narrow: "+chapterIndex); if(chapterIndex + 1 == mCourseInfo.chapterInfos.size()){ //如果是最后一个,则滚动到展开的最后一个item mRecyclerView.smoothScrollToPosition(chapterAdapter.getItemCount()); Timber.v("---onClick---scroll to bottom"); } }else{ onClickChapter(chapterIndex); } break; case CHAPTER_ITEM_PRACTISE: onClickPractise(chapterIndex); break; case SECTION_ITEM: onClickSection(chapterIndex, sectionIndex); break; } } }); //以下是对布局进行控制,让课时占据一行,小节每四个占据一行,结果就是相当于一个ListView嵌套GridView的效果。 final GridLayoutManager manager = new GridLayoutManager(this, 4); manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return chapterAdapter.getItemViewType(position) == ChapterAdapter.VIEW_TYPE_CHAPTER ? 4 : 1; } }); mRecyclerView.setLayoutManager(manager); } private void onClickChapter(int chapterIndex){ Timber.v("---onClick---play chapter: "+chapterIndex); ToastUtil.showToast(ExpandRecyclerViewActivity.this, "播放"+chapterIndex); } private void onClickSection(int chapterIndex, int sectionIndex){ Timber.v("---onClick---play---section: "+chapterIndex+", "+sectionIndex); ToastUtil.showToast(ExpandRecyclerViewActivity.this, "播放"+chapterIndex+", "+sectionIndex); } private void onClickPractise(int chapterIndex){ Timber.v("---onClick---practise: "+chapterIndex); }}
五、代码看似简单,其中adapter有几个坑:
1、插入或者移除后其他的item中的position不会变(此position不仅是指我设置的tag那个position),比如原来有4个item,position为0 1 2 3,中间插入两个item后,position变为0 1 2 3 2 3,非常奇葩,会出现各种问题,当然如果调用notifyDataSetChanged();进行刷新,那不会有什么问题,只是RecyclerView的添加移除item动画效果就没了。网上解决方法千篇一律,重点是还达不到效果。个人的解决方法在2中说明。
2、解决之前,先说明另一个方法public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads),看到没有,它笔平常使用的多了一个参数payloads,经过了解,它的用途是可以在不用刷新整个item,而对部分item中的某些信息进行修改。我们正好利用这个特点,前面说position不会自动更新,那好,我们就调用这个方法notifyItemRangeChanged(0, getItemCount(), "change_position");让它去更新item,这样既不会重新刷新整个item,又能保证position的正确性。这样就完美解决事情了。
六、总结,RecyclerView有以下几个好东西:
ItemDecoration :设置item间隔
GridLayoutManager :设置显示布局,比如上面例子中,哪个item占据一行,哪个item多个占据一行,可以方便实现GridView效果。
itemAnimator:设置item添加和移除等的各种效果。
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads):改方法最后一个参数可以设置不同值,例如notifyItemRangeChanged(0, getItemCount(), "change_position");,从而在更新中能只对item中某个信息进行修改,而不用整个item刷新。
原创文章,转载请注明出处:http://blog.csdn.net/lin_dianwei/article/details/78725014
- 自定义Adapter实现RecyclerView的可展开二级列表expand效果
- RecyclerView实现二级列表
- Android -- RecyclerView(超简单)实现可展开列表
- Android -- RecyclerView(超简单)实现可展开列表
- 使用RecyclerView 简单实现QQ好友列表展开效果
- Android RecyclerView(超简单)实现可展开列表——单项展开
- MVP+OKhttp+拦截器+RecyclerView+自定义view 实现请求网络数据的二级列表购物车
- Expand 二级目录 集合实现的
- Expand 二级目录集合实现的
- Android RecyclerView 二级列表实现
- 二级列表的简单效果实现
- 二级列表实现购物车的效果
- RecyclerView.Adapter的实现
- 使用RecyclerView实现列表展开动画
- Expand()展开树hRoot列表函数
- expand--符号矩阵的展开
- ExpandableListView 二级展开列表
- 展开二级列表
- 【递推+lucas定理】BZOJ2111 [ZJOI2010]Perm 排列计数
- python之sqlalchemy使用
- 布尔类型、引用
- Codeforces Gym-101617F Move Away [计算几何]
- 【java day4】 数组 Array,最大/小值,进制转换
- 自定义Adapter实现RecyclerView的可展开二级列表expand效果
- 数字与静态(HeadfirstJava随笔)(转)
- bzoj 1041 [HAOI2008]圆上的整点
- 三极管开关电路
- Java生成csv文件代码
- netty源码分析之-开发过程中重要事项分析(10)
- C++杂忆集(七)
- Eclipse 快捷键
- redis 安装配置