自定义RecyclerView中遇见的问题
通用实现起来复杂,效率高则简单,但是如何实现高效和简单呢,就是把功能开源成库工程,这样的话既提高了效率,又提高了通用性。而且不要重复造轮子,要学会在别人的基础上,取其精华,弄清原理,根据自己的业务需求创造出属于自己的轮子。
第一步,建立安卓库工程
在Android Studio 中的菜单栏找到,File->New->New Module
然后填写一些基本资料
第二步,项目工程依赖库工程
库工程和普通项目Module不同之处是Build.gradle的一些配置选项不一样;
下面这个是库工程的配置
apply plugin: 'com.android.library'
下面这个是普通项目Module的配置
apply plugin: 'com.android.application'
在普通项目Module的配置文件bulid.gradle中添加,注意那个名字要用你建库工程时候的名称
compile project(path: ':myutillibrary')
需要掌握的基础知识
声明自定义的RecyclerView.Adapter,RecyclerVie.ViewHolder和DataModel;
注意RecyclerView的执行流程
1.RecyclerView通过Adapter的getItemCount来动态决定视图中显示的item个数;
2.getItemViewType用来决定每一个Item对应的布局;
3.createViewHolder用来创建ViewHolder,用来避免错位问题和提高加载效率。
4.bindViewHolder用来添加相关逻辑处理和UI数据界面的显示
MyDynamicsRvAdapter.java
public class MyDynamicsRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<MyDynamicsDataModel> modelList;
private LayoutInflater mInflater;
private int iconSize,screenSize,picHeight;
public MyDynamicsRvAdapter(Context context , List<MyDynamicsDataModel> modelList) {
this.mContext = context;
this.modelList = modelList;
mInflater = LayoutInflater.from(this.mContext);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 给ViewHolder设置布局文件
View v = mInflater.inflate(R.layout.item_mydynamics, parent, false);
return new MyViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
// 给ViewHolder设置元素
MyDynamicsDataModel u = modelList.get(position);
MyViewHolder mHolder = (MyViewHolder) holder;
...
//相关逻辑处理
}
@Override
public int getItemCount() {
// 返回数据总数
return modelList == null ? 0 : modelList.size();
}
@Override
public int getItemViewType(int position) {
return CustomRecyclerView.TYPE_NORMAL;//注意这个关键点要和自定义View一样
}
//重写的自定义ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder {
private TextView mUsername,mTime,mState,mMoreContent;
private ImageView mUserIcon,mPic,mFavoriteIcon,mCommentIcon,mThumbUpIcon;
private CommentTextView commentTextView;
private MyViewHolder(View v) {
super(v);
//绑定ID
}
}
}
MyDynamicsDataModel.java (根据自己业务需求编写)
public class MyDynamicsDataModel {
public int userIcon;
public String username;
public String time;
public int pic;
public String state;
public String[] commentList;//用户评论信息列表,index 0 为用户名 index 1 为对应评论内容
public MyDynamicsDataModel(int userIcon, String username, String time, int pic,
String state, String[] commentList) {
this.userIcon = userIcon;
this.username = username;
this.time = time;
this.pic = pic;
this.state = state;
this.commentList = commentList;
}
}
自定义RecyclerView需要考虑的问题
1.根据业务需求定制上拉加载和下拉刷新的形式。
决定头布局
header_holder.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:id="@+id/cv_item"
android:foreground="?android:attr/selectableItemBackground"
android:visibility="visible"
card_view:cardCornerRadius="3dp"
card_view:cardElevation="4dp"
card_view:cardBackgroundColor="@color/white">
<include
android:id="@+id/rl_one"
android:visibility="visible"
layout="@layout/sample_common_list_header_loading"
android:layout_width="match_parent"
android:layout_height="50dp" />
<include
android:id="@+id/rl_two"
android:visibility="visible"
layout="@layout/sample_common_list_header_finish"
android:layout_width="match_parent"
android:layout_height="50dp" />
</android.support.v7.widget.CardView>
显示效果
决定尾布局
footer_holder.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/loading_view"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="38dp">
<include
android:id="@+id/loading_viewStub"
layout="@layout/sample_common_list_footer_loading"
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="38dp" />
<include
android:id="@+id/end_viewStub"
android:visibility="visible"
layout="@layout/sample_common_list_footer_end"
android:layout_width="match_parent"
android:layout_height="38dp" />
<include
android:id="@+id/network_error_viewStub"
android:visibility="visible"
layout="@layout/sample_common_list_footer_network_error"
android:layout_width="match_parent"
android:layout_height="38dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/_grey"
android:layout_gravity="bottom"
/>
</FrameLayout>
显示效果
2.如何添加个性化的footerView和HeaderView。
我的实现原理是通过封装一个内部adapter来,利用数学逻辑来计算viewType和position
关键代码如下
CustomRecyclerView.java
private class WrapAdapter extends Adapter<ViewHolder> {
private Adapter mAdapter;//容器Adapter
private View mHeaderView, mFooterView;
private InnerFooterHolder mFooterHolder;
private InnerHeaderHolder mHeaderHolder;
private int mHeaderCount;
private int mFooterCount;
public WrapAdapter(Adapter adapter) {
this.mAdapter = adapter;
this.mHeaderCount = 0;
this.mFooterCount = 0;
}
public WrapAdapter(Adapter adapter,View mHeaderView, View mFooterView) {
this.mAdapter = adapter;
this.mHeaderView = mHeaderView;
this.mFooterView = mFooterView;
this.mHeaderCount = 1;
this.mFooterCount = 1;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case TYPE_NORMAL:
return this.mAdapter.onCreateViewHolder(parent,viewType);
case TYPE_FOOTER:
mFooterHolder = new InnerFooterHolder(mFooterView);
return mFooterHolder;
case TYPE_HEADER:
mHeaderHolder = new InnerHeaderHolder(mHeaderView);
return mHeaderHolder;
default:
break;
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int viewType = getItemViewType(position);
int realPosition;
if((mFooterCount == 0 && mHeaderCount == 0) ||
(mFooterCount == 1 && mHeaderCount == 0)) {
realPosition = position;
} else {
realPosition = position - 1;
}
switch (viewType) {
case TYPE_NORMAL:
this.mAdapter.onBindViewHolder(holder,realPosition);
break;
case TYPE_FOOTER:
//默认为Normal状态
((InnerFooterHolder)holder).setState(FooterState.NORMAL);
break;
case TYPE_HEADER:
((InnerHeaderHolder)holder).setState(HeaderState.NONE);
break;
default:
break;
}
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemViewType(int position) {
if(mFooterCount == 1 && mHeaderCount == 1) {
return position == (getItemCount() - 1) ?
TYPE_FOOTER : (position == 0 ? TYPE_HEADER : TYPE_NORMAL);
} else if(mFooterCount == 1 && mHeaderCount == 0) {
return position == (getItemCount() - 1) ? TYPE_FOOTER : TYPE_NORMAL;
} else if(mFooterCount == 0 && mHeaderCount == 1) {
return position == 0 ? TYPE_HEADER : TYPE_NORMAL;
} else {
return TYPE_NORMAL;
}
}
@Override
public int getItemCount() {
return this.mAdapter.getItemCount() == 0 ? 0 : (this.mAdapter.getItemCount() + mFooterCount + mHeaderCount);
}
}
3.添加自定义的监听器监听RecyclerView的滚动变化。
关键原理是如何判断recyclerView到达了顶部和底部,我这里采用的是判断其第一个完全可见的Item位置是不是0,并且通过recyclerView.canScrollVertically(-1)来结合判断是否到达了顶部;
DataStateChangeCheck.java
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
switch (getLayoutManagerType()) {
case LinearLayout:
if(getFirstCompletelyVisibleItemPosition()==0) {
if(recyclerView.canScrollVertically(-1)) {
//到达顶部判定
mListener.onRefreshPage(recyclerView);
}
}
break;
case GridLayout:
if(recyclerView.getChildAt(0).getY()==0 && getFirstCompletelyVisibleItemPosition()==0) {
if(recyclerView.canScrollVertically(-1)) {
//到达顶部判定
mListener.onRefreshPage(recyclerView);
}
}
break;
case StaggeredGridLayout:
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int flag[] = ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(null);
//达到这个条件就说明滑到了顶部
if(recyclerView.getChildAt(0).getY()==0f && flag[0]==0&&
recyclerView.canScrollVertically(-1)) {
mListener.onRefreshPage(recyclerView);
}
break;
}
}
到达底部的判定,我这里采用的是判断可见item数要大于零,并且RecyclerView处于 RecyclerView.SCROLL_STATE_IDLE状态,并且最后一个可见Item为总数-1
DataStateChangeCheck.java
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
//到达底部的判定
if ((visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE &&
(getLastVisibleItemPosition()) >= totalItemCount - 1)) {
mListener.onLoadNextPage(recyclerView);
}
}
自定义的CustomRecyclerView实现了该接口,DataStateChangeCheck.LoadDataListener
/**
* 必须设置加载数据监听器
* @param listener
*/
public void setLoadDataListener(DataStateChangeCheck.LoadDataListener listener) {
this.mListener = listener;
}
4.提供外部接口供外部调用,实现自定义View和外部容器Fragment和Activity的关联。
CustomRecyclerView.java
/**
* 对外的数据操作接口
*/
public interface DataOperation {
void onLoadMore();
void onRefresh();
}
5.新数据到达后自定义View的数据的添加是正向还是逆向。
这里我的视线原理是通过将传入自定义adapter中的List定义为LinkedList,来实现正向和逆向添加
private LinkedList<MyDynamicsDataModel> dataModels = new LinkedList<>();
使用示例例代码
关键代码如下
//数据状态监听器
myRecyclerView.addHeaderView(getActivity());//添加头布局
myRecyclerView.addFooterView(getActivity());//添加尾布局
myRecyclerView.setTotalCount(TOTAL_COUNT);//设置总数
myRecyclerView.setRequestCount(REQUEST_COUNT);//设置一次请求数
myRecyclerView.setDataOperation(this);//实现数据操作接口
myRecyclerView.Initialize();//初始化
在新开的子线程刷新完毕后需要调用
myRecyclerView.finishComplete();
...
adapter = new MyDynamicsRvAdapter(getActivity(), dataModels);
完整使用代码
public class DynamicsFragment extends Fragment implements CustomRecyclerView.DataOperation{
private static final String ARG = "arg";
public static DynamicsFragment newInstance(String arg){
DynamicsFragment fragment = new DynamicsFragment();
Bundle bundle = new Bundle();
bundle.putString(ARG, arg);
fragment.setArguments(bundle);
return fragment;
}
private ImageView addFriend,publishDynamics;
private View rootView;
private CustomRecyclerView myRecyclerView;
private RecyclerView.Adapter adapter;
private LinkedList<MyDynamicsDataModel> dataModels = new LinkedList<>();
private String[] states ={"[审核中]","[进行中]","[筹资中]","[已结束]"};
private int[] pics ={R.mipmap.family,R.mipmap.family,R.mipmap.family,R.mipmap.family};
private int[] userIcons ={R.mipmap.paintbox,R.mipmap.paintbox,
R.mipmap.paintbox,R.mipmap.paintbox};
private String[] userNames = {"大丸子","二丸子","三丸子","四丸子"};
private String[] times = {"2017.6.11","2017.6.12","2017.6.13","2017.6.14"};
private String[] commentOne = {"大丸子","一级棒"};
private List<String[]> lists = new ArrayList<>();
// 服务器端一共多少条数据
private static final int TOTAL_COUNT = 10;
// 每一页展示多少条数据
private static final int REQUEST_COUNT = 2;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(rootView ==null) {
rootView = inflater.inflate(R.layout.fragment_dynamics,container,false);
init(rootView);
initRecyclerView(rootView);
}
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {
parent.removeView(rootView);
}
return rootView;
}
private void init(View view) {
lists.add(commentOne);
addFriend = (ImageView) view.findViewById(R.id.iv_add_friend);
publishDynamics = (ImageView) view.findViewById(R.id.iv_publish);
myRecyclerView = (CustomRecyclerView) view.findViewById(R.id.my_recycler_view);
addFriend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IntentSkipUtil.skipToNextActivity(v.getContext(),FriendAddActivity.class);
}
});
publishDynamics.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IntentSkipUtil.skipToNextActivity(v.getContext(),DynamicPublishingActivity.class);
}
});
}
private void initRecyclerView(View view) {
//获取RecyclerV
myRecyclerView = (CustomRecyclerView) view.findViewById(R.id.my_recycler_view);
//使用此设置,以提高性能,如果内容不改变RecyclerView的布局尺寸,也称为设置固定大小
myRecyclerView.setHasFixedSize(true);
//设置ITEM 动画管理者
myRecyclerView.setItemAnimator(new DefaultItemAnimator());
//初始化自定义适配器
adapter = new MyDynamicsRvAdapter(getActivity(), dataModels);
// specify(指定) an adapter (see also next example)
myRecyclerView.setAdapter(adapter);
myRecyclerView.addHeaderView(getActivity());
myRecyclerView.addFooterView(getActivity());
myRecyclerView.setTotalCount(TOTAL_COUNT);
myRecyclerView.setRequestCount(REQUEST_COUNT);
myRecyclerView.setDataOperation(this);
myRecyclerView.Initialize();
//网格布局管理器
final GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),1);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return 1;
}
});
//网格布局管理器
myRecyclerView.setLayoutManager(gridLayoutManager);
}
@Override
public void onLoadMore() {
new LoadMoreThread(new DataGetInterface() {
@Override
public void finishWork(Object obj) {
}
@Override
public void interrupt(Exception e) {
}
}, dataModels).start();
}
@Override
public void onRefresh() {
new RefreshThread(new DataGetInterface() {
@Override
public void finishWork(Object obj) {
myRecyclerView.finishComplete();
}
@Override
public void interrupt(Exception e) {
}
},dataModels).start();
}
}
显示效果
Notes:开源过程中的一些心得和收获
1.自定义View会根据业务需求的变化而变化,功能会越趋近于完善。
2.自定义View中避免使用过多的子线程,不仅使自定义代码复杂,而且会使其有意想不到的Bug。
Notes:开源过程中遇见的基本知识
1.Visibility的每个常量的取值问题
Constant | Value |
---|---|
VISIBLE | 0 (0x00000000) |
INVISIBLE | 4 (0x00000004) |
GONE | 8 (0x00000008) |
博主的GitHub地址,会不定时完善更新,目前正在捣鼓如何上传的Maven仓库。更详细的代码在github,有兴趣的可以去看一下。
https://github.com/geminiyang/ShareTransilation/tree/master/easyrefreshandload