Android Leanback结构源码简析
2019-11-29 本文已影响0人
seagazer
Leanback页面构建主要类
-
BaseGridView
继承RecyclerView
,重写所有焦点逻辑,Leanback页面根布局容器
-
HorizontalGridView
继承BaseGridView
,提供水平布局能力
-
VerticalGridView
继承BaseGridView
,提供垂直布局能力
-
ArrayObjectAdapter
数据适配器,继承ObjectAdapter
,内部可包含数据和视图结构内容信息
两个构造方法:
// 一般用于每行或列数据的创建
/**
* Constructs an adapter with the given {@link PresenterSelector}.
*/
public ArrayObjectAdapter(PresenterSelector presenterSelector) {
super(presenterSelector);
}
// 一般为ItemBridgeAdapter创建构造参数时使用
/**
* Constructs an adapter that uses the given {@link Presenter} for all items.
*/
public ArrayObjectAdapter(Presenter presenter) {
super(presenter);
}
-
ListRow
行视图提供者,Android原生封装好了,支持子视图焦点动效及行标题展示
-
Presenter
提供视图创建及数据绑定,类似RecyclerView.Adapter
的功能,注意是类似,下面的ItemBridgeAdapter
才是填充到BaseGridView中真正的Adapter
。目前暂且称之为视图加载器
/**
* Creates a new {@link View}.
*/
public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
/**
* Binds a {@link View} to an item.
*/
public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
-
PresenterSelector
根据不同的Item Object
类型提供不同的Presenter
对象,进行不同的布局视图创建和数据绑定,目前暂且称之为视图构造筛选器
/**
* Returns a presenter for the given item.
*/
public abstract Presenter getPresenter(Object item);
// 这个方法需要重写,根据item返回对应的presenter
/**
* Returns an array of all possible presenters. The returned array should
* not be modified.
*/
public Presenter[] getPresenters() {
return null;
}
// 这个方法需要返回所有的presenters数组,中途不可改变数据
-
ItemBridgeAdapter
填充至BaseGridView
的适配器,继承RecyclerView.Adapter
主要有两个构造方法,需要传递一个ObjectAdapter
public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
setAdapter(adapter);
mPresenterSelector = presenterSelector;
}
public ItemBridgeAdapter(ObjectAdapter adapter) {
this(adapter, null);
}
基本使用(以VerticalGridView
垂直视图为例)
- 自定义
Presenter
,每一行的视图提供者
public class PresenterSample extends Presenter {
// 创建BaseGridView中每一个Item的视图,如果使用ListRow则是创建每一行中的每一个Item视图
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
return new HolderSample(...ItemView...);
}
// 数据绑定,item即是外层ArrayObjectAdapter中包含的数据类型
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
if (viewHolder instanceof HolderSample) {
HolderSample holder = (HolderSample) viewHolder;
if (item instanceof YourDataType) {
bindDataWithViews()...
}
}
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
releaseSource()...
}
// 自定义Holder,处理视图点击,焦点事件等
static class HolderSample extends ViewHolder {
HolderSample(View view) {
super(view);
}
}
}
- 构造每一行的数据
-
ListRow
方式构造// 构造一个ArrayObjectAdapter,填充一个Presenter ArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(new PresenterSample()); // 填充数据 rowAdapter.add(...Data...); // 构造一个ListRow ListRow listRow = new ListRow(rowAdapter);
- 普通方式构造
// 构造一个指定数据类型对象 CustomData data = new CustomData();
- 构造一个
PresenterSelector
public class PresenterSelectorSample extends PresenterSelector {
// 此处item就是外层ArrayObjectAdapter添加进来的数据,按添加索引排序
@Override
public Presenter getPresenter(Object item) {
if (item.getClass == ListRow.class) {
return new ListRowPresenter();
} else if (item.getClass == CustomData.class) {
return new PresenterCustom();
} else {
return ...
}
}
@Override
public Presenter[] getPresenters() {
return mPresenters.toArray(new Presenter[]{});
}
}
- 构造一个
ArrayObjectAdapter
,装载垂直视图每一行的数据
// 构建一个自定义的PresenterSelectorSample
PresenterSelectorSample presenterSelector = new PresenterSelectorSample();
// 构建一个装载每行数据的ArrayObjectAdapter
ArrayObjectAdapter verticalAdapter = new ArrayObjectAdapter(presenterSelector);
// 填充数据
verticalAdapter.add(listRow);
verticalAdapter.add(CustomData);
- 构造一个
ItemBridgeAdapter
,填充给VerticalGridView
// 该Adapter只是作为一个桥梁作用,将每一行结构对应的presenter和data进行关联
ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(verticalAdapter);
VerticalGridView.setAdapter(bridgeAdapter);
至此,页面展示效果如下:
leanback1.png
源码分析
首先分析下非使用ListRow场景下视图的创建及数据绑定流程
- 首先看下
ArrayObjectAdapter
,它内部有个ArrayList<Object>
保存数据,同时其父类ObjectAdapter
中有个mPresenterSelector
变量保存当前adapter
对应的presenterSelector
。
<ArrayObjectAdapter.java>
public class ArrayObjectAdapter extends ObjectAdapter {
...
// 当ArrayObjectAdapter作为行/列的数据提供者时(ListRow),缓存每行/列的每个子Item的数据
// 当ArrayObjectAdapter作为ItemBridgeAdapter的构造参数时,缓存每行/列的数据对象
private final List mItems = new ArrayList<Object>();
public ArrayObjectAdapter(PresenterSelector presenterSelector) {
super(presenterSelector);
}
public ArrayObjectAdapter(Presenter presenter) {
super(presenter);
}
// 包含数据添加,删除,修改,查询方法
public void add(Object item) {
add(mItems.size(), item);
}
remove(...)
move(...)
replace(...)
get(...)
...
}
<ObjectAdapter.java>
// 当ArrayObjectAdapter作为行/列的数据提供者时,缓存每行/列的视图数据提供者
private PresenterSelector mPresenterSelector;// 缓存presenter
// 提供get方法获取当前的presenter
public final Presenter getPresenter(Object item) {
if (mPresenterSelector == null) {
throw new IllegalStateException("Presenter selector must not be null");
}
return mPresenterSelector.getPresenter(item);
}
- 主适配器
ItemBridgeAdapter
,看android命名应该是一个桥接的适配器,这也是整个模块中核心类之一
<ItemBridgeAdapter.java>
// 缓存了构造传进来的ArrayObjectAdapter
private ObjectAdapter mAdapter;
// 缓存了PresenterSelector选择器,根据不同ViewType获取不同的Presenter进行不同的视图加载
private PresenterSelector mPresenterSelector;
// 焦点动效辅助类
FocusHighlightHandler mFocusHighlight;
// 缓存了根据PresenterSelector创建出来的各个不同的Presenter视图加载器
private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
- 接着就按照正常使用
RecyclerView
的流程去分析ItemBridgeAdapter
,首先是getItemViewType()
方法。
<ItemBridgeAdapter.java>
@Override
public int getItemViewType(int position) {
// mPresenterSelector可以直接调用setter主动赋值,如果没有赋值过,则会通过构造方法中的ArrayObjectAdapter.getPresenterSelector进行获取视图构造筛选器
PresenterSelector presenterSelector = mPresenterSelector != null
? mPresenterSelector : mAdapter.getPresenterSelector();
// 这个Object就是构造传进来的ArrayObjectAdapter中的数据
Object item = mAdapter.get(position);
// 根据Object对象获取对应的presenter,这里是在自定义的PresenterSelector中进行分支判断处理
Presenter presenter = presenterSelector.getPresenter(item);
// 根据索引判断缓存中该Object是否有presenter对象
int type = mPresenters.indexOf(presenter);
if (type < 0) {
// 不存在,将presenter加入缓存
mPresenters.add(presenter);
// 将索引值赋值给viewType
type = mPresenters.indexOf(presenter);
if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
// 回调通知外部当前添加了presenter
onAddPresenter(presenter, type);
if (mAdapterListener != null) {
mAdapterListener.onAddPresenter(presenter, type);
}
}
return type;
}
- 在这里我们先暂停,去看下PresenterSelector中创建Presenter对象部分
<PresenterSelector.java>
// PresenterSelector是一个抽象类,需要我们自己实现以下两个方法
public abstract class PresenterSelector {
public abstract Presenter getPresenter(Object item);
public Presenter[] getPresenters() {
return null;
}
}
// 这里以我们自己定义的一个Sample为例
<PresenterSelectorSample.java>
public class PresenterSelectorSample extends PresenterSelector {
private List<Presenter> mPresenters = new ArrayList<>();
private Map<Class<?>, Presenter> mPresenterCache = new HashMap<>();
public void addPresenter(Class<?> cls, Presenter presenter) {
mPresenterCache.put(cls, presenter);
if (!mPresenters.contains(presenter)) {
mPresenters.add(presenter);
}
}
@Override
public Presenter getPresenter(Object item) {
// 我们会调用addPresenter方法进行setter操作,此处通过map进行缓存
// 注意:实际中还要进行class的重复冲突处理,例如有多个ListRow,但是每个ListRow中的Presenter视图展示效果不一样
Class<?> cls = item.getClass();
// 然后通过class进行getter取操作
Presenter presenter = mPresenterCache.get(cls);
if (presenter != null) {
return presenter;
} else {
return null;
}
}
@Override
public Presenter[] getPresenters() {
// 返回所有的Presenters
return mPresenters.toArray(new Presenter[]{});
}
}
// sample code
PresenterSelectorSample presenterSelector = new PresenterSelectorSample();
presenterSelector.addPresenter(ListRow.class, new ListRowPresenter());
presenterSelector.addPresenter(CustomDataObject.class, new CustomPresenter());
ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
adapter.add(new ListRow);
adapter.add(new CustomDataObject());
ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(adapter);
// 1.这样ItemBridgeAdapter.getItemViewType中mAdapter.getPresenterSelector()取出来的就是我们构建的PresenterSelectorSample。
// 2.然后mAdapter.get(position)就是我们上面adapter添加进去的数据。例如position=0时,取出来的就是一个ListRow对象。
// 3.接着调用我们PresenterSelectorSample中的getPresenter(item)方法,会根据ListRow.class返回一个ListRowPresenter。同时缓存到ItemBridgeAdapter的mPresenters变量中。并且将ViewType用presenter在缓存池中的索引与之对应起来,方便后面onCreateViewHolder中的获取。
-
此时,我们就可以理解了Presenter,PresenterSelector,ArrayObjectAdapter,ItemBridgeAdapter之间的关系。
-
回到
ItemBridgeAdapter
,分析其onCreateViewHolder
方法
<ItemBridgeAdapter.java>
/**
* {@link View.OnFocusChangeListener} that assigned in
* {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
* {@link View.OnFocusChangeListener} after that.
*/
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 首先取出presenter,前面也说了viewType已经通过缓存池中的索引关联了presenter
Presenter presenter = mPresenters.get(viewType);
// 我们熟悉的ViewHolder,注意,这个是我们presenter中自定义的viewHolder
Presenter.ViewHolder presenterVh;
// viewHolder中的view
View view;
if (mWrapper != null) {
view = mWrapper.createWrapper(parent);
presenterVh = presenter.onCreateViewHolder(parent);
mWrapper.wrap(view, presenterVh.view);
} else {// 一般走这里
// 这里会去调用我们自定义Presenter中的onCreateViewHolder进行holder和view的创建
presenterVh = presenter.onCreateViewHolder(parent);
// 将试图赋值给viewHolder中的view
view = presenterVh.view;
}
// 将我们presenter的viewHolder,presenter,view一起打包进行封装
ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
// 回调通知
onCreate(viewHolder);
if (mAdapterListener != null) {
mAdapterListener.onCreate(viewHolder);
}
// 这个view就是我们presenter中的创建的视图
View presenterView = viewHolder.mHolder.view;
if (presenterView != null) {
// 为我们presenter中的view设置focus监听,焦点变化时如果设置了FocusHighlight则会自动执行动效
viewHolder.mFocusChangeListener.mChainedListener =
presenterView.getOnFocusChangeListener();
presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
}
if (mFocusHighlight != null) {
// 如果设置了FocusHighlight,在此焦点动效初始化
mFocusHighlight.onInitializeView(view);
}
// 返回创建好的viewHolder(里面包含presenter,holder,view信息)
return viewHolder;
}
- 接下去就是
ItemBridgeAdapter
的onCreateViewHolder
方法
<ItemBridgeAdapter.java>
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
// 这个holder包含了presenter,该presenter中对应的holder,view
ViewHolder viewHolder = (ViewHolder) holder;
// mItem是一个Object对象,也就是上面getItemViewType所说的ArrayObjectAdapter中的数据,例如sample中的CustomDataObject和ListRow
viewHolder.mItem = mAdapter.get(position);
// 调用对应presenter中的onBindViewHolder方法
viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
// 回调通知
onBind(viewHolder);
if (mAdapterListener != null) {
mAdapterListener.onBind(viewHolder);
}
}
- 抛开
RecyclerView
视图部分原理,此时视图创建和数据绑定都已经完成了,界面上已经可以展示了。
接下来分析下Leanback中常用的ListRow
的源码
-
ListRow
继承Row
是android
封装好的行数据展示的一种抽象(并不是实际View
的展示,leanback
系统中view
的创建都是在presenter
层,对应ListRowPresenter
),其结构如下:
<ListRow.java>
public class ListRow extends Row {
// 这个adapter包含了该行中所有子Item的数据
private final ObjectAdapter mAdapter;
// 行标题文字,对应父类Row中的HeaderItem
private CharSequence mContentDescription;
...
}
<Row.java>
public class Row {
// 行标题的结构体
private HeaderItem mHeaderItem;
...
}
<HeaderItem.java>
// 也是一种抽象,对应视图也是在RowHeaderPresenter中创建
public class HeaderItem {
private final long mId;
private final String mName;
private CharSequence mDescription;
private CharSequence mContentDescription;
...
}
-
接下来看下视图部分
ListRowPresenter
,其继承RowPresenter
,而RowPresenter
继承Presenter
,Presenter
在上面已经介绍过了,主要有这几个抽象方法:onCreateViewHolder
,onBindViewHolder
,onUnbindViewHolder
-
首先看下
ListRowPresenter
内部的几个内部类:
<RowPresenter.java>
public abstract class RowPresenter extends Presenter {
...
// RowPresenter这里不作重点分析,主要是对ViewHolder的抽象封装
// 1.ContainerViewHolder,它内部持有一个ViewHolder
// 2.ViewHolder
}
<ListRowPresenter.java>
public class ListRowPresenter extends RowPresenter {
// 行视图的ViewHolder
public static class ViewHolder extends RowPresenter.ViewHolder {
// 持有presenter
final ListRowPresenter mListRowPresenter;
// 这个其实就是展示水平列表视图的HorizontalGridView
final HorizontalGridView mGridView;
// 可以类比上面垂直视图案例中的ItemBridgeAdapter,作为桥梁关联mGridView中每个item视图创建者presenter
ItemBridgeAdapter mItemBridgeAdapter;
// 暂不分析
final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
// layout参数
final int mPaddingTop;
...
public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
super(rootView);
mGridView = gridView;
mListRowPresenter = p;
mPaddingTop = mGridView.getPaddingTop();
...
}
// 下面就是一些getter属性方法
...
}
// 选中的Position变化回调监听
/**
* A task on the ListRowPresenter.ViewHolder that can select an item by position in the
* HorizontalGridView and perform an optional item task on it.
*/
public static class SelectItemViewHolderTask extends Presenter.ViewHolderTask {
private int mItemPosition;// 选中的position
private boolean mSmoothScroll = true;
Presenter.ViewHolderTask mItemTask;// 缓存task
/**
* Sets task to run when the item is selected, null for no task.
* @param itemTask Optional task to run when the item is selected, null for no task.
*/
public void setItemTask(Presenter.ViewHolderTask itemTask) {
// 设置task,等待时机执行run方法,如果没设置,run方法无效。本质上只是给外部提供一个监听选中position变化的回调
mItemTask = itemTask;
}
// 主要是提供一些设置选中position,是否平滑滚动之类的方法
...
// 关键是run方法
@Override
public void run(Presenter.ViewHolder holder) {
if (holder instanceof ListRowPresenter.ViewHolder) {
// 获取到列表视图HorizontalGridView
HorizontalGridView gridView = ((ListRowPresenter.ViewHolder) holder).getGridView();
androidx.leanback.widget.ViewHolderTask task = null;
// 如果之前设置过task,则新创建
if (mItemTask != null) {
task = new androidx.leanback.widget.ViewHolderTask() {
final Presenter.ViewHolderTask itemTask = mItemTask;
@Override
public void run(RecyclerView.ViewHolder rvh) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) rvh;
itemTask.run(ibvh.getViewHolder());
}
};
}
// 设置选中的position,并将task和postition传递给BaseGridView,里面会执行task的run方法,再执行我们外部setter进来的Presenter.ViewHolderTask的run方法
if (isSmoothScroll()) {
gridView.setSelectedPositionSmooth(mItemPosition, task);
} else {
gridView.setSelectedPosition(mItemPosition, task);
}
}
}
}
// 对交互事件的处理
class ListRowPresenterItemBridgeAdapter extends ItemBridgeAdapter {
ListRowPresenter.ViewHolder mRowViewHolder;
ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) {
mRowViewHolder = rowViewHolder;
}
@Override
protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
if (viewHolder.itemView instanceof ViewGroup) {
TransitionHelper.setTransitionGroup((ViewGroup) viewHolder.itemView, true);
}
if (mShadowOverlayHelper != null) {
mShadowOverlayHelper.onViewCreated(viewHolder.itemView);
}
}
@Override
public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) {
// 监听点击事件
// Only when having an OnItemClickListener, we will attach the OnClickListener.
if (mRowViewHolder.getOnItemViewClickedListener() != null) {
viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
mRowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView);
if (mRowViewHolder.getOnItemViewClickedListener() != null) {
mRowViewHolder.getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
ibh.mItem, mRowViewHolder, (ListRow) mRowViewHolder.mRow);
}
}
});
}
}
@Override
public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) {
// 移除点击事件
if (mRowViewHolder.getOnItemViewClickedListener() != null) {
viewHolder.mHolder.view.setOnClickListener(null);
}
}
@Override
public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
applySelectLevelToChild(mRowViewHolder, viewHolder.itemView);
// 设置是否持久化状态显示
mRowViewHolder.syncActivatedStatus(viewHolder.itemView);
}
@Override
public void onAddPresenter(Presenter presenter, int type) {
// 默认缓存池大小为24个,针对每种不同presenter可以设置不同的缓存空间
mRowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(
type, getRecycledPoolSize(presenter));
}
}
}
- 接下来就是真正的
ListRowPresenter
<ListRowPresenter.java>
public class ListRowPresenter extends RowPresenter {
// 行数,这个行数不是ListRow的行数,ListRow抽象上的单行,这个行数是指其内部HorizontalGridView的行数
private int mNumRows = 1;
// 行高度
private int mRowHeight;
// 展开行高度
private int mExpandedRowHeight;
private PresenterSelector mHoverCardPresenterSelector;
// 缩放比例:FocusHighlight中定义的常量
private int mFocusZoomFactor;
// 是否使用聚焦高亮那种效果
private boolean mUseFocusDimmer;
// 是否支持阴影
private boolean mShadowEnabled = true;
private int mBrowseRowsFadingEdgeLength = -1;
private boolean mRoundedCornersEnabled = true;
private boolean mKeepChildForeground = true;
// 缓存池
private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>();
ShadowOverlayHelper mShadowOverlayHelper;
// 主要功能就是通过一层FrameLayout(ShadowOverlayContainer)包裹当前View实现阴影,高亮昏暗,圆角效果,api21以上才能使用
private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper;
private static int sSelectedRowTopPadding;
private static int sExpandedSelectedRowTopPadding;
private static int sExpandedRowNoHovercardBottomPadding;
// 3个构造方法,主要是设置聚焦缩放等级和阴影效果
public ListRowPresenter() {
this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
}
public ListRowPresenter(int focusZoomFactor) {
this(focusZoomFactor, false);
}
public ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer) {
if (!FocusHighlightHelper.isValidZoomIndex(focusZoomFactor)) {
throw new IllegalArgumentException("Unhandled zoom factor");
}
mFocusZoomFactor = focusZoomFactor;
mUseFocusDimmer = useFocusDimmer;
}
<RowPresenter.java>
// 接下来直接看onCreateViewHolder方法,在父类RowPresenter中
@Override
public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
// 创建临时ViewHolder,这个holder只包含列表视图HorizontalGridView,不包含头部视图
ViewHolder vh = createRowViewHolder(parent);
vh.mInitialzed = false;// 标记未初始化过
// 最终真正的ViewHolder
Presenter.ViewHolder result;
// 如果设置了Header标题视图,需要在外部添加布局和头部视图
if (needsRowContainerView()) {
RowContainerView containerView = new RowContainerView(parent.getContext());
if (mHeaderPresenter != null) {
vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
}
result = new ContainerViewHolder(containerView, vh);
} else {// 没有设置头部标题
result = vh;
}
// 初始化holder
initializeRowViewHolder(vh);
if (!vh.mInitialzed) {
throw new RuntimeException("super.initializeRowViewHolder() must be called");
}
return result;
}
<ListRowPresenter.java>
// 创建ViewHolder
@Override
protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
initStatics(parent.getContext());
// ListRowView其实就是一个布局封装,里面包含一个HorizontalGridView
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 initializeRowViewHolder(RowPresenter.ViewHolder holder) {
// 父类RowPresenter中只是设置clipChildren属性为false,因为设置true的话会影响缩放动效
super.initializeRowViewHolder(holder);
// 获取holder
final ViewHolder rowViewHolder = (ViewHolder) holder;
// ItemView的context
Context context = holder.view.getContext();
// 阴影效果相关,暂不分析,内部就是通过一层FrameLayout包裹当前的View实现阴影等效果
if (mShadowOverlayHelper == null) {
mShadowOverlayHelper = new ShadowOverlayHelper.Builder()
.needsOverlay(needsDefaultListSelectEffect())
.needsShadow(needsDefaultShadow())
.needsRoundedCorner(isUsingOutlineClipping(context)
&& areChildRoundedCornersEnabled())
.preferZOrder(isUsingZOrder(context))
.keepForegroundDrawable(mKeepChildForeground)
.options(createShadowOverlayOptions())
.build(context);
if (mShadowOverlayHelper.needsWrapper()) {
mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper(
mShadowOverlayHelper);
}
}
// 构造桥接ItemBridgeAdapter
rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder);
// set wrapper if needed
rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper);
mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView);
// ListRow默认会给设置Item的焦点缩放动效,下面动效部分单独分析
FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter,
mFocusZoomFactor, mUseFocusDimmer);
rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType()
!= ShadowOverlayHelper.SHADOW_DYNAMIC);
// 通过BaseGridView监听焦点选中回调
rowViewHolder.mGridView.setOnChildSelectedListener(
new OnChildSelectedListener() {
@Override
public void onChildSelected(ViewGroup parent, View view, int position, long id) {
selectChildView(rowViewHolder, view, true);
}
});
// 通过BaseGridView监听按键事件
rowViewHolder.mGridView.setOnUnhandledKeyListener(
new BaseGridView.OnUnhandledKeyListener() {
@Override
public boolean onUnhandledKey(KeyEvent event) {
return rowViewHolder.getOnKeyListener() != null
&& rowViewHolder.getOnKeyListener().onKey(
rowViewHolder.view, event.getKeyCode(), event);
}
});
// 设置HorizontalGridView的行数
rowViewHolder.mGridView.setNumRows(mNumRows);
}
<RowPresenter.java>
// 父类RowPresenter的初始化holder方法
protected void initializeRowViewHolder(ViewHolder vh) {
vh.mInitialzed = true;// 标记已经初始化过
if (!isClippingChildren()) {
// set clip children to false for slide transition
if (vh.view instanceof ViewGroup) {
((ViewGroup) vh.view).setClipChildren(false);
}
if (vh.mContainerViewHolder != null) {
((ViewGroup) vh.mContainerViewHolder.view).setClipChildren(false);
}
}
}
<ListRowPresenter.java>
// onBindRowViewHolder方法
@Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
super.onBindRowViewHolder(holder, item);
// 获取holder
ViewHolder vh = (ViewHolder) holder;
// 获取到ListRow
ListRow rowItem = (ListRow) item;
// ListRow中的ObjectAdapter,设置到桥接的ItemBridgeAdapter中
vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
// 设置HorizontalGridView的adapter,而ItemBridgeAdapter的createRowViewHolder会调用我们ListRow中ObjectAdapter的自定义Presenter创建每一个子Item的视图,onBindRowViewHolder会将数据绑定
vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
// 设置row描述信息
vh.mGridView.setContentDescription(rowItem.getContentDescription());
}
}
-
至此,
ListRow
的视图创建和数据绑定已经分析完了,其实内部子Item
的视图创建和数据绑定是沿用ItemBridgeAdapter
方式。 -
在
Leanback
中的横竖列表展现形式都是通过这种Presenter
与BaseGridView
之间的嵌套关系进行剥离。例如在多ViewType
的形式下,一般我们写RecyclerView.Adapter
是这样的:
public class CutstomAdapter extends RecyclerView.Adapter<VH> {
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == Type1) {
return VH1;// 不同行为对象
} else if (viewType == Type2) {
return VH2;// 不同行为对象
} else ...
...// 不同行为对象...
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
if (holder instance VH1) {
bind1();
} else if (holder instance VH2) {
bind2();
} else ...
...
}
class VH1 extends Vh {}
class VH2 extends Vh {}
...
}
- 在
Leanback
结构中对应的是ItemBridgeAdapter
,其仅充当一个桥接作用,结构中增加定义了一个Presenter
抽象,将onCreateViewHolder
和onBindViewHolder
等行为抽离出去,让每个有不同样式的CustomPresenter
自身去实现具体视图和数据行为,这样当需要增加新的样式和数据时,只需要往桥接类中添加对应的Presenter
实现即可(往ArrayObjectAdapter
中添加)。
Leanback中焦点动效分析
-
对于
Leanback
中使用原生展示控件,比如ListRow
这种,其默认是会实现焦点缩放动效。上面分析ListRowPresenter
时可以看到,其内部默认帮我们调用了FocusHighlightHelper.setupBrowseItemFocusHighlight()
方法,在Item
发生焦点变化时,焦点的监听回调中会通过Helper
的方法实现缩放效果。 -
首先看下
FocusHighlightHelper
这个类
<FocusHighlightHelper.java>
public class FocusHighlightHelper {
// 是否可缩放
static boolean isValidZoomIndex(int zoomIndex) {
return zoomIndex == ZOOM_FACTOR_NONE || getResId(zoomIndex) > 0;
}
// 获取缩放比例
static int getResId(int zoomIndex) {
switch (zoomIndex) {
case ZOOM_FACTOR_SMALL:
return R.fraction.lb_focus_zoom_factor_small;
case ZOOM_FACTOR_XSMALL:
return R.fraction.lb_focus_zoom_factor_xsmall;
...
// 具体值在res的value文件中定义缩放比例
<item name="lb_focus_zoom_factor_large" type="fraction">118%</item>
<item name="lb_focus_zoom_factor_medium" type="fraction">114%</item>
<item name="lb_focus_zoom_factor_small" type="fraction">110%</item>
<item name="lb_focus_zoom_factor_xsmall" type="fraction">106%</item>
}
// 绑定焦点动效
public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex,
boolean useDimmer) {
// 这里我们只关注BrowseItemFocusHighlight,HeaderItemFocusHighlight类似
adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex, useDimmer));
}
static class BrowseItemFocusHighlight implements FocusHighlightHandler {
// 时长
private static final int DURATION_MS = 150;
// 缩放等级
private int mScaleIndex;
// 是否使用阴影
private final boolean mUseDimmer;
BrowseItemFocusHighlight(int zoomIndex, boolean useDimmer) {
if (!isValidZoomIndex(zoomIndex)) {
throw new IllegalArgumentException("Unhandled zoom index");
}
mScaleIndex = zoomIndex;
mUseDimmer = useDimmer;
}
...
// 焦点变化监听回调
@Override
public void onItemFocused(View view, boolean hasFocus) {
view.setSelected(hasFocus);
// 第一个参数是否聚焦,第二个参数表示是否跳过动画执行过程直接展示结果
getOrCreateAnimator(view).animateFocus(hasFocus, false);
}
// 初始化,如果绑定了动画,ItemBridgeAdapter的onCreateViewHolder中会调用
@Override
public void onInitializeView(View view) {
getOrCreateAnimator(view).animateFocus(false, true);
}
// 创建或者获取动画对象
private FocusAnimator getOrCreateAnimator(View view) {
// 此处通过view的tag进行缓存,避免了频繁创建动画对象的开销,google的开发工程师这种对性能敏感度的思想非常值得学习
FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
if (animator == null) {
animator = new FocusAnimator(
view, getScale(view.getResources()), mUseDimmer, DURATION_MS);
view.setTag(R.id.lb_focus_animator, animator);
}
return animator;
}
}
// 动画对象
static class FocusAnimator implements TimeAnimator.TimeListener {
private final View mView;
private final int mDuration;
// 支持阴影的FrameLayout,SDK_INT >= 21以上才支持,此处不详细分析了
private final ShadowOverlayContainer mWrapper;
private final float mScaleDiff;
private float mFocusLevel = 0f;
private float mFocusLevelStart;
private float mFocusLevelDelta;
private final TimeAnimator mAnimator = new TimeAnimator();
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
// 颜色遮罩,就是那种聚焦高亮,非聚焦昏暗的效果层,内部通过canvas的drawRect方式实现,此处不详细分析了
private final ColorOverlayDimmer mDimmer;
void animateFocus(boolean select, boolean immediate) {
// 先结束上一次动画
endAnimation();
final float end = select ? 1 : 0;
if (immediate) {
// 不需要过程,直接setScale设置最终效果,结束
setFocusLevel(end);
} else if (mFocusLevel != end) {
// 需要动画过程,开始执行动画
mFocusLevelStart = mFocusLevel;
mFocusLevelDelta = end - mFocusLevelStart;
mAnimator.start();
}
}
FocusAnimator(View view, float scale, boolean useDimmer, int duration) {
// 动画执行的view
mView = view;
// 动画时长
mDuration = duration;
// 动画缩放的比例差值
mScaleDiff = scale - 1f;
// 阴影和高亮效果
if (view instanceof ShadowOverlayContainer) {
mWrapper = (ShadowOverlayContainer) view;
} else {
mWrapper = null;
}
mAnimator.setTimeListener(this);
if (useDimmer) {
mDimmer = ColorOverlayDimmer.createDefault(view.getContext());
} else {
mDimmer = null;
}
}
// 改变当前动画值
void setFocusLevel(float level) {
mFocusLevel = level;
float scale = 1f + mScaleDiff * level;
// 缩放
mView.setScaleX(scale);
mView.setScaleY(scale);
// 阴影和高亮效果
if (mWrapper != null) {
mWrapper.setShadowFocusLevel(level);
} else {
ShadowOverlayHelper.setNoneWrapperShadowFocusLevel(mView, level);
}
if (mDimmer != null) {
// 改变高亮或者昏暗的透明度值
mDimmer.setActiveLevel(level);
int color = mDimmer.getPaint().getColor();
if (mWrapper != null) {
// 设置阴影
mWrapper.setOverlayColor(color);
} else {
// 取消阴影
ShadowOverlayHelper.setNoneWrapperOverlayColor(mView, color);
}
}
}
...
void endAnimation() {
mAnimator.end();
}
// 估值器
@Override
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
float fraction;
if (totalTime >= mDuration) {
// 动画结束
fraction = 1;
mAnimator.end();
} else {
// 计算当前动画执行进度
fraction = (float) (totalTime / (double) mDuration);
}
if (mInterpolator != null) {
// 有插值器的情况下计算的动画执行进度
fraction = mInterpolator.getInterpolation(fraction);
}
// 改变当前动画的值
setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
}
}
}
- 下面我们看下是如何监听
Item
的焦点变化的
<ItemBridgeAdapter.java>
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
...
if (presenterView != null) {
// 设置焦点变化监听,这个Listener是每个ViewHolder中对应的,监听的是ViewHolder的ItemView
viewHolder.mFocusChangeListener.mChainedListener =
presenterView.getOnFocusChangeListener();
presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
}
if (mFocusHighlight != null) {
// 这里会创建动画对象,并且缓存到view的tag中
mFocusHighlight.onInitializeView(view);
}
return viewHolder;
}
// 焦点监听回调
final class OnFocusChangeListener implements View.OnFocusChangeListener {
// 这个内部的listener实在没搞懂干嘛用的,可能是为以后扩展准备的吧
View.OnFocusChangeListener mChainedListener;
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (DEBUG) {
Log.v(TAG, "onFocusChange " + hasFocus + " " + view
+ " mFocusHighlight" + mFocusHighlight);
}
if (mWrapper != null) {
view = (View) view.getParent();
}
if (mFocusHighlight != null) {
// 看到了,这里就会执行BrowseItemFocusHighlight的onItemFocused方法
mFocusHighlight.onItemFocused(view, hasFocus);
}
if (mChainedListener != null) {
mChainedListener.onFocusChange(view, hasFocus);
}
}
}
- 至此,
Leanback
中焦点缩放动效也分析完了,里面其实就是监听焦点变化,执行相应的scale
动画而已。不过里面为了节省频繁创建动画对象的性能开销,通过View.Tag
缓存思想的确值得学习借鉴。 - 结构部分已经分析完了,后面抽时间重点分析下BaseGridView对焦点及按键事件的处理。