LeanBack:HorizontalGridView和VerticalGridView使用详解

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

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

LeanBack:HorizontalGridView和VerticalGridView使用详解_第1张图片 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的用法类似,便不再赘述。

你可能感兴趣的:(Android)