【從 0 開始開發一款直播 APP】3.1 高層封裝之 Adapter — ListView & GridView

本文為菜鳥窩作者蔣志碧的連載。“從 0 開始開發一款直播 APP ”系列來聊聊時下最火的直播 APP,如何完整的實現一個類"騰訊直播"的商業化項目
視頻地址:http://www.cniao5.com/course/10121


【從 0 開始開發一款直播 APP】3.1 高層封裝之 Adapter — ListView & GridView
【從 0 開始開發一款直播 APP】3.2 高層封裝之 Adapter — RecyclerView 實現單布局展示
【從 0 開始開發一款直播 APP】3.3 高層封裝之 Adapter -- RecyclerView 實現多條目展示
【從 0 開始開發一款直播 APP】3.4 高層封裝之 Adapter -- RecyclerView 優雅的添加 Header、Footer


一、前言

我們在開發中寫得最多的就是對 ListView、GridView 的適配器,我們熟悉得不能再熟悉,Adapter 一般都是繼承 BaseAdapter 并復寫其中的方法,getView 里面使用 ViewHolder 綁定控件。

二、常見示例

public class TraditionAdapter extends BaseAdapter {

    private Context mContext;
    private List<Item> mItems;

    public TraditionAdapter(Context context, List<Item> items) {
        mContext = context;
        mItems = items;
    }

    @Override
    public int getCount() {
        return mItems.size();
    }

    @Override
    public Object getItem(int position) {
        return mItems.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
      
        if (holder == null){
            convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item,parent,false);
            holder = new ViewHolder();
            
            holder.titleText = (TextView) convertView.findViewById(R.id.tv1);
            holder.descText = (TextView) convertView.findViewById(R.id.tv2);
            holder.img = (ImageView) convertView.findViewById(R.id.img);
          
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }
      
        Item item = mItems.get(position);
        holder.titleText.setText(item.getTv1());
        holder.descText.setText(item.getTv2());
        holder.img.setImageResource(item.getRes());
        return convertView;
    }

    class ViewHolder{
        TextView titleText;
        TextView descText;
        ImageView img;
    }
}

這種重復的代碼大家應該都寫了很多遍了,TraditionAdapter 繼承 BaseAdapter,getView 使用 ViewHolder 綁定控件。而通常每個 ListView 布局都會有一個 Adapter,Adapter 中也會有一個對應的 ViewHolder。由此看來,要減少代碼量就要將 Adapter、ViewHolder 封裝成通用類。

想要 AdapterViewHolder 通用,目前需要以下幾步:

2.1、數據是活的,TraditionAdapter 中的 Item 類應作為范型傳入 Adapter

2.2、getCount()、getItem()、getItemId() 這三個方法一直都不變,將其封裝

2.3、抽取 ViewHolder 類,解決控件綁定問題

2.4、Adapter 類封裝,實現與 ViewHolder 神匹配

知道了大概步驟,先來封裝 ViewHolder 吧。

三、ViewHodler 類封裝

ViewHolder 通過 convertView.setTag() 與 convertView 進行綁定,然后當 convertView復用時,直接利用 convertView.getTag() 獲取的ViewHolder 的 convertView 布局中的控件,省去了findViewById() 的時間

實際上每個 convertView 會綁定一個 ViewHolder 對象,這個 ViewHolder 主要用于幫 convertView 存儲布局中的控件。

那么我們只要寫出一個通用的 ViewHolder,對于任意的 convertView,提供一個對象讓其 setTag() 即可

ViewHodler 類封裝,需要做以下幾步:

1、返回 ViewHolder

2、獲取控件

3、設置控件

4、convertView 的復用

每個布局有不同的控件,每個控件有自己的 id 和數據。存儲這些控件需要使用 SparseArray。

SparseArray 簡介

SparseArray 是 android 提供的新的存儲鍵值對的 API,相比 HashMap,SparseArray 性能更好。原因如下:

1、SparseArray 更加優化內存

2、key 的類型是 int 型,免去裝箱操作,時間性能優于 HashMap

3、SparseArray 結構簡單,使用一位數組存儲 key 和 value

開始 ViewHolder 的封裝。

3.1、創建 BaseViewHolder 類,需要的變量大概有 View mConvertView 復用機制,SparseArray<View> mViews 存儲所有控件,int mPosition 記錄 View 位置信息

public class BaseViewHolder{
    //復用的View
    private final View mConvertView;
    //所有控件集合
    private SparseArray<View> mViews;
    //記錄位置信息
    private int mPosition;

    /**
     * BaseViewHolder 構造函數
     * @param context 上下文對象
     * @param parent 父類容器
     * @param layoutId 布局 Id
     * @param position item位置信息
     */
    public BaseViewHolder(Context context, ViewGroup parent, int layoutId, int position) {
        this.mPosition = position;
        this.mViews = new SparseArray<View>();
        mConvertView = LayoutInflater.from(context).inflate(layoutId, parent, false);
        //設置 tag
        mConvertView.setTag(this);
    }
  
   /**
     * 通過 viewId 獲取控件
     * @param viewId 控件id
     * @param <T> View 子類
     * @return 返回 View
     */
    public <T extends View> T getView(int viewId) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = mConvertView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return (T) view;
    }

    //返回 ViewHolder
    public static BaseViewHolder getViewHolder(Context context, View convertView, ViewGroup parent,int layoutId, int position) {
        
        //BaseViewHolder 為空,創建新的,否則返回已存在的
        if (convertView == null) {
            return new BaseViewHolder(context, parent, layoutId, position);
        } else {
            BaseViewHolder holder = (BaseViewHolder) convertView.getTag();
            //更新 item 位置信息
            holder.mPosition = position;
            return holder;
        }
    }
    
   //獲取 convertView
    public View getConvertView() {
        return mConvertView;
    }
}

3.2、設置控件以及監聽(采用鏈式編程方法)

/**
 * 設置 TextView 的值
 * @param viewId
 * @param text
 * @return
 */
public BaseViewHolder setText(int viewId, String text)
{
    TextView tv = getView(viewId);
    tv.setText(text);
    return this;
}

/**
 * 設置TImageView的值
 * @param viewId
 * @param resId
 * @return
 */
public BaseViewHolder setImageResource(int viewId, int resId)
{
    ImageView view = getView(viewId);
    view.setImageResource(resId);
    return this;
}

/**
 * 設置是否可見
 * @param viewId
 * @param visible
 * @return
 */
public BaseViewHolder setVisible(int viewId, boolean visible)
{
    View view = getView(viewId);
    view.setVisibility(visible ? View.VISIBLE : View.GONE);
    return this;
}

/**
 * 設置tag
 * @param viewId
 * @param tag
 * @return
 */
public BaseViewHolder setTag(int viewId, Object tag)
{
    View view = getView(viewId);
    view.setTag(tag);
    return this;
}

public BaseViewHolder setTag(int viewId, int key, Object tag)
{
    View view = getView(viewId);
    view.setTag(key, tag);
    return this;
}
/**
 * 設置 Checkable
 * @param viewId
 * @param checked
 * @return
 */
public BaseViewHolder setChecked(int viewId, boolean checked)
{
    Checkable view = (Checkable) getView(viewId);
    view.setChecked(checked);
    return this;
}

/**
 * 點擊事件
 */
public BaseViewHolder setOnClickListener(int viewId,View.OnClickListener listener)
{
    View view = getView(viewId);
    view.setOnClickListener(listener);
    return this;
}

/**
 * 觸摸事件
 */
public BaseViewHolder setOnTouchListener(int viewId,View.OnTouchListener listener)
{
    View view = getView(viewId);
    view.setOnTouchListener(listener);
    return this;
}

/**
 * 長按事件
 */
public BaseViewHolder setOnLongClickListener(int viewId,View.OnLongClickListener listener)
{
    View view = getView(viewId);
    view.setOnLongClickListener(listener);
    return this;
}z

四、Adapter 類封裝

Adapter 封裝需要以下幾步:

1、上例一直存在的 Item 類將作為范型傳入Adapter

2、封裝 getCount()、getItem()、getItemId() 三個方法

3、封裝 getView()

4、綁定 ViewHolder

創建 BaseAdapter 繼承 android.widget.BaseAdapter 類,重寫方法及構造函數,根據需求,需要的參數有 List<T> mDatas 數據源,Context mContext 上下文對象,int mLayoutId 布局。

public abstract class BaseAdapter<T> extends android.widget.BaseAdapter {
    protected List<T> mDatas;
    protected Context mContext;
    protected int mLayoutId;

    public BaseAdapter(List<T> datas, Context context, int layoutId) {
        mDatas = datas;
        mContext = context;
        this.mLayoutId = layoutId;
    }

    @Override
    public int getCount() {
        return mDatas == null ? 0 : mDatas.size();
    }

    @Override
    public T getItem(int position) {
        return mDatas == null ? null : mDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        BaseViewHolder holder = BaseViewHolder.getViewHolder(mContext, convertView, parent, mLayoutId, position);

        T t = mDatas.get(position);
        
        //抽象出 ViewHolder 讓用戶去實現填充數據
        bindData(holder, t);

        return holder.getConvertView();
    }

    public abstract void bindData(BaseViewHolder holder, T t);
}

五、Adapter 和 ViewHolder 的使用

**5.1、 創建 SimpleAdapter.java 繼承 BaseAdapter 類,傳入 Item 數據源,根據布局找到需要的控件并填充數據,添加監聽等。 **

public class SimpleAdapter extends BaseAdapter<Item> {

    public SimpleAdapter(List datas, Context context) {
        super(datas, context, R.layout.list_item);
    }

    @Override
    public void bindData(BaseViewHolder holder, final Item item) {

        holder.setText(R.id.tv1,item.getTv1())
                .setText(R.id.tv2,item.getTv2())
                .setImageResource(R.id.img,item.getRes())
                .setOnClickListener(R.id.tv2, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                       Toast.makeText(mContext,item.getTv2(),Toast.LENGTH_SHORT).show();
                    }
                });
    }
}

5.2、由于加了點擊事件,運行起來點擊事件無效果,并不是因為代碼有問題,而是焦點搶占原因,因此需要在布局文件 activity_adapter.xml 中設置是否可點擊

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:clickable="false"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

    <ImageView
        android:padding="5dp"
        android:layout_marginRight="40dp"
        android:id="@+id/img"
        android:layout_width="100dp"
        android:layout_height="100dp"/>

    <LinearLayout
        android:clickable="false"
        android:padding="10dp"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        <TextView
            android:paddingTop="10dp"
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="40dp"/>

        <TextView
            android:id="@+id/tv2"
            android:clickable="true"
            android:layout_width="wrap_content"
            android:layout_height="40dp"/>
    </LinearLayout>
</LinearLayout>

5.3、 AdapterActivity.java

public class AdapterActivity extends BaseActivity {

    private SimpleAdapter mAdapter;
    private ListView mListView;
    private ArrayList<Item> Datas;

    @Override
    protected void setToolbar() {
    }

    @Override
    protected void setListener() {
    }

    //添加數據
    @Override
    protected void initData() {
        Datas = new ArrayList<>();
        for (int i = 1; i <= 30; i++) {
            Item item = new Item(R.drawable.tab_publish_normal, " get 新技能" + i, "揀到漂亮妹子 "+i+" 枚,在大街上");
            Datas.add(item);
        }

        mAdapter = new SimpleAdapter(Datas,this);
        mListView.setAdapter(mAdapter);
    }

    @Override
    protected void initView() {
          mListView = obtainView(R.id.list);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_adapter;
    }
}

六、運行效果展示

6.1、ListView

6.2、GridView

七、總結

Adapter 傳統寫法熟練,對 ConverView 復用了解

ViewHolder 的使用熟悉,尤其是控件綁定

了解 SparseArray 的優缺點,對其基本使用熟悉

筆者只是傳達了封裝的思想,能力有限,封裝不到位的地方還望大家留言指正

更多內容,請關注菜鳥窩(微信公眾號ID: cniao5),程序猿的在線學習平臺。轉載請注明出處,本文出自菜鳥窩,原文鏈接http://www.cniao5.com/forum/thread/2ac69d820f0611e790dc00163e0230fa

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容