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

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/Android_Technology/article/details/52860531

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


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

同样是得自定义View,不过我们继承的不再是ListView,而是LinearLayout。(感谢@张旭童同志的封装,文末贴出源码及源地址)
下面是自定义NestFullListView代码
<span style="font-size:12px;">/**
 * 介绍:完全伸展开的ListView(LinearLayout)
 * 作者:zhangxutong
 * 邮箱:[email protected]
 * 时间: 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>

代码解析:代码增加一个变量 private List<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;
    }
}


代码解析:
利用 private SparseArray<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
 * 邮箱:[email protected]
 * 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,小伙伴们还有什么问题可以私信我哦
 
 


猜你喜欢

转载自blog.csdn.net/Android_Technology/article/details/52860531