ScrollView、RecyclerView、ScrollView嵌套ListView性能优化方案

来源:互联网 发布:Mac 不能共享文件夹 编辑:程序博客网 时间:2024/05/26 07:28

因为项目的需要我们不可避免的需要使用类似的布局方案,我之前写过的一篇文章总结ScrollView嵌套ListView的解决方法,提出了相应的解决方案。但是却陷入了一个性能的大坑:因为之前的解决方案都是以计算出ListView控件的总高度并固定,那么自然就破坏了LisView内置的特性,造成了Adapter中的 getView会被疯狂的调用。(这里就不贴代码了,用过的童鞋应该都懂)


来,让我们直接开启优化模式。

同样是得自定义View,不过我们继承的不再是ListView,而是LinearLayout。(感谢@张旭童同志的封装,文末贴出源码及源地址)
下面是自定义NestFullListView代码
<span style="font-size:12px;">/** * 介绍:完全伸展开的ListView(LinearLayout) * 作者:zhangxutong * 邮箱:zhangxutong@imcoming.com * 时间: 2016/9/9. */public class NestFullListView extends LinearLayout {    private LayoutInflater mInflater;    private List<NestFullViewHolder> mVHCahces;//缓存ViewHolder,按照add的顺序缓存,    public NestFullListView(Context context) {        this(context, null);    }    public NestFullListView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public NestFullListView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context);    }    private void init(Context context) {        mInflater = LayoutInflater.from(context);        mVHCahces = new ArrayList<NestFullViewHolder>();        //annotate by zhangxutong 2016 09 23 for 让本控件能支持水平布局,项目的意外收获= =        //setOrientation(VERTICAL);    }    private NestFullListViewAdapter mAdapter;    /**     * 外部调用  同时刷新视图     *     * @param mAdapter     */    public void setAdapter(NestFullListViewAdapter mAdapter) {        this.mAdapter = mAdapter;        updateUI();    }    public void updateUI() {        if (null != mAdapter) {            if (null != mAdapter.getDatas() && !mAdapter.getDatas().isEmpty()) {                //数据源有数据                if (mAdapter.getDatas().size() > getChildCount()) {//数据源大于现有子View不清空                } else if (mAdapter.getDatas().size() < getChildCount()) {//数据源小于现有子View,删除后面多的                    removeViews(mAdapter.getDatas().size(), getChildCount() - mAdapter.getDatas().size());                    //删除View也清缓存                    while (mVHCahces.size() > mAdapter.getDatas().size()) {                        mVHCahces.remove(mVHCahces.size() - 1);                    }                }                for (int i = 0; i < mAdapter.getDatas().size(); i++) {                    NestFullViewHolder holder;                    if (mVHCahces.size() - 1 >= i) {//说明有缓存,不用inflate,否则inflate                        holder = mVHCahces.get(i);                    } else {                        holder = new NestFullViewHolder(getContext(), mInflater.inflate(mAdapter.getItemLayoutId(), this, false));                        mVHCahces.add(holder);//inflate 出来后 add进来缓存                    }                    mAdapter.onBind(i, holder);                    //如果View没有父控件 添加                    if (null == holder.getConvertView().getParent()) {                        this.addView(holder.getConvertView());                    }                }            } else {                removeAllViews();//数据源没数据 清空视图            }        } else {            removeAllViews();//适配器为空 清空视图        }    }}</span>

代码解析:代码增加一个变量 privateList<NestFullViewHolder>mVHCahces;//缓存ViewHolder,按照add的顺序缓存

每次updateUI()时,如果是异常情况:适配器为空 清空视图,数据源没数据 清空视图 

那么数据源有数据的情况下,比较数据源的size 和现在子View(ItemView)的size:
1、如果数据源大于现有子View,说明屏幕上的View不够用,当然不remove子View,也不用清缓存。 
2、如果数据源小于现有子View,删除尾部多的子View,清理多余缓存的ItemView。

遍历数据源,比较i(postion)和viewCaches的size:
1、如果缓存不够就inflate一个新View。
2、如果缓存有,就取出缓存的View。 
回调Adapter的onBind方法, 
判断这个View有没有父控件, 
如果View没有父控件 才addView()。



在上面的代码中已经尽可能的避免了View的inflate,addView()操作。可是我们都知道,findViewById()的操作也是很费时的,所以在上面的代码中
使用了ViewHolder,下面就来看看这个ViewHolder,代码有点长,作者考虑的比较细致,封装一些常用的方法,例如setText、setImageResource
等,供外部调用使用,同时还包括一些监听事件。

下面是NestFullViewHolder代码
/** * NestFullListView 的ViewHolder ,使用者无需关心 * Created by zhangxutong . * Date: 16/03/11 */public class NestFullViewHolder {    private SparseArray<View> mViews;    private View mConvertView;    private Context mContext;    public NestFullViewHolder(Context context, View view) {        mContext = context;        this.mViews = new SparseArray<View>();        mConvertView = view;    }    /**     * 通过viewId获取控件     *     * @param viewId     * @return     */    public <T extends View> T getView(int viewId) {        View view = mViews.get(viewId);        if (view == null) {            view = mConvertView.findViewById(viewId);            mViews.put(viewId, view);        }        return (T) view;    }    public View getConvertView() {        return mConvertView;    }    public NestFullViewHolder setSelected(int viewId, boolean flag) {        View v = getView(viewId);        v.setSelected(flag);        return this;    }    /**     * 设置TextView的值     *     * @param viewId     * @param text     * @return     */    public NestFullViewHolder setText(int viewId, String text) {        TextView tv = getView(viewId);        tv.setText(text);        return this;    }    public NestFullViewHolder setImageResource(int viewId, int resId) {        ImageView view = getView(viewId);        view.setImageResource(resId);        return this;    }    public NestFullViewHolder setImageBitmap(int viewId, Bitmap bitmap) {        ImageView view = getView(viewId);        view.setImageBitmap(bitmap);        return this;    }    public NestFullViewHolder setImageDrawable(int viewId, Drawable drawable) {        ImageView view = getView(viewId);        view.setImageDrawable(drawable);        return this;    }    public NestFullViewHolder setBackgroundColor(int viewId, int color) {        View view = getView(viewId);        view.setBackgroundColor(color);        return this;    }    public NestFullViewHolder setBackgroundRes(int viewId, int backgroundRes) {        View view = getView(viewId);        view.setBackgroundResource(backgroundRes);        return this;    }    public NestFullViewHolder setTextColor(int viewId, int textColor) {        TextView view = getView(viewId);        view.setTextColor(textColor);        return this;    }    public NestFullViewHolder setTextColorRes(int viewId, int textColorRes) {        TextView view = getView(viewId);        view.setTextColor(mContext.getResources().getColor(textColorRes));        return this;    }    @SuppressLint("NewApi")    public NestFullViewHolder setAlpha(int viewId, float value) {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {            getView(viewId).setAlpha(value);        } else {            // Pre-honeycomb hack to set Alpha value            AlphaAnimation alpha = new AlphaAnimation(value, value);            alpha.setDuration(0);            alpha.setFillAfter(true);            getView(viewId).startAnimation(alpha);        }        return this;    }    public NestFullViewHolder setVisible(int viewId, boolean visible) {        View view = getView(viewId);        view.setVisibility(visible ? View.VISIBLE : View.GONE);        return this;    }    public NestFullViewHolder linkify(int viewId) {        TextView view = getView(viewId);        Linkify.addLinks(view, Linkify.ALL);        return this;    }    public NestFullViewHolder setTypeface(Typeface typeface, int... viewIds) {        for (int viewId : viewIds) {            TextView view = getView(viewId);            view.setTypeface(typeface);            view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);        }        return this;    }    public NestFullViewHolder setProgress(int viewId, int progress) {        ProgressBar view = getView(viewId);        view.setProgress(progress);        return this;    }    public NestFullViewHolder setProgress(int viewId, int progress, int max) {        ProgressBar view = getView(viewId);        view.setMax(max);        view.setProgress(progress);        return this;    }    public NestFullViewHolder setMax(int viewId, int max) {        ProgressBar view = getView(viewId);        view.setMax(max);        return this;    }    public NestFullViewHolder setRating(int viewId, float rating) {        RatingBar view = getView(viewId);        view.setRating(rating);        return this;    }    public NestFullViewHolder setRating(int viewId, float rating, int max) {        RatingBar view = getView(viewId);        view.setMax(max);        view.setRating(rating);        return this;    }    public NestFullViewHolder setTag(int viewId, Object tag) {        View view = getView(viewId);        view.setTag(tag);        return this;    }    public NestFullViewHolder setTag(int viewId, int key, Object tag) {        View view = getView(viewId);        view.setTag(key, tag);        return this;    }    public NestFullViewHolder setChecked(int viewId, boolean checked) {        Checkable view = (Checkable) getView(viewId);        view.setChecked(checked);        return this;    }    /**     * 关于事件的     */    public NestFullViewHolder setOnClickListener(int viewId,                                                 View.OnClickListener listener) {        View view = getView(viewId);        view.setOnClickListener(listener);        return this;    }    public NestFullViewHolder setOnTouchListener(int viewId,                                                 View.OnTouchListener listener) {        View view = getView(viewId);        view.setOnTouchListener(listener);        return this;    }    public NestFullViewHolder setOnLongClickListener(int viewId,                                                     View.OnLongClickListener listener) {        View view = getView(viewId);        view.setOnLongClickListener(listener);        return this;    }}


代码解析:
利用privateSparseArray<View>mViews,以viewId为key,存储ItemView里的各种View。(这里简单解释一下SparseArray是Android 特有的类似于Java的HashMap的键值对存储方式,只不过SparseArray必须以<Integer, E>>类型来的使用,效率更高,本文正好可以用上)

通过public T getView(int viewId)方法,以viewId为key,获取ItemView里的各种View, 
该方法是先从mViews的缓存里寻找View,如果找到了直接返回, 
如果没找到就view = mConvertView.findViewById(viewId);执行findViewById,得到这个View,并放入mViews的缓存里,这样下次就不用执行findViewById方法。


好了,最后我们来构造我们的NestFullListViewAdapter

/** * 介绍:完全伸展开的ListView的适配器 * 作者:zhangxutong * 邮箱:mcxtzhang@163.com * CSDN:http://blog.csdn.net/zxt0601 * 时间: 16/09/09. */public abstract class NestFullListViewAdapter<T> {    private int mItemLayoutId;//看名字    private List<T> mDatas;//数据源    public NestFullListViewAdapter(int mItemLayoutId, List<T> mDatas) {        this.mItemLayoutId = mItemLayoutId;        this.mDatas = mDatas;    }    /**     * 被FullListView调用     *     * @param i     * @param holder     */    public void onBind(int i, NestFullViewHolder holder) {        //回调bind方法,多传一个data过去        onBind(i, mDatas.get(i), holder);    }    /**     * 数据绑定方法     *     * @param pos    位置     * @param t      数据     * @param holder ItemView的ViewHolder     */    public abstract void onBind(int pos, T t, NestFullViewHolder holder);    public int getItemLayoutId() {        return mItemLayoutId;    }    public void setItemLayoutId(int mItemLayoutId) {        this.mItemLayoutId = mItemLayoutId;    }    public List<T> getDatas() {        return mDatas;    }    public void setDatas(List<T> mDatas) {        this.mDatas = mDatas;    }}
核心代码主要就在onBind()方法中,如果对这种封装方式不是很清楚的的童鞋强烈建议有时间去看看   鸿洋大神的文章 鸿洋:打造万能适配器

好了,最重要的代码我们已经完成了剩下的就是调用了,So easy! 
首先我们得在我们需要使用Xml文件中写入类似的代码(我们继承的是LinearLayout,所以理所应当线性布局的属性我们都能够使用拉!哈哈,想到这儿是不是发现还能来个orientatiion="horizonal"呢?当然拉,有兴趣的童鞋可以试试。)
<xxx.xxx.xxx.NestFullListView
  android:id="@+id/cstFullShowListView"
android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical"
android:showDividers="middle"/>
最后在我们需要使用的地方这样调用一下
nestFullListView = (NestFullListView) findViewById(R.id.cstFullShowListView);nestFullListView.setAdapter(new NestFullListViewAdapter<TestBean>(R.layout.item_lv, mDatas) {    @Override    public void onBind(int pos, TestBean testBean, NestFullViewHolder holder) {        holder.setText(R.id.tv, testBean.getName());//按照这种方式写起来是很爽的吧A_V;    }});
好啦相对而言性能优化方案已经出来啦。这里有原作者的Demo
github传送门: 
https://github.com/mcxtzhang/NestFullListView 
复制FullListView包下三个文件(NestFullListView NestFullListViewAdapter NestFullViewHolder)即可畅快使用
Ok,小伙伴们还有什么问题可以私信我哦


1 0