从 Android 开发到读懂源码 第05期

发布时间:2025-12-09 12:04:43 浏览次数:1

作者简介

罗铁锤,六年安卓踩坑经验,致力于底层平台、上层应用等多领域开发。文能静坐弹吉他,武能通宵写代码

这是《从 Android 开发到读懂源码》系列文章最后一篇,感谢你的陪伴。

无论你是对 Android 感兴趣还是对系列文件有建议,都欢迎加入 Android 交流群(文末有进群方式)。

最后这一节内容,让我一起聊聊 Leanback。

1 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);    }

2 基本使用(以 VerticalGridView 垂直视图为例)

2.1 自定义 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);        }    }}

2.2 构造每一行的数据

ListRow 方式构造

// 构造一个 ArrayObjectAdapter,填充一个 PresenterArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(new PresenterSample());// 填充数据rowAdapter.add(...Data...);// 构造一个 ListRowListRow listRow = new ListRow(rowAdapter);

普通方式构造

// 构造一个指定数据类型对象CustomData data = new CustomData();

2.3 构造一个 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[]{});    }}

2.4 构造一个 ArrayObjectAdapter,装载垂直视图每一行的数据

   // 构建一个自定义的 PresenterSelectorSample    PresenterSelectorSample presenterSelector = new PresenterSelectorSample();    // 构建一个装载每行数据的 ArrayObjectAdapter    ArrayObjectAdapter verticalAdapter = new ArrayObjectAdapter(presenterSelector);    // 填充数据    verticalAdapter.add(listRow);    verticalAdapter.add(CustomData);

2.5 构造一个 ItemBridgeAdapter ,填充给 VerticalGridView

该 Adapter 只是作为一个桥梁作用,将每一行结构对应的 presenter 和 data 进行关联

ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(verticalAdapter);VerticalGridView.setAdapter(bridgeAdapter);

至此,页面展示效果如下:

3 源码分析

3.1 非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 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 视图部分原理,此时视图创建和数据绑定都已经完成了,界面上已经可以展示了。

3.2 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 中添加)。

4 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 进行缓存,避免了频繁创建动画对象的开销,这种对性能敏感度的思想非常值得学习            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 源码解析

leanback
需要做网站?需要网络推广?欢迎咨询客户经理 13272073477