BRVAH:基本使用源碼分析

簡單介紹

BRVAH:是由大佬 陳宇明 大佬開源的一款 RecyclerView 的Adapter 框架。官方介紹在此 http://www.recyclerview.org/ 厅缺。首先表示非常感謝與佩服!

廢話開篇

本人由于一直想要深入 RecyclerView 的學(xué)習(xí)與源碼的閱讀(在那么多人的文章帶領(lǐng)下肚了很多源碼都記不住窄刘,無奈面試的時(shí)候一直被問讀了什么源碼舷胜,??)烹骨。而在自己與公司的項(xiàng)目中也一直使用這個(gè)框架沮焕,所以就想要深入學(xué)習(xí)一下這個(gè)框架。

這將是一系列文章辣辫,僅僅記錄我閱讀這個(gè)源碼時(shí)的思路與理解急灭。關(guān)于有意義和沒意義這件事谷遂,嗯~~~~~~~我開心就好。所以如果有人不小心看到我的這個(gè)文章扫尖,希望不會辣你的眼睛掠廓。如果你讀完了蟀瞧,我表示感謝悦污。如果你發(fā)現(xiàn)了錯(cuò)誤,歡迎指正

本文思路

  1. 分析RecyclerView.Adapter 的基本使用來找出需要關(guān)注的重點(diǎn)
  2. 通過 分析 BRVAH 的基本使用 找出 BRVAH 的重點(diǎn)
  3. 按照重點(diǎn)的先后順序逐個(gè)分析
  4. 總結(jié)

RecyclerView.Adapter 的基本使用

  1. xml中聲明RecyclerView
<android.support.v7.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
  1. Activity 通過setAdapterRecyclerView 賦值 Adapter
private RecyclerView mRecyclerView;
private DemoAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_demo);
    mRecyclerView = findViewById(R.id.recycler_view);
    mAdapter = new DemoAdapter(getData());
    mRecyclerView.setAdapter(mAdapter);
}
// 生成
private List<String> getData() {
    List<String> data = new ArrayList<>();
    for (int i = 0; i < 30; i++) {
        data.add("item" + i);
    }
    return data;
}
  1. 接下來看 DemoAdapter。自定義 Adapter 需要繼承自 RecyclerView.Adapter昌屉,并制定 ViewHolder 的范型
public class DemoAdapter extends RecyclerView.Adapter<DemoAdapter.DemoViewHolder> {
        // 很簡單的數(shù)據(jù)項(xiàng)
    private List<String> mData;

    public DemoAdapter(List<String> data) {
        mData = data;
    }
        // 重點(diǎn)1:創(chuàng)建泛型制定的 ViewHolder
    @NonNull
    @Override
    public DemoViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.demo_adapter, viewGroup, false);
        return new DemoViewHolder(itemView);
    }
        // 重點(diǎn)2:在這里借助 ViewHodler 操作每一個(gè) position 中的 View
    @Override
    public void onBindViewHolder(@NonNull DemoViewHolder demoViewHolder, int position) {
        demoViewHolder.mTvItem.setText(mData.get(i));
    }
        // 重點(diǎn)3:返回 item 個(gè)數(shù)
    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }
        // 重點(diǎn)4:ViewHolder 提供可供操作的 ItemView極其子View
    class DemoViewHolder extends RecyclerView.ViewHolder {
        private TextView mTvItem;

        public DemoViewHolder(@NonNull View itemView) {
            super(itemView);
            mTvItem = itemView.findViewById(R.id.tv_item);
        }
    }
}

以上重點(diǎn)1、重點(diǎn)2竞帽、重點(diǎn)3對應(yīng)于 BaseQuickAdapter鸿捧,重點(diǎn)4對應(yīng)于 BaseViewHolder 將在接下來被著重分析匙奴。首先我們看一下 BRVAH 的基本使用饥脑。

BRVAH 的基本使用

  1. 創(chuàng)建 BRVAHDemoAdapter 并繼承自 BaseQuickAdapter,并指定數(shù)據(jù)類型為 String , ViewHolder 類型為 BaseViewHolder谣沸。
// 重點(diǎn)5 指定item數(shù)據(jù)類型與 BaseViewHolder
public class BRVAHDemoAdapter extends BaseQuickAdapter<String, BaseViewHolder> {
    public BRVAHDemoAdapter(@Nullable List<String> data) {
            // 重點(diǎn)6 調(diào)用父類構(gòu)造方法
        super(R.layout.demo_adapter, data);
    }
        // 借助 BaseViewHolder 實(shí)現(xiàn)數(shù)據(jù)與UI的綁定
    @Override
    protected void convert(BaseViewHolder helper, String item) {
        helper.setText(R.id.tv_item, item);
    }
}
  1. 創(chuàng)建 BRVAHDemoAdapter 對象 并替換原來的 DemoAdpter 對象賦值給 RecyclerView乳附「吵可以看到實(shí)現(xiàn)了與原來一樣的效果,但是代碼量卻大大地減少荆针,真的是灰常優(yōu)雅了航背。??

通過 RecyclerView.Adapter 的基本使用和 BRVAH 的基本使用對比玖媚,接下來將會按照 重點(diǎn)5—>重點(diǎn)6 —> 重點(diǎn)1 —>重點(diǎn)2—> 重點(diǎn)3 —>重點(diǎn)4 的順序進(jìn)行分析婚脱。

BRVAH 的實(shí)現(xiàn)分析

重點(diǎn)5:BaseQuickAdapter的聲明

public abstract class BaseQuickAdapter<T, K extends BaseViewHolder> extends RecyclerView.Adapter<K>{//...}
public class BaseViewHolder extends RecyclerView.ViewHolder{//...}

可以看到 BaseViewHolder 繼承自RecyclerView.ViewHolder

  1. BaseQuickAdapter的聲明如上所示:可以看到 BaseQuickAdapter 是一個(gè)抽象類,繼承自RecyclerView.Adapter障贸。T 用于表示數(shù)據(jù)項(xiàng)的類型错森;K 表示繼承自 BaseViewHolder 的類型。
  2. 再看BaseViewHolder惹想,繼承自RecyclerView.ViewHolder问词。嗯督函?這不是和我們原來直接繼承RecyclerView.Adapter<T> 一樣了嗎嘀粱???

重點(diǎn)6:BaseQuickAdapter 構(gòu)造方法

三個(gè)重載構(gòu)造方法,最終都調(diào)用BaseQuickAdapter(@LayoutRes int layoutResId, @Nullable List<T> data)

// layoutResId:布局id辰狡;data:數(shù)據(jù)項(xiàng),為空就創(chuàng)建一個(gè)空的 ArrayList
public BaseQuickAdapter(@LayoutRes int layoutResId, @Nullable List<T> data) {
    // mData 表示內(nèi)部維護(hù)的數(shù)據(jù)項(xiàng)聲明宛篇,沒有則直接新建一個(gè)空的 ArrayList
    this.mData = data == null ? new ArrayList<T>() : data;
    if (layoutResId != 0) {
        this.mLayoutResId = layoutResId;
    }
}

public BaseQuickAdapter(@Nullable List<T> data) {
    this(0, data);
}

public BaseQuickAdapter(@LayoutRes int layoutResId) {
    this(layoutResId, null);
}

重點(diǎn)1:onCreateViewHolder

該方法返回一個(gè) BaseViewHolder或其子類娃磺,具體看以下注釋

@Override
public K onCreateViewHolder(ViewGroup parent, int viewType) {
    K baseViewHolder = null;
    this.mContext = parent.getContext();
    // 獲取 LayoutInflater,后續(xù) getItemView 中會用到
    this.mLayoutInflater = LayoutInflater.from(mContext);
    // 根據(jù)不同的 viewType 創(chuàng)建不同的 ViewHolder
    switch (viewType) {
        // ...先忽略這一部分
        default:
                // 分析1:創(chuàng)建默認(rèn)的 ViewHolder
            baseViewHolder = onCreateDefViewHolder(parent, viewType);
                // 綁定監(jiān)聽事件 先忽略
            bindViewClickListener(baseViewHolder);
    }
    // 傳入當(dāng)前Adapter到BaseViewHolder中
    baseViewHolder.setAdapter(this);
    return baseViewHolder;
}
// 分析1: onCreateDefViewHolder 
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
    int layoutId = mLayoutResId;
    // 先忽略
    if (mMultiTypeDelegate != null) {
        layoutId = mMultiTypeDelegate.getLayoutId(viewType);
    }
    // 分析2:調(diào)用 createBaseViewHolder
    return createBaseViewHolder(parent, layoutId);
}
// 分析2
protected K createBaseViewHolder(ViewGroup parent, int layoutResId) {
    // 分析3:getItemView(@LayoutRes int layoutResId, ViewGroup parent)
    // 分析4:createBaseViewHolder(View view)
    return createBaseViewHolder(getItemView(layoutResId, parent));
}
// 分析3 是不是很熟悉,借助 LayoutInflater 根據(jù) layoutResId 返回一個(gè) View
// 然后將這個(gè)View 傳遞給 ViewHolder叫倍。這和我們在 DemoAdapter#onCreateViewHolder中做的不是一樣要到嘛
protected View getItemView(@LayoutRes int layoutResId, ViewGroup parent) {
    return mLayoutInflater.inflate(layoutResId, parent, false);
}
// 分析4:根據(jù)泛型和反射獲取 K 的實(shí)際類型
protected K createBaseViewHolder(View view) {
    Class temp = getClass();
    Class z = null;
    while (z == null && null != temp) {
        // 獲取 K 的實(shí)際類型
        z = getInstancedGenericKClass(temp);
        temp = temp.getSuperclass();
    }
    K k;
    // 泛型擦除會導(dǎo)致z為null
    if (z == null) {
        k = (K) new BaseViewHolder(view);
    } else {
        // 使用反射 根據(jù) K 的實(shí)際類型創(chuàng)建對象
        k = createGenericKInstance(z, view);
    }
    return k != null ? k : (K) new BaseViewHolder(view);
}
// 獲取泛型類型
private Class getInstancedGenericKClass(Class z) {
    // 獲取當(dāng)前父類的范型類型
    Type type = z.getGenericSuperclass();
    // 父類是否包含泛型
    if (type instanceof ParameterizedType) {
        // 獲取泛型數(shù)組<T, K extends BaseViewHolder>
        Type[] types = ((ParameterizedType) type).getActualTypeArguments();
        // 遍歷泛型數(shù)組
        for (Type temp : types) {
            // 如果這個(gè)泛型是一個(gè)類
            if (temp instanceof Class) {
                Class tempClass = (Class) temp;
                // isAssignableFrom 表示 tempClass 是否是 BaseViewHolder 的子類或者接口
                if (BaseViewHolder.class.isAssignableFrom(tempClass)) {
                    return tempClass;
                }
            } else if (temp instanceof ParameterizedType) {
                //  如果這個(gè)泛型是一個(gè)泛型參數(shù)偷卧,那么就獲取他的實(shí)際類型
                Type rawType = ((ParameterizedType) temp).getRawType();
                if (rawType instanceof Class && BaseViewHolder.class.isAssignableFrom((Class<?>) rawType)) {
                    return (Class<?>) rawType;
                }
            }
        }
    }
    return null;
}

private K createGenericKInstance(Class z, View view) {
    try {
        Constructor constructor;
        // 如果是內(nèi)部類,且不是靜態(tài)類
        if (z.isMemberClass() && !Modifier.isStatic(z.getModifiers())) {
            // 獲取指定的吆倦、不包括繼承的構(gòu)造函數(shù)
            constructor = z.getDeclaredConstructor(getClass(), View.class);
            // 屏蔽Java 語言的訪問檢查听诸,使得構(gòu)造函數(shù)可以訪問
            constructor.setAccessible(true);
            // 為什么要傳入 this,不是特別明白
            return (K) constructor.newInstance(this, view);
        } else {
            // 同上,不再重復(fù)說明
            constructor = z.getDeclaredConstructor(View.class);
            constructor.setAccessible(true);
            return (K) constructor.newInstance(view);
        }
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return null;
}

從以上注釋可以看到:BaseQuickAdapter 內(nèi)部重寫了 onCreateViewHolder 然后通過反射或者 new 的方式返回一個(gè) BaseViewHolder蚕泽。

重點(diǎn)2:onBindViewHolder

二話不說晌梨,直接先上代碼:

@Override
public void onBindViewHolder(K holder, int position) {
    autoUpFetch(position);
    autoLoadMore(position);
    int viewType = holder.getItemViewType();

    switch (viewType) {
        case 0:
            convert(holder, getItem(position - getHeaderLayoutCount()));
            break;
        case LOADING_VIEW:
            mLoadMoreView.convert(holder);
            break;
        case HEADER_VIEW:
            break;
        case EMPTY_VIEW:
            break;
        case FOOTER_VIEW:
            break;
        default:
                // 以上可以先 忽略
                // 分析1: getHeaderLayoutCount() 獲取是否存在 Header 
                // 分析2: getItem(position - getHeaderLayoutCount()) 用于獲取當(dāng)前
                // position對應(yīng)的 data 數(shù)據(jù),當(dāng)前 position 需要去除頭部個(gè)數(shù)(1或0)
                // 分析3:
            convert(holder, getItem(position - getHeaderLayoutCount()));
            break;
    }
}
// 分析1: 如果存在 Header 就返回1 否則返回0
public int getHeaderLayoutCount() {
    if (mHeaderLayout == null || mHeaderLayout.getChildCount() == 0) {
        return 0;
    }
    return 1;
}

// 分析2:從 mData 獲取 position 對應(yīng)的 data
public T getItem(@IntRange(from = 0) int position) {
    // 對 position 做安全范圍判斷
    if (position >= 0 && position < mData.size())
        return mData.get(position);
    else
        return null;
}
// 分析3: 子類通過重寫父類的這個(gè)方法,拿到 BaseViewHolder 和Item數(shù)據(jù)
// 并在這個(gè)方法中對 Item 項(xiàng)實(shí)現(xiàn)數(shù)據(jù)操作
protected abstract void convert(K helper, T item);

從以上代碼可以看出:BaseQuickAdapter 通過重寫 onBindViewHolder 方法仔蝌,對于一般情況泛领,通過 convert 抽象方法向子類暴露了 BaseViewHolder 和 當(dāng)前位置的 數(shù)據(jù)項(xiàng) 以供開發(fā)者使用。

重點(diǎn)3:getItemCount

區(qū)別于我們自定義的 Adapter敛惊,在計(jì)算 item 個(gè)數(shù)的時(shí)候渊鞋,需要考慮 EmptyViewHeaderFooter 的情況

@Override
public int getItemCount() {
    int count;
    // 是否存在 EmptyView瞧挤。后續(xù)分析
    if (1 == getEmptyViewCount()) {
        // 存在 EmptyView篓像; 則count = 1 + header(1/0) + footer(1/0)
        count = 1;
        if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
            count++;
        }
        if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
            count++;
        }
    } else {
        // 數(shù)量等于 (1/0) + 數(shù)據(jù)項(xiàng) + (1/0) + (1/0)
        count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
    }
    return count;
}

在重點(diǎn)2中我們看到,最終生成了一個(gè) BaseViewHolder 對象并在重點(diǎn) 3 中通過 convert 方法向子類暴露用來操作 Item 的UI皿伺。接下來我們就看看 BaseViewHolder 內(nèi)部都做了什么员辩。

重點(diǎn)4:BaseViewHolder

BaseViewHolder 最大的特點(diǎn)就是通過一個(gè)泛型方法 getView和一系列 setXXX 方法。使得代碼的調(diào)用更加簡潔鸵鸥。

setXXX()

以上方法大都類似奠滑,選一個(gè)我們最常用的設(shè)置文字方法看一下:

public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
    // 分析1 getView 獲取 TextView 實(shí)例
    // 并設(shè)置文字
    TextView view = getView(viewId);
    view.setText(value);
    return this;
}
//分析1
public <T extends View> T getView(@IdRes int viewId) {
    // views 是一個(gè) SparseArray<View> 對象 
    // 以下代碼達(dá)到僅在第一此需要某個(gè) View 時(shí)通過 findViewById 獲取
    // 以后都從 views 通過 id 獲取
    // 這是 Flyweight 設(shè)計(jì)模式的思想嘛
    View view = views.get(viewId);
    if (view == null) {
        view = itemView.findViewById(viewId);
        views.put(viewId, view);
    }
    return (T) view;
}

總結(jié)

基本用法的分析就結(jié)束啦,接下來是總結(jié)妒穴。通過以上的分析宋税,我有以下的收獲:

  1. 明白了 BRVAH 基本實(shí)現(xiàn)原理,其本質(zhì)和我們常規(guī)寫的 RecyclerView.Adapter一樣讼油,只是包裝了關(guān)鍵方法onCreateViewHolder杰赛、onBindViewHoldergetItemCount矮台。同時(shí)自定義了通用的 BaseViewHolder
  2. 復(fù)習(xí)了 享元設(shè)計(jì)模式( Flyweight ) 和 建造者設(shè)計(jì)模式( Builder )的相關(guān)思想(PS:setXXX實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用)
  3. 發(fā)現(xiàn)了自己知識的欠缺:關(guān)于 反射與泛型的結(jié)合使用乏屯。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘦赫,隨后出現(xiàn)的幾起案子辰晕,更是在濱河造成了極大的恐慌,老刑警劉巖确虱,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件含友,死亡現(xiàn)場離奇詭異,居然都是意外死亡校辩,警方通過查閱死者的電腦和手機(jī)窘问,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宜咒,“玉大人惠赫,你說我怎么就攤上這事∮牛” “怎么了汉形?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵纸镊,是天一觀的道長。 經(jīng)常有香客問我概疆,道長逗威,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任岔冀,我火速辦了婚禮凯旭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘使套。我一直安慰自己罐呼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布侦高。 她就那樣靜靜地躺著嫉柴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奉呛。 梳的紋絲不亂的頭發(fā)上计螺,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天,我揣著相機(jī)與錄音瞧壮,去河邊找鬼登馒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛咆槽,可吹牛的內(nèi)容都是我干的陈轿。 我是一名探鬼主播,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼秦忿,長吁一口氣:“原來是場噩夢啊……” “哼麦射!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起小渊,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤法褥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酬屉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揍愁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年呐萨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莽囤。...
    茶點(diǎn)故事閱讀 40,918評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谬擦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朽缎,到底是詐尸還是另有隱情惨远,我是刑警寧澤谜悟,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站北秽,受9級特大地震影響葡幸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贺氓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一蔚叨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辙培,春花似錦蔑水、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尾抑,卻和暖如春领曼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛮穿。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工庶骄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人践磅。 一個(gè)月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓单刁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親府适。 傳聞我的和親對象是個(gè)殘疾皇子羔飞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內(nèi)容