积累一些关于ANDROID TV开发的经验

本文分享了Android TV开发经验,重点讨论了Recyclerview在电视端的应用,包括焦点控制、内容分类加载、加载更多功能的实现。通过自定义RecyclerView解决焦点错乱问题,使用布局切换处理选中效果,并探讨了电视UI设计的固定性及其对项目的影响。此外,还提到了Android开发的挑战,如自定义View和混合开发的学习。
摘要由CSDN通过智能技术生成

    新公司业务主要是电视端。之前也做过,但也是浅尝辄止。主要是recyclerview与焦点的控制

    

技术点:

1.上面导航栏遥控器切换,下面要刷新数据

2.内容的分类,分页(加载更多),上下滑动的时候导航栏跟着动

3.焦点选中效果

4.导航栏下按的时候,跳到内容的第一个item,同理第一排内容的item上按的时候跳到对应的导航栏

解决方案:

1.用一个recyclerview来做这个页面

a.item的分类:头部+标题+内容

b.合并item

  RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if (position==0||getItemViewType(position)==2){
                        return 5;
                    } else {
                        return 1;
                    }
                }
            });
        }

这个方法的意思呢,当item是头部或者标题的时候,我将我的5列合成一列,是内容的时候不合并。假设我有一个页面,第一行有2个item,第二行有三个item,第三行有4个item,那么我的girdlayoutmanager设置的spancount应该是2,3,4的最小公倍数12。两列的时候return6,三列的时候return4,四列的时候return3。知道这个加上recyclerview的分类那么剩下的事情就是数据扁平化了。

2。焦点控制。

     首先要确定的一点是,遥控器的确定事件就是onclick。其余的就是不要在activity里实现onkeydown方法,这样代码很乱,最好自定义recyclerview。如果不自定义的话,那么从导航栏按下,下个聚焦的不是内容的第一个,而是中间,此时要在recyclerview的dispatchKeyEvent方法处理key_down事件

  case KeyEvent.KEYCODE_DPAD_DOWN:
                        //让第一个默认选中
                        View lView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
                        View rView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);
                        View downView;
                        if (lView==null&&rView==null){
                            //焦点左边的view与焦点右边的view都是空的话,说明此事焦点在头部,
                            // 说明此时是从导航栏按下来,要让第一个item获得焦点
                            downView= getChildAt(2);//第一个item就是第二个position的位置

                        }else{
                          downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);
                        }

                        Log.i(TAG, " downView is null:" + (downView == null));
                        if (downView != null) {
                            downView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(0, dy);
                            return true;
                        }

从第一排往上按回到指定的导航button的处理

思路:焦点上去的时候不要让导航的button去获取焦点,而是传给导航的linearlayout。也就是三个button不用focus监听,之间用setSelect(true)来切换图片文字。我知道在左右的keydown事件从处理。当然得先知道此时是导航的linearlayout获取焦点。

                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
                        View rightViews = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);

                        //左右两边都是空说明此事焦点在头部的linearlayout
                        if (rightViews==null&&leftView==null){
                            if (getAdapter()!=null){
                                ( (PersonalityGrowthAdapter)getAdapter()).func(-1);
                            }
                        }
        case KeyEvent.KEYCODE_DPAD_RIGHT:
                        View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);

                        View  leftViews=FocusFinder.getInstance().findNextFocus(this,focusView,View.FOCUS_LEFT);

                        if (rightView==null&&leftViews==null){
                            if (getAdapter()!=null){
                                ( (PersonalityGrowthAdapter)getAdapter()).func(1);
                            }
                        }
 //用于导航栏左右逻辑
    public void func(int dir) {
            //当前是否在导航栏上,正数向右,负数向左
            Toast.makeText(context,"当前在导航栏上", Toast.LENGTH_SHORT).show();
            if (dir > 0) {
                if (currentNavIndex >= 0 && currentNavIndex < 2) {
                    currentNavIndex++;
                }
                for (int i = 0; i < ll_tbar.getChildCount(); i++) {
                    ll_tbar.getChildAt(i).setSelected(false);
                    TextPaint tp = ((TextView) ll_tbar.getChildAt(i)).getPaint();
                    tp.setFakeBoldText(false);
                }
                ll_tbar.getChildAt(currentNavIndex).setSelected(true);

                TextPaint tp = ((TextView) ll_tbar.getChildAt(currentNavIndex)).getPaint();
                tp.setFakeBoldText(true);
            }

            if (dir < 0) {
                if (currentNavIndex > 0 && currentNavIndex <= 2) {
                    currentNavIndex--;
                }
                for (int i = 0; i < ll_tbar.getChildCount(); i++) {
                    ll_tbar.getChildAt(i).setSelected(false);
                    TextPaint tp = ((TextView) ll_tbar.getChildAt(i)).getPaint();
                    tp.setFakeBoldText(false);
                }
                ll_tbar.getChildAt(currentNavIndex).setSelected(true);
                TextPaint tp = ((TextView) ll_tbar.getChildAt(currentNavIndex)).getPaint();
                tp.setFakeBoldText(true);
            }


            if (remoteControlListener!=null){
                remoteControlListener.changeNav(currentNavIndex);
            }

    }

3.选中布局的切换,这主要是才用组件的显示与隐藏

  holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                    @Override
                    public void onFocusChange(View view, boolean isfous) {
                        if (isfous){
                            mCurrentfocus=position;
                            Drawable drawable=context.getResources().getDrawable(R.drawable.border_color);
                            ((PGView_ContentHolder)holder).cv_01.setForeground(drawable);

                            if (!testData01.isFunc()){
                                ((PGView_ContentHolder)holder).tv_content_s.setVisibility(View.VISIBLE);
                                ((PGView_ContentHolder)holder).tv_content.setVisibility(View.GONE);
                            }

                            enlargeAnim(view);
                        }else{
                            ((PGView_ContentHolder)holder).cv_01.setForeground(null);
                            if (!testData01.isFunc()){
                                ((PGView_ContentHolder)holder).tv_content_s.setVisibility(View.GONE);
                                ((PGView_ContentHolder)holder).tv_content.setVisibility(View.VISIBLE);
                            }
                            reduceAnim(view);
                        }

                    }
                });

4.焦点错乱。一个是遥控器上按与下按由于recyclerview复用导致的焦点错乱。

        /**
         防止RecyclerView刷新时焦点不错乱bug的步骤如下:
         (1)adapter执行setHasStableIds(true)方法
         (2)重写getItemId()方法,让每个view都有各自的id
         (3)RecyclerView的动画必须去掉
         */
        setItemAnimator(null);
 @Override
    public boolean isInTouchMode() {
        // 解决4.4版本抢焦点的问题
        if (Build.VERSION.SDK_INT == 19) {
            return !(hasFocus() && !super.isInTouchMode());
        } else {
            return super.isInTouchMode();
        }

        //return super.isInTouchMode();
    }

 

 

 

还有一个是加载更多时候导致的错乱

    //加载新一页的数据

    public void loadmore(List<TestData01> list,int currentfocus) {
        int start=dataList.size()-1;
        int length=list.size();
        dataList.addAll(list);
        mCurrentfocus=currentfocus;
        notifyItemRangeChanged(start,length);//处理焦点的错乱
    }

5.下按与上按的时候要伴随着recyclerview的滑动

@Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInterceptLister != null && mInterceptLister.onIntercept(event)) {
            return true;
        }

        boolean result = super.dispatchKeyEvent(event);
        View focusView = this.getFocusedChild();

        currentfocus=getChildPosition(focusView);//把当前的聚焦对象的位置记录下来


        Log.d(TAG, "dispatchKeyEvent:--> focusView: "+currentfocus);
        if (focusView == null) {
            return result;
        } else {

            int dy = 0;
            int dx = 0;
            if (getChildCount() > 0) {
                View firstView = this.getChildAt(0);
                dy = firstView.getHeight();
                dx = firstView.getWidth();
            }
            if (event.getAction() == KeyEvent.ACTION_UP) {

                //Log.i(TAG, "遥控器向上按");
               // View UPView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP);

              //

                return true;
            } else {
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);

                        View  leftViews=FocusFinder.getInstance().findNextFocus(this,focusView,View.FOCUS_LEFT);

                        if (rightView==null&&leftViews==null){
                            if (getAdapter()!=null){
                                ( (PersonalityGrowthAdapter)getAdapter()).func(1);
                            }
                        }
                        Log.i(TAG, "rightView is null:" + (rightView == null));
                        Log.i(TAG, "leftViews is null:" + (leftViews == null));

                        if (rightView != null) {
                            rightView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(dx, 0);
                            return true;
                        }

                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
                        View rightViews = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);

                        //左右两边都是空说明此事焦点在头部的linearlayout
                        if (rightViews==null&&leftView==null){
                            if (getAdapter()!=null){
                                ( (PersonalityGrowthAdapter)getAdapter()).func(-1);
                            }
                        }
                        Log.i(TAG, "leftView is null:" + (leftView == null));
                        if (leftView != null) {
                            leftView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(-dx, 0);
                            return true;
                        }
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        //让第一个默认选中
                        View lView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);
                        View rView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);
                        View downView;
                        if (lView==null&&rView==null){
                            //焦点左边的view与焦点右边的view都是空的话,说明此事焦点在头部,
                            // 说明此时是从导航栏按下来,要让第一个item获得焦点
                            downView= getChildAt(2);//第一个item就是第二个position的位置

                        }else{
                          downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);
                        }

                        Log.i(TAG, " downView is null:" + (downView == null));
                        if (downView != null) {
                            downView.requestFocus();
                            return true;
                        } else {
                            this.smoothScrollBy(0, dy);
                            return true;
                        }
                    case KeyEvent.KEYCODE_DPAD_UP:
                        View upView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP);
                      //  Log.i(TAG, "upView is null:" + (upView == null));
                        if (event.getAction() == KeyEvent.ACTION_UP) {
                            return true;
                        } else {
                            if (upView != null) {
                                this.smoothScrollBy(0, -dy);
                                upView.requestFocus();

                               /* View upViews = FocusFinder.getInstance().findNextFocus(this, upView, View.FOCUS_UP);
                                View rViews = FocusFinder.getInstance().findNextFocus(this, upView, View.FOCUS_RIGHT);
                                View lViews = FocusFinder.getInstance().findNextFocus(this, upView, View.FOCUS_LEFT);

                                if (upViews==null&&rViews==null&&lViews==null){
                                    Log.i("FFDDD::","导航");
                                    upView.requestFocus();
                                   smoothScrollToPosition(0);
                                }else{
                                    Log.i("FFDDD::","不是导航");
                                    Log.i(TAG, "upView is null:" + (upView == null)+dy);
                                    this.smoothScrollBy(0, -dy);
                                    upView.requestFocus();
                                }*/
                                return true;
                            } else {

                                Log.i("FFDDD::","顶部view为空");
                                LayoutManager layoutManager = getLayoutManager();
                                int childLayoutPosition = getChildLayoutPosition(focusView);
                                if (layoutManager instanceof GridLayoutManager) {
                                    int spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
                                    if (childLayoutPosition <= spanCount) {
                                        return result;
                                    }
                                } else if (layoutManager instanceof LinearLayoutManager) {
                                    if (childLayoutPosition == 0) {
                                        return result;
                                    }
                                }
                                this.smoothScrollBy(0, -dy);
                                return true;
                            }

                        }
                }

            }

        }
        return result;
    }

还有就是细节问题了,间距之类的,这个就不谈了

附件:

自定义recyclerview

package com.familybox.ui.family.widget.thrid_party;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Re
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值