Tv开发初体验 焦点移动

来源:互联网 发布:钓鱼岛实际控制权知乎 编辑:程序博客网 时间:2024/06/06 06:46

  开发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 获取不到焦点。需要我们做一些配置。
  这里写图片描述
  这里写图片描述