欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tv开发初体验 焦点移动

程序员文章站 2022-07-14 23:19:47
...

  开发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 获取不到焦点。需要我们做一些配置。
  Tv开发初体验 焦点移动
  Tv开发初体验 焦点移动

相关标签: 电视 app 移动