android-tvdemo.zip

一、创建TV版的页面:

1.新建一个项目:

image.png

2.在TV中选择Android TV Activity的项目:

image.png

3.给TV设置名称,包名,项目所在位置,语言,选择最低版本的api:

image.png

4.在grable里面给java配置为1.8版本,因为能接受的最低版本不能小于1.8:

image.png

5.写上butterknife相关配置语句(目前我也还不太懂这个是什么):

image.png

6.file→setting,在setting里面寻找这个配置,选中,点击OK安装:

image.png

7.在TV里面加入语句(你想怎么设置就怎么设置,这里我只是随便写一下,主要是测试他的功能):

image.png

8.在手机型号里面找到TV版的页面,这里两个,看你需要的选择):

image.png

9.TV版选择之后的页面如下:

image.png

10.在主要的main.java里面找到绑定的布局,然后右击,选择Generate:

image.png

11.再选择Generate Butterknife lnjections:

image.png

12.选择你要绑定的控件的定义和后面绑定按钮需要的点击事件,然后confirm(这里是快捷键导出的控件绑定和点击事件,初学者建议手写,熟练之后,再用这种操作):

image.png

13.打开values下的styles.xml,给需要的颜色命名:

image.png

14.在colors.xml下给命名添加你所需要的颜色值,然后在布局页面中调用即可:

image.png

15.电视上需要用到焦点,电视上都是通过控件获取焦点来实现点击效果的:

image.png

16.在java中绑定:

image.png

17.在已知控件ID的情况下我们可以设置上下左右的移动控件

image.png

18.在java中绑定:

image.png

19.遥控器的按键监听,毕竟是用遥控器来操作的啊,按键监听代码如下:

image.png
image.png

20.如果要监听Home键的话,就需要通过广播来,在MainActivity中创建一个class:

image.png

21.在onCreate()方法中注册广播,只要调用initReceiver()方法即可:

image.png

22.页面销毁时,注销掉广播:

image.png

23.在确定键的遥控器按键监听代码中加入弹窗显示:

image.png

二、整体代码:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:app="http://schemas.android.com/apk/res-auto"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical"
  7. android:gravity="center"
  8. tools:context=".MainActivity">
  9. <LinearLayout
  10. android:layout_width="match_parent"
  11. android:layout_height="match_parent"
  12. android:orientation="vertical">
  13. <!--自定义的VideoView 做了绘制改变,和网络地址许可-->
  14. <com.llw.androidtvdemo.view.MyVideoView
  15. android:id="@+id/video_view"
  16. android:layout_width="match_parent"
  17. android:layout_height="match_parent"
  18. android:layout_gravity="center" />
  19. </LinearLayout>
  20. <!--底部控制栏 开始时间 进度条 结束时间-->
  21. <RelativeLayout
  22. android:layout_width="match_parent"
  23. android:layout_height="@dimen/dp_100"
  24. android:layout_alignParentBottom="true"
  25. android:background="@drawable/shape_gradual_change">
  26. <LinearLayout
  27. android:layout_width="match_parent"
  28. android:layout_height="wrap_content"
  29. android:layout_alignParentBottom="true"
  30. android:layout_margin="@dimen/dp_10"
  31. android:gravity="center_vertical"
  32. android:orientation="horizontal">
  33. <TextView
  34. android:id="@+id/tv_play_time"
  35. android:layout_width="wrap_content"
  36. android:layout_height="wrap_content"
  37. android:text="00:00"
  38. android:textColor="@color/white"
  39. android:textSize="@dimen/sp_24" />
  40. <SeekBar
  41. android:id="@+id/time_seekBar"
  42. android:layout_width="0dp"
  43. android:layout_height="wrap_content"
  44. android:layout_centerInParent="true"
  45. android:layout_marginLeft="@dimen/dp_20"
  46. android:layout_marginRight="@dimen/dp_20"
  47. android:layout_weight="1"
  48. android:max="100"
  49. android:maxHeight="3dp"
  50. android:minHeight="3dp"
  51. android:progress="0"
  52. android:progressDrawable="@drawable/seekbar_style"
  53. android:thumb="@drawable/thumb" />
  54. <TextView
  55. android:id="@+id/tv_total_time"
  56. android:layout_width="wrap_content"
  57. android:layout_height="wrap_content"
  58. android:text="00:00"
  59. android:textColor="@color/white"
  60. android:textSize="@dimen/sp_24" />
  61. </LinearLayout>
  62. </RelativeLayout>
  63. <!--视频结束时 显示黑色背景-->
  64. <RelativeLayout
  65. android:visibility="gone"
  66. android:id="@+id/lay_finish_bg"
  67. android:background="#000"
  68. android:layout_width="match_parent"
  69. android:layout_height="match_parent"/>
  70. <!--视频播放中 控制暂停和播放的按钮-->
  71. <ImageButton
  72. android:visibility="gone"
  73. android:focusable="true"
  74. android:layout_centerInParent="true"
  75. android:id="@+id/btn_play_or_pause"
  76. android:background="@mipmap/icon_pause"
  77. android:layout_width="@dimen/dp_100"
  78. android:layout_height="@dimen/dp_100"/>
  79. <!--视频结束时 显示重播图标-->
  80. <ImageButton
  81. android:visibility="gone"
  82. android:layout_centerInParent="true"
  83. android:id="@+id/btn_restart_play"
  84. android:background="@mipmap/icon_restart_play"
  85. android:layout_width="@dimen/dp_100"
  86. android:layout_height="@dimen/dp_100"/>
  87. <LinearLayout
  88. android:layout_width="match_parent"
  89. android:layout_height="match_parent"
  90. android:orientation="vertical"
  91. android:gravity="center">
  92. <TextView
  93. android:id="@+id/tv_test"
  94. android:layout_width="wrap_content"
  95. android:layout_height="wrap_content"
  96. android:text="Hello TV"
  97. />
  98. <Button
  99. android:layout_width="wrap_content"
  100. android:layout_height="wrap_content"
  101. android:id="@+id/btn_test"
  102. android:text="TV"
  103. android:layout_marginTop="20dp"
  104. android:focusable="true"
  105. android:nextFocusDown="@id/tv_test"
  106. android:nextFocusUp="@id/tv_test"
  107. android:nextFocusLeft="@id/tv_test"
  108. android:nextFocusRight="@id/tv_test"
  109. />
  110. </LinearLayout>
  111. </RelativeLayout>
  1. package com.llw.androidtvdemo;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.util.Log;
  6. import android.widget.Toast;
  7. import static androidx.constraintlayout.widget.Constraints.TAG;
  8. public class HomeReceiver extends BroadcastReceiver {
  9. public final String SYSTEM_DIALOG_REASON_KEY="reason";
  10. public final String SYSTEM_DIALOG_REASON_HOME_KEY="homekey";
  11. @Override
  12. public void onReceive(Context context, Intent intent) {
  13. String action=intent.getAction();
  14. if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){
  15. String reason=intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
  16. if (SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)){
  17. Toast.makeText(context, "home键触发", Toast.LENGTH_SHORT).show();
  18. Log.d(TAG,"home键触发");
  19. }
  20. }
  21. }
  22. }
  1. package com.llw.androidtvdemo;
  2. import android.content.Intent;
  3. import android.content.IntentFilter;
  4. import android.media.MediaPlayer;
  5. import android.net.Uri;
  6. import android.os.Bundle;
  7. import android.os.Handler;
  8. import android.util.Log;
  9. import android.view.KeyEvent;
  10. import android.view.View;
  11. import android.widget.Button;
  12. import android.widget.ImageButton;
  13. import android.widget.RelativeLayout;
  14. import android.widget.SeekBar;
  15. import android.widget.TextView;
  16. import android.widget.Toast;
  17. import androidx.appcompat.app.AppCompatActivity;
  18. import com.llw.androidtvdemo.view.MyVideoView;
  19. import java.text.SimpleDateFormat;
  20. import java.util.Calendar;
  21. import java.util.Formatter;
  22. import java.util.Locale;
  23. import butterknife.BindView;
  24. import butterknife.ButterKnife;
  25. import butterknife.OnClick;
  26. public class MainActivity extends AppCompatActivity {
  27. @BindView(R.id.video_view)
  28. MyVideoView videoView;
  29. @BindView(R.id.tv_play_time)
  30. TextView tvPlayTime;
  31. @BindView(R.id.time_seekBar)
  32. SeekBar timeSeekBar;
  33. @BindView(R.id.tv_total_time)
  34. TextView tvTotalTime;
  35. @BindView(R.id.lay_finish_bg)
  36. RelativeLayout layFinishBg;
  37. @BindView(R.id.btn_play_or_pause)
  38. ImageButton btnPlayOrPause;
  39. @BindView(R.id.btn_restart_play)
  40. ImageButton btnRestartPlay;
  41. @BindView(R.id.tv_test)
  42. TextView tvTest;
  43. @BindView(R.id.btn_test)
  44. Button btnTest;
  45. private HomeReceiver homeReceiver;
  46. private void initReceiver(){
  47. homeReceiver=new HomeReceiver();
  48. IntentFilter filter=new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
  49. registerReceiver(homeReceiver,filter);
  50. }
  51. private int key = 0;
  52. private Handler handler = new Handler();
  53. private Runnable runnable = new Runnable() {
  54. public void run() {
  55. if (videoView.isPlaying()) {
  56. int current = videoView.getCurrentPosition();
  57. timeSeekBar.setProgress(current);
  58. tvPlayTime.setText(time(videoView.getCurrentPosition()));
  59. }
  60. handler.postDelayed(runnable, 500);
  61. }
  62. };
  63. @Override
  64. protected void onCreate(Bundle savedInstanceState) {
  65. super.onCreate(savedInstanceState);
  66. setContentView(R.layout.activity_main);
  67. ButterKnife.bind(this);
  68. btnTest.setFocusable(true);
  69. btnTest.setNextFocusUpId(R.id.tv_test);
  70. btnTest.setNextFocusDownId(R.id.tv_test);
  71. btnTest.setNextFocusLeftId(R.id.tv_test);
  72. btnTest.setNextFocusRightId(R.id.tv_test);
  73. timeSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);
  74. initVideo();
  75. videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
  76. @Override
  77. public void onCompletion(MediaPlayer mp) {
  78. key = 1;
  79. btnRestartPlay.setVisibility(View.VISIBLE);
  80. layFinishBg.setVisibility(View.VISIBLE);
  81. }
  82. });
  83. videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
  84. @Override
  85. public boolean onError(MediaPlayer mp, int what, int extra) {
  86. Toast.makeText(MainActivity.this, "播放出错", Toast.LENGTH_SHORT).show();
  87. return false;
  88. }
  89. });
  90. }
  91. /**
  92. * 时间转换方法
  93. *
  94. * @param millionSeconds
  95. * @return
  96. */
  97. protected String time(long millionSeconds) {
  98. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
  99. Calendar c = Calendar.getInstance();
  100. c.setTimeInMillis(millionSeconds);
  101. return simpleDateFormat.format(c.getTime());
  102. }
  103. /**
  104. * 初始化VideoView
  105. */
  106. private void initVideo() {
  107. //本地视频
  108. //videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/raw/test"));
  109. //网络视频
  110. final Uri uri = Uri.parse("http://gslb.miaopai.com/stream/ed5HCfnhovu3tyIQAiv60Q__.mp4");
  111. videoView.setVideoURI(uri);
  112. videoView.requestFocus();
  113. videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
  114. @Override
  115. public void onPrepared(MediaPlayer mp) {
  116. int totalTime = videoView.getDuration();//获取视频的总时长
  117. tvTotalTime.setText(stringForTime(totalTime));
  118. // 开始线程,更新进度条的刻度
  119. handler.postDelayed(runnable, 0);
  120. timeSeekBar.setMax(videoView.getDuration());
  121. //视频加载完成,准备好播放视频的回调
  122. videoView.start();
  123. }
  124. });
  125. }
  126. /**
  127. * 控制视频是 播放还是暂停 或者是重播
  128. *
  129. * @param isPlay
  130. * @param keys
  131. */
  132. private void isVideoPlay(boolean isPlay, int keys) {
  133. switch (keys) {
  134. case 0:
  135. if (isPlay) {//暂停
  136. btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));
  137. btnPlayOrPause.setVisibility(View.VISIBLE);
  138. videoView.pause();
  139. } else {//继续播放
  140. btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));
  141. btnPlayOrPause.setVisibility(View.VISIBLE);
  142. // 开始线程,更新进度条的刻度
  143. handler.postDelayed(runnable, 0);
  144. videoView.start();
  145. timeSeekBar.setMax(videoView.getDuration());
  146. timeGone();
  147. }
  148. break;
  149. case 1://重新播放
  150. initVideo();
  151. btnRestartPlay.setVisibility(View.GONE);
  152. layFinishBg.setVisibility(View.GONE);
  153. key = 0;
  154. break;
  155. }
  156. }
  157. /**
  158. * 延时隐藏
  159. */
  160. private void timeGone() {
  161. new Handler().postDelayed(new Runnable() {
  162. @Override
  163. public void run() {
  164. btnPlayOrPause.setVisibility(View.INVISIBLE);
  165. }
  166. }, 1500);
  167. }
  168. /**
  169. * 进度条监听
  170. */
  171. private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
  172. // 当进度条停止修改的时候触发
  173. @Override
  174. public void onStopTrackingTouch(SeekBar seekBar) {
  175. // 取得当前进度条的刻度
  176. int progress = seekBar.getProgress();
  177. if (videoView.isPlaying()) {
  178. // 设置当前播放的位置
  179. videoView.seekTo(progress);
  180. }
  181. }
  182. @Override
  183. public void onStartTrackingTouch(SeekBar seekBar) {
  184. }
  185. @Override
  186. public void onProgressChanged(SeekBar seekBar, int progress,
  187. boolean fromUser) {
  188. }
  189. };
  190. //将长度转换为时间
  191. StringBuilder mFormatBuilder = new StringBuilder();
  192. Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
  193. private String stringForTime(int timeMs) {
  194. int totalSeconds = timeMs / 1000;
  195. int seconds = totalSeconds % 60;
  196. int minutes = (totalSeconds / 60) % 60;
  197. int hours = totalSeconds / 3600;
  198. mFormatBuilder.setLength(0);
  199. if (hours > 0) {
  200. return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
  201. } else {
  202. return mFormatter.format("%02d:%02d", minutes, seconds).toString();
  203. }
  204. }
  205. private String TAG = "key";
  206. /**
  207. * 遥控器按键监听
  208. *
  209. * @param keyCode
  210. * @param event
  211. * @return
  212. */
  213. @Override
  214. public boolean onKeyDown(int keyCode, KeyEvent event) {
  215. switch (keyCode) {
  216. case KeyEvent.KEYCODE_ENTER: //确定键enter
  217. case KeyEvent.KEYCODE_DPAD_CENTER:
  218. Log.d(TAG, "enter--->");
  219. Toast.makeText(this,"确定", Toast.LENGTH_SHORT).show();
  220. //如果是播放中则暂停、如果是暂停则继续播放
  221. //isVideoPlay(videoView.isPlaying(), key);
  222. break;
  223. case KeyEvent.KEYCODE_BACK: //返回键
  224. Toast.makeText(this,"返回键", Toast.LENGTH_SHORT).show();
  225. Log.d(TAG, "back--->");
  226. return true; //这里由于break会退出,所以我们自己要处理掉 不返回上一层
  227. case KeyEvent.KEYCODE_SETTINGS: //设置键
  228. Log.d(TAG, "setting--->");
  229. break;
  230. case KeyEvent.KEYCODE_DPAD_DOWN: //向下键
  231. /* 实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发
  232. * exp:KeyEvent.ACTION_UP
  233. */
  234. if (event.getAction() == KeyEvent.ACTION_DOWN) {
  235. Log.d(TAG, "down--->");
  236. }
  237. break;
  238. case KeyEvent.KEYCODE_DPAD_UP: //向上键
  239. Log.d(TAG, "up--->");
  240. break;
  241. case KeyEvent.KEYCODE_0:
  242. Log.d(TAG,"0--->");
  243. case KeyEvent.KEYCODE_DPAD_LEFT: //向左键
  244. Log.d(TAG, "left--->");
  245. if (videoView.getCurrentPosition() > 4) {
  246. videoView.seekTo(videoView.getCurrentPosition() - 5 * 1000);
  247. }
  248. break;
  249. case KeyEvent.KEYCODE_DPAD_RIGHT: //向右键
  250. Log.d(TAG, "right--->");
  251. videoView.seekTo(videoView.getCurrentPosition() + 5 * 1000);
  252. break;
  253. //info键
  254. case KeyEvent.KEYCODE_INFO:
  255. Log.d(TAG,"info--->");
  256. break;
  257. //向上翻页键
  258. case KeyEvent.KEYCODE_PAGE_DOWN:
  259. case KeyEvent.KEYCODE_MEDIA_NEXT:
  260. Log.d(TAG,"page down--->");
  261. break;
  262. //向下翻页键
  263. case KeyEvent.KEYCODE_PAGE_UP:
  264. case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
  265. Log.d(TAG,"page up--->");
  266. break;
  267. case KeyEvent.KEYCODE_VOLUME_UP: //调大声音键
  268. Log.d(TAG, "voice up--->");
  269. break;
  270. case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键
  271. Log.d(TAG, "voice down--->");
  272. break;
  273. case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音
  274. Log.d(TAG, "voice mute--->");
  275. break;
  276. default:
  277. break;
  278. }
  279. return super.onKeyDown(keyCode, event);
  280. }
  281. @OnClick(R.id.btn_test)
  282. public void onViewClicked() {
  283. //Toast 提示
  284. Toast.makeText(this,tvTest.getText().toString(), Toast.LENGTH_SHORT).show();
  285. }
  286. protected void onDestroy(){
  287. super.onDestroy();
  288. if (homeReceiver!=null){
  289. unregisterReceiver(homeReceiver);
  290. }
  291. }
  292. }