适配多种数据结构和ui的万能适配器

首页作为app的访问最多的页面,注定是拥有丰富的ui元素,要驾驭这么多的ui元素并不影响性能的话,listview是个非常适合的选择,问题是不同的元素需要的数据结构体也不一样,item的布局文件也不一样。该如何处理呢

这是万能适配器应运而生了,主要思路是通过一个map来记录list中元素的种类,之后就是让对应的item都继承于同一个接口,统一调用adapter的getView方法进行模版化的渲染动作,具体的渲染交给各种item的方法来进行,在初始化的时候,主需要传递数据集和对应的ui布局文件就可以了。

核心代码

MultiCollectionAdapter
public abstract class MultiCollectionAdapter extends BaseAdapter {

    final SparseArray<List> mMapOfTypeViewList;
    final SparseIntArray mMapOfLayoutIdType;

    public MultiCollectionAdapter() {
        mMapOfTypeViewList = new SparseArray<>(10);
        mMapOfLayoutIdType = new SparseIntArray(10);
    }

    public MultiCollectionAdapter(SparseArray<List> mapOfTypeViewList,SparseIntArray mapOfLayoutIdType) {
        this.mMapOfTypeViewList = mapOfTypeViewList;
        this.mMapOfLayoutIdType = mapOfLayoutIdType;
    }

    public int getLayoutId(int position) {
        if (null != mMapOfLayoutIdType && mMapOfLayoutIdType.size() != 0) {
            int itemViewType = getItemViewType(position);
            return mMapOfLayoutIdType.get(itemViewType);
        } else {
            throw new IllegalStateException("please override "+getClass().getName()+".getLayoutId or build by "+getClass().getName()+"(SparseArray<List>,SparseArray<Integer>)");
        }
    }

    public void putLayoutId(int key, int layoutId) {
        mMapOfLayoutIdType.put(key, layoutId);
    }

    public void updateData(int key,List list) {
        mMapOfTypeViewList.put(key, list);
        notifyDataSetChanged();
    }

    public void removeData(int key) {
        mMapOfTypeViewList.delete(key);
        notifyDataSetChanged();
    }

    public void appendData(int key, List list) {
        mMapOfTypeViewList.append(key, list);
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        int size = 0;
        for(int i = 0; i< mMapOfTypeViewList.size(); i++ ) {
            int key = mMapOfTypeViewList.keyAt(i);
            List list = mMapOfTypeViewList.get(key);
            size += getSizeOfListValue(list);
        }
        return size;
    }

    @Override
    public int getItemViewType(int position) {
        int prt = 0;
        int type = 0;
        for(int i = 0; i < mMapOfTypeViewList.size(); i++) {
            int key = mMapOfTypeViewList.keyAt(i);
            List list = mMapOfTypeViewList.get(key) ;
            prt += getSizeOfListValue(list);
            if (prt > position) {
                type = key;
                break;
            }
        }
        return type;
    }

    @Override
    public Object getItem(int position) {
        int size = 0;
        List list = null;
        int i = 0;
        for(; i < mMapOfTypeViewList.size(); i++) {
            int key = mMapOfTypeViewList.keyAt(i);
            list = mMapOfTypeViewList.get(key);
            size += getSizeOfListValue(list);
            if (size -1 >= position) {
                break;
            }
        }

        int p = getSizeOfListValue(list) - size + position;
        if (null!=list && 0 != list.size()) {
            return list.get(p);
        }

        return null;
    }

    protected int getSizeOfListValue(List list) {
        if(null!=list)
            return list.size();
        else
            return 1;
    }

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

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent);
        convert(viewHolder, getItem(position));
        View view = viewHolder.getConvertView();
        return view;
    }

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

    public boolean useCustomListSelector() {
        return false;
    }

    public SparseArray<List> getData() {
        return mMapOfTypeViewList;
    }

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

    private MultiLayoutAdapter.OnItemClickListener mItemClickListener;

    public void setOnItemClickListener(MultiLayoutAdapter.OnItemClickListener listener) {
        mItemClickListener = listener;
    }

    public interface OnItemClickListener{
        void onItemClick(int position, Object data, View itemView);
    }
}

 万能适配器负责管理两个map,一个mMapOfLayoutIdType是元素类型列表,记录list中的所有类型

另一个是mMapOfTypeViewList,记录各个类型对应的数据集,相当一个二维数组,第一维是layoutId,第二层是元素对应的数据集list

要做到适配不同的item,必须重写getItem方法(从list获取item数据)和getItemViewType方法

因为只有position这个参数可用,所以需要通过判断当前position落于哪一段,才能拿到正确的类型

值得注意的是getSizeOfListValue方法默认返回一个1,因为之后的判断会进行减

 

适配器最重要的方法莫过于getView

这里只是一个模版代码

final CommonViewHolder viewHolder = getViewHolder(position, convertView, parent);

        convert(viewHolder, getItem(position));

        View view = viewHolder.getConvertView();
     

convert方法是一个抽象方法,子类必须实现这个方法,相当于baseAdapter的getView方法,不过这里封装成传递的参数是更为实用的CommenViewHolder和对应的数据结构体(item的)

CommenViewHolder可以理解为封装好的ui的管理器,本身不是view,但只能通过

View convertView = helper.getConvertView();

获取converView

public class CommonViewHolder {

	private final SparseArray<View> mViews;
	private final int mLayoutId;
	private int mPosition;
	private final View mConvertView;

	public int getLayoutId() {
		return mLayoutId;
	}

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

	public static CommonViewHolder get(View convertView,
			ViewGroup parent, int layoutId, int position) {
		CommonViewHolder holder = null;
		if (convertView == null || ((CommonViewHolder) convertView.getTag()).getLayoutId() != layoutId) {
			holder = new CommonViewHolder(parent, layoutId, position);
		} else {
			holder = (CommonViewHolder) convertView.getTag();
			holder.mPosition = position;
		}
		return holder;
	}

	public View getConvertView() {
		return mConvertView;
	}

    @SuppressWarnings("unchecked")
    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 CommonViewHolder setText(int viewId, String text) {
		TextView view = getView(viewId);
		view.setText(text);
		return this;
	}

	public CommonViewHolder setImageResource(int viewId, int drawableId) {
		ImageView view = getView(viewId);
		if (view != null) {
			view.setImageResource(drawableId);
		}

		return this;
	}

	public CommonViewHolder setImageBitmap(int viewId, Bitmap bm) {
		ImageView view = getView(viewId);
		view.setImageBitmap(bm);
		return this;
	}


	public int getPosition() {
		return mPosition;
	}

}

 

通用viewholder,需要传入layoutId初始化,,初始化的时候,每个mConverView都会被打标签,然后在get方法,通过标签获取view,如果为空,则再new一个,new的同时也把标签打好了,由此做到复用converView,这种设计把复用的代码搬到CommenViewHolder中进行,有针对性。毕竟adapter主要处理的是数据,ui方面的处理交给CommenViewHolder处理就可以了。

 private class MyListViewAdapter extends MultiCollectionAdapter {

        static final int TYPE_HEAD_SELECTOR = 0;
        static final int TYPE_SELECTOR = 1;
        static final int TYPE_LAST_ITEM = 6;

        RecommendListViewAdapter(SparseArray<List> mapOfTypeViewList, SparseIntArray mapOfLayoutIdType) {
            super(mapOfTypeViewList, mapOfLayoutIdType);
        }

        @Override
        public void convert(CommonViewHolder helper, Object item) {
            View convertView = helper.getConvertView();
            if (convertView instanceof HolderViewListItem) {
                ((HolderViewListItem) convertView).setup(item);
            }

        }

       
    }

适配器的导出类很简单,只需要重写convert方法就可以了,需要注意的是,convert方法里面也是模版代码

具体的实现交给对应的item类处理即可,调用item的setUp方法

关于我一值提到的item,就是真正意义上的ui实现类,继承于Viewgroup,并实现接口HolderViewListItem

public interface HolderViewListItem<T> {
    void setup(T obj);

    void onItemClick(View.OnClickListener listener);
}

为什么item都要实现这个接口呢,因为convert方法里面用到的是模版代码,获取的都是HolderViewListItem类型,并且使用了setup方法,这种方式,非常灵活,item可以根据需要实现多个接口,但各个接口之间并不影响,执行

 ((HolderViewListItem) convertView).setup(item);方法的时候,编辑器会通过向下转型自动找到对应的实现类,并准确得调用setup方法,这也是接口的基本用法。对外提供统一接口,运行时解析成具体的方法,统一封装代码,降低耦合性,各司其职。

现在来看看item的实现类

public class MyListItem extends RelativeLayout implements HolderViewListItem<SelectorInfo> {

    private TextView mText;

    public MyListItem (Context context) {
        super(context);
    }

    public MyListItem (Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyListItem (Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    public static MyListItem newInstance(Context context) {
        return (MyListItem ) LayoutInflater.from(context).inflate(R.layout.item_main_my_list_item, null);
    }

    private void initView() {
        mText = (TextView) findViewById(R.id.main_item_text);
    }


    @Override
    public void setup(final MyInfo data) {
        initView();
        mText.setText(data.modelName);
    }

    @Override
    public void onItemClick(OnClickListener listener) {
        setOnClickListener(listener);
    }
}

item的布局文件里面,根目录也是这个类的引用,在这里就不列出来了

以上只是零部件,接下来看看如何组装

实例化万能适配器

mAdapter = new ListViewAdapter(generateDataCollection(), generateLayoutIdCollection());
 private SparseArray<List> generateDataCollection() {
        SparseArray<List> map = new SparseArray<>(10);
        map.put(MyListViewAdapter.TYPE_HEAD_SELECTOR, null);
        map.append(MyListViewAdapter.TYPE_SELECTOR, selectorData);
        return map;
    }


    private SparseIntArray generateLayoutIdCollection() {
        SparseIntArray map = new SparseIntArray(10);
        map.put(MyListViewAdapter.TYPE_HEAD_SELECTOR, R.layout.widget_main_selector_head);
        map.append(MyListViewAdapter.TYPE_SELECTOR, R.layout.item_main_selector);
        return map;
    }

刷新的用法和平常的用法一样

 

 

 

转载于:https://my.oschina.net/carbenson/blog/907560

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值