最近项目有个列表页须要实现线性列表和瀑布流展现的切换,首先我想到的就是上
[RecyclerView],他自己已经很好的提供了三种布局方式,只是简单作个切换应该是很简单的事情,若是要用RecyclerView的方式来实现,那就是目前的设计方案(listView)都不能用,更改成RecyclerView,我须要作以下工做:java
为了实现自动加载更多的功能,我选择自定义实现一个特殊的RecyclerView来实现,网络上也有自定义一个包含RecyclerView和loading_more的布局,如今我想试试adapter处理的方式,因此有了这个自定义实现类android
java片断1: RecyclerView实现LoadMoreRecyclerViewgit
/** * Alipay.com Inc. * Copyright (c) 2004-2015 All Rights Reserved. */ package com.leaf8.alicx.myapplication; import android.content.Context; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * 支持上拉加载更多的 * * @author 肖肖 * @version $$Id: LoadMoreRecyclerView.java, v 0.1 11/17/15 10:07 alicx Exp $$ */ public class LoadMoreRecyclerView extends RecyclerView { /** * item 类型 */ public final static int TYPE_NORMAL = 0; public final static int TYPE_HEADER = 1;//头部--支持头部增长一个headerView public final static int TYPE_FOOTER = 2;//底部--每每是loading_more public final static int TYPE_LIST = 3;//表明item展现的模式是list模式 public final static int TYPE_STAGGER = 4;//代码item展现模式是网格模式 private boolean mIsFooterEnable = false;//是否容许加载更多 /** * 自定义实现了头部和底部加载更多的adapter */ private AutoLoadAdapter mAutoLoadAdapter; /** * 标记是否正在加载更多,防止再次调用加载更多接口 */ private boolean mIsLoadingMore; /** * 标记加载更多的position */ private int mLoadMorePosition; /** * 加载更多的监听-业务须要实现加载数据 */ private LoadMoreListener mListener; public LoadMoreRecyclerView(Context context) { super(context); init(); } public LoadMoreRecyclerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public LoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } /** * 初始化-添加滚动监听 * <p/> * 回调加载更多方法,前提是 * <pre> * 一、有监听而且支持加载更多:null != mListener && mIsFooterEnable * 二、目前没有在加载,正在上拉(dy>0),当前最后一条可见的view是不是当前数据列表的最好一条--及加载更多 * </pre> */ private void init() { super.addOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (null != mListener && mIsFooterEnable && !mIsLoadingMore && dy > 0) { int lastVisiblePosition = getLastVisiblePosition(); if (lastVisiblePosition + 1 == mAutoLoadAdapter.getItemCount()) { setLoadingMore(true); mLoadMorePosition = lastVisiblePosition; mListener.onLoadMore(); } } } }); } /** * 设置加载更多的监听 * * @param listener */ public void setLoadMoreListener(LoadMoreListener listener) { mListener = listener; } /** * 设置正在加载更多 * * @param loadingMore */ public void setLoadingMore(boolean loadingMore) { this.mIsLoadingMore = loadingMore; } /** * 加载更多监听 */ public interface LoadMoreListener { /** * 加载更多 */ void onLoadMore(); } /** * */ public class AutoLoadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { /** * 数据adapter */ private RecyclerView.Adapter mInternalAdapter; private boolean mIsHeaderEnable; private int mHeaderResId; public AutoLoadAdapter(RecyclerView.Adapter adapter) { mInternalAdapter = adapter; mIsHeaderEnable = false; } @Override public int getItemViewType(int position) { int headerPosition = 0; int footerPosition = getItemCount() - 1; if (headerPosition == position && mIsHeaderEnable && mHeaderResId > 0) { return TYPE_HEADER; } if (footerPosition == position && mIsFooterEnable) { return TYPE_FOOTER; } /** * 这么作保证layoutManager切换以后能及时的刷新上对的布局 */ if (getLayoutManager() instanceof LinearLayoutManager) { return TYPE_LIST; } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) { return TYPE_STAGGER; } else { return TYPE_NORMAL; } } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == TYPE_HEADER) { return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate( mHeaderResId, parent, false)); } if (viewType == TYPE_FOOTER) { return new FooterViewHolder( LayoutInflater.from(parent.getContext()).inflate( R.layout.list_foot_loading, parent, false)); } else { // type normal return mInternalAdapter.onCreateViewHolder(parent, viewType); } } public class FooterViewHolder extends RecyclerView.ViewHolder { public FooterViewHolder(View itemView) { super(itemView); } } public class HeaderViewHolder extends RecyclerView.ViewHolder { public HeaderViewHolder(View itemView) { super(itemView); } } @Override public void onBindViewHolder(ViewHolder holder, int position) { int type = getItemViewType(position); if (type != TYPE_FOOTER && type != TYPE_HEADER) { mInternalAdapter.onBindViewHolder(holder, position); } } /** * 须要计算上加载更多和添加的头部俩个 * * @return */ @Override public int getItemCount() { int count = mInternalAdapter.getItemCount(); if (mIsFooterEnable) count++; if (mIsHeaderEnable) count++; return count; } public void setHeaderEnable(boolean enable) { mIsHeaderEnable = enable; } public void addHeaderView(int resId) { mHeaderResId = resId; } } @Override public void setAdapter(RecyclerView.Adapter adapter) { if (adapter != null) { mAutoLoadAdapter = new AutoLoadAdapter(adapter); } super.swapAdapter(mAutoLoadAdapter, true); } /** * 切换layoutManager * * 为了保证切换以后页面上仍是停留在当前展现的位置,记录下切换以前的第一条展现位置,切换完成以后滚动到该位置 * 另外切换以后必需要从新刷新下当前已经缓存的itemView,不然会出现布局错乱(俩种模式下的item布局不一样), * RecyclerView提供了swapAdapter来进行切换adapter并清理老的itemView cache * * @param layoutManager */ public void switchLayoutManager(LayoutManager layoutManager) { int firstVisiblePosition = getFirstVisiblePosition(); // getLayoutManager().removeAllViews(); setLayoutManager(layoutManager); //super.swapAdapter(mAutoLoadAdapter, true); getLayoutManager().scrollToPosition(firstVisiblePosition); } /** * 获取第一条展现的位置 * * @return */ private int getFirstVisiblePosition() { int position; if (getLayoutManager() instanceof LinearLayoutManager) { position = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition(); } else if (getLayoutManager() instanceof GridLayoutManager) { position = ((GridLayoutManager) getLayoutManager()).findFirstVisibleItemPosition(); } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) getLayoutManager(); int[] lastPositions = layoutManager.findFirstVisibleItemPositions(new int[layoutManager.getSpanCount()]); position = getMinPositions(lastPositions); } else { position = 0; } return position; } /** * 得到当前展现最小的position * * @param positions * @return */ private int getMinPositions(int[] positions) { int size = positions.length; int minPosition = Integer.MAX_VALUE; for (int i = 0; i < size; i++) { minPosition = Math.min(minPosition, positions[i]); } return minPosition; } /** * 获取最后一条展现的位置 * * @return */ private int getLastVisiblePosition() { int position; if (getLayoutManager() instanceof LinearLayoutManager) { position = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition(); } else if (getLayoutManager() instanceof GridLayoutManager) { position = ((GridLayoutManager) getLayoutManager()).findLastVisibleItemPosition(); } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) getLayoutManager(); int[] lastPositions = layoutManager.findLastVisibleItemPositions(new int[layoutManager.getSpanCount()]); position = getMaxPosition(lastPositions); } else { position = getLayoutManager().getItemCount() - 1; } return position; } /** * 得到最大的位置 * * @param positions * @return */ private int getMaxPosition(int[] positions) { int size = positions.length; int maxPosition = Integer.MIN_VALUE; for (int i = 0; i < size; i++) { maxPosition = Math.max(maxPosition, positions[i]); } return maxPosition; } /** * 添加头部view * * @param resId */ public void addHeaderView(int resId) { mAutoLoadAdapter.addHeaderView(resId); } /** * 设置头部view是否展现 * @param enable */ public void setHeaderEnable(boolean enable) { mAutoLoadAdapter.setHeaderEnable(enable); } /** * 设置是否支持自动加载更多 * * @param autoLoadMore */ public void setAutoLoadMoreEnable(boolean autoLoadMore) { mIsFooterEnable = autoLoadMore; } /** * 通知更多的数据已经加载 * * 每次加载完成以后添加了Data数据,用notifyItemRemoved来刷新列表展现, * 而不是用notifyDataSetChanged来刷新列表 * * @param hasMore */ public void notifyMoreFinish(boolean hasMore) { setAutoLoadMoreEnable(hasMore); getAdapter().notifyItemRemoved(mLoadMorePosition); mIsLoadingMore = false; } }
实现原理是利用RecyclerView.Adapter的getItemType来区别不同的item布局,这里是实现了一个支持头部和底部的额外itemVIew的apdater壳子,将数据的adapter放到这个壳子中去代理,保证业务方实现的adapter的纯净。
另外LoadMoreRecyclerView管理了5个itemType,除了TYPE_HEADER和TYPE_FOOTER以外,其他三个会传递给业务方的onCreateViewHolder方法,业务方根据这个参数决定使用布局,固然业务方页能够用本身的方式实现判断条件:github
java片断2: 业务方可能的onCreateViewHolder实现缓存
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == LoadMoreRecyclerView.TYPE_STAGGER) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.fragment_item_staggel, parent, false); return new StaggerViewHolder(view); } else { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.fragment_item, parent, false); return new ViewHolder(view); } }
下拉刷新的实现,我也是直接用了google提供的support-v4包种的SwipeRefreshLayout,只要实现下拉刷新的获取数据动做便可,网络
layout1:架构
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <com.leaf8.alicx.myapplication.LoadMoreRecyclerView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="LinearLayoutManager" tools:listitem="@layout/fragment_item" /> </android.support.v4.widget.SwipeRefreshLayout>
java片断3: refreshlistenerapp
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { swipeRefreshLayout.setRefreshing(false); page = 0; myItemRecyclerViewAdapter.setData(DummyContent.generyData(page)); recyclerView.setAutoLoadMoreEnable(DummyContent.hasMore(page)); myItemRecyclerViewAdapter.notifyDataSetChanged(); } });
SwipeRefreshLayout没什么能够多说的,你们都知道的东西。ide
我本身实现了一个LoadMoreRecyclerView,来完成自动加载的功能。代码见#java片断1#,这个LoadMoreRecyclerView须要业务方设置LoadMoreListener,当分页结束还须要设置再也不继续自动加载,LoadMoreRecyclerView开放了一个接口notifyMoreFinish
来支持该设置。布局
java片断4
recyclerView.setLoadMoreListener(new LoadMoreRecyclerView.LoadMoreListener() { @Override public void onLoadMore() { recyclerView.postDelayed(new Runnable() { @Override public void run() { swipeRefreshLayout.setRefreshing(false); myItemRecyclerViewAdapter.addDatas(DummyContent.generyData(++page)); recyclerView.notifyMoreFinish(DummyContent.hasMore(page)); } }, 1000); } });
这样完了以后,就能实现上拉自动加载,而且加载中还有一个加载中...
显示。
在notifyMoreFinish方法中为了实现新数据被刷新到页面上,用了adapter.notifyItemRemoved(lastvisiblePosition),发现用notifyDataSetChanged在此时不能起做用,缘由应该是加载中...
这个,在列表中是一个正常的view,而新数据加上以后,实际上是在这个view的位置插入数据的操做,因此此处adapter.notifyItemInsert/adapter.notifyItemRemoved都能起做用,可是notifyDataSetChanged不会从新执行该position的CreateView(缘由应该是他认为该position的view已经被缓存了,因此直接展现了缓存中的view)。这点在平常运用中实现对数据的增删的时候也有遇到。
新版列表还要作的一个事情是要实现瀑布流和线性流的切换,这个开始作以前我认为应该很简单,可是当我真的去实施的时候,发现其实还有不少细节问题。
demo中我只是对这个切换简单的作了一个文案按钮,点击实现切换:
java片断5
if (1 == mColumnCount) { mColumnCount = 2; ((TextView) v).setText(R.string.list_mode_stagger); myItemRecyclerViewAdapter.switchMode(true); recyclerView.switchLayoutManager(new StaggeredGridLayoutManager(mColumnCount, StaggeredGridLayoutManager.VERTICAL)); } else { mColumnCount = 1; ((TextView) v).setText(R.string.list_mode_list); myItemRecyclerViewAdapter.switchMode(false); recyclerView.switchLayoutManager(new LinearLayoutManager(getActivity())); }
看看switchLayoutManager方法实现:
java片断6
int firstVisiblePosition = getFirstVisiblePosition(); // getLayoutManager().removeAllViews(); setLayoutManager(layoutManager); //super.swapAdapter(mAutoLoadAdapter, true); getLayoutManager().scrollToPosition(firstVisiblePosition);
实现原则就是切换布局以后,页面仍是停留在当前浏览的position位置,因此切换以前,我得先记录下当前的第一个可见的item的位置,代码不贴了,就看LoadMoreRecyclerView.getFirstVisiblePosition()方法。而后我setLayoutManager设置布局,而后我经过scrollToPosition滚动到先前停留的位置,结果发现切换以后页面上会出现俩种布局错乱。恩应该是LoadMoreRecyclerView中还保留了先前布局的item view cache致使,必需要让他知道个人模板已通过时了须要从新生成了,经过什么方法告知我须要刷新布局呢,这个我尝试了几种方法,如上注释掉的,可是仍是不顶用,最后我想到adapter.getItemType(),这个应该可行,因而我针对俩种布局添加了俩个类型TYPE_LIST和TYPE_STAGGER,传递到业务的onCreateViewHolder,让业务知道当前的不一样布局标识,结果证明该方案可行。
待解问题
解决了切换布局混乱的问题,又遇到了一个新问题,从list切换到StaggerGride,若是当前的position是在第二屏,切换过来以后,滑动到第一屏发现瀑布流的第一行俩个列是不对齐的,右边一列会滑动上去的一个过程。这个时候再上拉翻到第三屏就会出现一个IndexOutOfBoundsException的crash异常,可是当我切换过来以后,不去下滑,而是继续上拉,则不会出现这个问题,可是下滑到第一屏仍是会有一个第二列往上移动的过程。感受是这个移动的过程形成了后续数据的一个position错乱,目前还在排查这个问题
无图无真相
RecyclerView某些状况下确实很好用的,很方便的就实现了一些特性。可是由于是个新东西,不少方面仍是欠缺了一些成熟性,不可控性,在实际应用中仍是须要多多演练才行。
打上个人demo地址:供你们参考-->
注
:由于业务须要,该业务涉及的list须要放到Fragment中实现,因此本demo也是在这种架构中实现