在新公司干了快一个月了,感觉对tv开发有了一定的经验。主要对recyclerview有了一定的认识。
1.调试盒子。
盒子的ip地址与电脑的ip地址保持一致
adb connect ip地址
adb uninstall 包名
2.焦点控制需要的一些方法。
requestFocus() //获得焦点
setNextFocusXXX//就是让下一个按上下左右键聚焦的view
3.Recyclerview的用法
TV布局:1.如果是简单的页面(死界面),建议直接就用textview直接布局,焦点逻辑用setNextFocusXXX就可以了。像这种:
动态界面:较复杂的那种,就需要自定义recyclerview,像:
主要是一个recyclerview来布局这个页面,用到recyclerview的分类,gridlayout的“合并单元格”的知识点。这个已经总结过了。下面开始总结我开发中问道的头疼的问题。
.滑动焦点错乱了
.当页面遥控器控制不了滑动了怎么办
.如何判定分页以及分页加载刷新后焦点逻辑
.删除后我的焦点该如何控制
.导航栏下来如何保存导航的状态以及第一个聚焦的始终是第一个item。
滑动焦点错乱了
.当遥控一直按住的时候,突然不知道焦点飞哪去了。
1.看看position是否在乱跳,我的主要是position乱跳导致焦点乱跳,然后我把布局都用LinearLayout限定宽高解决的
2.当遥控器按到底部的时候,如果底部还有数据但并没有显示在屏幕上,可能也会导致焦点乱跳。所以尽量让底部的界面露出来一些像上图一样。当然也可代码判断当焦点失去的时候,让页面先滑上来一些
3.什么去除recyclerview的动画以及写找不到焦点的处理方法
private void initView() {
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setHasFixedSize(true);
setWillNotDraw(true);
setOverScrollMode(View.OVER_SCROLL_NEVER);
setChildrenDrawingOrderEnabled(true);
setClipChildren(false);
setClipToPadding(false);
setClickable(false);
setFocusable(true);
setFocusableInTouchMode(true);
/**
防止RecyclerView刷新时焦点不错乱bug的步骤如下:
(1)adapter执行setHasStableIds(true)方法
(2)重写getItemId()方法,让每个view都有各自的id
(3)RecyclerView的动画必须去掉
*/
setItemAnimator(null);
}
onSearchFocusfailed方法百度很多,我没用到过
当页面遥控器控制不了滑动了怎么办
当遥控器向下按时没问题的,但向上按的时候滑不上去了比如下图:
此时屏幕上部应该还有数据,但并不显示在屏幕上,此时如果焦点在第一个item上,遥控器向上按就没反应,超出屏幕的部分应该是获取不到焦点的。
解决方案有两种:
1.同事的方案:
package com.familybox.ui.family.widget;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
/**
* 日期:2018/4/4
* <p>
* 作者:xudiwei
* <p>
* 描述:用于TV垂直方向的RecyclerView.
*/
public class ParentRecyclerView extends RecyclerView {
private static final String TAG = "ParentSchoolRecyclerVie";
public ParentRecyclerView(Context context) {
super(context);
init();
}
public ParentRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ParentRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setClipChildren(false);
setClipToPadding(false);
setWillNotDraw(false);
setChildrenDrawingOrderEnabled(true);
}
@Override
public void smoothScrollBy(int dx, int dy) {
if (dy > 0) {
super.smoothScrollBy(dx, dy + 350);
} else if (dy < 0) {
super.smoothScrollBy(dx, dy - 350);
} else {
super.smoothScrollBy(dx, dy);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d(TAG, "dispatchKeyEvent:--> "+event.isLongPress());
return super.dispatchKeyEvent(event);
}
private int focusViewIndex;
private int focusId;
/**
* 自定义重绘排序,好让获取焦点的子view最后绘制,这样子view放大时不会被遮住.
*
* @param childCount
* @param i
* @return
*/
@Override
protected int getChildDrawingOrder(int childCount, int i) {
focusViewIndex = indexOfChild(getFocusedChild());
Log.d(TAG, "getChildDrawingOrder:--> Index: " + focusViewIndex);
if (focusViewIndex == -1) {
return i;
}
if (focusViewIndex == i) {
focusId = i;
return childCount - 1;
} else if (i == childCount - 1) {
return focusId;
} else {
return i;
}
}
}
重写了smoothScrollby的方法,让每次recyclerview默认滑动的时候多滑一段距离。就是始终保持顶部会露出一点(有缺陷的,不适用于这个界面,适用于界面比较固定,不会有二级标题的那种
焦点搜索失败同样也处理了
package com.familybox.ui.family.widget;
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
/**
* 日期:2018/4/8
* <p>
* 作者:XXX
* <p>
* 描述:处理找不到焦点时的处理方式的GridLayoutManager
*/
public class FocusGridLayoutManager extends GridLayoutManager {
private static final String TAG = "FocusGridLayoutManager";
public FocusGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public FocusGridLayoutManager(Context context, int spanCount) {
super(context, spanCount);
}
public FocusGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
super(context, spanCount, orientation, reverseLayout);
}
/* @Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}*/
@Override
public View onFocusSearchFailed(View focused, int focusDirection, RecyclerView.Recycler recycler, RecyclerView.State state) {
// ViewParent parent = focused.getParent();
// if (parent instanceof RecyclerView) {
// RecyclerView recyclerView = (RecyclerView) parent;
// if (focusDirection == View.FOCUS_DOWN) {
// recyclerView.smoothScrollBy(0, 1);
// } else if (focusDirection == View.FOCUS_UP) {
// recyclerView.smoothScrollBy(0, -1);
// }
// }
return focused;
// return super.onFocusSearchFailed(focused, focusDirection, recycler, state);
}
}
我的方案就是重写了recyclerview分发事件的方法,当滑动的时候如果焦点得不到了(指的是超出屏幕外,就让它滑出来)缺点也很明显,先滑出来再按一下才聚焦
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.i("VVCCC::", event.getKeyCode() + "");
if (mInterceptLister != null && mInterceptLister.onIntercept(event)) {
return true;
}
if (KeyEvent.KEYCODE_BACK == event.getKeyCode()) {
Log.i("RRRRFF::", "isreturn");
return false;
}
boolean result = super.dispatchKeyEvent(event);
View focusView = this.getFocusedChild();
currentfocus = getChildPosition(focusView);//把当前的聚焦对象的位置记录下来
if (focusView == null) {
Log.d(TAG, "dispatchKeyEvent:--> focusView == null" + currentfocus);
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("FFDDD225::", "");
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_SETTINGS:
if (mOnLoadMoreListener != null) {
mOnLoadMoreListener.ParentsSubscribe();
}
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);
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();
} else {
this.smoothScrollBy(0, dy);
}
if (mOnLoadMoreListener != null) {
if (downView == null) {
if (isSlideToBottom(this)) {
mOnLoadMoreListener.onLoadMore(currentfocus);
}
}
}
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) {
Log.i("FFDDD::", "66666");
return true;
} else {
if (upView != null) {
Log.i("FFDDD::", "DDDD");
View topView = FocusFinder.getInstance().findNextFocus(this, upView, View.FOCUS_UP);
if (topView != null) {
this.smoothScrollBy(0, -dy);
} else {
//this.smoothMoveToPosition(0);
// this.smoothScrollBy(0, -this.getChildAt(0).getHeight()*2);
}
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;
}
如何判定分页以及分页加载刷新后焦点逻辑
判定分页,也就是判定滑动到底部了,通常按道理来说可以和手机recyeclerview一样在这里处理
@Override
public void onScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE) {
// 加载更多回调,个人认为要保存一下上次焦点的位置
/*if (null != mOnLoadMoreListener) {
if (getLastVisiblePosition() ==getAdapter().getItemCount() - (1 + mLoadMoreBeforehandCount)) {
mOnLoadMoreListener.onLoadMore(currentfocus);
isBottom=true;
}else{
isBottom=false;
}
}*/
}
super.onScrollStateChanged(state);
if (mShouldScroll) {
mShouldScroll = false;
smoothMoveToPosition(mToPosition);
}
}
但我用了之后发现了一个问题:就是我遥控器控制的焦点还没有到底部的时候,就触发了。我的方案是在遥控器下按的时候,判断是否完全到了屏幕底部,在触发加载更多的事件
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, "