发布时间:2025-12-09 12:04:43 浏览次数:1
作者简介
罗铁锤,六年安卓踩坑经验,致力于底层平台、上层应用等多领域开发。文能静坐弹吉他,武能通宵写代码
这是《从 Android 开发到读懂源码》系列文章最后一篇,感谢你的陪伴。
无论你是对 Android 感兴趣还是对系列文件有建议,都欢迎加入 Android 交流群(文末有进群方式)。
最后这一节内容,让我一起聊聊 Leanback。
BaseGridView 继承 RecyclerView ,重写所有焦点逻辑,Leanback 页面根布局容器
HorizontalGridView 继承 BaseGridView ,提供水平布局能力
VerticalGridView 继承 BaseGridView ,提供垂直布局能力
ArrayObjectAdapter 数据适配器,继承 ObjectAdapter ,内部可包含数据和视图结构内容信息
ArrayObjectAdapter 的两个构造方法:
// 一般用于每行或列数据的创建 /** * 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); }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,填充一个 PresenterArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(new PresenterSample());// 填充数据rowAdapter.add(...Data...);// 构造一个 ListRowListRow listRow = new ListRow(rowAdapter);普通方式构造
// 构造一个指定数据类型对象CustomData data = new CustomData();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[]{}); }} // 构建一个自定义的 PresenterSelectorSample PresenterSelectorSample presenterSelector = new PresenterSelectorSample(); // 构建一个装载每行数据的 ArrayObjectAdapter ArrayObjectAdapter verticalAdapter = new ArrayObjectAdapter(presenterSelector); // 填充数据 verticalAdapter.add(listRow); verticalAdapter.add(CustomData);该 Adapter 只是作为一个桥梁作用,将每一行结构对应的 presenter 和 data 进行关联
ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(verticalAdapter);VerticalGridView.setAdapter(bridgeAdapter);至此,页面展示效果如下:
首先看下 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 codePresenterSelectorSample 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 = 1 时,取出来的就是一个 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 视图部分原理,此时视图创建和数据绑定都已经完成了,界面上已经可以展示了。
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 中使用原生展示控件,比如 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 进行缓存,避免了频繁创建动画对象的开销,这种对性能敏感度的思想非常值得学习 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 缓存思想的确值得学习借鉴。
专栏《从 Android 开发到读懂源码》系列文章推荐
第01期:requestFocus 源码分析
第02期:NestScroll 机制源码解析
第03期:View.post 源码解析
第04期:LiveData 源码解析