android之优雅书写多类型Adapter

1 篇文章 0 订阅

写了这么多年的adapter,一直以来都是在搬砖,按部就班,从没想过如何将adapter写的更加优雅,这个实在是不应该,内心着实惭愧,直到看了recyclerView书写的优雅adapter才恍然大悟,以下是原作者的博客地址:

http://www.jianshu.com/p/1297d2e4d27a?winzoom=1

由于自己项目还是listview,所以参考了作者写的recycleview,改动了一番。写下此博客一个是对可访问者模式的理解,一个是练习面向接口编程,将代码解耦。这篇文章简单,但是涉及东西还是值得一学

啰嗦一句,设计模式看的再多如果没有实际东西操作,真的很难去掌握,也很难深入骨髓,这也是作者本人亲身感受,所以借此机会能够用上一番,对自己也有莫大的好处

涉及的知识点:

    1、可访问者模式:这是设计模式中的一种,若还不了解自行百度,也比较简单
  2、简单的泛型
  
基本这些就够了。接下来我们直接看代码
MutilTypeAdapter:

按照以前的写法,我相信很多人都是一下这样的或者根据model的type逐一分类
@Override
	public int getItemViewType(int position) {
		// TODO Auto-generated method stub
		Object obj = mDataList.get(position);
		if (obj instanceof AdInfo) {
			
			return 0;
		} else if (obj instanceof UserListInfo) {
			
			return 1;
		} else if (obj instanceof TopicListInfo) {
			
			return 2;
		} else if (obj instanceof BangListInfo) {
			
			return 3;
		} else if (obj instanceof .GroupListInfo) {
						return 4;
		} else if (obj instanceof .TagListInfo) {
			
			return 5;
		} else if (obj instanceof VideoListInfo) {
			
			return 6;
		} else if (obj instanceof .KnowledgeListInfo) {
			
			return 7;
		}
		return super.getItemViewType(position);
	}



我相信绝大多人都写过类似的烂代码,这种陋习在没学过访问者模式或者没熟用这个模式之前很容易就写出来,而且绝大多数人都觉得这是理所应当的,可能还自我感觉良好,,接下来看看经过改良过的方法:


 @Override
    public int getItemViewType(int position) {
        return modelList.get(position).type(typeFactory);
    }

一行代码就可以搞定,这是多么神奇又令人兴奋的事儿,这个不但是一行代码的问题,它的可扩展性绝对比你之前的写法要好上好多,也符合开闭原则“对外可扩展,又不用修改原有代码”,何乐而不为呢。接下来针对demo讲解访问者模式中的结构对象与访问者
结构对象:
    在这里的结构对象就是数据源(item的model),所以我们这里定义个接口,所有类型的model都要实现它,因为访问者访问model后获取与其相对应的type值然后根据type值创建相应的holder与view:

public interface Visitable {
    int type(TypeVisitorFactory typeFactory);
}
当所有model都实现这个接口后,他们就具有结构对象的能力,而且只要是实现这个接口就一定有这个能力,所以这又可以从复杂业务中解耦。 那么我们再来看看访问者
访问者:TypeVisitoryFactory
public interface TypeVisitorFactory {
    int type(One one);

    int type(Two two);

    int type(Three three);

    int type(Normal normal);

    BaseViewHolder createViewHolder(int type,Context context);
}

这一看也是个接口,而且重载了很多type()方法,这个是干什么用的呢?我们来看看它的一个实现:
public class TypeVisitorFactoryImp implements TypeVisitorFactory {
    private static final String TAG = "TypeVisitorFactoryImp";
    private final int TYPE_ONE = 0;//type 值必须从0开始  详解http://www.cnblogs.com/RGogoing/p/5872217.html
    private final int TYPE_TWO = 1;
    private final int TYPE_THREE = 2;
    private final int TYPE_NORMAL = 3;
    @Override
    public int type(One one) {
        Log.d(TAG, "TYPE_ONE = " + TYPE_ONE);
        return TYPE_ONE;
    }

    @Override
    public int type(Two one) {
        Log.d(TAG,"TYPE_TWO = "+TYPE_TWO);
        return TYPE_TWO;
    }

    @Override
    public int type(Three one) {
        Log.d(TAG,"TYPE_THREE = "+TYPE_THREE);
        return TYPE_THREE;
    }

    @Override
    public int type(Normal normal) {
        Log.d(TAG,"TYPE_NORMAL = "+TYPE_NORMAL);
        return TYPE_NORMAL;
    }

    @Override
    public BaseViewHolder createViewHolder(int type,Context context) {
        if (TYPE_ONE == type) {
            return new OneViewHolder(context);
        } else if (TYPE_TWO == type) {
            return new TwoViewHolder(context);
        } else if (TYPE_THREE == type) {
            return new ThreeViewHolder(context);
        } else if (TYPE_NORMAL == type) {
            return new NormalViewHolder(context);
        }
        return null;
    }
}

这一看type()方法返回的都是类型,而且参数都是各个类型的model,也许你猜出来了,由于之前都是通过instanceof 来返回type值,但是现在只要通过实现相应的type(model),就可以返回相应的type值。这是怎么做到的呢。

在回头捋一遍:model实现了Visitable接口具有结构对象的能力,只要model传入相应的访问者(TypeVisitoryFactory),访问者调用type(model),即可得到type。由于java的高级用法多态,使我们在调用方法时至多父类接口,它会自行调用子类的实现。所以只要adapter的数据源是一个visitable就很轻而易举的实现了现在的方式

结构对象和访问者讲完了,接下来看看adapter的数据源于getView是怎样工作的
数据源:
 private List<Visitable> modelList = new ArrayList<Visitable>();
数据源果然是一个Visitable,那你是不是有个疑问,数据源是一个接口形式,那么每次获取model的时候是不是都要强转,而且还要根据类型去强转,一来二去的好麻烦的样子。其实不然,细心的你可能发现上边访问者的接口和实现时发现一个熟悉的方法,bingo,就是BaseViewHolder createViewHolder(type);想当然的是,根据type值返回一个holview对象。那么接下来我们看看BaseViewHolder的定义:
public abstract class BaseViewHolder<T> {
    private SparseArray<View> views;
    private View mItemView;

    public BaseViewHolder(Context context, int resId) {
        views = new SparseArray<>();
        this.mItemView = View.inflate(context, resId, null);
        mItemView.setTag(this);
    }
    public View getmItemView() {
        return mItemView;
    }

    public abstract void setUpView(T model, int position, MultiTypeAdapter adapter);
}
这是一个抽象类,而且还是一个带泛型的抽象类,有一个抽象方法一定引起了你的注意:
setUpView(T model xxxx);
相信聪明的你已经猜出来了,T model就是我们各个类型对应的实际model,每种model对应一个holder类。所以这里要对泛型的知识有一些基础性的了解就很容易明白了。
这里只贴出我项目中优化过的holder实例:
public abstract class BaseViewHolder<T> {

    private View mItemView;
    protected Context context;

    public BaseViewHolder(Context context, int resId) {
        this.context = context;
        this.mItemView = View.inflate(context, resId, null);
        mItemView.setTag(this);
        initView(mItemView);
    }

    public View getmItemView() {
        return mItemView;
    }

    /**更新view的数据*/
    public abstract void updateViewData(T model, int position, SearchResultAllAdapter adapter);

    /***
     * findViewById
     *
     * @param mItemView
     */
    abstract void initView(View mItemView);

}

这里不需要将所有findviewByid的view缓存到SparseArray中,直接按照以前的adapter的holder方式就行,只要findviewById一次就好:只要在各个实现类中实现initView,初始化一次就好了:
public class ArticleViewHolder extends BaseViewHolder<One > {
    private  TextView tvArticleTitle;
    private  TextView tvArticleContent;
    private  TextView tvArticleFrom;
    public ArticleViewHolder(Context context) {
        super(context,R.layout.lmb_all_search_article_item);
    }

    @Override
    public void updateViewData(One model, int position, AllAdapter adapter) {
        tvArticleFrom.setText(model.bname);
        final String articleId = model.id;
        getmItemView().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               
    }

    @Override
    void initView(View mItemView) {
        tvArticleTitle = (TextView) mItemView.findViewById(R.id.tv_search_article_title);
        tvArticleContent = (TextView) mItemView.findViewById(R.id.tv_search_article_content);
        tvArticleFrom = (TextView) mItemView.findViewById(R.id.tv_search_article_from);
    }


}



这样是不是很明了了,每个类型对应一个Holer,所有操作互不关心,只要是实现holder的相应方法就可以了;扩展性都好;


那么我们看看getView是怎么工作的:

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

        // 帖子信息
        final BaseViewHolder holder;
        if (convertView == null) {
            Log.d(TAG, "convertView == null = " + position);
            holder = typeFactory.createViewHolder(getItemViewType(position), mContext);
            convertView = holder.getmItemView();
        } else {
            Log.d(TAG, "convertView != null = " + position);
            holder = (BaseViewHolder) convertView.getTag();
        }
        holder.setUpView(modelList.get(position), position, this);
        return convertView;
    }

holder = typeFactory.createViewHolder(getItemViewType(position), mContext);

主要是这句:直接通过getItemViewType,创建相应的holder,然后直接调用holder.setUpView();直接将接口model传递过去,由于是泛型所以解决了上述说的强转的问题;
这么一看这样的getview即简洁又明了,而且后续增加新的item都不用动其中的代码即可完成。只需要添加新的HolderView、新增类型的model且model实现Visitable 最后重载访问者的 type(model)方法就完成了。



这里需要注意的地方:
    listview的adapter中的getItemviewType必须是以0开始,多一个类型自增1,相应的getItemViewCount必须是item种类的个数,即有几个type其count=type个数
可以查阅这篇文章

这里提一点不相关的可能又用得上的建议有些模块需要互相通信的,比如各个fragment相互通信的,可以通过中介者模式完全解耦,第三方框架evenBus用起来也是解耦但是就是太乱了太杂了,搞不好都不好管理,所以有时自己以中介者模式或变形来做会更好

这样我们就讲完了。对以上不清楚的,可下载demo自行琢磨






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 开发中,ListView 是常用的列表控件,而 Adapter 则是 ListView 显示列表数据的适配器。当数据源改变时,我们需要调用 Adapter 的 notifyDataSetChanged() 方法来通知 ListView 更新数据。不过有时候,我们会发现调用 notifyDataSetChanged() 方法后,ListView 并没有更新数据,这通常是由以下几个原因造成的: 1. 数据源没有更新 在调用 notifyDataSetChanged() 方法之前,需要先确保数据源已经更新了。如果数据源没有更新,调用 notifyDataSetChanged() 方法也不会更新 ListView 显示的数据。 2. Adapter 对象没有重新设置 如果使用的是同一个 Adapter 对象,那么需要重新设置 Adapter 对象才能更新 ListView 显示的数据。可以通过 setAdapter() 方法重新设置 Adapter 对象。 3. ListView 没有重新绘制 当调用 notifyDataSetChanged() 方法后,ListView 并不会立即重新绘制,而是等到系统认为需要重新绘制时才会更新。可以通过调用 invalidate() 方法让 ListView 立即重新绘制。 4. 数据源和 Adapter 对象不匹配 如果数据源和 Adapter 对象不匹配,即数据源中的数据类型Adapter 中的数据类型不一致,调用 notifyDataSetChanged() 方法也无法更新 ListView 显示的数据。 综上所述,如果在 ListView 中调用 notifyDataSetChanged() 方法无效,可以先检查数据源是否更新,是否重新设置 Adapter 对象,是否调用了 invalidate() 方法以及数据源和 Adapter 对象是否匹配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值