Android应用架构系列——ListView的模板化

 Android开发基本都会有ListView,用来加载一个列表数据,如果列表的每一项视图不确定时,如何为每一项加载不同的数据呢?

先说普通的情况,大家都用过ListView的headview吧,可能会和ListView分开处理,还使逻辑更复杂;再有,碰到ListView增加一种类型的数据,如果差别不太大的,大家可能就在itemView的layout里面支持所有的,然后getView里面根据类别控制可见来达到不同的效果了,这也是使逻辑复杂,再碰到改动的需求就要疯了。

其实可以用模板化的方法让ListView支持多种类型的数据,甚至headView也可以作为ListView的一项(在listView里面本质就是如此)。

这里我介绍两种方法,一种是使用ListView,一种是使用RecycleView。

第一种是以前公司使用的,逻辑比较复杂。这里就简单介绍下思路。

ListView的每一个Item都对应一个模板,有一个基本模板类BaseView,每一种模板需要继承BaseView。然后有个大容器类管理模板Id和模板View的关系,大容器去加载数据时,根据模板Id加载出不同模板View,add进ListView中。 除了这些基本的事情,大容器管理类还需要做缓存,上拉下拉等处理。

整个结构最大的优点是模板与使用者完全分离,但逻辑复杂,加载速度偏慢,还有滑动卡顿问题,优化后还是较差。

第二种是现在很多公司使用的。用RecycleView多种ViewHolder。

先贴下使用实例,主要介绍思路,所以并没有单独做出demo,直接源码中找出来的。

 class GroupAdapter extends BasicRecyclerWithTaleAdapter<DiscoverResponse> {
        protected static final int TYPE_GALLERY_BANNER = -2147483645;
        ArrayList<DiscoverBanner> mBanners;
        ArrayList<DiscoverSection> mSections;
        GetGroupDiscoverRequest request;

        public GroupAdapter(OnRecyclerItemClickListener l) {
            super(true, true, l);
            this.mBanners = new ArrayList();
            this.mSections = new ArrayList();
        }

        public int getItemCount() {
            return (Math.max(1, this.mContents.size()) + getHeadPlaceHolderSize()) + getTalePlaceHolderSize();
        }

        public boolean isBanner(int position) {
            int pos = position - getHeadPlaceHolderSize();
            return pos >= 0 && pos < this.mContents.size() && this.mContents.get(pos) != null && (((BaseRecyclerItem) this.mContents.get(pos)).getData() instanceof ArrayList);
        }

        public boolean isCellHeader(int position) {
            int pos = position - getHeadPlaceHolderSize();
            return pos >= 0 && pos < this.mContents.size() && this.mContents.get(pos) != null && (((BaseRecyclerItem) this.mContents.get(pos)).getData() instanceof DiscoverSection);
        }

        public void appendResult(DiscoverResponse response) {
            this.mBanners = ((Discover) response.getContent()).banners;
            this.mSections = ((Discover) response.getContent()).sections;
            if (!(this.mBanners == null || this.mBanners.size() == 0)) {
                addItem(new GroupDiscoveryBannerItem(this.mBanners, true));
            }
            if (this.mSections != null) {
                Iterator it = this.mSections.iterator();
                while (it.hasNext()) {
                    DiscoverSection section = (DiscoverSection) it.next();
                    addItem(new GroupDiscoverySectionItem(section));
                    if (section.groups != null) {
                        int size = section.groups.size();
                        for (int i = 0; i < size; i++) {
                            Group group = (Group) section.groups.get(i);
                            if (i % 3 == 0) {
                                addItem(new GroupDiscoveryGridItem(group, 1));
                            } else if (i % 3 == 2) {
                                addItem(new GroupDiscoveryGridItem(group, 2));
                            } else {
                                addItem(new GroupDiscoveryGridItem(group));
                            }
                        }
                    }
                }
            }
            this.isEnd = true;
            notifyDataSetChanged();
            GroupDiscoverFragment.this.validateAfterLoad();
        }

        public void reload() {
            if (this.mBanners != null) {
                this.mBanners.clear();
            }
            if (this.mSections != null) {
                this.mSections.clear();
            }
            super.reload();
        }

        public void loadMore() {
            if (this.request == null) {
                GroupDiscoverFragment.this.showRefreshView();
                this.request = new GetGroupDiscoverRequest(GroupDiscoverFragment.this.getZhihuSpiceClient());
                GroupDiscoverFragment.this.execute(this.request, new RequestListener<DiscoverResponse>() {
                    public void onRequestFailure(SpiceException pSpiceException) {
                        super.onRequestFailure(pSpiceException);
                        GroupAdapter.this.errorMsg = pSpiceException.getMessage();
                        GroupAdapter.this.isEnd = true;
                        GroupAdapter.this.notifyDataSetChanged();
                        GroupDiscoverFragment.this.validateAfterLoad(1);
                        GroupAdapter.this.request = null;
                    }

                    public void onRequestSuccess(DiscoverResponse pResult) {
                        super.onRequestSuccess(pResult);
                        if (((Discover) pResult.getContent()).isSuccess()) {
                            GroupAdapter.this.isEnd = true;
                            GroupAdapter.this.appendResult(pResult);
                            GroupAdapter.this.notifyDataSetChanged();
                        } else {
                            GroupAdapter.this.errorMsg = ((Discover) pResult.getContent()).getErrorMessage();
                            GroupAdapter.this.isEnd = true;
                            GroupAdapter.this.notifyDataSetChanged();
                            GroupDiscoverFragment.this.validateAfterLoad(User.BADGE_COOL);
                        }
                        GroupAdapter.this.request = null;
                    }
                });
            }
            GroupDiscoverFragment.this.saveCurrentLoadTime();
        }

        public void resetContents() {
            super.resetContents();
        }
    }

这是Adapter的写法,大部分都是加载数据的,跟界面有关的就是addItem()了。再看BasicRecyclerWithTaleAdapter中addItem做了什么。

public abstract class BasicRecyclerWithTaleAdapter<T> extends Adapter<ViewHolder> implements DownScrollable {
    public static final int TYPE_EMPTY_VIEW = -2147483646;
    public static final int TYPE_PLACEHOLDER = Integer.MIN_VALUE;
    public static final int TYPE_TALE_PLACEHOLDER = -2147483647;
    private EmptyInfo emptyInfo;
    public String errorMsg;
    public boolean isEnd;
    protected long lastItemtId;
    protected ArrayList<BaseRecyclerItem> mContents;
    protected boolean mHasHeaderPlaceholder;
    protected boolean mHasTalePlaceHolder;
    private OnRecyclerItemClickListener mOnRecyclerItemClickListener;
    public int mPaging;
    protected int mTalePlaceHolderHeight;

    public static class ViewHolder<T extends BaseRecyclerItem> extends android.support.v7.widget.RecyclerView.ViewHolder {
        private View view;

        public ViewHolder(View v) {
            super(v);
            this.view = findRecyclerItemViewById(v, null);
        }

        public ViewHolder(View v, OnRecyclerItemClickListener l) {
            super(v);
            this.view = findRecyclerItemViewById(v, l);
        }

        public void build(T item) {
            if (this.view instanceof BaseRecyclerItemView) {
                ((BaseRecyclerItemView) this.view).build(item);
            }
        }

        private View findRecyclerItemViewById(View v, OnRecyclerItemClickListener l) {
            this.view = v.findViewById(R.id.recycler_item_view);
            if (this.view == null || !(this.view instanceof BaseRecyclerItemView)) {
                return v;
            }
            ((BaseRecyclerItemView) this.view).setOnItemClickListener(l);
            return this.view;
        }
    }

    class AnonymousClass_1 extends ViewHolder {
        AnonymousClass_1(View v) {
            super(v);
        }
    }

    class AnonymousClass_2 extends ViewHolder {
        AnonymousClass_2(View v) {
            super(v);
        }
    }

    class AnonymousClass_3 extends ViewHolder {
        AnonymousClass_3(View v) {
            super(v);
        }
    }

    public abstract void appendResult(T t);

    public abstract void loadMore();

    public BasicRecyclerWithTaleAdapter() {
        this(false, false, null);
    }

    public BasicRecyclerWithTaleAdapter(OnRecyclerItemClickListener l) {
        this(false, false, l);
    }

    public BasicRecyclerWithTaleAdapter(boolean hasHeader, boolean hasTale) {
        this(hasHeader, hasTale, null);
    }

    public BasicRecyclerWithTaleAdapter(boolean hasHeader, boolean hasTale, OnRecyclerItemClickListener l) {
        this.mHasHeaderPlaceholder = false;
        this.mHasTalePlaceHolder = false;
        this.mTalePlaceHolderHeight = 0;
        this.mContents = new ArrayList();
        this.mPaging = 1;
        this.lastItemtId = 0;
        this.mHasHeaderPlaceholder = hasHeader;
        this.mHasTalePlaceHolder = hasTale;
        this.mOnRecyclerItemClickListener = l;
    }

    public ArrayList<BaseRecyclerItem> getItems() {
        return this.mContents;
    }

    public int getItemCount() {
        int i;
        int i2 = 1;
        int max = Math.max(1, this.mContents.size());
        if (this.mHasTalePlaceHolder) {
            i = 1;
        } else {
            i = 0;
        }
        i += max;
        if (!this.mHasTalePlaceHolder) {
            i2 = 0;
        }
        return i + i2;
    }

    public int getDataCount() {
        return this.mContents.size();
    }

    public BaseRecyclerItem getItem(int pPosition) {
        return pPosition < this.mContents.size() ? (BaseRecyclerItem) this.mContents.get(pPosition) : null;
    }

    public Object getData(int pos) {
        return ((BaseRecyclerItem) this.mContents.get(pos)).getData();
    }

    public int getItemPositionByData(Object o) {
        for (int i = 0; i < this.mContents.size(); i++) {
            if (o.equals(((BaseRecyclerItem) this.mContents.get(i)).getData())) {
                return i;
            }
        }
        return -1;
    }

    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TYPE_PLACEHOLDER /*-2147483648*/:
                return new AnonymousClass_1(LayoutInflater.from(parent.getContext()).inflate(R.layout.material_view_pager_placeholder, parent, false));
            case TYPE_TALE_PLACEHOLDER /*-2147483647*/:
                return new AnonymousClass_2(LayoutInflater.from(parent.getContext()).inflate(R.layout.material_view_pager_tail_placeholder, parent, false));
            case TYPE_EMPTY_VIEW /*-2147483646*/:
                return new AnonymousClass_3(LayoutInflater.from(parent.getContext()).inflate(R.layout.empty_item_view, parent, false));
            default:
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false), this.mOnRecyclerItemClickListener);
        }
    }

    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        switch (getItemViewType(position)) {
            case TYPE_PLACEHOLDER /*-2147483648*/:
            case TYPE_TALE_PLACEHOLDER /*-2147483647*/:
                if (position == findLastTaleEmptyViewPosition()) {
                    ((MaterialViewPagerTailView) viewHolder.itemView).setMaterialHeight(this.mTalePlaceHolderHeight + getAdditionalTalePlaceHolderSize());
                }
            case TYPE_EMPTY_VIEW /*-2147483646*/:
                if (this.emptyInfo != null) {
                    View view = viewHolder.itemView;
                    if (view instanceof EmptyItemView) {
                        ((EmptyItemView) view).build(new EmptyItem(this.emptyInfo));
                        view.setVisibility(0);
                        return;
                    }
                    return;
                }
                viewHolder.itemView.setVisibility(ConnectionResult.INTERNAL_ERROR);
            default:
                viewHolder.build((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize()));
        }
    }

    protected int findLastTaleEmptyViewPosition() {
        return getItemCount() - 1;
    }

    public boolean isDataItemType(int position) {
        int type = getItemViewType(position);
        return (type == Integer.MIN_VALUE || type == -2147483647) ? false : true;
    }

    public int getItemViewType(int position) {
        if (position < getHeadPlaceHolderSize()) {
            return TYPE_PLACEHOLDER;
        }
        if (position >= Math.max(1, this.mContents.size()) + getHeadPlaceHolderSize()) {
            return TYPE_TALE_PLACEHOLDER;
        }
        return this.mContents.size() == 0 ? TYPE_EMPTY_VIEW : ((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize())).getLayout();
    }

    public void reload() {
        this.mPaging = 1;
        this.isEnd = false;
        this.lastItemtId = 0;
        resetContents();
        loadMore();
    }

    public long getItemId(int position) {
        return (position < 0 || position >= getItemCount()) ? -1 : (long) position;
    }

    public boolean isEnd() {
        return this.isEnd;
    }

    public void resetContents() {
        if (this.mContents != null) {
            this.mContents.clear();
        }
    }

    public void addItem(BaseRecyclerItem item) {
        this.mContents.add(item);
        notifyItemInserted(getHeadPlaceHolderSize() + this.mContents.size());
    }

    public void addItem(int index, BaseRecyclerItem item, boolean isNotifyAll) {
        this.mContents.add(index, item);
        if (isNotifyAll) {
            notifyDataSetChanged();
        } else {
            notifyItemInserted(getHeadPlaceHolderSize() + index);
        }
    }

    public void addAllItems(int index, ArrayList<BaseRecyclerItem> items, boolean isNotifyAll) {
        this.mContents.addAll(index, items);
        ArrayList<Object> datas = new ArrayList();
        Iterator it = items.iterator();
        while (it.hasNext()) {
            datas.add(((BaseRecyclerItem) it.next()).getData());
        }
        if (isNotifyAll) {
            notifyDataSetChanged();
        } else {
            notifyItemRangeInserted(getHeadPlaceHolderSize() + index, items.size());
        }
    }

    public void addAllItems(int index, ArrayList<BaseRecyclerItem> items) {
        addAllItems(index, items, true);
    }

    public void addAllItems(ArrayList<BaseRecyclerItem> items, boolean isNotifyAll) {
        if (isNotifyAll) {
            this.mContents.addAll(items);
            notifyDataSetChanged();
            return;
        }
        int index = this.mContents.size() + getHeadPlaceHolderSize();
        this.mContents.addAll(items);
        notifyItemRangeInserted(index, items.size());
    }

    public void addAllItems(ArrayList<BaseRecyclerItem> items) {
        addAllItems((ArrayList) items, true);
    }

    public void removeItem(int index) {
        this.mContents.remove(index);
        notifyItemRemoved(getHeadPlaceHolderSize() + index);
    }

    public void removeItem(BaseRecyclerItem item) {
        int index = this.mContents.indexOf(item);
        if (index >= 0) {
            this.mContents.remove(index);
            notifyItemRemoved(getHeadPlaceHolderSize() + index);
        }
    }

    public void removeItemByData(Object o, boolean isNotifyAll) {
        int index = getItemPositionByData(o);
        if (index >= 0) {
            this.mContents.remove(index);
            if (isNotifyAll) {
                notifyDataSetChanged();
            } else {
                notifyItemRemoved(getHeadPlaceHolderSize() + index);
            }
        }
    }

    public void clearAll() {
        this.mContents.clear();
        notifyDataSetChanged();
    }

    public void setTalePlaceHolderHeight(int height) {
        this.mTalePlaceHolderHeight = height;
        notifyItemChanged(findLastTaleEmptyViewPosition());
    }

    public void setEmptyInfo(EmptyInfo info) {
        this.emptyInfo = info;
        notifyDataSetChanged();
    }

    public int getAdditionalTalePlaceHolderSize() {
        return 0;
    }

    public int getTalePlaceHolderSize() {
        return this.mHasTalePlaceHolder ? 1 : 0;
    }

    public int getHeadPlaceHolderSize() {
        return this.mHasHeaderPlaceholder ? 1 : 0;
    }
}
看到里面addItem的实现就是为ArrayList<BaseRecyclerItem> mContents 添加数据,再刷新界面。再看BaseRecyclerItem。

public abstract class BaseRecyclerItem<T> {
    private T data;

    public abstract int getLayout();

    public BaseRecyclerItem(T o) {
        this.data = o;
    }

    public T getData() {
        return this.data;
    }
}
看到里面其实是数据和layout。再看BasicRecyclerWithTaleAdapter中对mContent的使用。在onCreateViewHolder中 return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false), this.mOnRecyclerItemClickListener),这里用到viewType,

    public int getItemViewType(int position) {
        if (position < getHeadPlaceHolderSize()) {
            return TYPE_PLACEHOLDER;
        }
        if (position >= Math.max(1, this.mContents.size()) + getHeadPlaceHolderSize()) {
            return TYPE_TALE_PLACEHOLDER;
        }
        return this.mContents.size() == 0 ? TYPE_EMPTY_VIEW : ((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize())).getLayout();
    }
发现viewType就是BaseRecyclerItem的layout。在看Adapter的onBindViewHolder,关键在这句 viewHolder.build((BaseRecyclerItem) getItems().get(position - getHeadPlaceHolderSize()));通过viewHoder更新view的视图,getItem()就是mContent,ViewHoder我们再看下代码:

 public static class ViewHolder<T extends BaseRecyclerItem> extends android.support.v7.widget.RecyclerView.ViewHolder {
        private View view;

        public ViewHolder(View v) {
            super(v);
            this.view = findRecyclerItemViewById(v, null);
        }

        public ViewHolder(View v, OnRecyclerItemClickListener l) {
            super(v);
            this.view = findRecyclerItemViewById(v, l);
        }

        public void build(T item) {
            if (this.view instanceof BaseRecyclerItemView) {
                ((BaseRecyclerItemView) this.view).build(item);
            }
        }

        private View findRecyclerItemViewById(View v, OnRecyclerItemClickListener l) {
            this.view = v.findViewById(R.id.recycler_item_view);
            if (this.view == null || !(this.view instanceof BaseRecyclerItemView)) {
                return v;
            }
            ((BaseRecyclerItemView) this.view).setOnItemClickListener(l);
            return this.view;
        }
    }
发现bulid最后是通过BaseRecyclerItemView的build方法构造的,BaseRecyclerItemView就只那个layout,所以layout必须继承这个,在xml里看到就是继承了这个的自定义View。看下BaseRecyclerItemView

public interface BaseRecyclerItemView<T> extends OnClickListener, OnLongClickListener {
    void build(T t);

    T getItem();

    void setOnItemClickListener(OnRecyclerItemClickListener onRecyclerItemClickListener);
}
就是一个接口,这里的T就对应BaseRecyclerItem,有build方法,getItem还有点击的监听,因为RecycleView没有Item点击事件,只能自己写。


用RecycleView实现多种样式在一起还是较为容易的,不过写出一个良好的通用的框架就不那么容易了。这里通过BaseRecyclerItem,BaseRecyclerItemView,ViewHoder,整个结构就很清晰了,再通过getItemViewType区分列表,再创建ViewHoder和绑定ViewHoder时处理,整个就很简单了。使用时只需要去对item数据操作就行了,主要是addItem,注意item数据要继承BaseRecyclerItem,BaseRecyclerItem的layout要继承BaseRecyclerItemView。

熟悉结构后就可以根据自己项目需要自己打造一个通用的可包含不同ItemView的adpater了。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值