LeanBack框架介紹

Leanback庫(kù)是Google開(kāi)源的一個(gè)高效開(kāi)發(fā)的支持庫(kù),它包含了一套完整的電視應(yīng)用開(kāi)發(fā)Api資源和組件。

Leanback庫(kù)是基于Model --> Presenter --> View 的樣式設(shè)計(jì)的(MVP)

Google官方DEMO: https://github.com/android/tv-samples

標(biāo)準(zhǔn)頁(yè)面

以VerticalGridFragment頁(yè)面為例:

頁(yè)面拆解:

image
VerticalGridFragment -->(
  VerticalGridPresenter -->(
    VerticalGridView -->(
      ItemBridgeAdapter{
        CursorObjectAdapter(
          CardPresenter --> ImageCardView,
          Object
        )
      }
    )
  )
)

界面背后的迷宮

階段一

setAdapter流程:

階段二

View Bind流程:

//ItemBridgeAdapter.java
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
    Presenter presenter = mPresenters.get(viewType);
    Presenter.ViewHolder presenterVh;
    View view;
    if (mWrapper != null) {
        view = mWrapper.createWrapper(parent);
        presenterVh = presenter.onCreateViewHolder(parent);
        mWrapper.wrap(view, presenterVh.view);
    } else {
        presenterVh = presenter.onCreateViewHolder(parent);
        view = presenterVh.view;
    }
    ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
    onCreate(viewHolder);
    if (mAdapterListener != null) {
        mAdapterListener.onCreate(viewHolder);
    }
    View presenterView = viewHolder.mHolder.view;
    if (presenterView != null) {
        viewHolder.mFocusChangeListener.mChainedListener =
                presenterView.getOnFocusChangeListener();
        presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
    }
    if (mFocusHighlight != null) {
        mFocusHighlight.onInitializeView(view);
    }
    return viewHolder;
}
//ItemBridgeAdapter.java
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
    ViewHolder viewHolder = (ViewHolder) holder;
    viewHolder.mItem = mAdapter.get(position);

    viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);

    onBind(viewHolder);
    if (mAdapterListener != null) {
        mAdapterListener.onBind(viewHolder);
    }
}

在tryGetViewHolderForPositionByDeadline時(shí)根據(jù)position獲取對(duì)應(yīng)的Presenter的type,在調(diào)用CreatViewHolder時(shí)通過(guò)type創(chuàng)建對(duì)應(yīng)的ViewHolder,可以理解為getItemViewType實(shí)現(xiàn)了position到ViewHolder的映射

//ItemBridgeAdapter.java
@Override
public int getItemViewType(int position) {
    PresenterSelector presenterSelector = mPresenterSelector != null
            ? mPresenterSelector : mAdapter.getPresenterSelector();
    Object item = mAdapter.get(position);
    Presenter presenter = presenterSelector.getPresenter(item);
    int type = mPresenters.indexOf(presenter);
    if (type < 0) {
        mPresenters.add(presenter);
        type = mPresenters.indexOf(presenter);
        if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
        onAddPresenter(presenter, type);
        if (mAdapterListener != null) {
            mAdapterListener.onAddPresenter(presenter, type);
        }
    }
    return type;
}

在VerticalGridFragment頁(yè)面有如下定義:

//VerticalGridFragment.java
private final CursorObjectAdapter mVideoCursorAdapter =
        new CursorObjectAdapter(new CardPresenter());
    //CardPresenter.java
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        ...
        ImageCardView cardView = new ImageCardView(parent.getContext()) {
            @Override
            public void setSelected(boolean selected) {
                updateCardBackgroundColor(this, selected);
                super.setSelected(selected);
            }
        };

        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        updateCardBackgroundColor(cardView, false);
        return new ViewHolder(cardView);
    }

    //CardPresenter.java
    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        Video video = (Video) item;

        ImageCardView cardView = (ImageCardView) viewHolder.view;
        cardView.setTitleText(video.title);
        cardView.setContentText(video.studio);

        if (video.cardImageUrl != null) {
            // Set card size from dimension resources.
            Resources res = cardView.getResources();
            int width = res.getDimensionPixelSize(R.dimen.card_width);
            int height = res.getDimensionPixelSize(R.dimen.card_height);
            cardView.setMainImageDimensions(width, height);

            Glide.with(cardView.getContext())
                    .load(video.cardImageUrl)
                    .apply(RequestOptions.errorOf(mDefaultCardImage))
                    .into(cardView.getMainImageView());
        }
    }

階段三

View Recycle流程:

 //CardPresenter.java
    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
        ImageCardView cardView = (ImageCardView) viewHolder.view;
        // Remove references to images so that the garbage collector can free up memory.
        cardView.setBadgeImage(null);
        cardView.setMainImage(null);
    }

在View Recycling策略下,tryGetViewHolderForPositionByDeadline先通過(guò)postion查找對(duì)應(yīng)的type,然后確定mAttachScrapCache、mChangeScrapCache、mCacheItem(和postion相關(guān))、mRecyclerPool(和type相關(guān))中是否有可復(fù)用的ViewHolder,減少重復(fù)創(chuàng)建帶來(lái)的性能影響。

以上分析可知:要了解當(dāng)前頁(yè)面View的Bind、Recycler整體流程,需要重點(diǎn)分析getItemViewType是如何實(shí)現(xiàn)position到ViewHolder映射的

如何寫(xiě)一個(gè)基本頁(yè)面:

  • Fragment
  • Presenter
  • PresenterSelecter
  • ItemBridgeAdapter

復(fù)雜頁(yè)面

以復(fù)雜的十字交錯(cuò)頁(yè)面為例:

拆解如下:

ItemBridgeAdapter {
  PresenterSelector{
    Presenter
  }
  ObjectAdapter{
    Object
  }
}
Presenter -- ViewHolder -- ObjectAdapter
HeadersSupportFragment -->(
  VerticalGridView -->(
    ItemBridgeAdapter{
       IconHeaderItemPresenter --> HeaderView,
       ArrayObjectAdapter(
        ListRow(HeaderItem + CursorObjectAdapter)
       )
    }
  )
)
RowsSupportFragment --> ( 
  VerticalGridView --> (
    ItemBridgeAdapter {
      ListRowPresenter --> ListRowView,
      ListRowDataAdapter(
        ArrayObjectAdapter (
          ListRow (HeaderItem + CursorObjectAdapter(CardPresenter))
       )
     ) 
   }
 )
)
ListRowView -->(
  HorizontalGridView -->(
    ItemBridgeAdapter {
       CardPresenter --> ImageCardView,
       CursorObjectAdapter
    }
  )
)

階段一

左側(cè)導(dǎo)航欄

右側(cè)內(nèi)容頁(yè)

階段二、三

左側(cè)導(dǎo)航欄

根據(jù)標(biāo)準(zhǔn)頁(yè)面的分析結(jié)論,同理可以分析getItemViewType中position到ViewHolder的映射關(guān)系:

//ItemBridgeAdapter.java
@Override
public int getItemViewType(int position) {
    PresenterSelector presenterSelector = mPresenterSelector != null
            ? mPresenterSelector : mAdapter.getPresenterSelector();
    Object item = mAdapter.get(position);
    Presenter presenter = presenterSelector.getPresenter(item);
    int type = mPresenters.indexOf(presenter);
    if (type < 0) {
        mPresenters.add(presenter);
        type = mPresenters.indexOf(presenter);
        if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
        onAddPresenter(presenter, type);
        if (mAdapterListener != null) {
            mAdapterListener.onAddPresenter(presenter, type);
        }
    }
    return type;
}

而HeadersSupportFragment的PresenterSelector聲明如下

setHeaderPresenterSelector(new PresenterSelector() {
    @Override
    public Presenter getPresenter(Object o) {
        return new IconHeaderItemPresenter();
    }
});

因此HeadersSupportFragment頁(yè)面的Bind、Recycler的流程就在IconHeaderItemPresenter內(nèi)實(shí)現(xiàn):

//IconHeaderItemPresenter.java
public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
    mUnselectedAlpha = viewGroup.getResources()
            .getFraction(R.fraction.lb_browse_header_unselect_alpha, 1, 1);
    LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    View view = inflater.inflate(R.layout.icon_header_item, null);
    view.setAlpha(mUnselectedAlpha); // Initialize icons to be at half-opacity.

    return new ViewHolder(view);
}

@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
    HeaderItem headerItem = ((ListRow) item).getHeaderItem();
    View rootView = viewHolder.view;
    rootView.setFocusable(true);

    ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
    Drawable icon = rootView.getResources().getDrawable(R.drawable.android_header, null);
    iconView.setImageDrawable(icon);

    TextView label = (TextView) rootView.findViewById(R.id.header_label);
    label.setText(headerItem.getName());
}

@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
    // no op
}

右側(cè)內(nèi)容頁(yè)

同理,分析getItemViewType的position到ViewHolder的映射關(guān)系

//MainFragment.java
mCategoryRowAdapter = new ArrayObjectAdapter(new ListRowPresenter());
setAdapter(mCategoryRowAdapter);

因此右側(cè)內(nèi)容欄頁(yè)面的Bind、Recycler的流程在ListRowPresenter內(nèi)實(shí)現(xiàn)

//ListRowPresenter.java
@Override
protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
    initStatics(parent.getContext());
    ListRowView rowView = new ListRowView(parent.getContext());
    setupFadingEffect(rowView);
    if (mRowHeight != 0) {
        rowView.getGridView().setRowHeight(mRowHeight);
    }
    return new ViewHolder(rowView, rowView.getGridView(), this);
}

@Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
    super.onBindRowViewHolder(holder, item);
    ViewHolder vh = (ViewHolder) holder;
    ListRow rowItem = (ListRow) item;
    vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
    vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
    vh.mGridView.setContentDescription(rowItem.getContentDescription());
}

@Override
protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
    ViewHolder vh = (ViewHolder) holder;
    vh.mGridView.setAdapter(null);
    vh.mItemBridgeAdapter.clear();
    super.onUnbindRowViewHolder(holder);
}

createRowViewHolder創(chuàng)建了一個(gè)ListRowView。HorizontalGridView和VerticalGridView實(shí)現(xiàn)方式一致,僅在LayoutManager上有區(qū)別

public final class ListRowView extends LinearLayout {

    private HorizontalGridView mGridView;
    ...
}

onBindRowViewHolder設(shè)置mGridView的Adapter, onUnbindRowViewHolder時(shí)釋放。

再看頁(yè)面源數(shù)據(jù)部分:

//MainFragment.java
CursorObjectAdapter videoCursorAdapter =
        new CursorObjectAdapter(new CardPresenter());
videoCursorAdapter.setMapper(new VideoCursorMapper());
mVideoCursorAdapters.put(videoLoaderId, videoCursorAdapter);

ListRow row = new ListRow(header, videoCursorAdapter);
mCategoryRowAdapter.add(row);

rowItem.getAdapter()返回的是videoCursorAdapter。

此后的流程和標(biāo)準(zhǔn)頁(yè)面分析的流程一樣。因?yàn)樵趏nBindRowViewHolder階段,設(shè)置ListRowView中HorizontalGridView的Adapter為CursorObjectAdapter ,對(duì)應(yīng)的Presenter為CardPresenter,因此getItemViewType中position到ViewHolder的映射時(shí),HorizontalGridView的每個(gè)子View的Bind、Recycler的流程在CardPresenter內(nèi)實(shí)現(xiàn)

public class CardPresenter extends Presenter {
    ...
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        mDefaultBackgroundColor =
            ContextCompat.getColor(parent.getContext(), R.color.default_background);
        mSelectedBackgroundColor =
                ContextCompat.getColor(parent.getContext(), R.color.selected_background);
        mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie, null);

        ImageCardView cardView = new ImageCardView(parent.getContext()) {
            @Override
            public void setSelected(boolean selected) {
                updateCardBackgroundColor(this, selected);
                super.setSelected(selected);
            }
        };

        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        updateCardBackgroundColor(cardView, false);
        return new ViewHolder(cardView);
    }

@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
    Video video = (Video) item;

    ImageCardView cardView = (ImageCardView) viewHolder.view;
    cardView.setTitleText(video.title);
    cardView.setContentText(video.studio);

    if (video.cardImageUrl != null) {
        // Set card size from dimension resources.
        Resources res = cardView.getResources();
        int width = res.getDimensionPixelSize(R.dimen.card_width);
        int height = res.getDimensionPixelSize(R.dimen.card_height);
        cardView.setMainImageDimensions(width, height);

        Glide.with(cardView.getContext())
                .load(video.cardImageUrl)
                .apply(RequestOptions.errorOf(mDefaultCardImage))
                .into(cardView.getMainImageView());
    }
}

@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
    ImageCardView cardView = (ImageCardView) viewHolder.view;

    // Remove references to images so that the garbage collector can free up memory.
    cardView.setBadgeImage(null);
    cardView.setMainImage(null);
}

以上分析可知:一個(gè)Row布局的Bind、Recycler流程,是先對(duì)RowPresnter的處理,然后對(duì)子Presnter的處理。其中都是通過(guò)getItemViewType實(shí)現(xiàn)position到ViewHolder映射的。

如何寫(xiě)一個(gè)包含Row結(jié)構(gòu)頁(yè)面:

  • Fragment
  • Presenter
  • PresenterSelecter
  • ItemBridgeAdapter
  • RowPresenter
  • ArrayObjectAdapter
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、RecyclerView的四級(jí)緩存 (1)mChangedScrap、mAttachedScrap: 用于屏幕...
    zzq_nene閱讀 2,285評(píng)論 1 2
  • 【Android 控件 RecyclerView】 概述 RecyclerView是什么 從Android 5.0...
    Rtia閱讀 308,476評(píng)論 27 440
  • 上一篇文章分析RecyclerView刷新機(jī)制知道LayoutManager在布局子View時(shí)會(huì)向Recycler...
    susion噠噠閱讀 17,507評(píng)論 13 45
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,895評(píng)論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn),但是人生放棄了冒險(xiǎn),也就放棄了無(wú)數(shù)的可能。 ...
    yichen大刀閱讀 8,166評(píng)論 0 4

友情鏈接更多精彩內(nèi)容