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 获取不到焦点。需要我们做一些配置。
上一篇: 好好学习,少熬夜(文不对题)