前陣子將一個手機APP改爲TV應用,由於首次開發TV,故把開發過程中的一些問題記錄下來,以備不時之需。電視應用和手機應用開發過程大同小異,電視應用主要注意三個地方:1是清單文件,2是佈局文件,3是處理好控件獲取焦點時的背景顯示,因爲對於沒有觸控功能的電視設備,用戶想要點擊某個控件時,只能先操作遙控器的方向鍵將焦點移到該控件上,接着才能按遙控器的確定鍵執行點擊,所以就需要處理好控件獲取焦點時的背景顯示(區別於沒有獲取焦點的其他控件),以便用戶能一眼看出來現在是哪個控件在獲取焦點並可點擊。
1.清單文件需要改的地方
1.1 處理電視可能不支持的硬件功能
電視上的android系統一般不支持以下硬件功能:
因此,需要在清單文件中將以上特性聲明爲非必須的,你的應用才能安裝在不支持這些特性的電視上,如下:
<uses-feature android:name="android.hardware.touchscreen"
android:required="false"/>
<uses-feature android:name="android.hardware.faketouch"
android:required="false"/>
<uses-feature android:name="android.hardware.telephony"
android:required="false"/>
<uses-feature android:name="android.hardware.camera"
android:required="false"/>
<uses-feature android:name="android.hardware.nfc"
android:required="false"/>
<uses-feature android:name="android.hardware.location.gps"
android:required="false"/>
<uses-feature android:name="android.hardware.microphone"
android:required="false"/>
<uses-feature android:name="android.hardware.sensor"
android:required="false"/>
當然,如果你的應用的某個功能需要用到以上某個特性,你應該在代碼中判斷設備是否支持改特性,以便做出對應的處理邏輯,怎麼判斷呢:
是否支持撥打電話:
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
//支持電話撥打
}
是否觸摸屏
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
//是觸摸屏
}
是否可開啓相機
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
//可開啓相機
}
其他特性判斷就不再贅述。
1.2 application節點下
如上圖,應用的啓動圖除了提供常規的手機APP的logo外,還需要另外提供一張橫幅圖片放在banner屬性下,什麼是橫幅?請看下圖:
橫幅圖片分辨率一般建議320*180的,放在xhdpi的圖片資源目錄下。
另外,當 TV 應用啓動時,系統會顯示動畫,就像一個不斷膨脹的實心圓。要自定義此動畫的顏色,請將 TV 應用或 Activity 的 android:colorPrimary 屬性設爲特定顏色。此外,還應將另外兩個過渡重疊屬性設爲 true,如主題背景資源 XML 文件中的以下代碼段所示:
<resources>
<style ... >
<item name="android:colorPrimary">@color/primary</item>
<item name="android:windowAllowReturnTransitionOverlap">true</item>
<item name="android:windowAllowEnterTransitionOverlap">true</item>
</style>
</resources>
1.3 啓動頁
電視應用的啓動頁需要將intent-filter的categorycategory值從LAUNCHER改爲LEANBACK_LAUNCHER:
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
否則,你的應用將不會在電視桌面或橫幅列表上顯示出來,反正,LEANBACK_LAUNCHER的電視應用也不會在手機桌面顯示。
1.4 啓動activity的config配置
config屬性需要配置navigation值,因爲當用戶在操作鍵盤方向鍵切換導航時,activity是會重走生命週期方法的,這是爲了不讓其重走生命週期方法:
另外,電視應用不支持豎屏顯示,所以我們可以直接在清單文件中將activity的屏幕方向限定爲橫屏.
2.佈局文件
2.1 由於電視應用都是橫屏顯示,所以界面佈局文件要放在layout-land目錄下。
2.2 電視沒有狀態欄,所以應用主題最好用NoActionBar的。
2.3 對於沒有觸控功能的電視,fragment的切換最好不要藉助Viewpage來管理
2.4 對於界面內容過長時,根佈局最好用NestedScrollView
3.處理焦點
3.1 爲了控件能響應遙控器的方向鍵切換時獲取焦點,需要將控件的focusable屬性設爲true,這樣設置過後,你會發現當將應用裝在具有觸控功能的設備時,點擊這個控件時,需要點擊兩次才執行,這是因爲控件要先走focused,才走onclick,爲了避免這種情況,再將focusable屬性設爲true後,記得同時顯性的將控件的clickable也設爲true。
3.2 對於幾個同一級別或類型的ImageView,爲了區分當前是哪張圖片獲得焦點,可以用放大的方式將獲取焦點圖片區別出來,如下圖:
仔細看上圖,“撥號”圖片當前是獲取焦點的,它相對於其他三張圖片放大了。要想圖片獲取焦點時放大,不獲取焦點時縮小到正常水平,其實只需要重寫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;
}
}
3.3 對於幾個同一級別或類型的TextView,區分的方式可以包括縮放、文字顏色等,看下圖:
上圖的“撥號”和“通訊錄”分別是兩個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);
}
}
3.4 區別RecyclerView中的哪個Item獲取焦點
同樣的,爲了時recyclerview的itemitem能夠獲取焦點,需要給item的佈局根節點設置focusable爲true,clickable爲true。還是看上面那個撥號界面:
當前獲取焦點的是RecyclerView中的第二個item,我這裏是顯示了一個紅色的線框來區別,當然,你也可以對這個item放大來區別,或者周邊高亮陰影來區別。下面代碼是該item中的狀態背景:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!--獲取焦點時的背景-->
<item android:drawable="@drawable/shape_rec_red_stroke" android:state_focused="true"/>
<!--按壓時的背景-->
<item android:drawable="@drawable/shape_rec_red_stroke" android:state_pressed="true"/>
<!--焦點掠過時的背景-->
<item android:drawable="@drawable/shape_rec_red_stroke" android:state_hovered="true"/>
<!--失去焦點時的背景-->
<item android:drawable="@drawable/item_tv_create_video_meeting"/>
</selector>
3.5 監聽用戶是否按下了遙控
這個場景主要是用於當監聽到用戶按下了遙控建時做對應的邏輯,例如視頻播放界面,當全屏播放時,用戶一段時間不操作遙控後,就隱藏某些按鈕,當用戶按下遙控時,再顯示隱藏的按鈕,只需要重寫Activity的onKeyDown方法即可:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((event.getSource() & InputDevice.SOURCE_DPAD)
!= InputDevice.SOURCE_DPAD) {
//用戶按了遙控
}
return super.onKeyDown(keyCode, event);
}
3.5 處理導航
一般不需要處理用戶操作遙控器方向鍵(上、下、左、右)時的導航順序,如果有這個需求,可以根據下面屬性在佈局文件中指定你的導航順序:
如果你想在最後一個控件獲取焦點後,繼續按右方向鍵的話導航到第一個控件,那麼你只需要將最後一個控件中nextFocusRight的值設爲第一個控件的id即可。