Android学习笔记(一)——ListView

2015年12月19日

ListView的优化

主要优化在Adapter,减少渲染每个Item视图的时间。

在Adapter的getView()方法中,有两处是比较耗时的,一个是LayoutInflater的inflate()方法,另一个是每次设置item里view属性时需要用到findViewById时。而这两种方法在listview中是需要多次调用的,完全可以通过缓存的方法进行优化。

首先看下不进行优化时的getView方法:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.layout_test, null);
        TextView tv_1 = view.findViewById(R.id.tv_1);
        tv_1.setText("text1");
        TextView tv_2 = view.findViewById(R.id.tv_2);
        tv_2.setText("text2");
        return view;
    }

现在利用convertView优化inflate部分,当ListView的某些Item被划出条目时,系统会收回这个Item的View,这个View就是convertView,我们可以利用这个convertView避免每次都要调用inflate()方法。代码实例:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(converView==null){
            convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_test, null);
        }
        TextView tv_1 = convertView.findViewById(R.id.tv_1);
        tv_1.setText("text1");
        TextView tv_2 = convertView.findViewById(R.id.tv_2);
        tv_2.setText("text2");
        return convertView;
    }

接下来利用ViewHolder类优化findViewById部分。ViewHolder可以理解为一种View的缓存机制,可以避免重复地调用findViewById。代码实例:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(converView==null){
            convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_test, null);
        }
        TextView tv_1 = ViewHolder.get(convertView, R.id.tv_1);
        tv_1.setText("text1");
        TextView tv_2 = ViewHolder.get(converView, R.id.tv_2);
        tv_2.setText("text2");
        return convertView;
    }

ViewHolder有很多种写法,下面是一个简单地ViewHolder示例:

public class ViewHolder {
    public static <T extends View> T get(View view, int id) {
        SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArray<View>();
            view.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = view.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (T) childView;
    }
}

###ListView中的Adapter复用

在设计每个ListView的Adapter时,Adapter需要继承于BaseAdapter,此时需要实现几个abstract的方法,但其中只有getView是比较厚重的,其余几个方法基本上所有Adapter都是一样的。所以我们可以只把getView方法抽象出来,定义一个CommonAdapter虚类:

public abstract class CommonAdapter<T> extends BaseAdapter {

    protected LayoutInflater mInflater;

    protected Context mContext;

    protected List<T> mDatas;

    protected final int mItemLayoutId;

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


    public void setDataList(List<T> data) {
        mDatas = data;
        notifyDataSetChanged();
    }

    public List<T> getDataList() {
        return mDatas;
    }

    public void addItem(T item) {
        if (mDatas == null) {
            mDatas = new ArrayList<T>();
        }
        mDatas.add(item);
        notifyDataSetChanged();
    }

    public void addItem(T item, int index) {
        if (mDatas == null) {
            mDatas = new ArrayList<T>();
        }
        mDatas.add(index, item);
        notifyDataSetChanged();
    }

    public void removeItem(T item) {
        removeItem(item, true);
    }

    public void removeItem(T item, boolean notifyDataSetChanged) {
        if (mDatas != null && mDatas.size() != 0) {
            mDatas.remove(item);

        }
        if (notifyDataSetChanged) {
            notifyDataSetChanged();
        }
    }

    public void replace(T old, T newItem) {
        int pos = mDatas.indexOf(old);
        if (pos >= 0) {
            removeItem(old, false);
            addItem(newItem, pos);
        } else {
            addItem(newItem, 0);
        }
    }

    @Override
    public int getCount() {
        return mDatas == null ? 0 : 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) {
        final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent);
        convert(viewHolder, getItem(position));
        return viewHolder.getConvertView();
    }

    public abstract void convert(CommonViewHolder helper, T item);

    private CommonViewHolder getViewHolder(int position, View convertView, ViewGroup parent) {
        return CommonViewHolder.get(mContext, convertView, parent, mItemLayoutId, position);
    }
}

其中的CommonViewHolder类:

public class CommonViewHolder {
	private final SparseArray<View> mViews;
	private int mPosition;
	private View mConvertView;

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

	/**
	 * 拿到一个ViewHolder对象
	 * 
	 * @param context
	 * @param convertView
	 * @param parent
	 * @param layoutId
	 * @param position
	 * @return
	 */
	public static CommonViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position)
	{
		if (convertView == null)
		{
			return new CommonViewHolder(context, parent, layoutId, position);
		}
		return (CommonViewHolder) convertView.getTag();
	}

	public View getConvertView()
	{
		return mConvertView;
	}

	/**
	 * 通过控件的Id获取对于的控件,如果没有则加入views
	 * 
	 * @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;
	}

	/**
	 * 为TextView设置字符串
	 * 
	 * @param viewId
	 * @param text
	 * @return
	 */
	public CommonViewHolder setText(int viewId, String text)
	{
		TextView view = getView(viewId);
		view.setText(text);
		return this;
	}

	/**
	 * 为ImageView设置图片
	 * 
	 * @param viewId
	 * @param drawableId
	 * @return
	 */
	public CommonViewHolder setImageResource(int viewId, int drawableId)
	{
		ImageView view = getView(viewId);
		view.setImageResource(drawableId);
		return this;
	}

	/**
	 * 为ImageView设置图片
	 * 
	 * @param viewId
	 * @param bm
	 * @return
	 */
	public CommonViewHolder setImageBitmap(int viewId, Bitmap bm)
	{
		ImageView view = getView(viewId);
		view.setImageBitmap(bm);
		return this;
	}

	public int getPosition()
	{
		return mPosition;
	}
}

这样具体的Adapter只要继承这个CommonAdapter,把具体的data类型传进来,再实现convert方法就可以了,节省了很多代码,也不用去特意地做优化了。