一、概述
VerticalGridView是Android TV开发中常用到的控件,是TV版的RecyclerView。不过他确实也是基于RecyclerView实现的,并针对TV的场景做了一些扩展,比如切换焦点时item的对齐,记住item焦点并恢复,item获取焦点后的放大效果等等。可以看到其实大部分都是与焦点相关的,确实,手机与电视开发中最大的差异就是交互方式不同,一个是触摸操作,一个是通过遥控器切换焦点来操作。
相比RecyclerView,VerticalGridView 用起来会麻烦一点,不仅仅只用设置 Adapter 和 LayoutManager 就可以让数据显示出来。它有几个主要的角色,分别是 ItemBridgeAdapter 、ArrayObjectAdapter 、Presenter 、PresenterSelector 、ListRowPresenter、ListRow。前面三个可以实现只有一种ItemType的简单列表,后面三个用于实现多类型的和复杂的列表。
二、单类型简单列表
VerticalGridView属于 leanback 库,使用他要先依赖:
implementation 'com.android.support:leanback-v17:28.0.0'
VerticalGridView不需要设置LayoutManager ,它内部已经设置好了,会在垂直方向布局Item。
在开始之前, 我们先了解下基本的。ItemBridgeAdapter继承自RecyclerView.Adapter,里面封装了很多逻辑,使用的时候只要构造它,设置给vgv即可,构造时需要传入一个ArrayObjectAdapter ;ArrayObjectAdapter 是数据持有者,可以添加任意类型的数据,构造时需要一个Presenter ;Presenter 是个抽象类,创建item视图,绑定数据的抽象方法需要我们自己实现。可以看到,ItemBridgeAdapter 将视图的创建和绑定转到了Presenter中。
获取到VerticalGridView的引用给vgv后,可以先进行一些配置。通过下面该方法设置有多少列,没有设置的话,默认为1列。
vgv.setNumColumns(3);
item水平方向的位置会根据列数自动算好,并留有适当的间隙。通过下面方法可以控制水平间隙和垂直间隙:
vgv.setVerticalSpacing(30);
vgv.setHorizontalSpacing(30);
接着需要创建一个Presenter,有三个抽象方法我们要去实现它:
public class MyPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
Context context = parent.getContext();
TextView tv = new TextView(context);
icv.setLayoutParams(new ViewGroup.LayoutParams(80, 60));
return new ViewHolder(tv);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
TextView tv = (TextView ) viewHolder.view;
tv.setText(item.toString());
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
}
}
onCreateViewHolder用于创建ViewHolder,onBindViewHolder用于绑定数据,里面的Object item 参数对应 ArrayObjectAdapter 中的数据。onUnbindViewHolder在view被回收时被调用(onViewRecycled)。
然后我们就可以将三者关联起来了:
ArrayObjectAdapter aoa = new ArrayObjectAdapter(new MyPresenter());
aoa.add("A");
aoa.add(1);
ItemBridgeAdapter iba = new ItemBridgeAdapter(aoa);
vgv.setAdapter(iba);
这样就可以显示两个item的列表了。这样创建的列表在选中时是没有选中放大的效果的,要有该效果需要使用 FocusHighlightHelper ,他也属于 leanback ,基本上与TV相关的都在这个库里面。看下它怎么用:
FocusHighlightHelper.setupBrowseItemFocusHighlight(iba, FocusHighlight.ZOOM_FACTOR_LARGE, false);
需要传三个参数,第一个是iba,第二个是放大级别,第三个useDimmer表示是否高亮,如果为true,则item没有选中时会比正常暗一点,选中时就变亮。
当然item能选中,首先需要 item 可以获得焦点。如果是Button类型,那自然可以获取焦点,如果是TextView,则需要设置能获得焦点属性或者直接设置点击事件。
tv.setFocusableInTouchMode(true);
放大级别有五个值可设置
int ZOOM_FACTOR_NONE = 0;
int ZOOM_FACTOR_SMALL = 1;
int ZOOM_FACTOR_MEDIUM = 2;
int ZOOM_FACTOR_LARGE = 3;
int ZOOM_FACTOR_XSMALL = 4;
其中ZOOM_FACTOR_NONE 表示不放大。其他四个分别对应下面的比例。
<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>
这些定义在leanback库中,如果想修改放大比例,可以在主module里面定义名字相同的资源,覆盖它即可。
选中事件
VerticalGridView有一个setOnChildViewHolderSelectedListener方法用来监听item获得焦点的事件:
vgv.setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
@Override
public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) {
//TODO
}
});
position为选中的索引值,subposition一般都是0(还不知道什么用意)。
OnChildViewHolderSelectedListener还有另外一个onChildViewHolderSelectedAndPositioned方法,两个方法在item选中后都会被调用,不过调用时机有先后,onChildViewHolderSelected调用比较早。onChildViewHolderSelectedAndPositioned会在item的位置确定(layout)之后才调用,当选中执行放大动画时,它会被调用多次。另外,第一次设置数据后,和修改数据并notify后,选中监听会被调用。
点击事件
要为item设置点击事件,可以在创建item的时候设置,即Presenter的onCreateViewHolder里面。还有一种方法也可以设置,可能会更好一点,通过ItemBridgeAdapter的setAdapterListener方法设置监听,在某些时机会回调这个监听:
iba.setAdapterListener(new ItemBridgeAdapter.AdapterListener() {
@Override
public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//TODO
}
});
});
onCreate是在Presenter的onCreateViewHolder执行之后回调的,这里可以拿到itemVIew进行相关的操作。
三、复杂的列表
在TV中,通常都是比较复杂的列表展示,比如下面的展示:
这种列表其实也是一个单类型,只不过每个item是由一个标题和一个横向的列表(HorizontalGridView)组成。
还是一样,先介绍 PresenterSelector 、ListRowPresenter、ListRow 这三个类。
先看 PresenterSelector,顾名思义他是一个Presenter选择器,ItemBridgeAdapter用它来选择 Presenter。不同的Presenter对应不同的 ItemType。
PresenterSelector presenterSelector = new PresenterSelector() {
@Override
public Presenter getPresenter(Object item) {
//TODO
}
};
他是个抽象类,只有一个getPresenter抽象方法需要实现,返回一个Presenter,方法中的 item参数是 ArrayObjectAdapter 中的数据,可以判断数据,来返回不同的 Presenter 。即不同类型的数据对应不同的 Presenter ,不同的 Presenter 也就是不同的 ItemView ,从而实现多类型的列表。
getPresenter这个方法会被 ItemBridgeAdapter 的 getItemViewType 调用:
public int getItemViewType(int position) {
PresenterSelector presenterSelector = this.mPresenterSelector != null ? this.mPresenterSelector : this.mAdapter.getPresenterSelector();
Object item = this.mAdapter.get(position);
Presenter presenter = presenterSelector.getPresenter(item);
int type = this.mPresenters.indexOf(presenter);
if (type < 0) {
this.mPresenters.add(presenter);
type = this.mPresenters.indexOf(presenter);
this.onAddPresenter(presenter, type);
if (this.mAdapterListener != null) {
this.mAdapterListener.onAddPresenter(presenter, type);
}
}
return type;
}
getItemViewType 里面会根据 PresenterSelector 返回的 Presenter 决定自身要返回的 type 。
PresenterSelector 既可以被 ItemBridgeAdapter 持有,也可以被 ArrayObjectAdapter 持有,构造的时候出入即可。
ArrayObjectAdapter aoa = new ArrayObjectAdapter();
ItemBridgeAdapter itemBridgeAdapter = new ItemBridgeAdapter(aoa, presenterSelector);
//ArrayObjectAdapter aoa = new ArrayObjectAdapter(presenterSelector);
//ItemBridgeAdapter itemBridgeAdapter = new ItemBridgeAdapter(aoa);
我们来看看ListRowPresenter,他继承了RowPresenter,RowPresenter又继承Presenter。下面我们理下RowPresenter相关的实现,主要看下创建了什么View。首当其冲的是onCreateViewHolder方法:
public final android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
RowPresenter.ViewHolder vh = this.createRowViewHolder(parent);
vh.mInitialzed = false;
Object result;
if (this.needsRowContainerView()) {
RowContainerView containerView = new RowContainerView(parent.getContext());
if (this.mHeaderPresenter != null) {
vh.mHeaderViewHolder = (android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder)this.mHeaderPresenter.onCreateViewHolder((ViewGroup)vh.view);
}
result = new RowPresenter.ContainerViewHolder(containerView, vh);
} else {
result = vh;
}
this.initializeRowViewHolder(vh);
if (!vh.mInitialzed) {
throw new RuntimeException("super.initializeRowViewHolder() must be called");
} else {
return (android.support.v17.leanback.widget.Presenter.ViewHolder)result;
}
}
首先它会去调用自己的createRowViewHolder方法,该方法由ListRowPresenter实现:
protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
initStatics(parent.getContext());
ListRowView rowView = new ListRowView(parent.getContext());
this.setupFadingEffect(rowView);
if (this.mRowHeight != 0) {
rowView.getGridView().setRowHeight(this.mRowHeight);
}
return new ListRowPresenter.ViewHolder(rowView, rowView.getGridView(), this);
}
createRowViewHolder里面会创建一个ListRowView,ListRowView是一个LinearLayout,只有一个HorizontalGridView的子view,可以理解为 ListRowView 就是一个横向的列表。
接着调用needsRowContainerView方法,该方法默认是返回true的,也就会创建一个RowContainerView,RowContainerView 也是一个垂直布局的LinearLayout;然后判断mHeaderPresenter 是否为空,默认情况下 mHeaderPresenter 在RowPresenter 初始化时也会被初始化:
private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
不为空时,就调用了mHeaderPresenter 的 onCreateViewHolder:
public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
View root = LayoutInflater.from(parent.getContext()).inflate(this.mLayoutResourceId, parent, false);
RowHeaderPresenter.ViewHolder viewHolder = new RowHeaderPresenter.ViewHolder(root);
if (this.mAnimateSelect) {
this.setSelectLevel(viewHolder, 0.0F);
}
return viewHolder;
}
它里面加载了mLayoutResourceId 布局,mLayoutResourceId 默认为:
public RowHeaderPresenter() {
this(layout.lb_row_header);
}
lb_row_header里面是两个TextView,用于显示标题和描述。
接着创建了RowPresenter.ContainerViewHolder,看里面的实现:
public ContainerViewHolder(RowContainerView containerView, RowPresenter.ViewHolder rowViewHolder) {
super(containerView);
containerView.addRowView(rowViewHolder.view);
if (rowViewHolder.mHeaderViewHolder != null) {
containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view);
}
this.mRowViewHolder = rowViewHolder;
this.mRowViewHolder.mContainerViewHolder = this;
}
}
调用了containerView的addRowView和addHeaderView方法:
public void addRowView(View view) {
this.addView(view);
}
public void addHeaderView(View headerView) {
if (this.mHeaderDock.indexOfChild(headerView) < 0) {
this.mHeaderDock.addView(headerView, 0);
}
}
可以看出,containerView 包含了rowViewHolder.view也就是ListRowView,和 mHeaderViewHolder.view也就是那两个TextView,并且他们在ListRowView的上面。
经过上面的分析,我们可以知道,ListRowPresenter 其实是一个有 TextView 和横向列表的Presenter,它对应图中的这个单元:
ListRowPresenter 帮我们创建了横向列表,需要我们做的就是为横向列表创建item视图和绑定数据。接下来,我们就来看看如何做。
从名字可以看出,ListRow 与 ListRowPresenter 肯定是有关系的。不错,ListRow 就是用来给 ListRowPresenter 提供数据的。他怎么使用:
ArrayObjectAdapter rowAoa = new ArrayObjectAdapter(new MyRowPresenter());
rowAoa .add("1");
rowAoa .add("2");
rowAoa .add("3");
HeaderItem headerItem = new HeaderItem(1, "title 001");
ListRow listRow = new ListRow(6, headerItem, rowAoa );
构造 ListRow 需要 设置一个ID,HeaderItem ,和 ArrayObjectAdapter 。ID不要重复,HeaderItem 也需要一个ID,一个name,另外他还可以设置描述:
headerItem.setDescription("description ");
那这个name和描述的就对应ListRowPresenter 里面的两个TextView了,他们的内容显示到TextView中。
这里又出现了一个 ArrayObjectAdapter ,很容易和最外层的 ArrayObjectAdapter 搞混,只要记住 最外层 ArrayObjectAdapter 是给垂直的 VerticalGridView 提供数据,这里的是给ListRowPresenter 里面横向列表(HorizontalGridView)提供数据。
这里的ArrayObjectAdapter 需要一个Presenter,这个Presenter就是给横向列表创建item视图和绑定数据用的。
看 ListRowPresenter 里面的onBindRowViewHolder方法就会明白,他会把listRow 里面的数据设置给他的mGridView(HorizontalGridView):
protected void onBindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder holder, Object item) {
super.onBindRowViewHolder(holder, item);
ListRowPresenter.ViewHolder vh = (ListRowPresenter.ViewHolder)holder;
ListRow rowItem = (ListRow)item;
vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
vh.mGridView.setContentDescription(rowItem.getContentDescription());
}
创建好 ListRow 之后要把它添加到 最外层的ArrayObjectAdapter 中:
aoa.add(listRow);
另外在PresenterSeletor中我们需要返回ListRowPresenter:
final ListRowPresenter listRowPresenter = new ListRowPresenter();
PresenterSelector presenterSelector = new PresenterSelector() {
@Override
public Presenter getPresenter(Object item) {
Presenter presenter;
if (item instanceof Row) {
presenter = listRowPresenter ;
} else {
//TODO
}
return presenter;
}
判断数据如果是Row(ListRow的父类),就返回 listRowPresenter 。这里的Object 参数就是我们上面添加到aoa里面的listRow了。注意如果设计UI中横向列表长得都一样的话,返回同一个listRowPresenter,不要每次都返回一个新的。
这样我们就实现了上面图的ui效果了(多添加几个ListRow)。总结一下:
- 首先创建 PresenterSelector ,getPresenter方法返回ListRowPresenter;
- 创建vgv的ArrayObjectArray,传入PresenterSelector ;
- 创建ItemBridgeAdapter,传入ArrayObjectArray,并将他设置给vgv;
- 创建一个Presenter,实现里面的onCreateViewHolder,onBindViewHolder,onUnbindViewHolder,分别是创建横向子视图,给子视图绑定数据,子视图被回收;
- 创建ListRow的 ArrayObjectAdapter ,传入Presenter,并添加数据;
- 创建HeaderItem ,传入ID和name;
- 创建ListRow,传入ID,HeaderItem ,ListRow的 ArrayObjectAdapter ;
- 添加ListRow到vgv的ArrayObjectArray,添加数据后ArrayObjectArray会自动刷新。
选中事件 和 点击事件
相比简单的类表,要麻烦很多,这里先直接上代码,后面再解析下:
itemBridgeAdapter.setAdapterListener(new ItemBridgeAdapter.AdapterListener() {
public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
setItemOnClickAndSelected(vh);
}
});
vgv.setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
private ItemBridgeAdapter.ViewHolder selectedViewHolder;
@Override
public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) {
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) child;
if (vh != null && vh.getPresenter() instanceof RowPresenter) {
if (selectedViewHolder != null) {
setRowViewSelected(selectedViewHolder, false);
}
selectedViewHolder = vh;
setRowViewSelected(selectedViewHolder, true);
}
}
});
private void setItemOnClickAndSelected(ItemBridgeAdapter.ViewHolder vh) {
if (vh.getPresenter() instanceof RowPresenter) {
RowPresenter presenter = (RowPresenter) vh.getPresenter();
RowPresenter.ViewHolder rowViewHolder = presenter.getRowViewHolder(vh.getViewHolder());
rowViewHolder.setOnItemViewClickedListener(new OnItemViewClickedListener() {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
//TODO
}
});
rowViewHolder.setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) {
//TODO
}
});
} else if (vh.getPresenter() instanceof ..) {
//TODO
}
}
private void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected) {
RowPresenter presenter = (RowPresenter) vh.getPresenter();
RowPresenter.ViewHolder rowViewHolder = presenter.getRowViewHolder(vh.getViewHolder());
presenter.setRowViewSelected(rowViewHolder, selected);
}
同样是需要给itemBridgeAdapter设置AdapterListene,在onCreate里面,需要获取到RowPresenter.ViewHolder,这里会有点绕,出现了多个ViewHolder ,主要是因为包裹了几层。接着调用它提供的方法设置选中和点击监听。
还需要给vgv设置OnChildViewHolderSelectedListener,通过RowPresenter 的setRowViewSelected方法将选中事件传给横向列表,横向列表接着调用自己的OnItemViewSelectedListener,将被选中的item传给外界。如果不给vgv设置选中监听,那横向列表也就不会回调自己的选中监听。
因为没有使用RowsFragment(在leanback库中),所以设置选中监听会比较麻烦,这里也是参考了RowsFragment的实现;
设置点击监听相对简单。设置选中监听,需要在vgv选中监听里面通知 ListRowPresenter里面的 hgv 你被选中了(通过setRowViewSelected方法,RowsFragment中也是这么做的);
关键变量是RowPresenter.ViewHolder里面的 mSelected。该值直接影响 ListRowPresenter里面的 hgv 的 setOnChildSelectedListener 监听里面的 selectChildView 方法是否会调用OnItemViewSelectedListener().onItemSelected(参考 ListRowPresenter 中 initializeRowViewHolder和selectChildView 方法);
所以如果该变量为false,即使设置给RowPresenter.ViewHolder设置了OnItemViewSelectedListener也不会被调用;
而mSelected只有RowPresenter的setRowViewSelected方法才可以改变。
setRowViewSelected调用了onRowViewSelected方法,而他又调用了dispatchItemSelectedListener,该方法就是回调选中监听。
我们还需要在ListRowPresenter重写onRowViewSelected的实现,因为他默认的实现会有一些其他的逻辑,我们并不需要:
//ListRowPresenter 的onRowViewSelected
protected void onRowViewSelected(android.support.v17.leanback.widget.RowPresenter.ViewHolder holder, boolean selected) {
super.onRowViewSelected(holder, selected);
ListRowPresenter.ViewHolder vh = (ListRowPresenter.ViewHolder)holder;
this.setVerticalPadding(vh);
this.updateFooterViewSwitcher(vh);
}
//RowPresenter 的onRowViewSelected
protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
this.dispatchItemSelectedListener(vh, selected);
this.updateHeaderViewVisibility(vh);
this.updateActivateStatus(vh, vh.view);
}
我们只需要执行this.dispatchItemSelectedListener(vh, selected);这个方法。下面是我们自己的实现:
public class CustomListRowPresenter extends ListRowPresenter {
public CustomListRowPresenter() {
super(FocusHighlight.ZOOM_FACTOR_LARGE, false);
}
@Override
public boolean isUsingOutlineClipping(Context context) {
return false;
}
@Override
public boolean isUsingDefaultShadow() {
return false;
}
@Override
protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
dispatchItemSelectedListener(holder, selected);
}
onRowViewSelected里面执行了dispatchItemSelectedListener方法,另外还重写了两个方法,作用是修改item的外观,isUsingOutlineClipping用来控制item是否有圆角,如果为返回 true 的话,会使用Outline裁剪出圆角,默认是为true的。isUsingDefaultShadow用来控制是否给item包裹一层阴影,默认是 true 有阴影,如果你的item有透明的部分,你就会看到这层阴影,这个阴影的高度(z轴)可以用下面的属性修改:
<dimen name="lb_material_shadow_focused_z">0dp</dimen>
如果改为0dp,会没有阴影,但是会导致item选中后,不能再最上层,有可能被其他item挡住(选中放大的比例比较大,或者item间隔比较小),所以最好通过isUsingDefaultShadow来控制,返回false即可。
另外,如果我们的横向列表的item都一样的话,可以让横向列表共用一个缓存,这样会有更好的性能:
private RecyclerView.RecycledViewPool mRecycledViewPool = null;
private void setupSharedViewPool(ItemBridgeAdapter.ViewHolder vh) {
if (vh.getPresenter() instanceof RowPresenter) {
RowPresenter presenter = (RowPresenter) vh.getPresenter();
RowPresenter.ViewHolder rowViewHolder = presenter.getRowViewHolder(vh.getViewHolder());
if (rowViewHolder instanceof ListRowPresenter.ViewHolder) {
HorizontalGridView gridView = ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView();
if (mRecycledViewPool == null) {
mRecycledViewPool = gridView.getRecycledViewPool();
} else {
gridView.setRecycledViewPool(mRecycledViewPool);
}
}
}
}
在AdapterListene的onCreate中调用该方法即可。
四、样式设置
ListRowPresenter会有一些默认的设置,除了上面我们提到的阴影,圆角外,还有一些padding,字体大小等等,要修改这些值,需要在style中定义:
<style name="AppTheme" parent="@style/Theme.Leanback"> <item name="rowHeaderStyle">@style/MyRowHeaderStyle</item> <item name="rowHeaderDescriptionStyle">@style/MyRowHeaderStyle</item> <item name="rowHeaderDockStyle">@style/MyRowHeaderDockStyle</item> <item name="rowHorizontalGridStyle">@style/HomeHorizontalGridStyle</item> </style>
<style name="MyRowHeaderStyle" parent="@style/Widget.Leanback.Row.Header"> <item name="android:textAppearance">@style/MyHeaderStyle.MyHeaderText</item> </style>
<style name="MyHeaderStyle.MyHeaderText" parent="TextAppearance.Leanback.Header"> <item name="android:textSize">16sp</item> <item name="android:textColor">@android:color/white</item> </style>
<style name="MyRowHeaderDockStyle" parent="@style/Widget.Leanback.Row.HeaderDock"> <item name="android:paddingStart">@dimen/hgv_padding</item> <item name="android:paddingBottom">2dp</item> </style>
<style name="HomeHorizontalGridStyle" parent="Widget.Leanback.Row.HorizontalGridView"> <item name="android:horizontalSpacing">@dimen/horizontalSpacing</item> <item name="android:verticalSpacing">@dimen/verticalSpacing</item> <item name="android:paddingStart">@dimen/hgv_padding</item> <item name="focusOutSideStart">true</item> </style>
rowHeaderStyle 和 rowHeaderDescriptionStyle 用于修改标题文字和描述文字的大小颜色。
rowHeaderDockStyle用于控制标题和描述容器的padding。
rowHorizontalGridStyle用于控制横向列表的间距,padding,焦点是否可移出去等等。
另外如果想要统一控制 标题描述容器 和 横向列表的 padding,只要在AppTheme加上:
<item name="browsePaddingStart">10dp</item>
五、焦点控制
最后,如果想控制VerticalGridView或者HorizontalGridStyle的焦点是否可以移出去,可以在它的style中设置下面四个属性:
<style name="MyVerticalGridStyle">
<item name="focusOutFront">true</item>
<item name="focusOutEnd">false</item>
<item name="focusOutSideStart">false</item>
<item name="focusOutSideEnd">true</item>
</style>
分别对应四个方向,设置为false,表示焦点不能从该方向移到外面,即使在该方向上有能获取焦点的View。如果都为false,那焦点只能在VerticalGridView里面切换了,无法移出到外面。