BRAVH流程簡讀

Rayhahah發表於2019-02-27

BRAVH原始碼解讀

對於BRAVH基本流程的梳理
所以不會針對每一個判斷每一個功能都詳細追溯
只會做大致的瞭解,和一些關鍵點的詳解

BaseViewHolder

首先我們來看一下BaseViewHolder的關鍵程式碼,相當簡單,可以看到主要是儲存這個View,然後views是這個條目的控制元件的集合,以setText()為例,根據id獲取到控制元件並新增到控制元件集合,然後view.setText,就醬
剩餘的是一些事件點選的繫結和處理,後面會單獨將事件點選這一塊

  public BaseViewHolder(final View view) {
        super(view);
        this.views = new SparseArray<>();
        this.childClickViewIds = new LinkedHashSet<>();
        this.itemChildLongClickViewIds = new LinkedHashSet<>();
        this.nestViews = new HashSet<>();
        convertView = view;
    }
    
 public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
        TextView view = getView(viewId);
        view.setText(value);
        return this;
    }

  public <T extends View> T getView(@IdRes int viewId) {
        View view = views.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            views.put(viewId, view);
        }
        return (T) view;
    }
複製程式碼

BaseQuickAdapter

這裡我們打算按照著Recycler的生命週期來講解會更好理解整個流程是如何運作的

構造方法

初始化layoutIddata
data不傳就預設是一個空的ArrayList

  public BaseQuickAdapter(@LayoutRes int layoutResId, @Nullable List<T> data) {
        this.mData = data == null ? new ArrayList<T>() : data;
        if (layoutResId != 0) {
            this.mLayoutResId = layoutResId;
        }
    }
複製程式碼

onAttachedToRecyclerView

Adapter與RecyclerView關聯起來
這裡面主要是做表格佈局管理器的頭佈局和腳佈局自佔一行的適配

 @Override
    public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int type = getItemViewType(position);
                    if (type == HEADER_VIEW && isHeaderViewAsFlow()) {
                        return 1;
                    }
                    if (type == FOOTER_VIEW && isFooterViewAsFlow()) {
                        return 1;
                    }
                    if (mSpanSizeLookup == null) {
                        return isFixedViewType(type) ? gridManager.getSpanCount() : 1;
                    } else {
                        return (isFixedViewType(type)) ? gridManager.getSpanCount() : mSpanSizeLookup.getSpanSize(gridManager,
                                position - getHeaderLayoutCount());
                    }
                }
            });
        }
    }
複製程式碼

getItemCount

獲取Item數量

    public int getItemCount() {
        int count;
        if (getEmptyViewCount() == 1) {
            count = 1;
            if (mHeadAndEmptyEnable && getHeaderLayoutCount() != 0) {
                count++;
            }
            if (mFootAndEmptyEnable && getFooterLayoutCount() != 0) {
                count++;
            }
        } else {
            count = getHeaderLayoutCount() + mData.size() + getFooterLayoutCount() + getLoadMoreViewCount();
        }
        return count;
    }
複製程式碼
  • 是空佈局:
    • 根據是否有頭佈局和腳佈局來增加數量
  • 沒有空佈局:
    • 數量 = 頭佈局+資料條目數量 + 腳佈局+載入更多佈局

getItemViewType

獲取Item型別

  @Override
    public int getItemViewType(int position) {
        if (getEmptyViewCount() == 1) {
            boolean header = mHeadAndEmptyEnable && getHeaderLayoutCount() != 0;
            switch (position) {
                case 0:
                    if (header) {
                        return HEADER_VIEW;
                    } else {
                        return EMPTY_VIEW;
                    }
                case 1:
                    if (header) {
                        return EMPTY_VIEW;
                    } else {
                        return FOOTER_VIEW;
                    }
                case 2:
                    return FOOTER_VIEW;
                default:
                    return EMPTY_VIEW;
            }
        }
        int numHeaders = getHeaderLayoutCount();
        if (position < numHeaders) {
            return HEADER_VIEW;
        } else {
            int adjPosition = position - numHeaders;
            int adapterCount = mData.size();
            if (adjPosition < adapterCount) {
                return getDefItemViewType(adjPosition);
            } else {
                adjPosition = adjPosition - adapterCount;
                int numFooters = getFooterLayoutCount();
                if (adjPosition < numFooters) {
                    return FOOTER_VIEW;
                } else {
                    return LOADING_VIEW;
                }
            }
        }
    }
複製程式碼
  1. 首先判斷是否是空佈局,如果是,則根據位置來判斷是空佈局、頭佈局、腳佈局的哪一個
  2. 不是空佈局的情況下
    1. 首先判斷是否是頭佈局,是就返回頭佈局
    2. 否則,拿到條目位置,然後和資料位置做比對,如果位置和資料大小一致,則判斷是否是載入佈局
    3. 如果位置是在資料列表中則呼叫getDefItemViewType()

getDefItemViewType()

  protected int getDefItemViewType(int position) {
        if (mMultiTypeDelegate != null) {
            return mMultiTypeDelegate.getDefItemViewType(mData, position);
        }
        return super.getItemViewType(position);
    }
複製程式碼

中間的判斷是是否啟用多型別條目的,如果沒啟用,就直接返回預設的條目型別,就是0!!

多型別條目

現在來看一下多型別條目
MultiTypeDelegate.getDefItemViewType()

public final int getDefItemViewType(List<T> data, int position) {
        T item = data.get(position);
        return item != null ? getItemType(item) : DEFAULT_VIEW_TYPE;
    }
複製程式碼

可以看到,做了判斷如果資料不為空就會呼叫MultiTypeDelegate.getItemType()
然後我們看到這個MultiTypeDelegate.getItemType()是一個抽象方法。

這裡我們要說一下多型別條目的使用了
程式碼如下:

public class MatchDataAdapter extends BaseQuickAdapter<MatchStatusBean.StatsBean, BaseViewHolder> {
    public MatchDataAdapter() {
        super(null);
        setMultiTypeDelegate(new MultiTypeDelegate<MatchStatusBean.StatsBean>() {
            @Override
            protected int getItemType(MatchStatusBean.StatsBean statsBean) {
                switch (Integer.parseInt(statsBean.type)) {
                    case 12:
//                        比分
                        return C.MATCH.ITEM_TYPE_MATCH_DATA_POINT;
                    case 14:
                        //球隊統計
                        return C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_COUNT;
                    case 13:
                        //全場最佳
                        return C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_BEST;
                    default:
                        break;
                }
                return 0;
            }
        });
        getMultiTypeDelegate()
		        .registerItemType(C.MATCH.ITEM_TYPE_MATCH_DATA_POINT, R.layout.item_match_data_point)
                .registerItemType(C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_COUNT, R.layout.item_match_data_count_list)
                .registerItemType(C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_BEST, R.layout.item_null);
    }

    @Override
    protected void convert(BaseViewHolder helper, MatchStatusBean.StatsBean item) {
        switch (helper.getItemViewType()) {
            //比分
            case C.MATCH.ITEM_TYPE_MATCH_DATA_POINT:
			具體邏輯操作。。。
                break;
            //球隊統計
            case C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_COUNT:
            具體邏輯操作。。。
            
                break;
            case C.MATCH.ITEM_TYPE_MATCH_DATA_TEAM_BEST:
           具體邏輯操作。。。

            default:
                break;

        }
    }
}
複製程式碼

簡單說一下

  1. setMultiTypeDelegate()設定一個MultiTypeDelegate物件並實現它的getItemType()方法,根據自己資料具體情況,來返回多型別條目的標識(這個也是上面我們分析的那個抽象方法的具體實現了)追進去看,其實就是初始化了BaseQuickAdapter內的MultiTypeDelegate物件,也就是我們剛剛getDefItemViewType()中所使用的那一個
public void setMultiTypeDelegate(MultiTypeDelegate<T> multiTypeDelegate) {
        mMultiTypeDelegate = multiTypeDelegate;
    }
複製程式碼
  1. getMultiTypeDelegate().registerItemType()getMultiTypeDelegate顧名思義也就是獲取我們剛剛初始化的那個MultiTypeDelegate物件,registerItemType()這裡先說一下,BRAVH提供了兩種模式來註冊多型別條目,並且兩種模式是不能同時使用的,這就是下面checkMode()的作用,防止你同時使用兩種方式來註冊多型別條目。
    然後進入addItmType() : 可以看到就是把多型別標識資源id都放進了layouts裡面,而這個layouts自然是在MultiTypeDelegate.getLayoutId()的時候被呼叫 ,這裡先留個概念,後面onCreateViewHolder的時候我們會用到
   public MultiTypeDelegate registerItemType(int type, @LayoutRes int layoutResId) {
        selfMode = true;
        checkMode(autoMode);
        addItemType(type, layoutResId);
        return this;
    }


 private void addItemType(int type, @LayoutRes int layoutResId) {
        if (this.layouts == null) {
            this.layouts = new SparseIntArray();
        }
        this.layouts.put(type, layoutResId);
    }
複製程式碼
  1. 最後就是在helper.getItemViewType()做判斷來確定具體條目的型別了,因為在上面重寫了MultiTypeDelegate.getItemType()所以根據我們之前的分析MultiTypeDelegate!=null的情況下,helper.getItemViewType()獲取到的就是我們MultiTypeDelegate.getItemType()返回的值

onCreateViewHolder

載入ViewHolder的佈局

 @Override
    public K onCreateViewHolder(ViewGroup parent, int viewType) {
        K baseViewHolder = null;
        this.mContext = parent.getContext();
        this.mLayoutInflater = LayoutInflater.from(mContext);
        switch (viewType) {
            case LOADING_VIEW:
                baseViewHolder = getLoadingView(parent);
                break;
            case HEADER_VIEW:
                baseViewHolder = createBaseViewHolder(mHeaderLayout);
                break;
            case EMPTY_VIEW:
                baseViewHolder = createBaseViewHolder(mEmptyLayout);
                break;
            case FOOTER_VIEW:
                baseViewHolder = createBaseViewHolder(mFooterLayout);
                break;
            default:
                baseViewHolder = onCreateDefViewHolder(parent, viewType);
                bindViewClickListener(baseViewHolder);
        }
        baseViewHolder.setAdapter(this);
        return baseViewHolder;

    }
複製程式碼
  1. getLoadingView() : BRAVH給我們內建提供了簡單的載入更多佈局,這個方法內部也是使用了createBaseViewHolder(),那麼我們就來看看createBaseViewHolder()
  2. createBaseViewHolder : 這裡它主要是針對自定義ViewHolder所做的事情,我們一般使用BaseViewHolder就足夠了,所以就只需要關注最後一行,其實他就是new BaseViewHolder(view)而已(關於BaseViewHolder,前面我們已經大致分析了)
    protected K createBaseViewHolder(View view) {
        Class temp = getClass();
        Class z = null;
        while (z == null && null != temp) {
            z = getInstancedGenericKClass(temp);
            temp = temp.getSuperclass();
        }
        K k = createGenericKInstance(z, view);
        return null != k ? k : (K) new BaseViewHolder(view);
    }
複製程式碼
  1. onCreateDefViewHolder() : 當不是頭佈局、載入佈局、腳佈局、空佈局的情況下(也就是正常的資料佈局),就會呼叫這個方法。進去一看,相當簡單,赫然看到了MultiTypeDelegate.getLayoutId(viewType),也就是我們之前分析的根據條目型別獲取到具體的佈局資源,然後createBaseViewHolder()
protected K onCreateDefViewHolder(ViewGroup parent, int viewType) {
        int layoutId = mLayoutResId;
        if (mMultiTypeDelegate != null) {
            layoutId = mMultiTypeDelegate.getLayoutId(viewType);
        }
        return createBaseViewHolder(parent, layoutId);
    }
複製程式碼
  1. bindViewClickListener() : 最後就是對BaseViewHolder的點選事件的繫結而已,就不貼程式碼了。

onBindViewHolder

將資料繫結到佈局上,以及一些邏輯的控制就寫這啦

 @Override
    public void onBindViewHolder(K holder, int positions) {
        //Add up fetch logic, almost like load more, but simpler.
        autoUpFetch(positions);
        //Do not move position, need to change before LoadMoreView binding
        autoLoadMore(positions);
        int viewType = holder.getItemViewType();

        switch (viewType) {
            case 0:

                convert(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
                break;
            case LOADING_VIEW:
                mLoadMoreView.convert(holder);
                break;
            case HEADER_VIEW:
                break;
            case EMPTY_VIEW:
                break;
            case FOOTER_VIEW:
                break;
            default:
                convert(holder, mData.get(holder.getLayoutPosition() - getHeaderLayoutCount()));
                break;
        }
    }

複製程式碼
  1. autoUpFetch() : BRAVH提供給我們下拉載入,判斷是否啟用,是否達到了我們需要的載入位置,來呼叫我們實現的載入邏輯
private void autoUpFetch(int positions) {
        if (!isUpFetchEnable() || isUpFetching()) {
            return;
        }
        if (positions <= mStartUpFetchPosition && mUpFetchListener != null) {
            mUpFetchListener.onUpFetch();
        }
    }
複製程式碼
  1. autoLoadMore() : 上拉載入更多,根據我們的位置和載入狀態來判斷是否呼叫我們實現的載入更多的邏輯,就不細看了
  2. int viewType = holder.getItemViewType() : 上面我們分析過getItemViewType(),預設就是返回0,convert() 就是我們使用過程中具體實現的邏輯了。
    看一眼mLoadMoreView.convert(holder),發現裡面就是根據狀態來選擇佈局的顯示
  public void convert(BaseViewHolder holder) {
        switch (mLoadMoreStatus) {
            case STATUS_LOADING:
                visibleLoading(holder, true);
                visibleLoadFail(holder, false);
                visibleLoadEnd(holder, false);
                break;
            case STATUS_FAIL:
                visibleLoading(holder, false);
                visibleLoadFail(holder, true);
                visibleLoadEnd(holder, false);
                break;
            case STATUS_END:
                visibleLoading(holder, false);
                visibleLoadFail(holder, false);
                visibleLoadEnd(holder, true);
                break;
            case STATUS_DEFAULT:
                visibleLoading(holder, false);
                visibleLoadFail(holder, false);
                visibleLoadEnd(holder, false);
                break;
        }
    }
複製程式碼

onViewAttachedToWindow

當Item進入這個頁面的時候呼叫

Override
    public void onViewAttachedToWindow(K holder) {
        super.onViewAttachedToWindow(holder);
        int type = holder.getItemViewType();
        if (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW) {
            setFullSpan(holder);
        } else {
            addAnimation(holder);
        }
    }
複製程式碼
  • setFullSpan(holder) : 當條目型別是空佈局、頭佈局、腳佈局、載入佈局的時候會觸發,進來以後發現了,原來就是針對StaggeredGridLayout做獨佔一行的適配
   protected void setFullSpan(RecyclerView.ViewHolder holder) {
        if (holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) {
            StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder
                    .itemView.getLayoutParams();
            params.setFullSpan(true);
        }
    }

複製程式碼
  • addAnimation() : 顧名思義,就是條目動畫的新增。進來看到,做了如下判斷:是否開啟動畫、是否初次進入才啟動動畫、然後判斷是否使用自定義動畫,預設動畫mSelectAnimation是一個淡入動畫。然後進來到真正啟用動畫的地方了
  private void addAnimation(RecyclerView.ViewHolder holder) {
        if (mOpenAnimationEnable) {
            if (!mFirstOnlyEnable || holder.getLayoutPosition() > mLastPosition) {
                BaseAnimation animation = null;
                if (mCustomAnimation != null) {
                    animation = mCustomAnimation;
                } else {
                    animation = mSelectAnimation;
                }
                for (Animator anim : animation.getAnimators(holder.itemView)) {
                    startAnim(anim, holder.getLayoutPosition());
                }
                mLastPosition = holder.getLayoutPosition();
            }
        }
    }

複製程式碼

在說動畫啟動之前,我們需要先知道BaseAnimation這個介面,只有一個Animator[] getAnimators(View view)方法,然後我們可以借鑑一下BRAVH內建的AlphaInAnimation,發現只是new了一個動畫陣列。

public class AlphaInAnimation implements BaseAnimation {

    private static final float DEFAULT_ALPHA_FROM = 0f;
    private final float mFrom;

    public AlphaInAnimation() {
        this(DEFAULT_ALPHA_FROM);
    }

    public AlphaInAnimation(float from) {
        mFrom = from;
    }

    @Override
    public Animator[] getAnimators(View view) {
        return new Animator[]{ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f)};
    }
}
複製程式碼

回過頭來看,也就是遍歷啟動這個動畫集合裡面的每一個動畫,也就是我們可以給我們的列表組合各種各樣的入場動畫。

   for (Animator anim : animation.getAnimators(holder.itemView)) {
                    startAnim(anim, holder.getLayoutPosition());
                }

  protected void startAnim(Animator anim, int index) {
        anim.setDuration(mDuration).start();
        anim.setInterpolator(mInterpolator);
    }

複製程式碼

onViewDetachedFromWindow

當Item離開這個頁面的時候呼叫

基本流程結束

來到這裡證明我們的基本流程已經講解完了
相信帶著這套流程的理解,在具體功能展開窺探原始碼也會更加如魚得水

相關文章