Android開發進階 — 通用適配器 CommonAdapter

  • 2020 年 3 月 12 日
  • 筆記

      在Android開發中,我們經常會用到ListView 這個組件,為了將ListView 的內容展示出來,我們會去實現一個Adapter來適配,將Layout中的布局以列表的形式展現到組件中。

    比如,像 GGTalk Android版的查找用戶功能,會把符合條件的用戶都列在下面:

      

      為了達到這個效果,我們需要實現一個自定義的Adapter,而其它地方的ListView也要實現一個Adapter,這些Adapter會有很多重複的程式碼,非常繁瑣,現在我就將重複程式碼封裝到了一個通用的適配器CommonAdapter中,這樣,在使用時只要繼承CommonAdapter就可以了,如此就避免了大段程式碼的重複,讓程式碼更簡潔易懂。我們先來看看CommonAdapter的定義。  

一.CommonAdapter 實現

public abstract class CommonAdapter<T> extends BaseAdapter {        private List<T> dataList;      protected Context context;      protected int item_layoutId=0;      protected HashMap<Integer,Integer> layoutIdMap; //多種itemView時用到。 第一個int對應type類型,第二個int對應 itemlayoutId        /**       * 設置單個子模版VIew,       * @param context       * @param datas 綁定的對象集合       * @param item_layoutId 被綁定的視圖layoutID      * */      public CommonAdapter(Context context, List<T> datas, int item_layoutId)      {          this.context=context;          this.dataList=datas;          this.item_layoutId=item_layoutId;      }        /**       *設置多個子模版VIew, 並配合重寫 getItemViewType(int position)來確定是設置哪個模版       * @param context       * @param datas 綁定的對象集合       * @param layoutIdMap 多種itemViewID Map 第一個int對應type類型,第二個int對應 itemlayoutId      */      public CommonAdapter(Context context, List<T> datas, HashMap<Integer,Integer> layoutIdMap)      {          this.context=context;          this.dataList=datas;          this.layoutIdMap=layoutIdMap;      }        @Override      public int getViewTypeCount() {          if(this.layoutIdMap==null)          {              return 1;          }          return this.layoutIdMap.size();      }          @Override      public int getCount() {          return this.dataList.size();      }        @Override      public Object getItem(int position) {          return this.dataList.get(position);      }        @Override      public long getItemId(int position) {          return position;      }        @Override      public View getView(int position, View convertView, ViewGroup parent) {          int type = getItemViewType(position);          return getConvertView(position,convertView,type);      }        private View getConvertView(int position,View convertView,int itemViewType)      {          if(convertView==null)          {              int layoutId =0;              if(this.item_layoutId!=0)              {                  layoutId=this.item_layoutId;              }              if (this.layoutIdMap != null) {                  layoutId = this.layoutIdMap.get(itemViewType);              }              convertView = LayoutInflater.from(context).inflate(layoutId, null);          }          T t = this.dataList.get(position);          this.setConvertView(convertView,t,itemViewType);          return convertView;      }        /**       * 局部更新數據,調用一次getView()方法;Google推薦的做法       *       * @param parent  要更新的listview       * @param position 要更新的位置       */      public void onOneItemChanged(ListView parent, int position) {          /**第一個可見的位置**/          int firstVisiblePosition = parent.getFirstVisiblePosition();          /**最後一個可見的位置**/          int lastVisiblePosition = parent.getLastVisiblePosition();            /**在看見範圍內才更新,不可見的滑動後自動會調用getView方法更新**/          if ((position >= firstVisiblePosition && position <= lastVisiblePosition)) {                /**獲取指定位置view對象**/              View view = parent.getChildAt(position - firstVisiblePosition);              getView(position, view, parent);          }      }        /**       * 需要去實現的對item中的view的設置操作       *       * @param convertView 轉換後的試圖       * @param t Model對象       * @param itemViewType 試圖類型。對應layoutIdMap中的key       */      protected abstract void setConvertView(View convertView, T t,int itemViewType);  }

 說明如下:

(1)泛型參數<T>就是我們要綁定數據Model對象的class類型。

(2)getViewTypeCount()方法會根據HashMap是否為空來識別是傳入的單一模版還是多模版.

(3)CommonAdapter還重寫了getCount(),getItem(int position),getView,getConvertView等BaseAdapter 的方法,這些方法的程式碼都基本每個Adapter都是一樣的,我們只需關心 setConvertView 這個抽象方法,在子類中重寫它,將具體的邏輯和數據的綁定在此即可。

二.使用CommonAdapter

  1. 先建一個類繼承CommonAdapter,在構造函數中調用父類的構造方法以初始化數據。實現 setConvertView 方法,將對象的數據綁定到模版上 即可
    public class SearchFriendAdapter extends CommonAdapter<OrayUser> {        /**       * 設置單個子模版VIew,       * @param context       * @param datas 綁定的對象集合       * @param item_layoutId 被綁定的視圖layoutID      * */     public SearchFriendAdapter(Context context,List<OrayUser> datas,int item_layoutId)     {         super(context,datas,item_layoutId);     }    @Override    protected void setConvertView(View view, OrayUser orayUser,int itemViewType)    {      ...具體將對象的數據綁定到模版上    }  } 

  2. Activity中使用時new 一個Adapter,然後將adapter綁定到 ListView
    adapter = new SearchFriendAdapter(this,this.userList,R.layout.friend_child);  listView.setAdapter(adapter);

  3. 更新數據源中的數據同步到View
    //有2種方式將數據源同步到View  adapter.notifyDataSetChanged();//全部刷新  adapter.onOneItemChanged(ListView parent, int position);//刷新指定位置的數據 

三. 讓CommonAdapter支援多模板

      當然CommonAdapter還支援多模版的應用,我們只需將第一步中的Adapter實現稍稍做改動即可

public class ListViewAdapter extends CommonAdapter<ListViewItemModel> {      /**       *設置多個子模版VIew, 並配合重寫 getItemViewType(int position)來確定是設置哪個模版       * @param context       * @param datas 綁定的對象集合       * @param layoutIdMap 多種itemViewID Map 第一個int對應type類型,第二個int對應 itemlayoutId      */      public ListViewAdapter(Context context, List<ListViewItemModel> datas, HashMap<Integer,Integer> layoutIdMap)      {          super(context,datas,layoutIdMap);      }      @Override      public int getItemViewType(int position)      {          ListViewItemModel model=  (ListViewItemModel)super.getItem(position);          if(model.floatRight)          {              return 1;          }          return 0;      }      @Override      protected void setConvertView(View view, ListViewItemModel listViewItemModel,int itemViewType) {      //根據itemViewType 的值來識別是哪個模版,以對應給模版綁定數據        }  }

如上圖我們將構造函數的最後一個參數從單一的layoutID 換成了 模版集合的HashMap,再就是多加了 getItemViewType(int position) 方法來返回 具體Model對應的是哪一個模版類型(即hashMap 中的key值),使用時更新數據源中的數據同步到View和上面的單一模版一樣使用。(同上2種更新方法)

        HashMap<Integer,Integer> layoutMap=new HashMap<>();          layoutMap.put(0,R.layout.listview_item);//key值最好從0開始向上遞增,否則可能會找不到key的BUG          layoutMap.put(1,R.layout.listview_right_item);          adapter=new ListViewAdapter(this,models,layoutMap);          listView.setAdapter(adapter);

 四.綜合實例

      最後我們還是以本文開頭的查找用戶的例子,來更全面地說明CommonAdapter的使用。頁面截圖如下:

      

 1.模版布局

<?xml version="1.0" encoding="utf-8"?>  <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto"      android:id="@+id/layout"      android:layout_width="wrap_content"      android:layout_height="wrap_content"      android:background="@drawable/selector_item"      android:descendantFocusability="blocksDescendants"      android:gravity="center_vertical"      android:orientation="horizontal"      android:padding="4dp">        <ImageView          android:id="@+id/ct_photo"          android:layout_width="@dimen/headImageSize"          android:layout_height="@dimen/headImageSize"          android:layout_margin="5dp"          android:src="@drawable/plus"          />        <ImageView          android:id="@+id/ct_status"          android:layout_width="11dip"          android:layout_height="11dip"          android:layout_marginLeft="@dimen/headImageSize"          android:layout_marginTop="@dimen/headImageSize"          android:src='@drawable/status_2' />        <LinearLayout          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:gravity="center_vertical"          android:layout_alignTop="@id/ct_photo"          android:layout_toRightOf="@id/ct_photo"          >            <TextView              android:id="@+id/ct_name"              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:paddingLeft="5dp"              android:paddingRight="5dp"              android:paddingBottom="5dp"              android:paddingTop="0dp"              android:text="name"              android:textColor="@color/dimgrey"              android:textSize="16sp"              android:textStyle="bold" />            <TextView              android:id="@+id/ct_describe"              android:layout_width="wrap_content"              android:layout_height="wrap_content"              android:layout_marginLeft="10dp"              android:layout_toRightOf="@id/ct_name"              android:paddingLeft="5dp"              android:paddingRight="5dp"              android:paddingBottom="5dp"              android:paddingTop="0dp"              android:text="describe"              android:textColor="@color/dimgrey"              android:textSize="14sp"              android:visibility="gone" />      </LinearLayout>      <TextView          android:id="@+id/ct_sign"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_alignBottom="@id/ct_photo"          android:layout_toRightOf="@id/ct_photo"          android:paddingLeft="5dp"          android:text="sign"          android:singleLine="true"          android:ellipsize="start"          android:textColor="@color/dimgrey"          android:textSize="12sp" />    </RelativeLayout>  

2.具體繼承CommonAdapter實現子類

public class SearchFriendAdapter extends CommonAdapter<OrayUser> {     public SearchFriendAdapter(Context context,List<OrayUser> datas,int item_layoutId)     {         super(context,datas,item_layoutId);     }        @Override      protected void setConvertView(View view, OrayUser orayUser,int itemViewType) {          try{              SearchFriendAdapter.ViewHolder holder;              if(view.getTag()==null)              {                  holder = new SearchFriendAdapter.ViewHolder(view);                  view.setTag(holder);              }              else {                  holder = (SearchFriendAdapter.ViewHolder) view.getTag();              }              HeadImgHelper.setUserHeadImg(holder.headImg,orayUser);              if(orayUser.getName().equals(orayUser.getCommentName()))              {                  holder.userName.setText(orayUser.getName());              }              else {                  holder.userName.setText(orayUser.getCommentName()+"("+ orayUser.getName()+")");              }              String catalogName= ClientGlobalCache.getInstance().getCurrentUser().getCatalogNameByFriend(orayUser.getUserID());              if(!StringHelper.isNullOrEmpty(catalogName))              {                  holder.describe.setText("[ "+ catalogName+" ]");                  holder.describe.setVisibility(View.VISIBLE);              }              else              {                  holder.describe.setText("");                  holder.describe.setVisibility(View.GONE);              }              holder.orgName.setText(orayUser.getUserID());          }catch (Exception ex){ex.printStackTrace();}      }        private class ViewHolder      {          public ImageView headImg;          public TextView userName;          public TextView describe;          public TextView orgName;            public ViewHolder(View view)          {              this.headImg = (ImageView) view.findViewById(R.id.ct_photo);              ImageView statusImg=(ImageView) view.findViewById(R.id.ct_status);              statusImg.setVisibility(View.GONE);              this.userName=(TextView) view.findViewById(R.id.ct_name);              this.orgName=(TextView) view.findViewById(R.id.ct_sign);              this.describe=(TextView) view.findViewById(R.id.ct_describe);          }      }  }

3. Activity類 核心程式碼

public class SearchFriendActivity extends Activity implements TextView.OnEditorActionListener{      private DrawableEditText input;      private ListView listView;      private TextView search_resultStr;      private List<OrayUser> userList=new ArrayList<OrayUser>() ;      private SearchFriendAdapter adapter;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_search_friend);          this.initView();      }        private void initView()      {          TextView pageTitle=(TextView)findViewById(R.id.headerText);          pageTitle.setText("查找用戶");          search_resultStr =(TextView) findViewById(R.id.search_resultStr);          listView=(ListView)findViewById(R.id.listview_user);          listView.setOnItemClickListener(this);          adapter = new SearchFriendAdapter(this,this.userList,R.layout.friend_child);          listView.setAdapter(adapter);               }   /**       * 執行點擊搜索指令       * */      private void action_search(String idOrName)      {          List<OrayUser> temp_users=Manager.getInstance().doSearchUser(idOrName);          search_resultStr.setText("沒有找到符合條件的用戶或群組");          this.setSearchResult(temp_users);      }        private void setSearchResult(List<OrayUser> temp_users)      {          this.userList.clear();          if(temp_users==null||temp_users.size()==0)          {              search_resultStr.setVisibility(View.VISIBLE);          }          else          {              for (OrayUser orayUser :temp_users) {                  if(orayUser.getUserID().equals(ClientGlobalCache.getInstance().getCurrentUser().getUserID()))                  {                      temp_users.remove(orayUser);                      break;                  }              }              this.userList.addAll(temp_users) ;              search_resultStr.setVisibility(View.GONE);          }          this.adapter.notifyDataSetChanged();      }  }