LeanBack:HorizontalGridView和VerticalGridView使用详解
LeanBack是Google官方推出的TV端的功能库,里面包含了很多在TV Android端开发常用的控件,本文重点介绍其对RecyclerView适配TV端做的封装:HorizontalGridView、VerticalGridView。
HorizontalGridView和VerticalGridView都继承自RecyclerView,针对TV的特性,在item排版、焦点流转、上/失焦动画、记住焦点、焦点item对齐位置等方面做了比较好的封装,其继承结果如下
从上图可见,HorizontalGridView和VerticalGridView皆继承自BaseGridView,BaseGridView则继承自RecyclerView,BaseGridView实现了大部分上述的封装,包括列表的滑动、焦点事件的分发等等。其中,BaseGridView持有了GridLayoutManager的引用,实际上,BaseGridView大量的方法都流转到GridLayoutManager,所以上述的功能的实现其实是在GridLayoutManager实现的。
注意这里的GridLayoutManager跟平常使用GridLayoutManager不是同一个,上图中可以看到,它直接继承自RecyclerView.LayoutManager,我们知道,RecyclerView将列表的具体展示模式交由LayoutManager实现和管理,因此只要切换不同的LayoutManager就可以实现不同的列表模式。这里的GridLayoutManager则是针对TV特性,实现了前面所述的功能的一个实现。这里暂时不对具体实现做深入的探讨,着重介绍HorizontalGridView和VerticalGridView的使用方法。
这里只对HorizontalGridView做介绍,VerticalGridView类似。
HorizontalGridView的封装采用了MVP模式,涉及的几个类及其功能如下:
- HorizontalGridView:RecyclerView的子类;
- GridLayoutManager:适配的LayoutManager,支持横向的多行列表和纵向的多列列表,已包含在BaseGridView里,不需要额外设置,这点要注意;
- ObjectAdapter:承担MVP中model的职责,负责提供数据访问接口
- Presenter:职责类似RecyclerView的adapter,辅助item视图的创建和数据绑定等
- PresenterSelector:根据不同的数据类型选择不同的Presenter,用于多item type列表模型
- ItemBridgeAdapter:HorizontalGridView和ObjectAdapter的桥梁,用于解耦双方
- FocusHighlightHandler:item上焦的处理接口
- FocusHighlightHelper:上焦动画帮助类,内置了两种上焦动画
简单用法如下:
- 布局中添加HorizontalGridView
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:padding="20dp"> <android.support.v17.leanback.widget.HorizontalGridView android:id="@+id/hgv" android:layout_width="0dp" android:layout_height="0dp" android:padding="10dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:layout_marginLeft="10dp" android:layout_marginStart="10dp" android:layout_marginRight="10dp" android:layout_marginEnd="10dp"/> </android.support.constraint.ConstraintLayout>
-
实现Presenter,用于创建item和绑定数据:
public class HPresenter extends Presenter { /** * 创建ViewHolder,作用同RecyclerView$Adapter的onCreateViewHolder * @param viewGroup * @return */ @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup) { View inflate = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_h, viewGroup, false); return new ViewHolder(inflate); } /** * 同RecyclerView$Adapter的onBindViewHolder,但是解耦了position * @param viewHolder * @param o */ @Override public void onBindViewHolder(ViewHolder viewHolder, Object o) { if (o instanceof Integer){ ((TextView)viewHolder.view.findViewById(R.id.tv_index)).setText(o.toString()); } } @Override public void onUnbindViewHolder(ViewHolder viewHolder) { //解绑时释放资源 } }
-
模拟数据:
private void initData(){ if (mDataList==null){ mDataList=new ArrayList<>(); for (int i = 0; i <100 ; i++) { mDataList.add(i); } } }
-
配置HorizontalGridView:
private void initViews() { mHgv= (HorizontalGridView) findViewById(R.id.hgv); //3行 mHgv.setNumRows(3); //item纵向和横向的距离 mHgv.setItemSpacing(20); //item的对齐方式 mHgv.setGravity(Gravity.CENTER_VERTICAL); //设置 mHgv.setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { @Override public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) { super.onChildViewHolderSelected(parent, child, position, subposition); Log.d(TAG, "onChildViewHolderSelected() returned: " + position); //大部分情况下可以通过该方法获取到position } @Override public void onChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) { super.onChildViewHolderSelectedAndPositioned(parent, child, position, subposition); Log.d(TAG, "onChildViewHolderSelectedAndPositioned() returned: " + position); //当通过setSelectedPosition()方法大幅移动列表时,该方法会回调,返回的是最终的真实的position(当set的值超出范围时...) } }); HPresenter presenter=new HPresenter(); //创建ObjectAdapter,用于提供数据,当有多种类型时,传入PresenterSelector ArrayObjectAdapter objectAdapter=new ArrayObjectAdapter(presenter); //初始化模拟数据 initData(); //添加数据 objectAdapter.addAll(0,mDataList); //通过前面创建的objectAdapter创建ItemBridgeAdapter,完成数据的传递 ItemBridgeAdapter bridgeAdapter=new ItemBridgeAdapter(objectAdapter); //将ItemBridgeAdapter传入HorizontalGridView mHgv.setAdapter(bridgeAdapter); mHgv.requestFocus(); //设置上焦动画 FocusHighlightHelper.setupHeaderItemFocusHighlight(bridgeAdapter); }
-
如果上焦的item除了要有动画,还需要有状态的改变,如背景变化等,则需要设置itemView的selected状态,FocusHighlightHelper内置的动画会在item被上焦时会调用itemView.setSelected(true):
private void viewFocused(View view, boolean hasFocus) { lazyInit(view); view.setSelected(hasFocus); FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator); if (animator == null) { animator = new HeaderFocusAnimator(view, mSelectScale, mDuration); view.setTag(R.id.lb_focus_animator, animator); } animator.animateFocus(hasFocus, false); }
-
完整代码如下:
public class MainActivity extends Activity { public static final String TAG="MainActivity"; private HorizontalGridView mHgv; private List<Integer> mDataList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); } private void initViews() { mHgv= (HorizontalGridView) findViewById(R.id.hgv); //3行 mHgv.setNumRows(3); //item纵向和横向的距离 mHgv.setItemSpacing(20); //item的对齐方式 mHgv.setGravity(Gravity.CENTER_VERTICAL); //设置 mHgv.setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { @Override public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) { super.onChildViewHolderSelected(parent, child, position, subposition); Log.d(TAG, "onChildViewHolderSelected() returned: " + position); //大部分情况下可以通过该方法获取到position } @Override public void onChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition) { super.onChildViewHolderSelectedAndPositioned(parent, child, position, subposition); Log.d(TAG, "onChildViewHolderSelectedAndPositioned() returned: " + position); //当通过setSelectedPosition()方法大幅移动列表时,该方法会回调,返回的是最终的真实的position(当set的值超出范围时...) } }); HPresenter presenter=new HPresenter(); //创建ObjectAdapter,用于提供数据,当有多种类型时,传入PresenterSelector ArrayObjectAdapter objectAdapter=new ArrayObjectAdapter(presenter); //初始化模拟数据 initData(); //添加数据 objectAdapter.addAll(0,mDataList); //通过前面创建的objectAdapter创建ItemBridgeAdapter,完成数据的传递 ItemBridgeAdapter bridgeAdapter=new ItemBridgeAdapter(objectAdapter); //将ItemBridgeAdapter传入HorizontalGridView mHgv.setAdapter(bridgeAdapter); mHgv.requestFocus(); //设置上焦动画 FocusHighlightHelper.setupHeaderItemFocusHighlight(bridgeAdapter); } private void initData(){ if (mDataList==null){ mDataList=new ArrayList<>(); for (int i = 0; i <100 ; i++) { mDataList.add(i); } } } }
-
效果如下:
需要注意的是,item的排布方式是先纵向后横向,及第2个view位于第二行第一列,而非第一行第二列
-
HorizontalGridView默认上焦的item会居中显示,可以通过BaseGridView的方法改变对齐方式:
//设置滚动策略 public void setFocusScrollStrategy(int scrollStrategy) { if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM && scrollStrategy != FOCUS_SCROLL_PAGE) { throw new IllegalArgumentException("Invalid scrollStrategy"); } mLayoutManager.setFocusScrollStrategy(scrollStrategy); requestLayout(); } //支持三种策略 //移动时是上焦的item对齐某个位置 public final static int FOCUS_SCROLL_ALIGNED = 0; //移动使上焦的item始终保持在显示区域内 public final static int FOCUS_SCROLL_ITEM = 1; //当焦点要移出显示区域时,滚动一页 public final static int FOCUS_SCROLL_PAGE = 2;
//当上面设置了对齐到指定位置时,可以通过下面的方法设置对齐的方式 public void setWindowAlignment(int windowAlignment) { mLayoutManager.setWindowAlignment(windowAlignment); requestLayout(); } //支持4中模式 //对齐左边或者上边 public final static int WINDOW_ALIGN_LOW_EDGE = 1; //对齐右边或者下边 public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1; //同时对齐以上两边 public final static int WINDOW_ALIGN_BOTH_EDGE = WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE; //对齐到指定的keyline public final static int WINDOW_ALIGN_NO_EDGE = 0; //以上4中方式都存在一条keyline,当焦点离开对齐的边时,保持在keyline的位置,keyline可以通过以下方法设置 //offset为正值,则表示keyline距离lowedge的像素值,如果为负值,则其绝对值表示距离highedge的距离,默认值为0 public void setWindowAlignmentOffset(int offset) { mLayoutManager.setWindowAlignmentOffset(offset); requestLayout(); } //offsetPercent表示keyline距离lowedge的百分比(width的百分比),默认值为50 public void setWindowAlignmentOffsetPercent(float offsetPercent) { mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent); requestLayout(); }
以上便是HorizontalGridView的基本用法,VerticalGridView的用法类似,便不再赘述。
上一篇: android leanback使用详解以及获取焦点高亮
下一篇: Python编码问题详解