Android 适配器模式应用及设计原理
适配器模式是一种重要的设计模式,在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"/>
RecyclerViewmrecycler=(RecyclerView)findViewById(R.id.mrecycler); mrecycler.setLayoutManager(newStaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));
2.Adapter使用,我们先从源码了解每个方法的回调周期。
publicstaticabstractclassAdapter<VHextendsViewHolder>{ privatefinalAdapterDataObservablemObservable=newAdapterDataObservable(); publicabstractVHonCreateViewHolder(ViewGroupparent,intviewType); publicabstractvoidonBindViewHolder(VHholder,intposition); publicvoidonBindViewHolder(VHholder,intposition,List<Object>payloads){ onBindViewHolder(holder,position); } /** *Thismethodcalls{@link#onCreateViewHolder(ViewGroup,int)}tocreateanew *{@linkViewHolder}andinitializessomeprivatefieldstobeusedbyRecyclerView. * *@see#onCreateViewHolder(ViewGroup,int) */ publicfinalVHcreateViewHolder(ViewGroupparent,intviewType){ TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); finalVHholder=onCreateViewHolder(parent,viewType); holder.mItemViewType=viewType; TraceCompat.endSection(); returnholder; } /** *Thismethodinternallycalls{@link#onBindViewHolder(ViewHolder,int)}toupdatethe *{@linkViewHolder}contentswiththeitematthegivenpositionandalsosetsupsome *privatefieldstobeusedbyRecyclerView. * *@see#onBindViewHolder(ViewHolder,int) */ publicfinalvoidbindViewHolder(VHholder,intposition){ 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(); } /** *Returntheviewtypeoftheitemat<code>position</code>forthepurposes *ofviewrecycling. * *<p>Thedefaultimplementationofthismethodreturns0,makingtheassumptionof *asingleviewtypefortheadapter.UnlikeListViewadapters,typesneednot *becontiguous.Considerusingidresourcestouniquelyidentifyitemviewtypes. * *@parampositionpositiontoquery *@returnintegervalueidentifyingthetypeoftheviewneededtorepresenttheitemat *<code>position</code>.Typecodesneednotbecontiguous. */ publicintgetItemViewType(intposition){ return0; } /** *Returnsthetotalnumberofitemsinthedatasetholdbytheadapter. * *@returnThetotalnumberofitemsinthisadapter. */ publicabstractintgetItemCount(); /** *Calledwhenaviewcreatedbythisadapterhasbeenattachedtoawindow. * *<p>Thiscanbeusedasareasonablesignalthattheviewisabouttobeseen *bytheuser.Iftheadapterpreviouslyfreedanyresourcesin *{@link#onViewDetachedFromWindow(RecyclerView.ViewHolder)onViewDetachedFromWindow} *thoseresourcesshouldberestoredhere.</p> * *@paramholderHolderoftheviewbeingattached */ publicvoidonViewAttachedToWindow(VHholder){ } /** *Calledwhenaviewcreatedbythisadapterhasbeendetachedfromitswindow. * *<p>Becomingdetachedfromthewindowisnotnecessarilyapermanentcondition; *theconsumerofanAdapter'sviewsmaychoosetocacheviewsoffscreenwhilethey *arenotvisible,attachingandetachingthemasappropriate.</p> * *@paramholderHolderoftheviewbeingdetached */ publicvoidonViewDetachedFromWindow(VHholder){ } /** *Registeranewobservertolistenfordatachanges. * *<p>Theadaptermaypublishavarietyofeventsdescribingspecificchanges. *Notalladaptersmaysupportallchangetypesandsomemayfallbacktoageneric *{@linkandroid.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged() *"somethingchanged"}eventifmorespecificdataisnotavailable.</p> * *<p>Componentsregisteringobserverswithanadapterareresponsiblefor *{@link#unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) *unregistering}thoseobserverswhenfinished.</p> * *@paramobserverObservertoregister * *@see#unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver) */ publicvoidregisterAdapterDataObserver(AdapterDataObserverobserver){ mObservable.registerObserver(observer); } /** *Unregisteranobservercurrentlylisteningfordatachanges. * *<p>Theunregisteredobserverwillnolongerreceiveeventsaboutchanges *totheadapter.</p> * *@paramobserverObservertounregister * *@see#registerAdapterDataObserver(RecyclerView.AdapterDataObserver) */ publicvoidunregisterAdapterDataObserver(AdapterDataObserverobserver){ mObservable.unregisterObserver(observer); } /** *CalledbyRecyclerViewwhenitstartsobservingthisAdapter. *<p> *KeepinmindthatsameadaptermaybeobservedbymultipleRecyclerViews. * *@paramrecyclerViewTheRecyclerViewinstancewhichstartedobservingthisadapter. *@see#onDetachedFromRecyclerView(RecyclerView) */ publicvoidonAttachedToRecyclerView(RecyclerViewrecyclerView){ } /** *CalledbyRecyclerViewwhenitstopsobservingthisAdapter. * *@paramrecyclerViewTheRecyclerViewinstancewhichstoppedobservingthisadapter. *@see#onAttachedToRecyclerView(RecyclerView) */ publicvoidonDetachedFromRecyclerView(RecyclerViewrecyclerView){ } /** *Notifyanyregisteredobserversthatthedatasethaschanged. */ publicfinalvoidnotifyDataSetChanged(){ mObservable.notifyChanged(); } /** *Notifyanyregisteredobserversthattheitemat<code>position</code>haschanged. *Equivalenttocalling<code>notifyItemChanged(position,null);</code>. * *<p>Thisisanitemchangeevent,notastructuralchangeevent.Itindicatesthatany *reflectionofthedataat<code>position</code>isoutofdateandshouldbeupdated. *Theitemat<code>position</code>retainsthesameidentity.</p> * *@parampositionPositionoftheitemthathaschanged * *@see#notifyItemRangeChanged(int,int) */ publicfinalvoidnotifyItemChanged(intposition){ mObservable.notifyItemRangeChanged(position,1); } /** *Notifyanyregisteredobserversthattheitemreflectedat<code>fromPosition</code> *hasbeenmovedto<code>toPosition</code>. * *<p>Thisisastructuralchangeevent.Representationsofotherexistingitemsinthe *datasetarestillconsidereduptodateandwillnotberebound,thoughtheir *positionsmaybealtered.</p> * *@paramfromPositionPreviouspositionoftheitem. *@paramtoPositionNewpositionoftheitem. */ publicfinalvoidnotifyItemMoved(intfromPosition,inttoPosition){ mObservable.notifyItemMoved(fromPosition,toPosition); } }
Adapter是RecyclerView的一个抽象内部类,我们只需要重写它暴露出来的各种回调方法(创建View,绑定View,获取数据内容,通知数据变化......),就可以达到创建及控制itemView内容的目的。这里挑我们创建Adapter时常用的重写方法讲解:
onCreateViewHolder:
根据需求,创建自定义样式的itemViw,最终return一个ViewHolder类型。由Adapter内部类中的createViewHolder调用。
//根据需求,创建自定义样式的itemViw @Override publicViewHolderonCreateViewHolder(ViewGroupviewGroup,intviewType){ Viewview=LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item,viewGroup,false); ViewHoldervh=newViewHolder(view); returnvh; }
onBindViewHolder:
将我们传递进来的数据bean与view绑定在一起,即决定我们的itemView的具体内容如何展示,由Adapter内部类中的bindViewHolder调用。同时也可以在这里处理触摸事件的回调,后面会讲到。
//将数据与界面进行绑定的操作 @Override publicvoidonBindViewHolder(ViewHolderviewHolder,intposition){ viewHolder.mTextView.setText(datas[position]); }
getItemCount:
返回数据bean的数量,即需要的itemView的个数。
//获取数据的数量 @Override publicintgetItemCount(){ returndatas.length; }
一般情况我们重写上述三个方法即可。
onViewAttachedToWindowonViewDetachedFromWindow:
在View依附/脱离window的时候回调
registerAdapterDataObserverunregisterAdapterDataObserver:
主要用于注册与解绑适配器数据的观察者模式
notifyDataSetChangednotifyItemMoved:
通过Adapter来通知数据或item的变化,请求更新view.
那怎么让Adapter来为我们处理触摸事件?通过接口回调的方法就能很简单地完成。
首先在我们的Adapter中添加一个内部接口,其中的方法在第一步实例化View的时候实现。
publicinterfaceOnItemClickListener{ voidItemClickListener(Viewview,intpostion); voidItemLongClickListener(Viewview,intpostion); }
然后在onBindViewHolder中回调。
if(mListener!=null){//如果设置了监听那么它就不为空,然后回调相应的方法 holder.itemView.setOnClickListener(newView.OnClickListener(){ @Override publicvoidonClick(Viewv){ intpos=holder.getLayoutPosition();//得到当前点击item的位置pos mListener.ItemClickListener(holder.itemView,pos);//把事件交给我们实现的接口那里处理 } }); holder.itemView.setOnLongClickListener(newView.OnLongClickListener(){ @Override publicbooleanonLongClick(Viewv){ intpos=holder.getLayoutPosition();//得到当前点击item的位置pos mListener.ItemLongClickListener(holder.itemView,pos);//把事件交给我们实现的接口那里处理 returntrue; } }); }
3.将RecyclerView与Adapter通过setAdapter联系起来,并实现Adapter内部的回调接口。
adapter=newMyRecyclerAdapter(this,list); mrecycler.setAdapter(adapter); adapter.setOnclickListener(newMyRecyclerAdapter.OnItemClickListener(){ @Override publicvoidItemClickListener(Viewview,intpostion){ Toast.makeText(MainActivity.this,"点击了:"+postion,Toast.LENGTH_SHORT).show(); } @Override publicvoidItemLongClickListener(Viewview,intpostion){ list.remove(postion); adapter.notifyItemRemoved(postion); } });
至此一个最简单的RecyclerViewAdapter就完成了,文末给出一个自己简单封装过后的CommonRecyclerAdapter,使用时我们只需要传入context,layoutResId,List<T>data即可。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!