1. <nobr id="easjo"><address id="easjo"></address></nobr>

      <track id="easjo"><source id="easjo"></source></track>
      1. 
        

      2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
      3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>
          貴州做網站公司
          貴州做網站公司~專業!靠譜!
          10年網站模板開發經驗,熟悉國內外開源網站程序,包括DEDECMS,WordPress,ZBlog,Discuz! 等網站程序,可為您提供網站建設,網站克隆,仿站,網頁設計,網站制作,網站推廣優化等服務。我們專注高端營銷型網站,企業官網,集團官網,自適應網站,手機網站,網絡營銷,網站優化,網站服務器環境搭建以及托管運維等。為客戶提供一站式網站解決方案?。?!

          從 Android 開發到讀懂源碼 第05期

          來源:互聯網轉載 時間:2024-01-29 07:43:04

          作者簡介

          羅鐵錘,六年安卓踩坑經驗,致力于底層平臺、上層應用等多領域開發。文能靜坐彈吉他,武能通宵寫代碼

          這是《從 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-

          網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...

          在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...

          在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...

          手機UC可以看的電影網站?用uc瀏覽器看電影的步驟如下:1.首先雙擊電腦上的UC瀏覽器打開。2、進入主頁面,點擊左側視頻。3.打開電影后,我們可以在搜索框中搜索想要觀看的電影名稱。這里我們以尋找鋼鐵俠2為例。4.搜索后,主頁面左側有一個可播放的視頻網站。下面是視頻。點擊觀看。這就解決了用uc瀏覽器看電影的問題。uc手機瀏覽器搜索引擎是多少?uc瀏覽器的默認搜索引擎是 "神馬搜索 ",可以設置。瀏覽...

          dnf浴血之怒如何獲得?血怒是DNF狂暴者的一大技能。你可以在一次睡眠后學習,然后升級你的等級DNF狂戰如何無限浴血之怒?DNF紅眼無限獄浴狂暴的裝備條件有:無雙護胸的魔法戰、無雙腿甲的魔法戰、無雙腰帶的魔法戰、時間控制者的斗篷、明天的洗禮春花或怪物圍攻buff。DNF紅眼無限監獄浴憤怒藥達到的條件:精神刺激長生不老藥。注意:紅眼的無限血怒需要時間才能達到,需要血怒等級。無限爆血派最好的武器就是三...

          保定東站到深圳多長時間?保定東站應該是高鐵站,所以從保定東站到深圳北高鐵站需要九個小時三十分鐘。北京到深圳的高鐵經過河北、河南、湖北、湖南、廣東。如果是快車,到深圳要25個小時。所以高鐵時代為大家節省了大量的時間,提高了工作效率,為的發展點贊 美國鐵路行業。保定東站到深圳多長時間? 71次,保定東-深圳北用時9小時23分;D927,保定東-深圳北用時10小時13分。(以上情況下,D927次列車為動...

          TOP
          国产初高中生视频在线观看|亚洲一区中文|久久亚洲欧美国产精品|黄色网站入口免费进人
          1. <nobr id="easjo"><address id="easjo"></address></nobr>

              <track id="easjo"><source id="easjo"></source></track>
              1. 
                

              2. <bdo id="easjo"><optgroup id="easjo"></optgroup></bdo>
              3. <track id="easjo"><source id="easjo"><em id="easjo"></em></source></track><option id="easjo"><span id="easjo"><em id="easjo"></em></span></option>