更新日志:
- 2016.08.28日更新,基于2.2.0版本,主要更新了header和footer支持隐藏或显示
- 2016.06.21日更新,基于2.1.0版本,主要更新了header和footer的支持,以及新版加载更多的API
做Android开发的都感同身受,写列表时最麻烦的就是要针对每个不同的列表写一个Adapter,这样下来一般的项目都得有几十个Adapter,要是Adapter中还有其它的功能比如多Item,以及加载更多那就更麻烦了。要是同样的内容在不同的页面还要写多次那就更苦逼了
其实我们仔细分析一下就可得知每个Adapter无非包含两部分
- 第一是Adapter本身,包含处理getView、itemType、加载更多以及维护数据
- 第二是Item,包含创建convertView、设置数据、设置并处理事件
那我们到底能不能写一个通用的能够处理所有ItemView的Adapter呢?根据我多年的经验告诉大家这绝逼是不可能的,这里面唯一的障碍就是ItemView,每个ItemView都是有很大不同的。
那么我们理想中的Adapter应该是什么样子的呢?
- Item部分完全独立,满足一处定义处处使用
- 支持header和footer,让GridView、RecyclerView也支持header和footer
- 自带加载更多功能
纵观能搜索到的那些号称通用的Adapter(这里有个比较完整的一个示例)最后还不都是让你传一个布局ID,然后通过一个接口让你来设置数据。其实这样的实现方式应付一些简单的场景尚可,但只要涉及到itemType复杂度就成几何倍数升级了,而现在稍微复杂点儿的列表几乎都离不开itemType,并且无法做到一处定义处处使用
AssemblyAdapter
今天就给大家介绍一款全新的Adapter库AssemblyAdapter(GitHub传送门),先来看一下其特性:
Item一处定义处处使用
. 你只需为每一个item layout写一个ItemFactory,然后使用ItemFactory即可便捷的组合式多Item
. 可以使用多个ItemFactory,每个ItemFactory就代表一种itemType支持header和footer
. 使用AssemblyAdapter可以让ExpandableListView、GridView、RecyclerView、ViewPager等也支持header和footer随意隐藏、显示header或footer
. header和footer还支持通过其setEnabled(boolean)方法控制隐藏或显示自带加载更多功能
. 自带滑动到列表底部触发加载更多功能,你只需定义一个专门用于加载更多的ItemFactory即可支持常用Adapter
. 支持BaseAdapter、RecyclerView.Adapter、BaseExpandableListAdapter、PagerAdapter、 FragmentPagerAdapter和FragmentStatePagerAdapter,涵盖了Android开发中常用的大部分Adapter无性能损耗
. 没有使用任何反射相关的技术,因此无须担心性能问题
共有6种Adapter:
Adapter | 父类 | 适用于 | 支持功能 |
---|---|---|---|
AssemblyAdapter | BaseAdapter | ListView、GridView、Spinner、Gallery | 多Item、header和footer、加载更多 |
AssemblyRecyclerViewAdapter | RecyclerView.Adapter | RecyclerView | 多Item、header和footer、加载更多 |
AssemblyExpandableAdapter | BaseExpandableListAdapter | ExpandableListView | 多Item、header和footer、加载更多 |
AssemblyPagerAdapter | PagerAdapter | ViewPager + View | 多Item、header和footer |
AssemblyFragmentPagerAdapter | FragmentPagerFragment | ViewPager + Fragment | 多Item、header和footer |
AssemblyFragmentStatePagerAdapter | FragmentStatePagerFragment | ViewPager + Fragment | 多Item、header和footer |
接下来以AssemblyAdapter为例讲解具体的用法,其它Adapter你只需照葫芦画瓢,然后ItemFactory和Item继承各自专属的类即可,详情请参考sample源码
AssemblyAdapter分为三部分:
- Adapter:负责维护数据、itemType以及加载更多的状态
- ItemFactory:负责匹配数据和创建Item
- Item:负责itemView的一切,包括创建itemView、设置数据、设置并处理事件
AssemblyAdapter与其它万能Adapter最根本的不同就是其把item相关的处理全部定义在了一个ItemFactory类里面,在使用的时候只需通过Adapter的addItemFactory(AssemblyItemFactory)方法将ItemFactory加到Adapter中即可。
这样的好处就是真正做到了一处定义处处使用,并且可以方便的在一个页面通过多次调用addItemFactory(AssemblyItemFactory)方法使用多个ItemFactory(每个ItemFactory就代表一种ItemType),这正体现了AssemblyAdapter名字中Assembly所表达的意思
另外由于支持多Item,一个Adapter又只有一个数据列表,所以数据列表的数据类型就得是Object
3. 创建ItemFactory
在使用AssemblyAdapter之前得先创建ItemFactory和Item,如下:
public class UserItemFactory extends AssemblyItemFactory<UserItemFactory.UserItem> {
@Override
public boolean isTarget(Object itemObject) {
return itemObject instanceof User;
}
@Override
public UserListItem createAssemblyItem(ViewGroup parent) {
return new UserListItem(R.layout.list_item_user, parent);
}
public class UserItem extends AssemblyItem<User> {
private ImageView headImageView;
private TextView nameTextView;
private TextView sexTextView;
private TextView ageTextView;
private TextView jobTextView;
public UserListItem(int itemLayoutId, ViewGroup parent) {
super(itemLayoutId, parent);
}
@Override
protected void onFindViews(View itemView) {
headImageView = (ImageView) findViewById(R.id.image_userListItem_head);
nameTextView = (TextView) findViewById(R.id.text_userListItem_name);
sexTextView = (TextView) findViewById(R.id.text_userListItem_sex);
ageTextView = (TextView) findViewById(R.id.text_userListItem_age);
jobTextView = (TextView) findViewById(R.id.text_userListItem_job);
}
@Override
protected void onConfigViews(Context context) {
getItemView().setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Toast.makeText(v.getConext(), "第" + (getPosition() + 1) + "条数据", Toast.LENGTH_LONG).show();
}
});
}
@Override
protected void onSetData(int position, User user) {
headImageView.setImageResource(user.headResId);
nameTextView.setText(user.name);
sexTextView.setText(user.sex);
ageTextView.setText(user.age);
jobTextView.setText(user.job);
}
}
}
复制代码
详解:
ItemFactory的泛型
是为了限定其createAssemblyItem(ViewGroup)方法返回的类型- ItemFactory的
isTarget()
方法是用来匹配数据列表中的数据的,Adapter从数据列表中拿到当前位置的数据后会依次调用其所有的ItemFactory的isTarget(Object)方法,谁返回true就用谁处理当前这条数据- ItemFactory的
createAssemblyItem(ViewGroup)
方法用来创建Item,返回的类型必须跟你在ItemFactory上配置的泛型一样Item的泛型
是用来指定对应的数据类型,会在onSetData和getData()方法中用到- Item的
onFindViews(View)
和onConfigViews(Context)
方法分别用来初始化View和配置View,只会在创建Item的时候调用一次
,另外在onFindViews方法中你可以直接使用findViewById(int)
法获取View- Item的
onSetData()
方法是用来设置数据的,在每次getView()的时候都会调用
- 你可以通过Item的
getPosition()
和getData()
方法可直接获取当前所对应的位置和数据,因此你在处理click的时候不再需要通过setTag()来绑定位置和数据了,直接获取即可- 你还可以通过过Item的
getItemView()
方法获取当前的itemView
另外你还可以通过ContentSetter简化你的代码,上述例子使用ContentSetter简化后如下:
public class UserItemFactory extends AssemblyItemFactory<UserListItemFactory.UserListItem> {
@Override
public boolean isTarget(Object itemObject) {
return itemObject instanceof User;
}
@Override
public UserListItem createAssemblyItem(ViewGroup parent) {
return new UserListItem(R.layout.list_item_user, parent);
}
public class UserItem extends AssemblyItem<User> {
public UserListItem(int itemLayoutId, ViewGroup parent) {
super(itemLayoutId, parent);
}
@Override
protected void onFindViews(View itemView) { }
@Override
protected void onConfigViews(Context context) {
getItemView().setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Toast.makeText(v.getConext(), "第" + (getPosition() + 1) + "条数据", Toast.LENGTH_LONG).show();
}
});
}
@Override
protected void onSetData(int position, User user) {
getSetter()
.setImageResource(R.id.image_userListItem_head, user.headResId)
.setText(R.id.text_userListItem_name, user.name)
.setText(R.id.text_userListItem_sex, user.sex)
.setText(R.id.text_userListItem_age, user.age)
.setText(R.id.text_userListItem_job, user.job);
}
}
}
复制代码
4. 使用ItemFactory
首先你要准备好数据并new一个AssemblyAdapter,然后通过Adapter的addItemFactory(AssemblyItemFactory)
方法添加ItemFactory即可,如下:
ListView listView = ...;
List<Object> dataList = new ArrayList<Object>;
dataList.add(new User("隔离老王"));
dataList.add(new User("隔壁老李"));
AssemblyAdapter adapter = new AssemblyAdapter(dataList);
adapter.addItemFactory(new UserItemFactory());
listView.setAdapter(adapter);
复制代码
你还可以一次使用多个ItemFactory,如下:
ListView listView = ...;
List<Object> dataList = new ArrayList<Object>;
dataList.add(new User("隔离老王"));
dataList.add(new Game("英雄联盟"));
dataList.add(new User("隔壁老李"));
dataList.add(new Game("守望先锋"));
AssemblyAdapter adapter = new AssemblyAdapter(dataList);
adapter.addItemFactory(new UserItemFactory());
adapter.addItemFactory(new GameItemFactory());
listView.setAdapter(adapter);
复制代码
5. 使用header和footer
AssemblyAdapter支持添加header和footer,可以方便的固定显示内容在列表的顶部或尾部。 Adapter支持header和footer的重要性在于可以让GridView、RecyclerView等也支持header和footer
首先定义好一个用于header或footer的ItemFactory
添加header、footer:
然后调用addHeaderItem(AssemblyItemFactory, Object)
或addFooterItem(AssemblyItemFactory, Object)
方法添加即可,如下:
AssemblyAdapter adapter = new AssemblyAdapter(objects);
adapter.addHeaderItem(new HeaderItemFactory(), "我是小额头呀!");
...
adapter.addFooterItem(new HeaderItemFactory(), "我是小尾巴呀!");
复制代码
addHeaderItem(AssemblyItemFactory, Object)和addFooterItem(AssemblyItemFactory, Object)的第二个参数是Item需要的数据,直接传进去即可
隐藏或显示header、footer
addHeaderItem()或addFooterItem()都会返回一个用于控制header或footer的FixedItemInfo对象,如下:
AssemblyAdapter adapter = new AssemblyAdapter(objects);
FixedItemInfo userFixedItemInfo = adapter.addHeaderItem(new HeaderItemFactory(), "我是小额头呀!");
// 隐藏
userFixedItemInfo.setEnabled(false);
// 显示
userFixedItemInfo.setEnabled(true);
复制代码
由于有了header和footer那么Item.getPosition()方法得到的位置就是Item在Adapter中的位置,要想得到其在所属部分的真实位置可通过Adapter的getPositionInPart(int)
获取
6. 使用加载更多功能
首先你需要创建一个继承自AssemblyLoadMoreItemFactory的ItemFactory,AssemblyLoadMoreItemFactory已经将加载更多相关逻辑部分的代码写好了,你只需关心界面即可,如下:
public class LoadMoreItemFactory extends AssemblyLoadMoreItemFactory {
public LoadMoreListItemFactory(OnLoadMoreListener eventListener) {
super(eventListener);
}
@Override
public AssemblyLoadMoreItem createAssemblyItem(ViewGroup parent) {
return new LoadMoreListItem(R.layout.list_item_load_more, parent);
}
public class LoadMoreItem extends AssemblyLoadMoreItem {
private View loadingView;
private View errorView;
private View endView;
public LoadMoreListItem(int itemLayoutId, ViewGroup parent) {
super(itemLayoutId, parent);
}
@Override
protected void onFindViews(View itemView) {
loadingView = findViewById(R.id.text_loadMoreListItem_loading);
errorView = findViewById(R.id.text_loadMoreListItem_error);
endView = findViewById(R.id.text_loadMoreListItem_end);
}
@Override
public View getErrorRetryView() {
return errorView;
}
@Override
public void showLoading() {
loadingView.setVisibility(View.VISIBLE);
errorView.setVisibility(View.INVISIBLE);
endView.setVisibility(View.INVISIBLE);
}
@Override
public void showErrorRetry() {
loadingView.setVisibility(View.INVISIBLE);
errorView.setVisibility(View.VISIBLE);
endView.setVisibility(View.INVISIBLE);
}
@Override
public void showEnd() {
loadingView.setVisibility(View.INVISIBLE);
errorView.setVisibility(View.INVISIBLE);
endView.setVisibility(View.VISIBLE);
}
}
}
复制代码
然后调用Adapter的setLoadMoreItem(AssemblyLoadMoreItemFactory)
方法设置加载更多ItemFactory即可,如下:
AssemblyAdapter adapter = ...;
adapter.setLoadMoreItem(new LoadMoreItemFactory(new OnLoadMoreListener(){
@Override
public void onLoadMore(AssemblyAdapter adapter) {
// 访问网络加载数据
...
boolean loadSuccess = ...;
if (loadSuccess) {
// 加载成功时判断是否已经全部加载完毕,然后调用Adapter的setLoadMoreEnd(boolean)方法设置加载更多是否结束
boolean loadMoreEnd = ...;
adapter.setLoadMoreEnd(loadMoreEnd);
} else {
// 加载失败时调用Adapter的loadMoreFailed()方法显示加载失败提示,用户点击失败提示则会重新触发加载更多
adapter.loadMoreFailed();
}
}
}));
复制代码
你还可以通过setDisableLoadMore(boolean)
方法替代setLoadMoreEnd(boolean)来控制是否禁用加载更多功能,两者的区别在于setLoadMoreEnd(boolean)为true时会在列表尾部显示end提示,而setDisableLoadMore(boolean)则是完全不显示加载更多尾巴