Leanback库是Google开源的一个高效开发的支持库,它包含了一套完整的电视应用开发Api资源和组件。
Leanback库是基于Model --> Presenter --> View 的样式设计的(MVP)
Google官方DEMO: https://github.com/android/tv-samples
标准页面
以VerticalGridFragment页面为例:
页面拆解:
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时根据position获取对应的Presenter的type,在调用CreatViewHolder时通过type创建对应的ViewHolder,可以理解为getItemViewType实现了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页面有如下定义:
//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先通过postion查找对应的type,然后确定mAttachScrapCache、mChangeScrapCache、mCacheItem(和postion相关)、mRecyclerPool(和type相关)中是否有可复用的ViewHolder,减少重复创建带来的性能影响。
以上分析可知:要了解当前页面View的Bind、Recycler整体流程,需要重点分析getItemViewType是如何实现position到ViewHolder映射的
如何写一个基本页面:
- Fragment
- Presenter
- PresenterSelecter
- ItemBridgeAdapter
复杂页面
以复杂的十字交错页面为例:
拆解如下:
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
}
)
)
阶段一
左侧导航栏
右侧内容页
阶段二、三
左侧导航栏
根据标准页面的分析结论,同理可以分析getItemViewType中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;
}
而HeadersSupportFragment的PresenterSelector声明如下
setHeaderPresenterSelector(new PresenterSelector() {
@Override
public Presenter getPresenter(Object o) {
return new IconHeaderItemPresenter();
}
});
因此HeadersSupportFragment页面的Bind、Recycler的流程就在IconHeaderItemPresenter内实现:
//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
}
右侧内容页
同理,分析getItemViewType的position到ViewHolder的映射关系
//MainFragment.java
mCategoryRowAdapter = new ArrayObjectAdapter(new ListRowPresenter());
setAdapter(mCategoryRowAdapter);
因此右侧内容栏页面的Bind、Recycler的流程在ListRowPresenter内实现
//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创建了一个ListRowView。HorizontalGridView和VerticalGridView实现方式一致,仅在LayoutManager上有区别
public final class ListRowView extends LinearLayout {
private HorizontalGridView mGridView;
...
}
onBindRowViewHolder设置mGridView的Adapter, onUnbindRowViewHolder时释放。
再看页面源数据部分:
//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。
此后的流程和标准页面分析的流程一样。因为在onBindRowViewHolder阶段,设置ListRowView中HorizontalGridView的Adapter为CursorObjectAdapter ,对应的Presenter为CardPresenter,因此getItemViewType中position到ViewHolder的映射时,HorizontalGridView的每个子View的Bind、Recycler的流程在CardPresenter内实现
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);
}
以上分析可知:一个Row布局的Bind、Recycler流程,是先对RowPresnter的处理,然后对子Presnter的处理。其中都是通过getItemViewType实现position到ViewHolder映射的。
如何写一个包含Row结构页面:
- Fragment
- Presenter
- PresenterSelecter
- ItemBridgeAdapter
- RowPresenter
- ArrayObjectAdapter