LeanBack:HorizontalGridView和VerticalGridView使用详解

LeanBack:HorizontalGridView和VerticalGridView使用详解LeanBack 是 Google 官方推出的 TV 端的功能库 里面包含了很多在 TVAndroid 端开发常用的控件 本文重点介绍其对 RecyclerView 适配 TV 端做的封装 HorizontalGr VerticalGrid HorizontalGr 和 VerticalGrid 都继承自 RecyclerView 针对 TV 的特性 在 item 排版 焦点流转 上 失焦

LeanBack是Google官方推出的TV端的功能库,里面包含了很多在TV Android端开发常用的控件,本文重点介绍其对RecyclerView适配TV端做的封装:HorizontalGridView、VerticalGridView。

HorizontalGridView和VerticalGridView都继承自RecyclerView,针对TV的特性,在item排版、焦点流转、上/失焦动画、记住焦点、焦点item对齐位置等方面做了比较好的封装,其继承结果如下

HorizontalGridView和VerticalGridView继承结构
HorizontalGridView和VerticalGridView继承结构

从上图可见,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模式,涉及的几个类及其功能如下:

  1. HorizontalGridView:RecyclerView的子类;
  2. GridLayoutManager:适配的LayoutManager,支持横向的多行列表和纵向的多列列表,已包含在BaseGridView里,不需要额外设置,这点要注意;
  3. ObjectAdapter:承担MVP中model的职责,负责提供数据访问接口
  4. Presenter:职责类似RecyclerView的adapter,辅助item视图的创建和数据绑定等
  5. PresenterSelector:根据不同的数据类型选择不同的Presenter,用于多item type列表模型
  6. ItemBridgeAdapter:HorizontalGridView和ObjectAdapter的桥梁,用于解耦双方
  7. FocusHighlightHandler:item上焦的处理接口
  8. FocusHighlightHelper:上焦动画帮助类,内置了两种上焦动画

简单用法如下:

  1. 布局中添加HorizontalGridView  
     
         
         
          
        
  2. 实现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) { //解绑时释放资源 } }

     

  3. 模拟数据:
    private void initData(){ if (mDataList==null){ mDataList=new ArrayList<>(); for (int i = 0; i <100 ; i++) { mDataList.add(i); } } }

     

  4. 配置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); } 

     

  5. 如果上焦的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); }

     

  6. 完整代码如下:
    public class MainActivity extends Activity { public static final String TAG="MainActivity"; private HorizontalGridView mHgv; private List 
        
          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); } } } } 
        

     

  7. 效果如下:
    效果图
    HorizontalGridView效果图

    需要注意的是,item的排布方式是先纵向后横向,及第2个view位于第二行第一列,而非第一行第二列

  8. 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的用法类似,便不再赘述。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229021.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午5:39
下一篇 2026年3月16日 下午5:39


相关推荐

  • n8n汉化后中文显示乱码如何解决?

    n8n汉化后中文显示乱码如何解决?

    2026年3月15日
    2
  • access2016访问mysql_关于VB连接access2016数据库

    在xp和access03运行没问题,换到win10和access2016就出错Publicmrc1AsADODB.RecordsetPublicmsgtextAsStringPublicfindstr1AsStringPublicmrc11AsADODB.RecordsetPublicmsgtext1AsStr…在xp和access03运行没问题,换到win10和access2016就出错Pu…

    2022年4月9日
    60
  • C++ override使用详解

    C++ override使用详解C++override从字面意思上,是覆盖的意思,实际上在C++中它是覆盖了一个方法并且对其重写,从而达到不同的作用。在我们C++编程过程中,最熟悉的就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。还有一个典型应用就是在继承中也可能会在子类覆盖父类的方法。   公有继承包含两部分:一是“接口”(interface),二是”实现”

    2025年7月10日
    8
  • 分组卷积(图文并茂)

    分组卷积(图文并茂)分组卷积 Groupconvolu 是将输入层的不同特征图进行分组 然后采用不同的卷积核再对各个组进行卷积 这样会降低卷积的计算量 因为一般的卷积都是在所有的输入特征图上做卷积 可以说是全通道卷积 这是一种通道密集连接方式 channeldense 而 groupconvolu 相比则是一种通道稀疏连接方式 channelspars 1 分组卷积的矛盾 计算量使用 groupconvolu 的网络有很多 如 Xception Mo

    2026年3月18日
    2
  • bash命令的使用方法

    bash命令的使用方法小编给大家分享一下 bash 命令的使用方法 相信大部分人都还不怎么了解 因此分享这篇文章给大家参考一下 希望大家阅读完这篇文章后大有收获 下面让我们一起去了解一下吧 Bash Bash 是 BourneAgainS 的缩写 用于执行描述命令 如 Linux 中的命令 的 shell 在 Linux 上采用 bash 作为标准 基本上它描述了对带有 sh 扩展名的 vi 编辑器等文本的处理并执行 与编程一样 它有许多函数 如变量 函数和算术处理 所以如果你是一个小程序 你可以用 bash 编写

    2026年3月19日
    2
  • 用友重磅发布BIP“本体智能体”(Ontology-Driven Agent),引领企业AI迈向自主决策时代!

    用友重磅发布BIP“本体智能体”(Ontology-Driven Agent),引领企业AI迈向自主决策时代!

    2026年3月16日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号