Leanback结构源码简析

1,851 阅读17分钟

本文主要针对Android TV开发或者对于Android TV感兴趣的同学,话不多说,直接开始正文:

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垂直视图为例)

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

  1. 构造每一行的数据
  • ListRow方式构造
    // 构造一个ArrayObjectAdapter,填充一个Presenter
    ArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(new PresenterSample());
    // 填充数据
    rowAdapter.add(...Data...);
    // 构造一个ListRow
    ListRow listRow = new ListRow(rowAdapter);
    
  • 普通方式构造
    // 构造一个指定数据类型对象
    CustomData data = new CustomData();
    
  1. 构造一个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[]{});
    }

}
  1. 构造一个ArrayObjectAdapter,装载垂直视图每一行的数据
    // 构建一个自定义的PresenterSelectorSample
    PresenterSelectorSample presenterSelector = new PresenterSelectorSample();
    // 构建一个装载每行数据的ArrayObjectAdapter
    ArrayObjectAdapter verticalAdapter = new ArrayObjectAdapter(presenterSelector);
    // 填充数据
    verticalAdapter.add(listRow);
    verticalAdapter.add(CustomData);
  1. 构造一个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=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;
    }
  • 接下去就是ItemBridgeAdapteronCreateViewHolder方法
<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继承Rowandroid封装好的行数据展示的一种抽象(并不是实际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继承PresenterPresenter在上面已经介绍过了,主要有这几个抽象方法:onCreateViewHolderonBindViewHolderonUnbindViewHolder

  • 首先看下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中的横竖列表展现形式都是通过这种PresenterBaseGridView之间的嵌套关系进行剥离。例如在多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抽象,将onCreateViewHolderonBindViewHolder等行为抽离出去,让每个有不同样式的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缓存思想的确值得学习借鉴。
  • 源码类的分析基本上都是以代码为主,会比较枯燥,最后分享下个人总结的阅读源码的一些技巧:
  1. 首先不能咬文爵字的去磕源码,否则会难以自拔,找不到东南西北了。
  2. 要带着问题去看,例如本文,RecyclerView大家都非常熟悉,Leanback中对其进行了进一步的封装,比如涉及到一些绘制顺序,焦点如何保持居中,我们可以先思考如果面对这些问题我们会如何构建方案去解决,然后再去看源码。
  3. 可以参考外界资料,推荐官方的,可以先阅读别人的文章一到两遍,然后自己去跟着流程看一遍。重点关注流程和设计思想,只需要记住一些关键函数即可,学习源码最重要的是学习其中的思想。例如上面动画构建通过Tag方式进行缓存的性能优化方式,这些思想都可以泛化用在其他场景的开发上。