Adapter基类抽象(一)

--清晰概念

--示例

--初步分析

--对比

--设计和实现

--知识点和总结


清晰概念

先来说说用的非常多的BaseAdapter,这是官方提供给我们的一个适配器基类,通过他我们可以自定义列表的项,实现各种列表。说到适配器这几个字,咱们先来分析下适配器的定义和作用,之前说到软件抽象于现实,现实当中我们身边充满着很多适配器作用的东西,比如一个两项插头需要插进三项插头,我们怎么办,我们可以找个转换器,这样就可以插进去了,这个转换器解决了形状不适用的问题。还比如电源适配器,解决了变压的问题,而且很多电源适配器上标写的既是Adapter(适配器),这种思想是可以用在很多地方的。再来说我们软件当中的适配器模式,Android中定义的就是Adapter,很直接,它实现了数据到列表视图的转换,这里能看出来了我们需要哪些东西来实现列表展示数据,数据+适配器+列表组件,组件官方已经提供给我们了,他们也把适配器定义好了,数据我们需要自己定义和获取,我们只需要按照他给出的接口实现即可。

一张图从形上来说明适配器思想(当然不要想歪了。。。):



示例

文章中我以实现、设计、分析反序的方式来讲可能效率会更高一些,这也是我的学习方法:实践观察然后再学习,学习过程效率会更高,容易理解。

关于适配器的封装优化先看下下面两个适配器实际使用对比


首先实体类:

public class User {
/**
  *  很多文章在性能优化中提到,实体的get、set可以舍弃,直接public即可。
  */
        public String name;
        public String sex;
        public User(String name, String sex) {
            this.name = name;
            this.sex = sex;
        }
    }

使用BaseAdapter开发示例代码:

public class TestListActivity extends Activity{
ListView lv;
    TestAdapter adapter;
    ArrayList<User> data=new ArrayList<User>();
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
        loadData();
    }
private void initView(){
        lv=new ListView(this);
        lv.setLayoutParams(new ViewGroup.LayoutParams(-1,-1));
        lv.setDrawingCacheBackgroundColor(Color.TRANSPARENT);
        setContentView(lv);
        adapter=new TestAdapter();
        lv.setAdapter(adapter);
    }
private void loadData(){
        data.add(new User("小京","男"));
        data.add(new User("大京","女"));
        data.add(new User("京京","不详"));
        adapter.notifyDataSetChanged();
    }
class TestAdapter extends BaseAdapter{
        @Override
        public int getCount() {
            return data.size();
        }
        @Override
        public Object getItem(int position) {
            return data.get(position);
        }
        @Override
        public long getItemId(int position) {
            return position;
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder=null;
            if(null==convertView){
                convertView= LayoutInflater.from(TestListActivity.this).inflate(R.layout.activity_list_test_item,null);
                viewHolder=new ViewHolder();
                viewHolder.tvName= (TextView) convertView.findViewById(R.id.tv_name);
                viewHolder.tvSex= (TextView) convertView.findViewById(R.id.tv_sex);
                convertView.setTag(viewHolder);
            }else{
                viewHolder= (ViewHolder) convertView.getTag();
            }
            User user=data.get(position);
            viewHolder.tvName.setText(user.name);
            viewHolder.tvSex.setText(user.sex);
            return convertView;
        }
        class ViewHolder{
            TextView tvName;
            TextView tvSex;
        }
    }
}

运行效果:



封装改造后,使用BasicAdapter使用代码:

public class OptimizedTestListActivity extends Activity{
ListView lv;
    OptimizedTestAdapter adapter;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
        loadData();
    }
private void initView(){
        lv=new ListView(this);
        lv.setLayoutParams(new ViewGroup.LayoutParams(-1,-1));
        lv.setDrawingCacheBackgroundColor(Color.TRANSPARENT);
        setContentView(lv);
        adapter=new OptimizedTestAdapter(this);
        lv.setAdapter(adapter);
    }
private void loadData(){
        ArrayList<User> data=new ArrayList<User>();
        data.add(new User("小京","男"));
        data.add(new User("大京","女"));
        data.add(new User("京京","不详"));
        adapter.addDataAndNotifyDataSetChanged(data);
    }
class OptimizedTestAdapter extends BasicAdapter<User> {
        public OptimizedTestAdapter(Context mctx) {
            super(mctx);
        }
        @Override
        protected int setItemLayout() {
            return R.layout.activity_list_test_item;
        }
        @Override
        protected void bindData(View convertView, User data, int position) {
            TextView tvName=get(convertView,R.id.tv_name);
            TextView tvSex=get(convertView,R.id.tv_sex);
            tvName.setText(data.name);
            tvSex.setText(data.sex);
        }
    }
}


初步分析

之前有提到,提取封装的时候我们需要找出“变”和“不变”的部分,不变的部分抽取出来,变得部分是我们提供出来的接口可供实现不同效果,关于分层抽象,我们根据不同的需要来抽象到什么程度取决于你定义的“不变部分”,有兴趣的可以看看比如SimpleAdapter的实现。

不变的部分:

1.数据容器声明

2.继承BaseAdapter后需要实现的方法

3.其中getView接口为了防止列表项混乱,我们还要引入ViewHolder

4.getView中部分执行过程

变的部分:

1.容器内放置的数据类型

2.列表项布局

3.数据与视图的映射关系


:这里我们把getView部分重新定义了,官方的定义是:列表组件根据适配器的getView可以获取到我们自定义的列表项View,来实现渲染。

大部分情况getView中的写法和执行过程是固定的,不变的只是数据和组件映射关系以及其他组件上的功能,所以我们重新定义一下叫bindData。


注意看上面优化后的适配器的使用,只需要实现变得部分即可,重复的部分已经不需要再写了。


对比

优点:不变的部分已经不需要重复编写了

1.数据容器,把数据容器直接定义在适配器中,操作数据的时直接使用适配器中声明的操作数据的接口即可

2.BaseAdapter中的抽象函数不需要再重复实现,如果有特殊需要你也可以override

3.通过调用get函数直接获取组件,已经实现了viewholder机制并且也不需要强制类型转换。

到这里,基本就实现了适配器的改造,把重复的部分都省去。关于get中实现的ViewHolder机制之前在博客看到的,具体忘记出处了,作者请谅解,这里逻辑是一样的,技巧在SparseArray的使用和泛型的使用去掉了强制类型转换。

4.变得部分也基本都能满足需求,对于数据展示,组件交互等等这些都是可以实现的。

 

设计和实现

下面来介绍如何实现这个BasicAdapter,是怎样的一个思考过程,这是比较有价值的地方。

完整的BasicAdapter的源代码:

public abstract class BasicAdapter<T> extends BaseAdapter {

	/**上下文*/
	protected Context mctx;
	
	/**数据源*/
	protected List<T> datas;//全量数据

	/**布局ID*/
	protected int layoutId;

	public BasicAdapter(Context mctx) {
		this.mctx = mctx;
		//初始化数据集合的引用
		this.datas=new ArrayList<T>();
		//初始化布局资源的id,通过抽象函数限制使用者必须传递必要参数
		layoutId=setItemLayout();
	}
	
	protected abstract int setItemLayout();

    public Context getContext(){
        return mctx;
    }

	public List<T> getDatas(){return datas;}

	public boolean isEmpty(){
		return datas.isEmpty();
	}

	@Override
	public int getCount() {
			return null != datas && !datas.isEmpty() ? datas.size() : 0;
	}

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

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		T t = datas.get(position);
		if (convertView == null) {
			convertView = LayoutInflater.from(mctx).inflate(layoutId, null);
		}
		//在这里我们调用抽象函数bindData,使用时不同的部分就在这个函数中,这是回调思想的使用
		bindData(convertView, t,position);
		//防止使用时没有使用get函数获取组件,我们可以加入异常机制
		if(null==convertView.getTag())throw new NullPointerException("bindData中请调用ViewHolder的get静态方法去实例化组件");
		return convertView;
	}

	/**
	 * 子类实现,自定义组件和数据之间的映射关系(必须用ViewHolder.get静态方法获取)
	 * @param convertView
	 * @param data
     * @param position
	 */
	protected abstract void bindData(View convertView, T data,int position);

	public void refreshDataAndNotifyDataSetChanged(List<? extends T> newDatas){
		if(null==newDatas||newDatas.isEmpty())return;
		datas.clear();
		datas.addAll(newDatas);
		notifyDataSetChanged();
	}
	
	public void addDataAndNotifyDataSetChanged(List<? extends T> newDatas){
		if(null==newDatas||newDatas.isEmpty())return;
        datas.addAll(newDatas);
        notifyDataSetChanged();
    }

    public void addDataAndNotifyDataSetChanged(T newDatas){
    	if(null==newDatas)return;
        datas.add(newDatas);
        notifyDataSetChanged();
    }

    public BasicAdapter clearDatas(){
    	datas.clear();
		return this;
    }

    @SuppressWarnings("unchecked")
    public <V extends View> V get(View convertView, int id) {
    	/*
    	 * 关于SparseArray的应用:可以去百度查阅资料学习。
    	 * 对于HashMap<Integer,Object>的使用,Android官方建议是使用SparseArray<E>的。
    	 * 参考:http://www.eoeandroid.com/thread-321547-1-1.html
    	 */
//    	Exception.isNull(convertView, "请检查getView中的convertView对象是否初始化");
    	
        SparseArray<View> viewHolder = (SparseArray<View>) convertView.getTag();
        if (viewHolder == null) {
            viewHolder = new SparseArray<View>();
            convertView.setTag(viewHolder);
        }
        View childView = viewHolder.get(id);
        if (childView == null) {
            childView = convertView.findViewById(id);
            viewHolder.put(id, childView);
        }
        return (V) childView;
    }
}


知识点和总结

1.泛型基本应用

2.抽象类抽象函数

封装好实现,给定约束也就是必须实现的接口

3.这里用到了框架开发思想,还有设计模式中的模板模式

框架中大量的抽象会用到模板模式,本质就是提取相同的运行机制,然后开放接口实现不同处。记住,实际运行时,是框架在调用你的代码。


前面分析到找出“变”和“不变”的部分,当我们不需要用到其他类型的时候,我们基于BaseAdapter扩展一层即可,这就是在针对BaseAdapter进行细分定义和改造,我们具象了我们一般用不到的接口,然后改造了getView处的逻辑和概念,因为我们找出的“变“的本质是”数据与组件之间的映射“,所以我们需要把映射以外重复的事情提取出来,提取出来的执行过程是不变的,这也就是模板模式的思想,抽象过程需要的必要参数我们可以以各种方式实现,比如本例中的布局id,我们可以像文中抽象函数方式要求使用者必须实现传递进来,或者传参定义在构造函数中。Android应用开发的过程,肯定会有大量的可复用部分,比如列表界面或者详情界面,根据不同的定义,可以抽取出很多层可复用的模板类,针对不同的变化程度来选择使用,这会大大提高开发效率、减少错误率、方便扩展和维护。建议大家平时就抓住机会完善自己的“工具”,用的时候才会得心应手,工具比如面向对象思想、设计模式、抽象思维、逻辑思维、反射技术、一些好的设计思想、现成的框架模型等等等。

 

下面我会接着本文再写几篇对于Adapter进一步扩展的文章,来更快速更好的实现更多需求。哪里讲的不对的直接联系我,看到错误并指出的人,我想说咱们做朋友~


by LuoJ


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值