【Android Adapter】是时候开启Adapter新时代了

更新日志:

  • 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父类适用于支持功能
AssemblyAdapterBaseAdapterListView、GridView、Spinner、Gallery多Item、header和footer、加载更多
AssemblyRecyclerViewAdapterRecyclerView.AdapterRecyclerView多Item、header和footer、加载更多
AssemblyExpandableAdapterBaseExpandableListAdapterExpandableListView多Item、header和footer、加载更多
AssemblyPagerAdapterPagerAdapterViewPager + View多Item、header和footer
AssemblyFragmentPagerAdapterFragmentPagerFragmentViewPager + Fragment多Item、header和footer
AssemblyFragmentStatePagerAdapterFragmentStatePagerFragmentViewPager + 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)则是完全不显示加载更多尾巴

转载于:https://juejin.im/post/5cb41f7bf265da038c02078a

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值