Android 適配器模式應(yīng)用及設(shè)計(jì)原理
適配器模式是一種重要的設(shè)計(jì)模式,在 Android 中得到了廣泛的應(yīng)用。適配器類似于現(xiàn)實(shí)世界里面的插頭,通過(guò)適配器,我們可以將分屬于不同類的兩種不同類型的數(shù)據(jù)整合起來(lái),而不必去根據(jù)某一需要增加或者修改類里面的方法。
適配器又分為單向適配器和雙向適配器,在 android 中前者使用的比較頻繁。比較常見(jiàn)的實(shí)現(xiàn)方式是:首先定義一個(gè)適配類,內(nèi)部定義一個(gè)私有的需要適配的對(duì)象,該類提供一個(gè)構(gòu)造函數(shù),將該對(duì)象的一個(gè)實(shí)例作為參數(shù)傳入,并在構(gòu)造函數(shù)里面進(jìn)行初始化,再提供一個(gè)公有的方法,返回另外一個(gè)需要適配的類所需要的數(shù)據(jù)類型。這樣通過(guò)創(chuàng)建一個(gè)額外的類,專門(mén)負(fù)責(zé)數(shù)據(jù)類型的轉(zhuǎn)換,在不改動(dòng)原有類的前提下實(shí)現(xiàn)了所需的功能。這種設(shè)計(jì)模式提供了更好的復(fù)用性和可擴(kuò)展性,尤其在我們無(wú)法獲修改其中一個(gè)類或者類與類之間有比較多的不同類型的數(shù)據(jù)需要進(jìn)行適配的時(shí)候顯得格外重要。
在 android 中常見(jiàn)的適配器類有: BaseAdapter 、 SimpleAdapter 等
初識(shí)Android時(shí),我對(duì)ListView、GradView中的Adapter一直半懂非懂,每次寫(xiě)Adapter都覺(jué)得異常痛苦,故而有了此文,希望能幫到一些初學(xué)者。
先來(lái)看下適配器模式的官方解釋,將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。乍看肯定不知所云,通俗說(shuō)下我個(gè)人的理解:當(dāng)我們創(chuàng)建一個(gè)View之后,肯定要接著規(guī)定該view的大小、位置、顯示的內(nèi)容信息、觸摸事件的回調(diào)等等,我們發(fā)現(xiàn)將這些東西放一起導(dǎo)致代碼太耦合甚至冗余了,這時(shí)候就可以使用Adapter模式,將這個(gè)流程分開(kāi),首先我們只管創(chuàng)建一個(gè)View,至于該View的內(nèi)容樣式我們完全不用關(guān)心,交給我們的小弟Adapter去做(前提是這個(gè)人是我們的小弟,和我們的View能聯(lián)系起來(lái)),當(dāng)小弟完成了我們分配給他的任務(wù)后(安排子View的樣式,信息的解析顯示,Event回調(diào)等),我們只需通過(guò)setAdapter()將他的工作內(nèi)容竊取過(guò)來(lái)就行。
或者再直觀點(diǎn)說(shuō),我們要買回來(lái)了一部Iphone7,我們只需要輕松愉快地用它來(lái)聽(tīng)歌,看電影,聊微信,至于當(dāng)他沒(méi)電了怎么辦?如何使用110V、220V電壓充電?怎么使用二孔、三孔插頭?如何快充如何慢充?這些都不需要我們關(guān)心,交給我們的充電器(Adapter)即可,我們要做的只需連上usb線和找到插座(setAdapter)就行了。
當(dāng)你不清楚適配的具體流程時(shí),寫(xiě)Adapter是非常痛苦的,接下來(lái)我們就舉栗子詳細(xì)分析一個(gè)完整的適配器模式工作流程(現(xiàn)在除了我的奇葩公司,應(yīng)該沒(méi)人會(huì)用ListView了吧,所以這里直接以RecyclerView為栗,其實(shí)ListView的adapter也是一樣的原理)。
1.創(chuàng)建一個(gè)RecyclerView并實(shí)例化它,然后等著我們的小弟(Adapter)完成剩下來(lái)的體力活。
<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使用,我們先從源碼了解每個(gè)方法的回調(diào)周期。
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的一個(gè)抽象內(nèi)部類,我們只需要重寫(xiě)它暴露出來(lái)的各種回調(diào)方法(創(chuàng)建View,綁定View,獲取數(shù)據(jù)內(nèi)容,通知數(shù)據(jù)變化......),就可以達(dá)到創(chuàng)建及控制itemView內(nèi)容的目的。這里挑我們創(chuàng)建Adapter時(shí)常用的重寫(xiě)方法講解:
onCreateViewHolder:
根據(jù)需求,創(chuàng)建自定義樣式的itemViw,最終return一個(gè)ViewHolder類型。由Adapter內(nèi)部類中的createViewHolder調(diào)用。
//根據(jù)需求,創(chuàng)建自定義樣式的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:
將我們傳遞進(jìn)來(lái)的數(shù)據(jù)bean與view綁定在一起,即決定我們的itemView的具體內(nèi)容如何展示,由Adapter內(nèi)部類中的bindViewHolder調(diào)用。同時(shí)也可以在這里處理觸摸事件的回調(diào),后面會(huì)講到。
//將數(shù)據(jù)與界面進(jìn)行綁定的操作 @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { viewHolder.mTextView.setText(datas[position]); }
getItemCount:
返回?cái)?shù)據(jù)bean的數(shù)量,即需要的itemView的個(gè)數(shù)。
//獲取數(shù)據(jù)的數(shù)量 @Override public int getItemCount() { return datas.length; }
一般情況我們重寫(xiě)上述三個(gè)方法即可。
onViewAttachedToWindow onViewDetachedFromWindow:
在View依附/脫離window的時(shí)候回調(diào)
registerAdapterDataObserver unregisterAdapterDataObserver:
主要用于注冊(cè)與解綁適配器數(shù)據(jù)的觀察者模式
notifyDataSetChanged notifyItemMoved:
通過(guò)Adapter來(lái)通知數(shù)據(jù)或item的變化,請(qǐng)求更新view.
那怎么讓Adapter來(lái)為我們處理觸摸事件?通過(guò)接口回調(diào)的方法就能很簡(jiǎn)單地完成。
首先在我們的Adapter中添加一個(gè)內(nèi)部接口,其中的方法在第一步實(shí)例化View的時(shí)候?qū)崿F(xiàn)。
public interface OnItemClickListener { void ItemClickListener(View view,int postion); void ItemLongClickListener(View view,int postion); }
然后在onBindViewHolder中回調(diào)。
if(mListener!=null){//如果設(shè)置了監(jiān)聽(tīng)那么它就不為空,然后回調(diào)相應(yīng)的方法 holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = holder.getLayoutPosition();//得到當(dāng)前點(diǎn)擊item的位置pos mListener.ItemClickListener(holder.itemView,pos);//把事件交給我們實(shí)現(xiàn)的接口那里處理 } }); holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { int pos = holder.getLayoutPosition();//得到當(dāng)前點(diǎn)擊item的位置pos mListener.ItemLongClickListener(holder.itemView,pos);//把事件交給我們實(shí)現(xiàn)的接口那里處理 return true; } }); }
3.將RecyclerView與Adapter通過(guò)setAdapter聯(lián)系起來(lái),并實(shí)現(xiàn)Adapter內(nèi)部的回調(diào)接口。
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,"點(diǎn)擊了:"+postion, Toast.LENGTH_SHORT).show(); } @Override public void ItemLongClickListener(View view, int postion) { list.remove(postion); adapter.notifyItemRemoved(postion); } });
至此一個(gè)最簡(jiǎn)單的RecyclerViewAdapter就完成了,文末給出一個(gè)自己簡(jiǎn)單封裝過(guò)后的CommonRecyclerAdapter,使用時(shí)我們只需要傳入 context, layoutResId,List<T> data即可。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
使用Messenger實(shí)現(xiàn)Service的雙向通信
這篇文章主要為大家詳細(xì)介紹了使用Messenger實(shí)現(xiàn)Service的雙向通信,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Android開(kāi)發(fā)實(shí)現(xiàn)的文本折疊點(diǎn)擊展開(kāi)功能示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)的文本折疊點(diǎn)擊展開(kāi)功能,涉及Android界面布局與屬性控制相關(guān)操作技巧,需要的朋友可以參考下2019-03-03Android中FoldingLayout折疊布局的用法及實(shí)戰(zhàn)全攻略
這篇文章主要介紹了Android中FoldingLayout折疊布局的用法及實(shí)例,通過(guò)FoldingLayout我們可以制作出炫酷的菜單折疊效果,文中的例子講解得非常詳細(xì),需要的朋友可以參考下2016-02-02android編程實(shí)現(xiàn)添加文本內(nèi)容到sqlite表中的方法
這篇文章主要介紹了android編程實(shí)現(xiàn)添加文本內(nèi)容到sqlite表中的方法,結(jié)合實(shí)例較為詳細(xì)的分析了Android針對(duì)txt文本文件的讀取及SQL數(shù)據(jù)庫(kù)操作的相關(guān)技巧,需要的朋友可以參考下2015-11-11設(shè)置Android系統(tǒng)永不鎖屏永不休眠的方法
在進(jìn)行Android系統(tǒng)開(kāi)發(fā)的時(shí)候,有些特定的情況需要設(shè)置系統(tǒng)永不鎖屏,永不休眠。本篇文章給大家介紹Android 永不鎖屏,開(kāi)機(jī)不鎖屏,刪除設(shè)置中休眠時(shí)間選項(xiàng),需要的朋友一起學(xué)習(xí)吧2016-03-03Android Scroll滑動(dòng)效果實(shí)例
這篇文章主要為大家分享了Android Scroll滑動(dòng)效果實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04Android源碼使用16進(jìn)制進(jìn)行狀態(tài)管理的方法
這篇文章主要介紹了Android源碼使用16進(jìn)制進(jìn)行狀態(tài)管理的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07