Android Leanback結(jié)構(gòu)源碼簡析

Leanback頁面構(gòu)建主要類

  • BaseGridView 繼承RecyclerView岳遥,重寫所有焦點邏輯习蓬,Leanback頁面根布局容器

  • HorizontalGridView 繼承BaseGridView,提供水平布局能力

  • VerticalGridView 繼承BaseGridView,提供垂直布局能力

  • ArrayObjectAdapter 數(shù)據(jù)適配器,繼承ObjectAdapter,內(nèi)部可包含數(shù)據(jù)和視圖結(jié)構(gòu)內(nèi)容信息
兩個構(gòu)造方法:

    // 一般用于每行或列數(shù)據(jù)的創(chuàng)建
    /**
     * Constructs an adapter with the given {@link PresenterSelector}.
     */
    public ArrayObjectAdapter(PresenterSelector presenterSelector) {
        super(presenterSelector);
    }

    // 一般為ItemBridgeAdapter創(chuàng)建構(gòu)造參數(shù)時使用
    /**
     * Constructs an adapter that uses the given {@link Presenter} for all items.
     */
    public ArrayObjectAdapter(Presenter presenter) {
        super(presenter);
    }

  • ListRow行視圖提供者涵妥,Android原生封裝好了,支持子視圖焦點動效及行標題展示

  • Presenter 提供視圖創(chuàng)建及數(shù)據(jù)綁定坡锡,類似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 根據(jù)不同的Item Object類型提供不同的Presenter對象拳缠,進行不同的布局視圖創(chuàng)建和數(shù)據(jù)綁定,目前暫且稱之為視圖構(gòu)造篩選器
    /**
     * Returns a presenter for the given item.
     */
    public abstract Presenter getPresenter(Object item);
    // 這個方法需要重寫贸弥,根據(jù)item返回對應的presenter

    /**
     * Returns an array of all possible presenters.  The returned array should
     * not be modified.
     */
    public Presenter[] getPresenters() {
        return null;
    }
    // 這個方法需要返回所有的presenters數(shù)組窟坐,中途不可改變數(shù)據(jù)

  • ItemBridgeAdapter 填充至BaseGridView的適配器,繼承RecyclerView.Adapter
主要有兩個構(gòu)造方法绵疲,需要傳遞一個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 {
    // 創(chuàng)建BaseGridView中每一個Item的視圖,如果使用ListRow則是創(chuàng)建每一行中的每一個Item視圖
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        return new HolderSample(...ItemView...);
    }

    // 數(shù)據(jù)綁定盔憨,item即是外層ArrayObjectAdapter中包含的數(shù)據(jù)類型
    @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. 構(gòu)造每一行的數(shù)據(jù)
  • ListRow方式構(gòu)造
    // 構(gòu)造一個ArrayObjectAdapter郁岩,填充一個Presenter
    ArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(new PresenterSample());
    // 填充數(shù)據(jù)
    rowAdapter.add(...Data...);
    // 構(gòu)造一個ListRow
    ListRow listRow = new ListRow(rowAdapter);
    
  • 普通方式構(gòu)造
    // 構(gòu)造一個指定數(shù)據(jù)類型對象
    CustomData data = new CustomData();
    
  1. 構(gòu)造一個PresenterSelector
public class PresenterSelectorSample extends PresenterSelector {

    // 此處item就是外層ArrayObjectAdapter添加進來的數(shù)據(jù)婿奔,按添加索引排序
    @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. 構(gòu)造一個ArrayObjectAdapter缺狠,裝載垂直視圖每一行的數(shù)據(jù)
    // 構(gòu)建一個自定義的PresenterSelectorSample
    PresenterSelectorSample presenterSelector = new PresenterSelectorSample();
    // 構(gòu)建一個裝載每行數(shù)據(jù)的ArrayObjectAdapter
    ArrayObjectAdapter verticalAdapter = new ArrayObjectAdapter(presenterSelector);
    // 填充數(shù)據(jù)
    verticalAdapter.add(listRow);
    verticalAdapter.add(CustomData);
  1. 構(gòu)造一個ItemBridgeAdapter,填充給VerticalGridView
// 該Adapter只是作為一個橋梁作用萍摊,將每一行結(jié)構(gòu)對應的presenter和data進行關(guān)聯(lián)
ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(verticalAdapter);
VerticalGridView.setAdapter(bridgeAdapter);

至此挤茄,頁面展示效果如下:


leanback1.png

源碼分析

首先分析下非使用ListRow場景下視圖的創(chuàng)建及數(shù)據(jù)綁定流程

  • 首先看下ArrayObjectAdapter,它內(nèi)部有個ArrayList<Object>保存數(shù)據(jù)冰木,同時其父類ObjectAdapter中有個mPresenterSelector變量保存當前adapter對應的presenterSelector穷劈。
<ArrayObjectAdapter.java>
    public class ArrayObjectAdapter extends ObjectAdapter {
        ...
        // 當ArrayObjectAdapter作為行/列的數(shù)據(jù)提供者時(ListRow),緩存每行/列的每個子Item的數(shù)據(jù)
        // 當ArrayObjectAdapter作為ItemBridgeAdapter的構(gòu)造參數(shù)時踊沸,緩存每行/列的數(shù)據(jù)對象
        private final List mItems = new ArrayList<Object>();

        public ArrayObjectAdapter(PresenterSelector presenterSelector) {
            super(presenterSelector);
        }

        public ArrayObjectAdapter(Presenter presenter) {
            super(presenter);
        }

        // 包含數(shù)據(jù)添加歇终,刪除,修改逼龟,查詢方法
        public void add(Object item) {
            add(mItems.size(), item);
        }
        remove(...)
        move(...)
        replace(...)
        get(...)
        ...
    }    

<ObjectAdapter.java>
    // 當ArrayObjectAdapter作為行/列的數(shù)據(jù)提供者時评凝,緩存每行/列的視圖數(shù)據(jù)提供者
    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>

    // 緩存了構(gòu)造傳進來的ArrayObjectAdapter
    private ObjectAdapter mAdapter;
    // 緩存了PresenterSelector選擇器肥哎,根據(jù)不同ViewType獲取不同的Presenter進行不同的視圖加載
    private PresenterSelector mPresenterSelector;
    // 焦點動效輔助類
    FocusHighlightHandler mFocusHighlight;
    // 緩存了根據(jù)PresenterSelector創(chuàng)建出來的各個不同的Presenter視圖加載器
    private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
  • 接著就按照正常使用RecyclerView的流程去分析ItemBridgeAdapter,首先是getItemViewType()方法疾渣。
<ItemBridgeAdapter.java>
    @Override
    public int getItemViewType(int position) {
        // mPresenterSelector可以直接調(diào)用setter主動賦值,如果沒有賦值過崖飘,則會通過構(gòu)造方法中的ArrayObjectAdapter.getPresenterSelector進行獲取視圖構(gòu)造篩選器
        PresenterSelector presenterSelector = mPresenterSelector != null
                ? mPresenterSelector : mAdapter.getPresenterSelector();
        // 這個Object就是構(gòu)造傳進來的ArrayObjectAdapter中的數(shù)據(jù)
        Object item = mAdapter.get(position);
        // 根據(jù)Object對象獲取對應的presenter榴捡,這里是在自定義的PresenterSelector中進行分支判斷處理
        Presenter presenter = presenterSelector.getPresenter(item);
        // 根據(jù)索引判斷緩存中該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);
            // 回調(diào)通知外部當前添加了presenter
            onAddPresenter(presenter, type);
            if (mAdapterListener != null) {
                mAdapterListener.onAddPresenter(presenter, type);
            }
        }
        return type;
    }
  • 在這里我們先暫停朱浴,去看下PresenterSelector中創(chuàng)建Presenter對象部分
<PresenterSelector.java>
    // PresenterSelector是一個抽象類吊圾,需要我們自己實現(xiàn)以下兩個方法
    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) {
        // 我們會調(diào)用addPresenter方法進行setter操作,此處通過map進行緩存
        // 注意:實際中還要進行class的重復沖突處理翰蠢,例如有多個ListRow项乒,但是每個ListRow中的Presenter視圖展示效果不一樣
        Class<?> cls = item.getClass();
        // 然后通過class進行g(shù)etter取操作
        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()取出來的就是我們構(gòu)建的PresenterSelectorSample。
// 2.然后mAdapter.get(position)就是我們上面adapter添加進去的數(shù)據(jù)梁沧。例如position=0時檀何,取出來的就是一個ListRow對象。
// 3.接著調(diào)用我們PresenterSelectorSample中的getPresenter(item)方法廷支,會根據(jù)ListRow.class返回一個ListRowPresenter频鉴。同時緩存到ItemBridgeAdapter的mPresenters變量中。并且將ViewType用presenter在緩存池中的索引與之對應起來恋拍,方便后面onCreateViewHolder中的獲取垛孔。
  • 此時,我們就可以理解了Presenter施敢,PresenterSelector周荐,ArrayObjectAdapter狭莱,ItemBridgeAdapter之間的關(guān)系。

  • 回到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已經(jīng)通過緩存池中的索引關(guān)聯(lián)了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 {// 一般走這里
            // 這里會去調(diào)用我們自定義Presenter中的onCreateViewHolder進行holder和view的創(chuàng)建
            presenterVh = presenter.onCreateViewHolder(parent);
            // 將試圖賦值給viewHolder中的view
            view = presenterVh.view;
        }
        // 將我們presenter的viewHolder辉阶,presenter,view一起打包進行封裝
        ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
        // 回調(diào)通知
        onCreate(viewHolder);
        if (mAdapterListener != null) {
            mAdapterListener.onCreate(viewHolder);
        }
        // 這個view就是我們presenter中的創(chuàng)建的視圖
        View presenterView = viewHolder.mHolder.view;
        if (presenterView != null) {
            // 為我們presenter中的view設置focus監(jiān)聽瘩扼,焦點變化時如果設置了FocusHighlight則會自動執(zhí)行動效
            viewHolder.mFocusChangeListener.mChainedListener =
                    presenterView.getOnFocusChangeListener();
            presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
        }
        if (mFocusHighlight != null) {
            // 如果設置了FocusHighlight谆甜,在此焦點動效初始化
            mFocusHighlight.onInitializeView(view);
        }
        // 返回創(chuàng)建好的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中的數(shù)據(jù)罕袋,例如sample中的CustomDataObject和ListRow
        viewHolder.mItem = mAdapter.get(position);
        // 調(diào)用對應presenter中的onBindViewHolder方法
        viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
        // 回調(diào)通知
        onBind(viewHolder);
        if (mAdapterListener != null) {
            mAdapterListener.onBind(viewHolder);
        }
    }
  • 拋開RecyclerView視圖部分原理,此時視圖創(chuàng)建和數(shù)據(jù)綁定都已經(jīng)完成了碍岔,界面上已經(jīng)可以展示了浴讯。

接下來分析下Leanback中常用的ListRow的源碼

  • ListRow繼承Rowandroid封裝好的行數(shù)據(jù)展示的一種抽象(并不是實際View的展示,leanback系統(tǒng)中view的創(chuàng)建都是在presenter層蔼啦,對應ListRowPresenter)榆纽,其結(jié)構(gòu)如下:
<ListRow.java>
public class ListRow extends Row {
    // 這個adapter包含了該行中所有子Item的數(shù)據(jù)
    private final ObjectAdapter mAdapter;
    // 行標題文字,對應父類Row中的HeaderItem
    private CharSequence mContentDescription;
    ...
}

<Row.java>
public class Row {
    // 行標題的結(jié)構(gòu)體
    private HeaderItem mHeaderItem;
    ...
}

<HeaderItem.java>
// 也是一種抽象捏肢,對應視圖也是在RowHeaderPresenter中創(chuàng)建
public class HeaderItem {
    private final long mId;
    private final String mName;
    private CharSequence mDescription;
    private CharSequence mContentDescription;
    ...
}
  • 接下來看下視圖部分ListRowPresenter奈籽,其繼承RowPresenter,而RowPresenter繼承Presenter鸵赫,Presenter在上面已經(jīng)介紹過了衣屏,主要有這幾個抽象方法:onCreateViewHolderonBindViewHolder辩棒,onUnbindViewHolder

  • 首先看下ListRowPresenter內(nèi)部的幾個內(nèi)部類:

<RowPresenter.java>
public abstract class RowPresenter extends Presenter {
    ...
    // RowPresenter這里不作重點分析狼忱,主要是對ViewHolder的抽象封裝
    // 1.ContainerViewHolder,它內(nèi)部持有一個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一睁,作為橋梁關(guān)聯(lián)mGridView中每個item視圖創(chuàng)建者presenter
        ItemBridgeAdapter mItemBridgeAdapter;
        // 暫不分析
        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
        // layout參數(shù)
        final int mPaddingTop;
        ...

        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
            super(rootView);
            mGridView = gridView;
            mListRowPresenter = p;
            mPaddingTop = mGridView.getPaddingTop();
            ...
        }
        // 下面就是一些getter屬性方法
        ...
    }

    // 選中的Position變化回調(diào)監(jiān)聽
    /**
     * 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藕赞,等待時機執(zhí)行run方法,如果沒設置卖局,run方法無效斧蜕。本質(zhì)上只是給外部提供一個監(jiān)聽選中position變化的回調(diào)
            mItemTask = itemTask;
        }

        // 主要是提供一些設置選中position,是否平滑滾動之類的方法
        ...

        // 關(guān)鍵是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砚偶,則新創(chuàng)建
                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洒闸,里面會執(zhí)行task的run方法,再執(zhí)行我們外部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) {
            // 監(jiān)聽點擊事件
            // 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);
            // 設置是否持久化狀態(tài)顯示
            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 {
    // 行數(shù)丘逸,這個行數(shù)不是ListRow的行數(shù),ListRow抽象上的單行掀宋,這個行數(shù)是指其內(nèi)部HorizontalGridView的行數(shù)
    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實現(xiàn)陰影深纲,高亮昏暗,圓角效果劲妙,api21以上才能使用
    private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper;

    private static int sSelectedRowTopPadding;
    private static int sExpandedSelectedRowTopPadding;
    private static int sExpandedRowNoHovercardBottomPadding;

    // 3個構(gòu)造方法湃鹊,主要是設置聚焦縮放等級和陰影效果
    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) {
        // 創(chuàng)建臨時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>
    // 創(chuàng)建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();
        // 陰影效果相關(guān),暫不分析哈垢,內(nèi)部就是通過一層FrameLayout包裹當前的View實現(xiàn)陰影等效果
        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);
            }
        }
        // 構(gòu)造橋接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監(jiān)聽焦點選中回調(diào)
        rowViewHolder.mGridView.setOnChildSelectedListener(
                new OnChildSelectedListener() {
            @Override
            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
                selectChildView(rowViewHolder, view, true);
            }
        });
        // 通過BaseGridView監(jiān)聽按鍵事件
        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的行數(shù)
        rowViewHolder.mGridView.setNumRows(mNumRows);
    }

<RowPresenter.java>
    // 父類RowPresenter的初始化holder方法
    protected void initializeRowViewHolder(ViewHolder vh) {
        vh.mInitialzed = true;// 標記已經(jīng)初始化過
        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會調(diào)用我們ListRow中ObjectAdapter的自定義Presenter創(chuàng)建每一個子Item的視圖举塔,onBindRowViewHolder會將數(shù)據(jù)綁定
        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
        // 設置row描述信息
        vh.mGridView.setContentDescription(rowItem.getContentDescription());
    }

}
  • 至此,ListRow的視圖創(chuàng)建和數(shù)據(jù)綁定已經(jīng)分析完了陶贼,其實內(nèi)部子Item的視圖創(chuàng)建和數(shù)據(jù)綁定是沿用ItemBridgeAdapter方式。

  • Leanback中的橫豎列表展現(xiàn)形式都是通過這種PresenterBaseGridView之間的嵌套關(guān)系進行剝離待秃。例如在多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結(jié)構(gòu)中對應的是ItemBridgeAdapter,其僅充當一個橋接作用章郁,結(jié)構(gòu)中增加定義了一個Presenter抽象枉氮,將onCreateViewHolderonBindViewHolder等行為抽離出去,讓每個有不同樣式的CustomPresenter自身去實現(xiàn)具體視圖和數(shù)據(jù)行為暖庄,這樣當需要增加新的樣式和數(shù)據(jù)時聊替,只需要往橋接類中添加對應的Presenter實現(xiàn)即可(往ArrayObjectAdapter中添加)。

Leanback中焦點動效分析

  • 對于Leanback中使用原生展示控件培廓,比如ListRow這種惹悄,其默認是會實現(xiàn)焦點縮放動效。上面分析ListRowPresenter時可以看到肩钠,其內(nèi)部默認幫我們調(diào)用了FocusHighlightHelper.setupBrowseItemFocusHighlight()方法泣港,在Item發(fā)生焦點變化時暂殖,焦點的監(jiān)聽回調(diào)中會通過Helper的方法實現(xiàn)縮放效果。

  • 首先看下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) {
        // 這里我們只關(guān)注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;
        }
        ...

        // 焦點變化監(jiān)聽回調(diào)
        @Override
        public void onItemFocused(View view, boolean hasFocus) {
            view.setSelected(hasFocus);
            // 第一個參數(shù)是否聚焦呛每,第二個參數(shù)表示是否跳過動畫執(zhí)行過程直接展示結(jié)果
            getOrCreateAnimator(view).animateFocus(hasFocus, false);
        }

        // 初始化,如果綁定了動畫坡氯,ItemBridgeAdapter的onCreateViewHolder中會調(diào)用
        @Override
        public void onInitializeView(View view) {
            getOrCreateAnimator(view).animateFocus(false, true);
        }

        // 創(chuàng)建或者獲取動畫對象
        private FocusAnimator getOrCreateAnimator(View view) {
            // 此處通過view的tag進行緩存晨横,避免了頻繁創(chuàng)建動畫對象的開銷,google的開發(fā)工程師這種對性能敏感度的思想非常值得學習
            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();
        // 顏色遮罩,就是那種聚焦高亮滞时,非聚焦昏暗的效果層叁幢,內(nèi)部通過canvas的drawRect方式實現(xiàn),此處不詳細分析了
        private final ColorOverlayDimmer mDimmer;

        void animateFocus(boolean select, boolean immediate) {
            // 先結(jié)束上一次動畫
            endAnimation();
            final float end = select ? 1 : 0;
            if (immediate) {
                // 不需要過程坪稽,直接setScale設置最終效果曼玩,結(jié)束
                setFocusLevel(end);
            } else if (mFocusLevel != end) {
                // 需要動畫過程,開始執(zhí)行動畫
                mFocusLevelStart = mFocusLevel;
                mFocusLevelDelta = end - mFocusLevelStart;
                mAnimator.start();
            }
        }

        FocusAnimator(View view, float scale, boolean useDimmer, int duration) {
            // 動畫執(zhí)行的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) {
                // 動畫結(jié)束
                fraction = 1;
                mAnimator.end();
            } else {
                // 計算當前動畫執(zhí)行進度
                fraction = (float) (totalTime / (double) mDuration);
            }
            if (mInterpolator != null) {
                // 有插值器的情況下計算的動畫執(zhí)行進度
                fraction = mInterpolator.getInterpolation(fraction);
            }
            // 改變當前動畫的值
            setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
        }
    }

}
  • 下面我們看下是如何監(jiān)聽Item的焦點變化的
<ItemBridgeAdapter.java>
    @Override
    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ...
        if (presenterView != null) {
            // 設置焦點變化監(jiān)聽窒百,這個Listener是每個ViewHolder中對應的黍判,監(jiān)聽的是ViewHolder的ItemView
            viewHolder.mFocusChangeListener.mChainedListener =
                    presenterView.getOnFocusChangeListener();
            presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
        }
        if (mFocusHighlight != null) {
            // 這里會創(chuàng)建動畫對象,并且緩存到view的tag中
            mFocusHighlight.onInitializeView(view);
        }
        return viewHolder;
    }

    // 焦點監(jiān)聽回調(diào)
    final class OnFocusChangeListener implements View.OnFocusChangeListener {
        // 這個內(nèi)部的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) {
                // 看到了顷帖,這里就會執(zhí)行BrowseItemFocusHighlight的onItemFocused方法
                mFocusHighlight.onItemFocused(view, hasFocus);
            }
            if (mChainedListener != null) {
                mChainedListener.onFocusChange(view, hasFocus);
            }
        }
    }
  • 至此,Leanback中焦點縮放動效也分析完了渤滞,里面其實就是監(jiān)聽焦點變化贬墩,執(zhí)行相應的scale動畫而已。不過里面為了節(jié)省頻繁創(chuàng)建動畫對象的性能開銷妄呕,通過View.Tag緩存思想的確值得學習借鑒陶舞。
  • 結(jié)構(gòu)部分已經(jīng)分析完了,后面抽時間重點分析下BaseGridView對焦點及按鍵事件的處理绪励。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肿孵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子疏魏,更是在濱河造成了極大的恐慌停做,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件大莫,死亡現(xiàn)場離奇詭異蛉腌,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門眉抬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贯吓,“玉大人,你說我怎么就攤上這事蜀变∏男常” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵库北,是天一觀的道長爬舰。 經(jīng)常有香客問我,道長寒瓦,這世上最難降的妖魔是什么情屹? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮杂腰,結(jié)果婚禮上垃你,老公的妹妹穿的比我還像新娘。我一直安慰自己喂很,他們只是感情好惜颇,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著少辣,像睡著了一般凌摄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漓帅,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天锨亏,我揣著相機與錄音,去河邊找鬼忙干。 笑死器予,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的捐迫。 我是一名探鬼主播乾翔,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼弓乙!你這毒婦竟也來了末融?” 一聲冷哼從身側(cè)響起钧惧,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤暇韧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后浓瞪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體懈玻,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年乾颁,在試婚紗的時候發(fā)現(xiàn)自己被綠了涂乌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艺栈。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖湾盒,靈堂內(nèi)的尸體忽然破棺而出湿右,到底是詐尸還是另有隱情,我是刑警寧澤罚勾,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布毅人,位于F島的核電站,受9級特大地震影響尖殃,放射性物質(zhì)發(fā)生泄漏丈莺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一送丰、第九天 我趴在偏房一處隱蔽的房頂上張望缔俄。 院中可真熱鬧,春花似錦器躏、人聲如沸敬特。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至三热,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茅郎,已是汗流浹背患雏。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捏萍,地道東北人太抓。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像令杈,于是被迫代替她去往敵國和親走敌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354