Androdi TV开发之VerticalGridView

一、概述

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)。总结一下:

  1. 首先创建 PresenterSelector ,getPresenter方法返回ListRowPresenter;
  2. 创建vgv的ArrayObjectArray,传入PresenterSelector ;
  3. 创建ItemBridgeAdapter,传入ArrayObjectArray,并将他设置给vgv;
  4. 创建一个Presenter,实现里面的onCreateViewHolder,onBindViewHolder,onUnbindViewHolder,分别是创建横向子视图,给子视图绑定数据,子视图被回收;
  5. 创建ListRow的 ArrayObjectAdapter ,传入Presenter,并添加数据;
  6. 创建HeaderItem ,传入ID和name;
  7. 创建ListRow,传入ID,HeaderItem ,ListRow的 ArrayObjectAdapter ;
  8. 添加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里面切换了,无法移出到外面。

猜你喜欢

转载自blog.csdn.net/ganduwei/article/details/108227375