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
垂直視圖為例)
- 自定義
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);
}
}
}
- 構(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();
- 構(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[]{});
}
}
- 構(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);
- 構(gòu)造一個
ItemBridgeAdapter
,填充給VerticalGridView
// 該Adapter只是作為一個橋梁作用萍摊,將每一行結(jié)構(gòu)對應的presenter和data進行關(guān)聯(lián)
ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(verticalAdapter);
VerticalGridView.setAdapter(bridgeAdapter);
至此挤茄,頁面展示效果如下:
源碼分析
首先分析下非使用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;
}
- 接下去就是
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中的數(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
繼承Row
是android
封裝好的行數(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)介紹過了衣屏,主要有這幾個抽象方法:onCreateViewHolder
,onBindViewHolder
辩棒,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)形式都是通過這種Presenter
與BaseGridView
之間的嵌套關(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
抽象枉氮,將onCreateViewHolder
和onBindViewHolder
等行為抽離出去,讓每個有不同樣式的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對焦點及按鍵事件的處理绪励。