Tv开发初体验 焦点移动

  开发tv项目 与传统app项目的差别其中之一是焦点问题控制,今天就错略说下焦点控制问题,传统app 项目 在做事件触发一般是通过点击和触摸。但是Tv开发由于一般的电视都是要通过遥控器来控制,所以tv项目是要处理遥控器按键的。如何根据遥控器按键来做相应的处理就是问题的关键。
  首先第一步就是监听按键的事件。这个可以通过dispatcKeyEvent 方法来处理。获取到了用户按键的事件
  获取到了按键 我们就可以 在用户按下按键的时候让某些控件获取焦点。并且通过控件的焦点变化监听 。在里面对该控件进行相应的背景变化或者做相应的动画效果。
  第二步 如何移动的时候获取下一个应该拿到焦点的控件。实际开发中可以根据情况来判断。如果相邻的控件是都要获取焦点的可以通过
FocusFinder.getInstance().findNextFocus(this,focusView,View.FOCUS_RIGHT);
这个方法就可以拿到当前焦点view 的下一个焦点view ,如果拿到了我们就可以让下一个焦点view 来requestfocus 获取焦点。这样就实现了相邻view 的焦点移动。
  其实焦点移动方面。知道以上两点就可以差不多完成了。下面我附上一个recyclerview 在tv 上的焦点移动 代码。供大家参考
首先是recyclerview 的 他主要处理按键和焦点的移动 这里增加了改变recyclerview 的绘制顺序。避免放大后item被其他相邻item遮挡问题。让焦点item最后绘制。

    public class MyRecyclerView extends RecyclerView {

    private int mSelectedPosition;

    public MyRecyclerView(Context context) {
        super(context);
        init();
    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();

    }

    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        //启用子视图排序功能
        setChildrenDrawingOrderEnabled(true);
    }

    @Override
    public void onDraw(Canvas c) {
        mSelectedPosition = getChildAdapterPosition(getFocusedChild());
        super.onDraw(c);
    }
    //改变 绘制顺序让焦点view 最后绘制。避免放大被其他控件遮挡
    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        int position = mSelectedPosition;
        if (position < 0) {
            return i;
        } else {
            if (i == childCount - 1) {
                if (position > i) {
                    position = i;
                }
                return position;
            }
            if (i == position) {
                return childCount - 1;
            }
        }
        return i;
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return ev.getAction() == MotionEvent.ACTION_MOVE || super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        boolean result =super.dispatchKeyEvent(event);
        int  dx=this.getChildAt(0).getWidth();
        View focusView=this.getFocusedChild();
        if(focusView!=null){
            switch(event.getKeyCode()){
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if(event.getAction()==KeyEvent.ACTION_UP){
                        return true;
                    }else{
                        View rightView= FocusFinder.getInstance().findNextFocus(this,focusView,View.FOCUS_RIGHT);
                        // 在recyclerview 的最右侧的item 的时候是拿不到下一个焦点view 的。这时候可以通过else 来进行处理。
                        if(rightView!=null){
                            //拿到了下一个焦点view 那么让他获取到焦点。
                            boolean b = rightView.requestFocusFromTouch();
                            return true;
                        }else{
                            //如果recyclerview控件宽度不够 显示不全时 可通过次方法移动 recyclerview
                            this.smoothScrollBy(dx,0);
                            return true;
                        }
                    }
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    if(event.getAction()==KeyEvent.ACTION_UP){
                        return true;
                    }else{
                        View leftView=FocusFinder.getInstance().findNextFocus(this,focusView,View.FOCUS_LEFT);
                        if(leftView!=null){
                            leftView.requestFocusFromTouch();
                            return true;
                        }else{
                            this.smoothScrollBy(-dx,0);
                            return true;
                        }
                    }

            }

        }
        return result;
    }

}
然后是adapter 的主要处理item 获取到了焦点的动画处理 
public abstract class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyHolder> {
    Context context;
    public MyRecyclerAdapter(Context context){
        this.context=context;
    }
    @Override
    public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyHolder myHolder=new MyHolder(View.inflate(context,R.layout.item_layout,null));
        return myHolder;
    }
    @SuppressLint("WrongConstant")
    public void showTaos(String msg){
        Toast.makeText(context,msg,0).show();
    }
    @Override
    public void onBindViewHolder(final MyHolder holder, int position) {
        holder.itemview.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean b) {
                if (b) {
                    focusStatus(view);
                } else {
                    normalStatus(view);
                }
            }
        });
        if(position==0){
            //延时申请焦点 避免布局没有刷新完毕就 执行动画产生空指针等问题
                holder.itemview.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        boolean b = holder.itemview.requestFocus();
                    }
                },500);
        }
    }

    private void focusStatus(View itemView){
        if(itemView==null){
            return;
        }
        onItemFocus(itemView);
        if(Build.VERSION.SDK_INT>=21){
            //太高z抽
            ViewCompat.animate(itemView).scaleX(1.10f).scaleY(1.10f).translationZ(1).start();
        }else{
            ViewCompat.animate(itemView).scaleX(1.10f).scaleY(1.10f).start();
            ViewGroup parent = (ViewGroup) itemView.getParent();
            parent.requestLayout();
            parent.invalidate();
        }

    }
    /**
     * 当item获得焦点时处理
     *
     * @param itemView itemView
     */
    protected abstract void onItemFocus(View itemView);
    /**
     * item失去焦点时
     *
     * @param itemView item对应的View
     */
    private void normalStatus(View itemView) {
        if (itemView == null) {
            return;
        }
        if (Build.VERSION.SDK_INT >= 21) {
            ViewCompat.animate(itemView).scaleX(1.0f).scaleY(1.0f).translationZ(0).start();
        } else {
            ViewCompat.animate(itemView).scaleX(1.0f).scaleY(1.0f).start();
            ViewGroup parent = (ViewGroup) itemView.getParent();
            parent.requestLayout();
            parent.invalidate();
        }
        onItemGetNormal(itemView);
    }
    /**
     * 当条目失去焦点时调用
     *
     * @param itemView 条目对应的View
     */
    protected abstract void onItemGetNormal(View itemView);
    @Override
    public int getItemCount() {
        return 20;
    }

    class MyHolder extends  RecyclerView.ViewHolder{
        View itemview;
        public MyHolder(View itemView) {
            super(itemView);
            this.itemview=itemView;
        }
    }

}

mainactivity 中 

GridLayoutManager gridLayoutManager=new GridLayoutManager(getApplicationContext(),3);
        rcv.setLayoutManager(gridLayoutManager);
        rcv.setAdapter(new MyRecyclerAdapter(getApplicationContext()) {
            @Override
            protected void onItemFocus(View itemView) {

            }
            @Override
            protected void onItemGetNormal(View itemView) {

            }
        });

后面附上一个可以获取焦点自动放大和加边框的LinearLayout 但是边框简单有需要的可以重新配置
public class TvLinearLayout extends LinearLayout {

    private Canvas mcanvas;
    private Paint paint;

    public TvLinearLayout(Context context) {
        super(context);
        init();
    }

    public TvLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TvLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    boolean isFocus = false;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i("2024", "===绘制");

        if (isFocus) {
            //画边框
            Rect rec = canvas.getClipBounds();
            paint = new Paint();
            //设置边框颜色
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);
            //设置边框宽度
            paint.setStrokeWidth(2f);
            canvas.drawRect(rec, paint);
        } else {
            //失去焦点啥也不花
        }

    }

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
        if (gainFocus) {
            invalidate();
            isFocus = true;
            if (Build.VERSION.SDK_INT >= 21) {
                //太高z抽
                ViewCompat.animate(this).scaleX(1.10f).scaleY(1.10f).translationZ(1).start();
            } else {
                ViewCompat.animate(this).scaleX(1.50f).scaleY(1.50f).start();
                ViewGroup parent = (ViewGroup) getParent();
                parent.requestLayout();
                parent.invalidate();
            }
        } else {
            invalidate();
            isFocus = false;
            if (Build.VERSION.SDK_INT >= 21) {
                ViewCompat.animate(this).scaleX(1.0f).scaleY(1.0f).translationZ(0).start();
            } else {
                ViewCompat.animate(this).scaleX(1.0f).scaleY(1.0f).start();
                ViewGroup parent = (ViewGroup) getParent();
                parent.requestLayout();
                parent.invalidate();
            }
        }
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);

    }

    private void init() {
        //设置 可以获得焦点。
        setFocusableInTouchMode(true);
        //调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag  才能让LinearLayout 走ondraw 方法看源码可看到
        setWillNotDraw(false);
    }

}

  实际操作中还发现一个问题就是 item 获取不到焦点。需要我们做一些配置。
  这里写图片描述
  这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值