Android TvRecyclerView-任意定义焦点位置-多布局等复杂布局适应-支持焦点缩放效果-尤其适用于TvLauncher。

先聊会天儿,正文开始有标记,可直接跳过。或者你看效果满足了你的需求,直接去下载项目也行。

        最近一直在做一个类TvLauncher,为什么说是“类”呢?因为在我实在Launcher3的基础上又添加了两类Launcher,一类是GameLauncer--类似XBox等大型游戏机的主界面;而另一类就是TvLauncher--同普通的智能电视主界面一样,但我们的设计更加酷炫。所以其实是一种带有三种不同类型的Launcher界面,这儿咱们就单说一下咱们的电视Launcher。

        想知道我们的产品是什么吗?京东搜索“万核客厅一体机”,即可找到我们的产品,在产品介绍中就可以看到我做的三种Launcher界面。还可以详细了解一下我们的产品,对于普通用户来说,它叫客厅一体机,集普通Android用户,Android游戏发烧友用户以及电视盒子用户于一身的一台神器!对于我们Android程序猿来说,他也是一台很不错的开发机哦!(我这么说,经理会不会***我?吐舌头

        简单看一下我们的产品的三类Launcher主界面吧!很炫吧?,那就快去仔细看看吧!Please Go--京东万核旗舰店

                

        看到里面的三种不同的主界面了吧,从左到右依次是:TvLauncher  PcLauncher  GameLauncher;

        看第一个 TVLauncher 这是头布局,有五种不同的FocusView!!!刚拿到时,还真是懵了一下子生气

        我们公司的产品及设计者决定要做一个与众不同的TvLauncher,看这篇文章的你应该懂得吧,他们稍稍的一个与众不同,我们就不知道要死多少脑细胞!不过,换个角度想想,还是挺喜欢他们的,因为这样可以让我的项目也与众不同,而且也会学习到更多的开发技巧。

        下面的这个TvRecyclerView基本就满足了你的各种开发需求了吧!

        我的项目是我参考别人的项目(参考文献见文末)修改完成的,但我的项目太复杂,展示起来麻烦,也增加了理解难度系数,这里就简单的把别人项目中的一个比较好的点摘出来,稍加修改,使之更能完美的符合我们的需求。

-------------------------正文开始-------------------------

首先看一下效果图:

   

主要功能:

        1:任意锁定焦点位置,如本例是将开始的焦点框跟随Item种的FocusView,后面的是让焦点框锁定在屏幕中间;

        2:自由定义焦点缩放比例;焦点框的视觉效果;

        3:可以任意定义FocusView,不受Item影响,复杂布局处理起来就简单了;

功能实现:

        在这里,我就只把关键代码附上,其它的可以去下载自由查看

        其中,我会我注意到的关键的地方做好注释,方便理解。

        1:RecyclerView的自定义及初始化:

        首先决定焦点框是否跟随Item移动和位置的关键方法如下:

    

@Override
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
    /**
     * Creator:David
     * 首先,我们需要在绑定Holder的时候,按需要给item设置不同的焦点状态标记;
         * 然后,通过标记决定我们焦点框的状态
         */
    Object tag = child.getTag(R.id.focus_item_view_type);
    if (tag != null && ((int) tag == 1)) {
        //不需要锁定焦点框,正常返回时,焦点框跟随item移动。
        return super.requestChildRectangleOnScreen(child, rect, immediate);
    }
    //锁定焦点位置,让recyclerView自己控制滑动距离,使Item和焦点框重合
        focusItemOffsetX = focusItemOffsetY = 0;
    View focusView = child.findFocus();
    Rect rectFocus = new Rect();
    focusView.getDrawingRect(rectFocus);
    offsetDescendantRectToMyCoords(focusView, rectFocus);
    //dx dy 决定了焦点框锁定位置,可以自己设计算法,按需求确定位置
    int dx = 0, dy = 0;
    if (getLayoutManager().canScrollHorizontally()) {
        dx = rectFocus.left - (getWidth() - rectFocus.width()) / 2;
    } else if (getLayoutManager().canScrollVertically()) {
        dy = rectFocus.top - (getHeight() - rectFocus.height() / 2);
    }
    if (immediate) {
        scrollBy(dx, dy);
    } else {
        smoothScrollBy(dx, dy);
    }
    return true;
}
        初始化RecyclerView:       


private void initView() {
    recyclerView.setHasFixedSize(true);//当确定Item的改变不影响RecyclerView的宽高的时候可以设置setHasFixedSize(true);
    recyclerView.setLastLineItemHandKey(true, false, true, false);//设置四个边缘是否消耗按键,屏幕的上下左右
         gridLayoutManager = new GridLayoutManager(this, 4, LinearLayoutManager.HORIZONTAL, false);
    recyclerView.setLayoutManager(gridLayoutManager);
    recyclerView.setOnLoadMoreListener(this);//自定义加载更多的回调接口
    recyclerView.addItemDecoration(new DividerGridItemDecoration(Color.GRAY, 20, 20, Color.GREEN, 20));
    /**
     * Creator:David
     * 设置需要占用多行或列的Item的占用列数
         */
    gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {

        @Override
        public int getSpanSize(int position) {
            switch (rvAdapter.getItemViewType(position)) {
                case 2:// ItemViewType2时,让该Item占用两行或两列
                    return 2;
                default:
                    return 1;
            }
        }
    });
    //初始化完成开始创建数据源并加载数据
        pageNumber = 0;
    createData(pageNumber);
}

        2:Adapter和Holder的处理:

          这里在Library_recyclerView中,定义了他们两个的抽象类,我们在书写的时候可以直接继承,减少了相同代码的重复书写。只简单介绍用法,Livrary_recyclerView可以下载带全部效果的项目,然后在项目中查看。

        先看用法:

    

    private void createData(int pageNumber) {
        RecommendBean model;
        for (int i = pageNumber * pageCount; i < (pageNumber + 1) * pageCount; i++) {
            model = new RecommendBean();
            model.type = i == 0 ? 2 : 0;
            model.title = "" + i;
            model.imgResColor = getResources().getColor(R.color.color_d7292b);
            data.add(model);
        }
        if (rvAdapter == null) {
            rvAdapter = new GridMiddleAdapter(this, data);
            recyclerView.setAdapter(rvAdapter);
        } else {
            recyclerView.setLoadMoreComplete();//加载完成时调用
                     rvAdapter.notifyDataSetChanged();
            /**
             * Creator:David
             * 此处可能会需要另外的两种处理方法,方法中重写了 notifyDataSetChanged();
             */
//            rvAdapter.setDataList(data);
//            //or
//            rvAdapter.addDataList(data);
        }

    }
          再看Adapter: RvAdapter是可以自己按需求修改的,方便开发。

    

public class GridMiddleAdapter extends RVAdapter<RecommendBean, RVHolder> {

    public GridMiddleAdapter(Context context, List<RecommendBean> mDataList) {
        super(context, mDataList);
    }

    @Override
    public RVHolder newViewHolder(ViewGroup parent, int viewType) {
        View view = null;
        RVHolder holder = null;
        switch (viewType) {
            case 1:
                view = mInflater.inflate(R.layout.item_grid_page_diff_span, parent, false);
                holder = new Holder(view, AbsFocusEffectView.FocusType.FOCUS_POSTER, true, true);
                break;
            case 2:
                view = mInflater.inflate(R.layout.item_grid_page_group, parent, false);
                holder = new HolderGroup(view, AbsFocusEffectView.FocusType.FOCUS_POSTER, true, true);
                break;
            default:
                view = mInflater.inflate(R.layout.item_grid_page_h, parent, false);
                holder = new Holder(view, AbsFocusEffectView.FocusType.FOCUS_POSTER, true, true);
                break;
        }

        return holder;
    }

    @Override
    public int getItemViewType(int position) {
        return getItem(position).type;
    }
}
         然后是Holder们:在RVHolder中可以自己按需求定义方法,方便开发。

    

public class Holder extends RVHolder<RecommendBean> {

    @BindView(R.id.recommend_img)
    ImageView bg;
    @BindView(R.id.recommend_title)
    TextView title;

    public Holder(View itemView, String focusViewType, boolean isFocusScaleAnim, boolean isTranslateAnim) {
        super(itemView, focusViewType, isFocusScaleAnim, isTranslateAnim);
        ButterKnife.bind(this, itemView);
    }

    @Override
    public void bindData(RecommendBean model, int position, int state, int dx, int dy, int firstVisPosition, int lastVisPosition) {
        /**
         * Creator:David
         * Item做标记,区分不需要焦点居中的Item                */
        if (position < 3) {
            this.itemView.setTag(R.id.focus_item_view_type, 1);
        }
        if (model != null) {
            bg.setBackgroundColor(model.imgResColor);
            title.setText(model.title);
        }
    }

    @Override
    public void onItemClick(View view) {
        if (getAdapterPosition() == 30) {
            recyclerView.smoothScrollToPosition(0);
        }
        Toast.makeText(view.getContext(), "点击中间焦点:" + getAdapterPosition(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onFocusChanged(View view, boolean b) {

    }

    @Override
    public void destory() {

    }
}
public class HolderGroup extends RVHolder<RecommendBean> {

    @BindView(R.id.recommend_img)
    ImageView bg;
    @BindView(R.id.recommend_title)
    TextView title;
    @BindView(R.id.item1)
    FrameLayout item1;
    @BindView(R.id.recommend_img2)
    ImageView bg2;
    @BindView(R.id.recommend_title2)
    TextView title2;
    @BindView(R.id.item2)
    FrameLayout item2;
    @BindView(R.id.recommend_img3)
    ImageView bg3;
    @BindView(R.id.recommend_title3)
    TextView title3;
    @BindView(R.id.item3)
    FrameLayout item3;
    @BindView(R.id.recommend_img4)
    ImageView bg4;
    @BindView(R.id.recommend_title4)
    TextView title4;
    @BindView(R.id.item4)
    FrameLayout item4;
    @BindView(R.id.recommend_img5)
    ImageView bg5;
    @BindView(R.id.recommend_title5)
    TextView title5;
    @BindView(R.id.item5)
    FrameLayout item5;

    private List<ImageView> images;
    private List<TextView> tvs;

    public HolderGroup(View itemView, String focusViewType, boolean isFocusScaleAnim, boolean isTranslateAnim) {
        super(itemView, focusViewType, isFocusScaleAnim, isTranslateAnim);
        ButterKnife.bind(this, itemView);
        /**
         * Creator:David
         * 初始化Group中的焦点View
         */
        initView(item1, focusViewType, isFocusScaleAnim, isTranslateAnim);
        initView(item2, focusViewType, isFocusScaleAnim, isTranslateAnim);
        initView(item3, focusViewType, isFocusScaleAnim, isTranslateAnim);
        initView(item4, focusViewType, isFocusScaleAnim, isTranslateAnim);
        initView(item5, focusViewType, isFocusScaleAnim, isTranslateAnim);

        images = new ArrayList<>();
        images.add(bg);
        images.add(bg2);
        images.add(bg3);
        images.add(bg4);
        images.add(bg5);

        tvs = new ArrayList<>();
        tvs.add(title);
        tvs.add(title2);
        tvs.add(title3);
        tvs.add(title4);
        tvs.add(title5);
    }

    @Override
    public void bindData(RecommendBean model, int position, int state, int dx, int dy, int firstVisPosition, int lastVisPosition) {
        /**
        * Creator:David
        * Item做标记,区分不需要焦点居中的Item              */
        if (position < 3) {
            this.itemView.setTag(R.id.focus_item_view_type, 1);
        }
        if (model != null) {
            for (ImageView iv : images) {
                showImageViewColor(iv, model.imgResColor);
            }
            for (TextView tv : tvs) {
                showTextview(tv, model.title);
            }
        }
 
    }

    @Override
    public void onItemClick(View view) {
        String clickViewwID;
        switch (view.getId()) {
            case R.id.item1:
                clickViewwID = "R.id.item1";
                break;
            case R.id.item2:
                clickViewwID = "R.id.item2";
                break;
            case R.id.item3:
                clickViewwID = "R.id.item3";
                break;
            case R.id.item4:
                clickViewwID = "R.id.item4";
                break;
            case R.id.item5:
                clickViewwID = "R.id.item5";
                break;
            default:
                clickViewwID = null;
                break;

        }
        Toast.makeText(view.getContext(), "点击:" + getAdapterPosition() + ";ViewID:" +
                clickViewwID, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onFocusChanged(View view, boolean b) {

    }

    @Override
    public void destory() {

    }
}

OK  需要自己写的东西基本上就结束了,其实很简单,不过,还是需要稍微理解一下library_recyclerview中的内容;

首先:目录结构:


 其实,这原先是个Module,但是个性使然,我不喜欢它,就把它移动到自己项目中,并且删掉了自己不需要的东西,你也可以去下载完整的依赖包和查看我参考的Demo,Demo中有所有可以实现的效果。


参考文献:https://github.com/ShuKeW/TVRecyclerViewAndFocus

本项目参考该项目,如有侵权,立即删除... ... 

==============================================
@ Name : David
@ email :david.forever.god@gmail.com
Learn from yesterday, live for today, hope for tomorrow.

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值