在5.0之前有listview gridView 如果分组列表有ExpandableListView,对应着Baseadapter 和 BaseExpandableAdapter,但是在5.0之后recycleView可以代替list到现在列表的展现功能基本都用它了吧
<com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh
android:id="@+id/pullSwipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullRecyclerView>
com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh>
其中MyPullSwipeRefresh包含着MyPullRecyclerView,MyPullRecyclerView
就是列表控件,看看MyPullSwipeRefresh里做什么
class MyPullSwipeRefresh extends SwipeRefreshLayout
MyPullSwipeRefresh继承了官方的刷新控件
// 记录viewPager是否拖拽的标记
case MotionEvent.ACTION_MOVE:
// 如果viewpager正在拖拽中,那么不拦截它的事件,直接return false;
if(mIsVpDragger) {
return false;
}
// 获取当前手指位置
float endY = ev.getY();
float endX = ev.getX();
float distanceX = Math.abs(endX - startX);
float distanceY = Math.abs(endY - startY);
// 如果X轴位移大于Y轴位移,那么将事件交给viewPager处理。
if(distanceX > mTouchSlop && distanceX > distanceY) {
mIsVpDragger = true;
return false;
}
这里解决了头部是viewpager轮播图时下拉刷新和滑动轮播的图的手势冲突
看看MyRecyclerView
MyPullRecyclerView extends MyRecyclerView
MyRecyclerView extends RecyclerView
dividerHeight = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_height, 2);
dividerWidth = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_width, 0);
dividerColor = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_color, ContextCompat.getColor(context, R.color.line_d));
dividerDrawable = attr.getInteger(R.styleable.MyRecyclerView_divider_decoration_drawable, 0);
layoutManagerType = attr.getInt (R.styleable.MyRecyclerView_layoutManagerType, 0);
在MyRecyclerView里指定了一些属性 包括分割线的样式,默认有条灰色的分割线,又判断了RecyclerView得ManagerType
app:divider_decoration_width="0"
app:divider_decoration_height="0"
app:layoutManagerType="linear_vertical"
divider_decoration_height和divider_decoration_width指定为0就看不见分割线了
layoutManagerType 可以不写 在代码中也不用创建layoutManager MyRecyclerView 默认是线性垂直的
看看主要的MyPullRecyclerView
private OnAddMoreListener addMoreListener;
@Override
public void onScrolled(int dx, int dy)
addMoreListener.addMoreListener();
myAdapter.setLoading(true);
myAdapter.getSwipeRefresh().setEnabled(false);
一个加载更多的接口回调,重写onScrolled判断是否加载更多,在加载更多时候调用了这行代码myAdapter.getSwipeRefresh().setEnabled(false); 这是拿到了下拉刷新控件并把他设置为不可用,这是因为在加载更多数据时 不能下拉刷新避免出现操作同一数据源的错误。
继续实现
private MyPullSwipeRefresh pullSwipeRefresh;
private MyPullRecyclerView recyclerView;
private PullRefreshAdapter adapter;
private ArrayList arrayList =new ArrayList<>();
private Demo1Delegate delegate;
pullSwipeRefresh = (MyPullSwipeRefresh) findViewById(R.id.pullSwipeRefresh);
recyclerView = (MyPullRecyclerView) findViewById(R.id.recyclerView);
**重点内容**
delegate = new Demo1Delegate();
adapter = new PullRefreshAdapter(this,arrayList,1,delegate);
recyclerView.setAdapter(adapter);
adapter.setSwipeRefresh(pullSwipeRefresh);
adapter.setSwipeRefresh(pullSwipeRefresh);这个行代码是把下拉刷新控件传过去以至于在加载更多的时候设置为不可用
这里有一个PullRefreshAdapter和Demo1Delegate
adapter = new PullRefreshAdapter(this,arrayList,1,delegate); 创建适配器参数分别为
看看构造方法
public PullRefreshAdapter(Context context, List strings, int headerCount, BaseDelegate baseDelegate)
之前说数据集合是空的 那么什么时候添加数据什么时候显示
private void initData(){
ArrayList initList = new ArrayList<>();
for (int i = 0; i <20 ; i++) {
Demo1 demo1 = new Demo1();
demo1.setName("第"+i+"条");
initList.add(demo1);
}
adapter.setTotalPage(2);
adapter.setPullData(initList);
}
这里又创建了新的集合,其实这很符合从服务器拿到数据在显示的流程
adapter.setTotalPage(2); 这是设置总页数这个必须指定 默认为0 设置为2就是可以加载两页,你可以设置成服务器返回的总页数
看看adapter.setPullData(initList);内部的实现
if (DEFAULT_PAGE == pageIndex) {
resetData(data);
if (mySwipeRefresh != null) {
mySwipeRefresh.setRefreshing(false);
}
} else {
addData(data);
isLoading = false;
mySwipeRefresh.setEnabled(true);
}
根据页数判断 如果是第一页数据 就重置数据 如果不是就添加新数据,因为下拉刷新和加载更多 拿到数据后都会调用
adapter.setTotalPage(2);
adapter.setPullData(initList);
这两行代码
那这个页数是怎么管理的呢
recyclerView.setOnAddMoreListener(new MyPullRecyclerView.OnAddMoreListener()
adapter.addPageIndex();
在加载更多的监听事件中 首先调用addPageIndex()方法
public void addPageIndex() {
this.pageIndex++;
}
pageIndex自动加1 ,那么你在请求服务器传递的页数时就调用下面代码获得当前页数
adapter.getPageIndex();
如果不需要加载更多功能可以设置adapter.setTotalPage(0);或者直接用MyRecyclerView而不是MyPullRecyclerView
下拉刷新时
pullSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
adapter.resetPageIndex();
先调用adapter.resetPageIndex();把页数设置为0
最后看看Demo1Delegate
public class Demo1Delegate extends BaseDelegate<Demo1> {
public Demo1Delegate() {
super(R.layout.header, R.layout.item);
}
@Override
public void initHeaderView(BaseViewHolder holder) {
TextView header = holder.findViewById(R.id.header);
header.setText("我是header");
super.initHeaderView(holder);
}
@Override
public void initCustomView(BaseViewHolder holder, List data, int position) {
TextView str = holder.findViewById(R.id.str);
str.setText(data.get(position).getName());
super.initCustomView(holder, data, position);
}
}
R.layout.header, R.layout.item 是头部不布局和 列表item布局
initHeaderView initCustomView方法相当于listview 的 getView和recycleView的bindView方法
class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<BaseViewHolder>
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return BaseViewHolder.creatViewHolder(mContext, parent, this.baseDelegate.getLayoutId(viewType));
}
@Override
public void onBindViewHolder(final BaseViewHolder holder, final int position) {
this.baseDelegate.initView(holder, mData, position - headerCount, getItemViewType(position));
这俩方法大家都不陌生
baseDelegate.initView方法里:
if (viewType == BaseRecyclerAdapter.RECYCLE_TYPE_HEADER) {
initHeaderView(holder);
} else if (viewType == BaseRecyclerAdapter.RECYCLE_TYPE_FOOTER) {
initFooterView(holder, position);
} else {
initCustomView(holder, obj, position);
}
明白了吧 所以继承baseDelegate 实现initXXX方法
最后说下点击事件调用
adapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener()
recycleView实现分组效果网上很多资料直接用getItemViewType方式 甚至可以直接根据数据源操作layou的隐藏和显示,用过ExpandableListView都知道这种效果很简单,使用ExpandableListView 的数据源就是一个group对应一个集合的child,我认为这总思路实现起来很好理解 数据源处理上也很清晰,现在就像使用ExpandableListView一样使用recycleView实现分组列表
<com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh
android:id="@+id/pullSwipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:divider_decoration_width="0"
app:divider_decoration_height="0"
>
com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView>
com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh>
跟之前一样但这里要改成ExpandablePullRecyclerView
delegate = new Demo2Delegate(this);
adapter = new ExpandablePullRefreshAdapter(this,demo2Groups,1,delegate);
recyclerView.setAdapter(adapter);
adapter.setSwipeRefresh(pullSwipeRefresh);
adapter.setClickGroup(true);
同样这里创建适配器的时候指定 1个header
adapter.setClickGroup(true);方法默认为true 是否可以点击头部展开收缩
public class Demo2Delegate extends ExpandableBaseDelegate
@Override
public int getGroupCount()
@Override
public int getChildCount(int groupPosition)
@Override
public Object getGroup(int groupPosition)
@Override
public Object getChild(int groupPosition, int childPosition)
创建delegate 继承ExpandableBaseDelegate
注意:这里的泛型同样是你自己定义好的实体
看看上面4个方法是不是跟BaseExpandableAdapter里的方法很相像,再看看我的实体类
public class Demo2Group {
private String name;
private ArrayList list;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList getList() {
return list;
}
public void setList(ArrayList list) {
this.list = list;
}
}
这样的数据源一看就非常的清晰
@Override
public void initHeaderView(ExpandableBaseViewHolder holder)
@Override
public void initGroupView(ExpandableBaseViewHolder holder, ExpandableBaseRecyclerAdapter.GroupModel groupModel, Object groupObject, int position)
public void initChildView(ExpandableBaseViewHolder holder, Object groupObject, Object childObject, int position)
**重点内容**
public void setArrayList(ArrayList arrayList) {
this.arrayList = arrayList;
}
上面有三个方法时绑定显示数据的方法
看看groupModel
public class GroupModel {
boolean isOpen;
boolean isShow;
Object mObject;
int groupPostion;
List childList;
这个GroupModel 我也参考了网上别人的信息 分别为是否展开 是否显示 group的实体对象 group的索引 child集合 有用的基本都全了
得到数据后
**重点内容**
delegate.setArrayList(groups);
adapter.setTotalPage(2);
adapter.setPullData(groups);
在获得数据后的调用除了要写setArrayList(groups) 同样要设置总页数和设置数据
看看ExpandableBaseRecyclerAdapter:
class ExpandableBaseRecyclerAdapter<T> extends RecyclerView.Adapter<ExpandableBaseViewHolder> {
public static final int RECYCLE_TYPE_ITEM_GROUP = 1003; //item group类型
public static final int RECYCLE_TYPE_ITEM_CHILD = 1000; //item child 类型
public static final int RECYCLE_TYPE_HEADER = 1001; //头部类型
public static final int RECYCLE_TYPE_FOOTER = 1002;//底部类型
这里多两个类型group child 同样在getItemViewType里要判断好类型
public void resetData(List newData) {
if (this.mData != null && newData != null) {
this.mData.clear();
ArrayList dataList = new ArrayList();
for (int i = 0; i < baseDelegate.getGroupCount(); i++) {
GroupModel groupModel = new GroupModel(true, true,baseDelegate.getGroup(i),i);
dataList.add(groupModel);
for (int j = 0; j < baseDelegate.getChildCount(i); j++) {
groupModel.childList.add(baseDelegate.getChild(i, j));
}
}
this.mData.addAll(dataList);
notifyDataSetChanged();
}
}
在你设置数据后 这里又用GroupModel 给你包了一层 然后在onBindViewHolder方法里给你返回对应的groupModel
点击事件:
adapter.setOnItemClickListener(new ExpandableBaseRecyclerAdapter.OnItemClickListener() {
@Override
public void onGroupItemClick(View view, int groupPosition) {
}
@Override
public void onChildItemClick(View view, int groupPosition, int childPosition) {
}
});
这里的groupPosition childPosition用过ExpandableListView一看就明白了
有的页面需求会有分组列表悬停吸顶效果,网上实现这种效果基本上有两种方式,第一种就是RecyclerView.ItemDecoration的方式,绘制分组,但是我这种分组逻辑应该不在适应这种方式,所以我用第二种更简单的方式实现
<com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh
android:id="@+id/pullSwipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
"match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:divider_decoration_height="0"
app:divider_decoration_width="0"
>
com.myrecyclers.tcy.imitationrecyclerslibrary.ExpandablePullRecyclerView>
**重点内容**
"match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
"@layout/header">
"@layout/item_group">
com.myrecyclers.tcy.imitationrecyclerslibrary.MyPullSwipeRefresh>
添加一个FrameLayout 如果有头部就把头部布局和group布局都放在一个线性布局里,如果没有头部就直接引用 group布局
header_ly = (RelativeLayout) findViewById(R.id.header_ly);
header_ly.setVisibility(View.INVISIBLE);
group_ly = (LinearLayout) findViewById(R.id.group_ly);
group_ly.setVisibility(View.INVISIBLE);
str = (TextView) findViewById(R.id.str);
**重点内容**
recyclerView.setHeaderView(header_ly);
recyclerView.setStickyheaderView(group_ly);
recyclerView.setStickyheaderText(str);
adapter.setClickGroup(false);
如果有头部 把头部布局和group布局设置为INVISIBLE 并设置三个方法 如果没有头部不需要设置setVisibility 不调用setHeaderView方法
设置setClickGroup方法使group不可点击,因为悬停了点击功能就没有必要了,你如果又要点击收缩效果又要悬停效果的话,自己要写group_ly的点击事件因为悬停在最上面的group是个临时的 不是真正的 item group
在你的initGroupView initChildView方法里设置
**重点内容**
holder.itemView.setContentDescription(demo2Group.getName());
注意:分组的列表只试用于LinearLayoutManager类型
github:https://github.com/tangchaoyu/ImitatioRecyclers