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