Android 适配器模式应用及设计原理
作者:lqh 发布时间:2023-08-01 19:08:04
适配器模式是一种重要的设计模式,在 Android 中得到了广泛的应用。适配器类似于现实世界里面的插头,通过适配器,我们可以将分属于不同类的两种不同类型的数据整合起来,而不必去根据某一需要增加或者修改类里面的方法。
适配器又分为单向适配器和双向适配器,在 android 中前者使用的比较频繁。比较常见的实现方式是:首先定义一个适配类,内部定义一个私有的需要适配的对象,该类提供一个构造函数,将该对象的一个实例作为参数传入,并在构造函数里面进行初始化,再提供一个公有的方法,返回另外一个需要适配的类所需要的数据类型。这样通过创建一个额外的类,专门负责数据类型的转换,在不改动原有类的前提下实现了所需的功能。这种设计模式提供了更好的复用性和可扩展性,尤其在我们无法获修改其中一个类或者类与类之间有比较多的不同类型的数据需要进行适配的时候显得格外重要。
在 android 中常见的适配器类有: BaseAdapter 、 SimpleAdapter 等
初识Android时,我对ListView、GradView中的Adapter一直半懂非懂,每次写Adapter都觉得异常痛苦,故而有了此文,希望能帮到一些初学者。
先来看下适配器模式的官方解释,将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。乍看肯定不知所云,通俗说下我个人的理解:当我们创建一个View之后,肯定要接着规定该view的大小、位置、显示的内容信息、触摸事件的回调等等,我们发现将这些东西放一起导致代码太耦合甚至冗余了,这时候就可以使用Adapter模式,将这个流程分开,首先我们只管创建一个View,至于该View的内容样式我们完全不用关心,交给我们的小弟Adapter去做(前提是这个人是我们的小弟,和我们的View能联系起来),当小弟完成了我们分配给他的任务后(安排子View的样式,信息的解析显示,Event回调等),我们只需通过setAdapter()将他的工作内容窃取过来就行。
或者再直观点说,我们要买回来了一部Iphone7,我们只需要轻松愉快地用它来听歌,看电影,聊微信,至于当他没电了怎么办?如何使用110V、220V电压充电?怎么使用二孔、三孔插头?如何快充如何慢充?这些都不需要我们关心,交给我们的充电器(Adapter)即可,我们要做的只需连上usb线和找到插座(setAdapter)就行了。
当你不清楚适配的具体流程时,写Adapter是非常痛苦的,接下来我们就举栗子详细分析一个完整的适配器模式工作流程(现在除了我的奇葩公司,应该没人会用ListView了吧,所以这里直接以RecyclerView为栗,其实ListView的adapter也是一样的原理)。
1.创建一个RecyclerView并实例化它,然后等着我们的小弟(Adapter)完成剩下来的体力活。
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/mrecycler"/>
RecyclerView mrecycler= (RecyclerView) findViewById(R.id.mrecycler);
mrecycler.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));
2.Adapter使用,我们先从源码了解每个方法的回调周期。
public static abstract class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
public abstract void onBindViewHolder(VH holder, int position);
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
onBindViewHolder(holder, position);
}
/**
* This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new
* {@link ViewHolder} and initializes some private fields to be used by RecyclerView.
*
* @see #onCreateViewHolder(ViewGroup, int)
*/
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
/**
* This method internally calls {@link #onBindViewHolder(ViewHolder, int)} to update the
* {@link ViewHolder} contents with the item at the given position and also sets up some
* private fields to be used by RecyclerView.
*
* @see #onBindViewHolder(ViewHolder, int)
*/
public final void bindViewHolder(VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
TraceCompat.endSection();
}
/**
* Return the view type of the item at <code>position</code> for the purposes
* of view recycling.
*
* <p>The default implementation of this method returns 0, making the assumption of
* a single view type for the adapter. Unlike ListView adapters, types need not
* be contiguous. Consider using id resources to uniquely identify item view types.
*
* @param position position to query
* @return integer value identifying the type of the view needed to represent the item at
* <code>position</code>. Type codes need not be contiguous.
*/
public int getItemViewType(int position) {
return 0;
}
/**
* Returns the total number of items in the data set hold by the adapter.
*
* @return The total number of items in this adapter.
*/
public abstract int getItemCount();
/**
* Called when a view created by this adapter has been attached to a window.
*
* <p>This can be used as a reasonable signal that the view is about to be seen
* by the user. If the adapter previously freed any resources in
* {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
* those resources should be restored here.</p>
*
* @param holder Holder of the view being attached
*/
public void onViewAttachedToWindow(VH holder) {
}
/**
* Called when a view created by this adapter has been detached from its window.
*
* <p>Becoming detached from the window is not necessarily a permanent condition;
* the consumer of an Adapter's views may choose to cache views offscreen while they
* are not visible, attaching an detaching them as appropriate.</p>
*
* @param holder Holder of the view being detached
*/
public void onViewDetachedFromWindow(VH holder) {
}
/**
* Register a new observer to listen for data changes.
*
* <p>The adapter may publish a variety of events describing specific changes.
* Not all adapters may support all change types and some may fall back to a generic
* {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged()
* "something changed"} event if more specific data is not available.</p>
*
* <p>Components registering observers with an adapter are responsible for
* {@link #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
* unregistering} those observers when finished.</p>
*
* @param observer Observer to register
*
* @see #unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver)
*/
public void registerAdapterDataObserver(AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
/**
* Unregister an observer currently listening for data changes.
*
* <p>The unregistered observer will no longer receive events about changes
* to the adapter.</p>
*
* @param observer Observer to unregister
*
* @see #registerAdapterDataObserver(RecyclerView.AdapterDataObserver)
*/
public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
/**
* Called by RecyclerView when it starts observing this Adapter.
* <p>
* Keep in mind that same adapter may be observed by multiple RecyclerViews.
*
* @param recyclerView The RecyclerView instance which started observing this adapter.
* @see #onDetachedFromRecyclerView(RecyclerView)
*/
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
}
/**
* Called by RecyclerView when it stops observing this Adapter.
*
* @param recyclerView The RecyclerView instance which stopped observing this adapter.
* @see #onAttachedToRecyclerView(RecyclerView)
*/
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
}
/**
* Notify any registered observers that the data set has changed.
*/
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
/**
* Notify any registered observers that the item at <code>position</code> has changed.
* Equivalent to calling <code>notifyItemChanged(position, null);</code>.
*
* <p>This is an item change event, not a structural change event. It indicates that any
* reflection of the data at <code>position</code> is out of date and should be updated.
* The item at <code>position</code> retains the same identity.</p>
*
* @param position Position of the item that has changed
*
* @see #notifyItemRangeChanged(int, int)
*/
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
/**
* Notify any registered observers that the item reflected at <code>fromPosition</code>
* has been moved to <code>toPosition</code>.
*
* <p>This is a structural change event. Representations of other existing items in the
* data set are still considered up to date and will not be rebound, though their
* positions may be altered.</p>
*
* @param fromPosition Previous position of the item.
* @param toPosition New position of the item.
*/
public final void notifyItemMoved(int fromPosition, int toPosition) {
mObservable.notifyItemMoved(fromPosition, toPosition);
}
}
Adapter是RecyclerView的一个抽象内部类,我们只需要重写它暴露出来的各种回调方法(创建View,绑定View,获取数据内容,通知数据变化......),就可以达到创建及控制itemView内容的目的。这里挑我们创建Adapter时常用的重写方法讲解:
onCreateViewHolder:
根据需求,创建自定义样式的itemViw,最终return一个ViewHolder类型。由Adapter内部类中的createViewHolder调用。
//根据需求,创建自定义样式的itemViw
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false);
ViewHolder vh = new ViewHolder(view);
return vh;
}
onBindViewHolder:
将我们传递进来的数据bean与view绑定在一起,即决定我们的itemView的具体内容如何展示,由Adapter内部类中的bindViewHolder调用。同时也可以在这里处理触摸事件的回调,后面会讲到。
//将数据与界面进行绑定的操作
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.mTextView.setText(datas[position]);
}
getItemCount:
返回数据bean的数量,即需要的itemView的个数。
//获取数据的数量
@Override
public int getItemCount() {
return datas.length;
}
一般情况我们重写上述三个方法即可。
onViewAttachedToWindow onViewDetachedFromWindow:
在View依附/脱离window的时候回调
registerAdapterDataObserver unregisterAdapterDataObserver:
主要用于注册与解绑适配器数据的观察者模式
notifyDataSetChanged notifyItemMoved:
通过Adapter来通知数据或item的变化,请求更新view.
那怎么让Adapter来为我们处理触摸事件?通过接口回调的方法就能很简单地完成。
首先在我们的Adapter中添加一个内部接口,其中的方法在第一步实例化View的时候实现。
public interface OnItemClickListener {
void ItemClickListener(View view,int postion);
void ItemLongClickListener(View view,int postion);
}
然后在onBindViewHolder中回调。
if(mListener!=null){//如果设置了监听那么它就不为空,然后回调相应的方法
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();//得到当前点击item的位置pos
mListener.ItemClickListener(holder.itemView,pos);//把事件交给我们实现的接口那里处理
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();//得到当前点击item的位置pos
mListener.ItemLongClickListener(holder.itemView,pos);//把事件交给我们实现的接口那里处理
return true;
}
});
}
3.将RecyclerView与Adapter通过setAdapter联系起来,并实现Adapter内部的回调接口。
adapter=new MyRecyclerAdapter(this, list);
mrecycler.setAdapter(adapter);
adapter.setOnclickListener(new MyRecyclerAdapter.OnItemClickListener() {
@Override
public void ItemClickListener(View view, int postion) {
Toast.makeText(MainActivity.this,"点击了:"+postion, Toast.LENGTH_SHORT).show();
}
@Override
public void ItemLongClickListener(View view, int postion) {
list.remove(postion);
adapter.notifyItemRemoved(postion);
}
});
至此一个最简单的RecyclerViewAdapter就完成了,文末给出一个自己简单封装过后的CommonRecyclerAdapter,使用时我们只需要传入 context, layoutResId,List<T> data即可。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
猜你喜欢
- 以前在别的地方见过一个通过调用系统API实现屏幕截图的例子,从内心来说我不太喜欢在C#代码中出现这种情况,现在什么都讲“和谐”,我觉得这种做
- 实现过滤器很简单,只需要继承ZuulFilter,并实现ZuulFilter中的抽象方法。filterType():定义过滤器的类型,它有4
- 一、树的概念和结构1.1 树的概念树是一种非线性的数据结构,它是由 n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因
- 介绍:unity界面开发,会用到很多导航的按钮,他们是公共的,单击其中一个按钮,显示对应的界面。unity中,UGUI自带Toggle组件,
- 本文实例为大家分享了SpringBoot实现动态多线程并发定时任务的具体代码,供大家参考,具体内容如下实现定时任务有多种方式,使用sprin
- 本文实例为大家分享了Java实现win10计算器的具体代码,供大家参考,具体内容如下这个小demo是我上学时的远古代码(嘻嘻嘻),今天整理代
- 程序员日常工作中,发送http请求特别常见。本文以Java为例,总结发送http请求的多种方式。1. HttpURLConnection使用
- 串口通讯是一种计算机常用的数据传输方式。程序运行如下:首先,检查计算机的串口,并获取所有串口信息。private void CheckPor
- 一、微信官方文档微信支付开发流程(公众号支付)首先我们到微信支付的官方文档的开发步骤部分查看一下需要的设置。[图片上传失败...(image
- 从接收输入值说起在日常的开发应用中,有时候需要直接接收外部设备如键盘等的输入值,而对于这种数据的接收方式,我们一般有三种方法:字节流读取,字
- 本文为大家分享了Unity3D实现虚拟按钮控制人物移动的具体代码,供大家参考,具体内容如下创建Image的UI组件,在Image下新建一个B
- 引入腾讯云依赖 <!--腾讯云核心API--> <dependency> <groupId>
- Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)用户线程即运行在前台的线程,而守护线程是运行
- 我们来讨论一个经常存在的账户转账的问题。账户A要转账给账户B。为了保证在转账的过程中A和B不被其他的线程意外的操作,我们需要给A和B加锁,然
- 最小二乘法拟合曲线成直线效果拟合前拟合后传入X轴和Y轴的数据,得到新的Y轴数据。将X值数据和拟合后的Y轴数据绑带即可。/// <sum
- 下表总结了Java NIO和IO之间的主要差别,我会更详细地描述表中每部分的差异。IO &nb
- 一、内部类介绍1.定义:一个类内部又嵌套了一个类,被嵌套的类就是内部类(inner class),嵌套其他类的称为外部类(outer cla
- 目录前言反射基础数据准备基于反射创建对象获取反射中的对象获取类中属性获取类中的构造方法获取类中方法结语前言大家好,瑞雪后的第一天,每个周一的
- 在Android开发中,录入信息是最基本的操作,使用非常广泛。但是Android对输入法弹出/收起的支持,并不是很好。对弹出,提供了forc
- 引言♀ 小AD:明哥,昨天气死我了,明哥要帮我出气。♂ 明世隐:咋了,有谁惹到你了。♀ 小AD:昨天辅助喷我小鲁班菜,我反手就对喷,然后竟然