【Android】第三方ONE开发之ListView的学习以及万能Adapter的打造

ListView的学习及万能Adapter的打造

ListView学习

ListView与RecyclerView的不同

  1. ListView可以用OnItemClickListener方便地对Item的点击事件作出回应。
  2. RecyclerView可以通过LayoutManager实现不同的布局效果。并且可设置横向或纵向滑动
  3. ListView本身没有ViewHolder,需要自己写ViewHolder来对其进行优化,而Recycler有了自己的ViewHolder,规范化了ViewHolder的写法。
  4. ListView可以设置选择模式,并添加MultiChoiceModeListener。
  5. RecyclerView在默认情况下并不在item之间展示间隔符,而在ListView中如果我们想要在item之间添加间隔符,我们只需要在布局文件中对ListView添加如下属性即可
android:divider="@android:color/transparent"
android:dividerHeight="5dp"

打造ListView的万能Adapter

传统的Adapter写法

public class MyAdapter extends BaseAdapter {
    private List<Book> mDatas;
    private LayoutInflater mInflater;

    public MyAdapter(Context context, List<Book> datas){
        mInflater = LayoutInflater.from(context);
        mDatas = datas;
    }

    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null){
            convertView = mInflater.inflate(R.layout.item_list,parent,false);
            holder = new ViewHolder();

            holder.mName = convertView.findViewById(R.id.tv_bookname);
            holder.mDesc = convertView.findViewById(R.id.tv_desc);
            holder.mPrice = convertView.findViewById(R.id.tv_price);

            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }

        Book book = mDatas.get(position);
        holder.mName.setText(book.getName());
        holder.mDesc.setText(book.getDesc());
        holder.mPrice.setText("¥"+String.valueOf(book.getPrice()));

        return convertView;
    }

    private class ViewHolder{
        TextView mName;
        TextView mDesc;
        TextView mPrice;
    }
}

可以发现,传统的Adapter非常长,并且其中有许多重复的代码,我们完全可以把它们抽出来封装成一个更简单的Adapter,所以我们在这里做一个万能的Adapter。

打造通用ViewHolder

传统的ViewHolder是通过convertView的setTag,然后在后面进行获取holder的
convertView.setTag(holder);
ViewHolder中存放了Item中各种控件的引用。然后再在getView方法中来获取初始化ViewHolder等操作。
现在由于我们需要做成一个通用的,所以我们需要一个容器,来存储我们的控件。这里存储控件的容器,由于Map效率不是那么高,所以我们这里使用SparseArray来存储。它实际上也是Map,key为Integer,value为Object,它比传统的HashMap效率更高,以后若是key是Integer的Map,均可以用它。然后可以通过ViewHolder的getView(int id)来获取到我们的控件。

代码如下

public class ViewHolder {
    private SparseArrayView mViews;
    private int mPosition;
    private View mConvertView;

    public ViewHolder(Context context, ViewGroup parent,int layoutId,int position){
        this.mPosition = position;
        this.mViews = new SparseArray<View>();
        mConvertView = LayoutInflater.from(context).inflate(layoutId,parent,false);
        mConvertView.setTag(this);
    }

    public static ViewHolder get(Context context,View convertView,ViewGroup parent,
                                 int layoutId,int position){
        if (convertView == null){
            return new ViewHolder(context,parent,layoutId,position);
        }else{
            ViewHolder holder = (ViewHolder) convertView.getTag();
            holder.mPosition = position;    //更新位置
            return holder;
        }
    }

    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;
    }
}

下面是我们运用了通用ViewHolder的Adapter,可以看到,相比原来的传统Adapter,代码量减少了一些。

public class MyAdapterWithViewHolder extends BaseAdapter {
    private List<Book> mDatas;
    private LayoutInflater mInflater;
    private Context mContext;

    public MyAdapterWithViewHolder(Context context, List<Book> datas){
        mInflater = LayoutInflater.from(context);
        mDatas = datas;
        mContext = context;
    }

    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = ViewHolder.get(mContext,convertView,parent, R.layout.item_list,position);

        Book book = mDatas.get(position);

        TextView mName = holder.getView(R.id.tv_bookname);
        TextView mDesc = holder.getView(R.id.tv_desc);
        TextView mPrice = holder.getView(R.id.tv_price);

        mName.setText(book.getName());
        mDesc.setText(book.getDesc());
        mPrice.setText("¥"+String.valueOf(book.getPrice()));

        return holder.getConvertView();
    }
}

二、打造通用的Adapter

观察传统的Adapter的代码可以发现,除了getView的代码不太一样以外,其余基本都大同小异,因此我们可以建立一个CommonAdapter类,抽取重复部分。

public abstract class CommonAdapterT extends BaseAdapter {
    protected Context mContext;
    protected ListT mDatas;
    protected LayoutInflater mInflater;

    public CommonAdapter(Context context, List<T> datas) {
        this.mDatas = datas;
        mInflater = LayoutInflater.from(context);
        this.mContext = context;
    }

    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public T getItem(int position) {
        return mDatas.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent){
        ViewHolder holder = ViewHolder.get(mContext,convertView,parent,R.layout.item_list,position);

        convert(holder,mDatas.get(position));

        return holder.getConvertView();
    }

    //将getView方法继续抽,抽掉return语句以及初始化holder语句。
    public abstract void convert(ViewHolder holder,T t);
}

于是,当我们写Adapter时,就只需要这样写就好了:

public class MyAdapterWithViewHolder extends CommonAdapterBook {

    public MyAdapterWithViewHolder(Context context, ListBook datas){
        super(context,datas);
    }

    @Override
    public void convert(ViewHolder holder, Book book) {
        TextView mName = holder.getView(R.id.tv_bookname);
        TextView mDesc = holder.getView(R.id.tv_desc);
        TextView mPrice = holder.getView(R.id.tv_price);

        mName.setText(book.getName());
        mDesc.setText(book.getDesc());
        mPrice.setText("¥"+String.valueOf(book.getPrice()));
    }
}

可以发现,代码相对于我们传统的Adapter,减少了接近60行代码。

三、够简单了么?还可以更简单!

我们还可以在ViewHolder中为TextView专门设计设置文字的方法,为ImageView专门设计设置图片的方法等等

public class ViewHolder {
    private SparseArrayView mViews;
    private int mPosition;
    private View mConvertView;

    public ViewHolder(Context context, ViewGroup parent,int layoutId,int position){
        this.mPosition = position;
        this.mViews = new SparseArray<View>();
        mConvertView = LayoutInflater.from(context).inflate(layoutId,parent,false);
        mConvertView.setTag(this);
    }

    public static ViewHolder get(Context context,View convertView,ViewGroup parent,
                                 int layoutId,int position){
        if (convertView == null){
            return new ViewHolder(context,parent,layoutId,position);
        }else{
            ViewHolder holder = (ViewHolder) convertView.getTag();
            holder.mPosition = position;    //更新位置
            return holder;
        }
    }

    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;
    }

    //设置TextView的值
    public ViewHolder setText(int viewId,CharSequence text){
        TextView textView = getView(viewId);
        textView.setText(text);
        return this;
    }

    //设置ImageView的图片(resource)
    public ViewHolder setImageResource(int viewId,int resId){
        ImageView imageView = getView(viewId);
        imageView.setImageResource(resId);
        return this;
    }

    //设置ImageView的图片(bitmap)
    public ViewHolder setImageBitmap(int viewId, Bitmap bitmap){
        ImageView imageView = getView(viewId);
        imageView.setImageBitmap(bitmap);
        return this;
    }

    //设置ImageView的图片(drawable)
    public ViewHolder setImageResource(int viewId, Drawable drawable){
        ImageView imageView = getView(viewId);
        imageView.setImageDrawable(drawable);
        return this;
    }

    //设置ImageView的图片(url)
    public ViewHolder setImageUrl(int viewId, String url){
        ImageView imageView = getView(viewId);
        //比如我这里有一个ImageLoader类
        //ImageLoader.getInstance().loadImg(view,url);
        return this;
    }
}

这样我们使用的时候只需要这样就可以成功写出Adapter了。

可以发现,代码量减少了非常非常多。

public void convert(ViewHolder holder, final Book book) {
        holder.setText(R.id.tv_bookname,book.getName())
                .setText(R.id.tv_desc,book.getDesc())
                .setText(R.id.tv_price,String.valueOf(book.getPrice()));
}

N0tExpectErr0r

N0tExpectErr0r

一名热爱代码的 Android 开发者

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>