Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 適配器模式應用及設計原理

Android 適配器模式應用及設計原理

編輯:關於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"/>
  
  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即可。

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved