前阵子将一个手机APP改为TV应用,由于首次开发TV,故把开发过程中的一些问题记录下来,以备不时之需。电视应用和手机应用开发过程大同小异,电视应用主要注意三个地方:1是清单文件,2是布局文件,3是处理好控件获取焦点时的背景显示,因为对于没有触控功能的电视设备,用户想要点击某个控件时,只能先操作遥控器的方向键将焦点移到该控件上,接着才能按遥控器的确定键执行点击,所以就需要处理好控件获取焦点时的背景显示(区别于没有获取焦点的其他控件),以便用户能一眼看出来现在是哪个控件在获取焦点并可点击。
电视上的android系统一般不支持以下硬件功能:
因此,需要在清单文件中将以上特性声明为非必须的,你的应用才能安装在不支持这些特性的电视上,如下:
当然,如果你的应用的某个功能需要用到以上某个特性,你应该在代码中判断设备是否支持改特性,以便做出对应的处理逻辑,怎么判断呢:
是否支持拨打电话:
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
//支持电话拨打
}
是否触摸屏
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
//是触摸屏
}
是否可开启相机
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
//可开启相机
}
其他特性判断就不再赘述。
如上图,应用的启动图除了提供常规的手机APP的logo外,还需要另外提供一张横幅图片放在banner属性下,什么是横幅?请看下图:
横幅图片分辨率一般建议320*180的,放在xhdpi的图片资源目录下。
另外,当 TV 应用启动时,系统会显示动画,就像一个不断膨胀的实心圆。要自定义此动画的颜色,请将 TV 应用或 Activity 的 android:colorPrimary 属性设为特定颜色。此外,还应将另外两个过渡重叠属性设为 true,如主题背景资源 XML 文件中的以下代码段所示:
电视应用的启动页需要将intent-filter的categorycategory值从LAUNCHER改为LEANBACK_LAUNCHER:
否则,你的应用将不会在电视桌面或横幅列表上显示出来,反正,LEANBACK_LAUNCHER的电视应用也不会在手机桌面显示。
config属性需要配置navigation值,因为当用户在操作键盘方向键切换导航时,activity是会重走生命周期方法的,这是为了不让其重走生命周期方法:
另外,电视应用不支持竖屏显示,所以我们可以直接在清单文件中将activity的屏幕方向限定为横屏.
仔细看上图,“拨号”图片当前是获取焦点的,它相对于其他三张图片放大了。要想图片获取焦点时放大,不获取焦点时缩小到正常水平,其实只需要重写AppCompatImageView的onFocusChanged方法即可:
/**
* @author Administrator
* @time 2019/9/5 17:12
* @des ${TODO}
* @updateAuthor $Author$
* @updateDate $Date$
* @updateDes ${TODO}
* 获取焦点放大的ImageView
*/
public class ScaleWithFocusImageView extends AppCompatImageView {
private FocusedListener mFocusedListener;
public ScaleWithFocusImageView(Context context) {
super(context);
}
public ScaleWithFocusImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFocusChanged(boolean gainFocus, int direction,
@Nullable Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
ViewCompat.animate(this)
.scaleX(gainFocus ? 1.3f : 1.0f)//x轴方向的缩放
.scaleY(gainFocus ? 1.3f : 1.0f)//y轴方向的缩放
.translationZ(gainFocus ? 1f : 0f)//z轴方向的移动
.start();
if (mFocusedListener != null) {
mFocusedListener.focused(gainFocus);
}
}
public void setFocusedListener(FocusedListener focusedListener) {
mFocusedListener = focusedListener;
}
}
上图的“拨号”和“通讯录”分别是两个Fragment的导航标题,当前获取焦点的是“拨号”的TextView,获取焦点时放大了,并且文字颜色为纯白色,相较于“通讯录”,还是很容易看得出来的。下面是重新改TextView的代码
public class FocuseScaleRadiubutton extends AppCompatRadioButton {
private int mWhiteColor;
private int mWhiteColor70Transp;
public FocuseScaleRadiubutton(Context context) {
super(context);
init();
}
public FocuseScaleRadiubutton(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mWhiteColor = getResources().getColor(R.color.color_white);
mWhiteColor70Transp = getResources().getColor(R.color.color_70_transp_white);
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
ViewCompat.animate(this)
.scaleX(focused ? 1.15f : 1.0f)
.scaleY(focused ? 1.15f : 1.0f)
.translationZ(focused ? 1.0f : 0f)
.start();
setTextColor(focused ? mWhiteColor : mWhiteColor70Transp);
}
}
同样的,为了时recyclerview的itemitem能够获取焦点,需要给item的布局根节点设置focusable为true,clickable为true。还是看上面那个拨号界面:
当前获取焦点的是RecyclerView中的第二个item,我这里是显示了一个红色的线框来区别,当然,你也可以对这个item放大来区别,或者周边高亮阴影来区别。下面代码是该item中的状态背景:
这个场景主要是用于当监听到用户按下了遥控建时做对应的逻辑,例如视频播放界面,当全屏播放时,用户一段时间不操作遥控后,就隐藏某些按钮,当用户按下遥控时,再显示隐藏的按钮,只需要重写Activity的onKeyDown方法即可:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((event.getSource() & InputDevice.SOURCE_DPAD)
!= InputDevice.SOURCE_DPAD) {
//用户按了遥控
}
return super.onKeyDown(keyCode, event);
}
一般不需要处理用户操作遥控器方向键(上、下、左、右)时的导航顺序,如果有这个需求,可以根据下面属性在布局文件中指定你的导航顺序:
如果你想在最后一个控件获取焦点后,继续按右方向键的话导航到第一个控件,那么你只需要将最后一个控件中nextFocusRight的值设为第一个控件的id即可。