Android 通用的ViewHolder和Adapter的打造

原来的Adapter写法

还记得刚学习Android时,刚写Adapter很直白,利用一个ViewHolder的缓存和convertview的复用来实现,当时感觉那种方式蛮不错的,最近准备中软杯项目的时候感觉总是要重复写getView里的方法真的是很不方便,我觉得还是可以进一步抽象的,这里参考了鸿洋大神的博客,博客地址http://blog.csdn.net/lmj623565791/article/details/38902805

package com.tmall.adapter;

import java.util.List;

import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.tmall.R;
import com.tmall.bean.CenterItem;
import com.tmall.view.base.BaseApplication;

public class CenterListAdapter extends MyBaseAdapter<CenterItem> {

    public CenterListAdapter(List<CenterItem> list) {
        super(list);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder holder = new ViewHolder();

        if (convertView == null) {
            convertView = View.inflate(BaseApplication.context,
                    R.layout.center_list_item, null);
            holder = new ViewHolder();
            holder.ivLeft = (ImageView) convertView
                    .findViewById(R.id.center_item_iv_left);
            holder.tv = (TextView) convertView
                    .findViewById(R.id.center_item_tv);
            holder.ivRight = (ImageView) convertView
                    .findViewById(R.id.center_item_iv_right);

            convertView.setTag(holder);

        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        CenterItem item = list.get(position);

        holder.ivLeft.setBackgroundResource(item.getIvLeftId());
        holder.tv.setText(item.getText());
        holder.ivRight.setBackgroundResource(item.getIvRightId());

        return convertView;
    }

    class ViewHolder {
        ImageView ivLeft;
        TextView tv;
        ImageView ivRight;
    }

}

就是像上面的那样,每个adapter要写一个ViewHolder来缓存一个View,并且每次判断convertView是否为空决定是否复用。这样性能是没问题的,其实也不完全是,如果为了某些业务(比如之前我为了实现一个ScrollView中嵌套ListView来实现滑动效果时,由于设置高度自己测量,因而list里有多少数据就会加载多少数据而且缓存住,不会被回收,这样如果数据少还看不太出来,数据量一大就oom了),当然这个问题这里先不探讨,我们现在先讨论怎么对这段代码进行简化。

打造通用的ViewHolder

其实如果ViewHolder能通用的话,getView里的代码都可以抽出去了,之前单独思考的时候就是没想到这个ViewHolder怎么抽出去,参考了鸿洋大神的博客之后,毛瑟顿开,后面的也就一股脑的写了。Android里有个SparseArray的类,只能存储int类型的数据,但是性能要比HashMap好很多。刚好在Android里布局文件id等都是以int的格式,方便存储。

public class ListViewHolder {

    private SparseArray<View> myViews;//view的缓存数组,性能比hashmap要好

    private View mContextView;//缓存的父类view


    private ListViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
        this.myViews = new SparseArray<>();
        this.mContextView = LayoutInflater.from(context).inflate(layoutId, parent, false);
        mContextView.setTag(this);
    }


    public static ListViewHolder getHolder(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
        if (convertView == null) {
            return new ListViewHolder(context, parent, layoutId, position);
        }
        return (ListViewHolder) convertView.getTag();
    }
}

这样adapter就可以调用getHolder方法成功的拿到通用的ViewHolder了,同时给它添加通用的getView方法让外部可以直接根据id拿到指定类型的view,由于类型不确定这里用泛型表示。

public class ListViewHolder {

    private SparseArray<View> myViews;//view的缓存数组,性能比hashmap要好

    private View mContextView;//缓存的父类view


    private ListViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
        this.myViews = new SparseArray<>();
        this.mContextView = LayoutInflater.from(context).inflate(layoutId, parent, false);
        mContextView.setTag(this);
    }


    public static ListViewHolder getHolder(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
        if (convertView == null) {
            return new ListViewHolder(context, parent, layoutId, position);
        }
        return (ListViewHolder) convertView.getTag();
    }

    /**
     * 通过控件的id来获取view
     *
     * @param viewId 控件的id
     * @param <T>    view的类型
     * @return view
     */
    public <T extends View> T getView(int viewId) {
        //先检测缓存view里是否存在该view,不存在则find出来添加进去
        View view = myViews.get(viewId);
        if (view == null) {
            view = mContextView.findViewById(viewId);
            myViews.put(viewId, view);
        }
        return (T) view;
    }
}

然后adapter里就好写啦,直接调用getHolder方法拿到返回的ViewHolder,就不用再adapter里写啦,剩下的adapter的通用代码就可以抽出来了,依赖子类实现的就给抽象方法交给子类。那么父类adapter的写法如下:

package com.we.piccategory.adapter;

import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import com.we.piccategory.ui.base.BaseApp;

import java.util.List;

/**
 * Created with Android Studio
 * User: 潘浩
 * School 南华大学
 * Date: 2017/5/20
 * Time: 13:30
 * Description:
 */
public abstract class AutoAdapter<T> extends BaseAdapter {

    protected List<T> list;

    public AutoAdapter(List<T> list) {
        this.list = list;
    }

    @Override
    public int getCount() {
        return list == null ? 0 : list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position) == null ? null : list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ListViewHolder holder = ListViewHolder.getHolder(BaseApp.context, convertView, parent, getLayoutId(), position);
        convert(holder, (T) getItem(position));
        return holder.getConvertView();
    }

    protected abstract void convert(ListViewHolder holder, T item);

    public abstract int getLayoutId();


}

因为getview方法里对convertview的服用判断做了实现,这里就不需要再那样写了,而剩下的不同的组件设置不同的内容全都交给子类实现就好了,这样子类只要实现这个父类就可以了。当然这里是可以给一些通用的方法实现,比如对textView操作比较多,就写一个供外部调用的功能出来,修正完毕的通用VIewHolder如下:

package com.we.piccategory.adapter;

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import static com.we.piccategory.ui.base.BaseApp.context;

/**
 * Created with Android Studio
 * User: 潘浩
 * School 南华大学
 * Date: 2017/5/20
 * Time: 13:35
 * Description:
 */
public class ListViewHolder {

    private SparseArray<View> myViews;//view的缓存数组,性能比hashmap要好

    private View mContextView;//缓存的父类view


    private ListViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
        this.myViews = new SparseArray<>();
        this.mContextView = LayoutInflater.from(context).inflate(layoutId, parent, false);
        mContextView.setTag(this);
    }


    public static ListViewHolder getHolder(Context context, View convertView, ViewGroup parent, int layoutId, int position) {
        if (convertView == null) {
            return new ListViewHolder(context, parent, layoutId, position);
        }
        return (ListViewHolder) convertView.getTag();
    }

    /**
     * 通过控件的id来获取view
     *
     * @param viewId 控件的id
     * @param <T>    view的类型
     * @return view
     */
    public <T extends View> T getView(int viewId) {
        //先检测缓存view里是否存在该view,不存在则find出来添加进去
        View view = myViews.get(viewId);
        if (view == null) {
            view = mContextView.findViewById(viewId);
            myViews.put(viewId, view);
        }
        return (T) view;
    }


    public ListViewHolder setText(int viewId, String text) {
        TextView view = getView(viewId);
        view.setText(text);
        return this;
    }

    public ListViewHolder setImageRes(int viewId, int resId) {
        ImageView iv = getView(viewId);
        Glide.with(context).load(resId).fitCenter().centerCrop().into(iv);
        return this;
    }

    public ListViewHolder setImgUrl(int viewId, String url) {
        ImageView iv = getView(viewId);
        Glide.with(context).load(url).fitCenter().centerCrop().into(iv);
        return this;
    }


    public View getConvertView() {
        return mContextView;
    }


}

然后子类里就非常代码量就非常好写啦,甚至可以写成内部类都可以,当然我一般是不会这么写的,因为我觉得能跟activity解耦的东西就要解耦,除了一些监听器之类的写在activity内部类,别的尽量还是抽出去保持adapter代码的纯洁性。实现的adapter代码如下:

package com.we.piccategory.adapter;

import com.we.piccategory.R;
import com.we.piccategory.bean.Pic;

import java.util.List;

/**
 * Created with Android Studio
 * User: 潘浩
 * School 南华大学
 * Date: 2017/5/22
 * Time: 14:42
 * Description:
 */
public class TestAdapter extends BaseRecycleAdapter<Pic> {

    public TestAdapter(List<Pic> list, int layoutId) {
        super(list, layoutId);
    }

    public void setList(List<Pic> list) {
        this.list = list;
        this.notifyDataSetChanged();
    }


    @Override
    protected void convert(RecycleViewHolder holder, Pic pic) {
        holder.setText(R.id.home_tv, pic.getName());
        holder.setImageRes(R.id.home_iv, pic.getResId());
        holder.buildHolder(R.id.home_iv);
    }
}

怎么样,是不是比原来那种写法好了很多呢?至少我是更情愿这样的写法,尤其是在做项目中感受尤为真切,这次中软杯客户端android基本我一个人纯手撸,说实话真的是相同的代码写到要吐,所以说还是不要重复造轮子的好。有人说大神都是写了几十万行代码堆出来的,这个怎么说呢,这种大神的几十万行代码,肯定是质量越来越高的,而不是无用代码堆出来的,所以说还是要掌握方式和方法才对。这学期算是真的蛮忙的,连博客都荒废了,不知道为啥自己之前不太喜欢写博客,但最近感觉不写博客会忘记一些东西,之前可能觉得天天自学想要学的更多更快,但其实学过的知识要是不巩固不复习的话,很容易忘记的,虽然有时候翻翻代码又会找出来,但是很难在原来的基础上有提升,不管再忙还是还是坚持吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值