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è)面拆解:
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