android-tvdemo.zip
一、创建TV版的页面:
1.新建一个项目:
2.在TV中选择Android TV Activity的项目:
3.给TV设置名称,包名,项目所在位置,语言,选择最低版本的api:
4.在grable里面给java配置为1.8版本,因为能接受的最低版本不能小于1.8:
5.写上butterknife相关配置语句(目前我也还不太懂这个是什么):
6.file→setting,在setting里面寻找这个配置,选中,点击OK安装:
7.在TV里面加入语句(你想怎么设置就怎么设置,这里我只是随便写一下,主要是测试他的功能):
8.在手机型号里面找到TV版的页面,这里两个,看你需要的选择):
9.TV版选择之后的页面如下:
10.在主要的main.java里面找到绑定的布局,然后右击,选择Generate:
11.再选择Generate Butterknife lnjections:
12.选择你要绑定的控件的定义和后面绑定按钮需要的点击事件,然后confirm(这里是快捷键导出的控件绑定和点击事件,初学者建议手写,熟练之后,再用这种操作):
13.打开values下的styles.xml,给需要的颜色命名:
14.在colors.xml下给命名添加你所需要的颜色值,然后在布局页面中调用即可:
15.电视上需要用到焦点,电视上都是通过控件获取焦点来实现点击效果的:
16.在java中绑定:
17.在已知控件ID的情况下我们可以设置上下左右的移动控件
18.在java中绑定:
19.遥控器的按键监听,毕竟是用遥控器来操作的啊,按键监听代码如下:

20.如果要监听Home键的话,就需要通过广播来,在MainActivity中创建一个class:
21.在onCreate()方法中注册广播,只要调用initReceiver()方法即可:
22.页面销毁时,注销掉广播:
23.在确定键的遥控器按键监听代码中加入弹窗显示:
二、整体代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--自定义的VideoView 做了绘制改变,和网络地址许可-->
<com.llw.androidtvdemo.view.MyVideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
</LinearLayout>
<!--底部控制栏 开始时间 进度条 结束时间-->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_100"
android:layout_alignParentBottom="true"
android:background="@drawable/shape_gradual_change">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="@dimen/dp_10"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_play_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="@color/white"
android:textSize="@dimen/sp_24" />
<SeekBar
android:id="@+id/time_seekBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="@dimen/dp_20"
android:layout_marginRight="@dimen/dp_20"
android:layout_weight="1"
android:max="100"
android:maxHeight="3dp"
android:minHeight="3dp"
android:progress="0"
android:progressDrawable="@drawable/seekbar_style"
android:thumb="@drawable/thumb" />
<TextView
android:id="@+id/tv_total_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:textColor="@color/white"
android:textSize="@dimen/sp_24" />
</LinearLayout>
</RelativeLayout>
<!--视频结束时 显示黑色背景-->
<RelativeLayout
android:visibility="gone"
android:id="@+id/lay_finish_bg"
android:background="#000"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<!--视频播放中 控制暂停和播放的按钮-->
<ImageButton
android:visibility="gone"
android:focusable="true"
android:layout_centerInParent="true"
android:id="@+id/btn_play_or_pause"
android:background="@mipmap/icon_pause"
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"/>
<!--视频结束时 显示重播图标-->
<ImageButton
android:visibility="gone"
android:layout_centerInParent="true"
android:id="@+id/btn_restart_play"
android:background="@mipmap/icon_restart_play"
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/tv_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello TV"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_test"
android:text="TV"
android:layout_marginTop="20dp"
android:focusable="true"
android:nextFocusDown="@id/tv_test"
android:nextFocusUp="@id/tv_test"
android:nextFocusLeft="@id/tv_test"
android:nextFocusRight="@id/tv_test"
/>
</LinearLayout>
</RelativeLayout>
package com.llw.androidtvdemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import static androidx.constraintlayout.widget.Constraints.TAG;
public class HomeReceiver extends BroadcastReceiver {
public final String SYSTEM_DIALOG_REASON_KEY="reason";
public final String SYSTEM_DIALOG_REASON_HOME_KEY="homekey";
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){
String reason=intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
if (SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)){
Toast.makeText(context, "home键触发", Toast.LENGTH_SHORT).show();
Log.d(TAG,"home键触发");
}
}
}
}
package com.llw.androidtvdemo;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.llw.androidtvdemo.view.MyVideoView;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Formatter;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.video_view)
MyVideoView videoView;
@BindView(R.id.tv_play_time)
TextView tvPlayTime;
@BindView(R.id.time_seekBar)
SeekBar timeSeekBar;
@BindView(R.id.tv_total_time)
TextView tvTotalTime;
@BindView(R.id.lay_finish_bg)
RelativeLayout layFinishBg;
@BindView(R.id.btn_play_or_pause)
ImageButton btnPlayOrPause;
@BindView(R.id.btn_restart_play)
ImageButton btnRestartPlay;
@BindView(R.id.tv_test)
TextView tvTest;
@BindView(R.id.btn_test)
Button btnTest;
private HomeReceiver homeReceiver;
private void initReceiver(){
homeReceiver=new HomeReceiver();
IntentFilter filter=new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
registerReceiver(homeReceiver,filter);
}
private int key = 0;
private Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
if (videoView.isPlaying()) {
int current = videoView.getCurrentPosition();
timeSeekBar.setProgress(current);
tvPlayTime.setText(time(videoView.getCurrentPosition()));
}
handler.postDelayed(runnable, 500);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
btnTest.setFocusable(true);
btnTest.setNextFocusUpId(R.id.tv_test);
btnTest.setNextFocusDownId(R.id.tv_test);
btnTest.setNextFocusLeftId(R.id.tv_test);
btnTest.setNextFocusRightId(R.id.tv_test);
timeSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);
initVideo();
videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
key = 1;
btnRestartPlay.setVisibility(View.VISIBLE);
layFinishBg.setVisibility(View.VISIBLE);
}
});
videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText(MainActivity.this, "播放出错", Toast.LENGTH_SHORT).show();
return false;
}
});
}
/**
* 时间转换方法
*
* @param millionSeconds
* @return
*/
protected String time(long millionSeconds) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
Calendar c = Calendar.getInstance();
c.setTimeInMillis(millionSeconds);
return simpleDateFormat.format(c.getTime());
}
/**
* 初始化VideoView
*/
private void initVideo() {
//本地视频
//videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/raw/test"));
//网络视频
final Uri uri = Uri.parse("http://gslb.miaopai.com/stream/ed5HCfnhovu3tyIQAiv60Q__.mp4");
videoView.setVideoURI(uri);
videoView.requestFocus();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
int totalTime = videoView.getDuration();//获取视频的总时长
tvTotalTime.setText(stringForTime(totalTime));
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
timeSeekBar.setMax(videoView.getDuration());
//视频加载完成,准备好播放视频的回调
videoView.start();
}
});
}
/**
* 控制视频是 播放还是暂停 或者是重播
*
* @param isPlay
* @param keys
*/
private void isVideoPlay(boolean isPlay, int keys) {
switch (keys) {
case 0:
if (isPlay) {//暂停
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));
btnPlayOrPause.setVisibility(View.VISIBLE);
videoView.pause();
} else {//继续播放
btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));
btnPlayOrPause.setVisibility(View.VISIBLE);
// 开始线程,更新进度条的刻度
handler.postDelayed(runnable, 0);
videoView.start();
timeSeekBar.setMax(videoView.getDuration());
timeGone();
}
break;
case 1://重新播放
initVideo();
btnRestartPlay.setVisibility(View.GONE);
layFinishBg.setVisibility(View.GONE);
key = 0;
break;
}
}
/**
* 延时隐藏
*/
private void timeGone() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
btnPlayOrPause.setVisibility(View.INVISIBLE);
}
}, 1500);
}
/**
* 进度条监听
*/
private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
// 当进度条停止修改的时候触发
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 取得当前进度条的刻度
int progress = seekBar.getProgress();
if (videoView.isPlaying()) {
// 设置当前播放的位置
videoView.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
}
};
//将长度转换为时间
StringBuilder mFormatBuilder = new StringBuilder();
Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
private String stringForTime(int timeMs) {
int totalSeconds = timeMs / 1000;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
mFormatBuilder.setLength(0);
if (hours > 0) {
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
} else {
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
}
}
private String TAG = "key";
/**
* 遥控器按键监听
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER: //确定键enter
case KeyEvent.KEYCODE_DPAD_CENTER:
Log.d(TAG, "enter--->");
Toast.makeText(this,"确定", Toast.LENGTH_SHORT).show();
//如果是播放中则暂停、如果是暂停则继续播放
//isVideoPlay(videoView.isPlaying(), key);
break;
case KeyEvent.KEYCODE_BACK: //返回键
Toast.makeText(this,"返回键", Toast.LENGTH_SHORT).show();
Log.d(TAG, "back--->");
return true; //这里由于break会退出,所以我们自己要处理掉 不返回上一层
case KeyEvent.KEYCODE_SETTINGS: //设置键
Log.d(TAG, "setting--->");
break;
case KeyEvent.KEYCODE_DPAD_DOWN: //向下键
/* 实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发
* exp:KeyEvent.ACTION_UP
*/
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.d(TAG, "down--->");
}
break;
case KeyEvent.KEYCODE_DPAD_UP: //向上键
Log.d(TAG, "up--->");
break;
case KeyEvent.KEYCODE_0:
Log.d(TAG,"0--->");
case KeyEvent.KEYCODE_DPAD_LEFT: //向左键
Log.d(TAG, "left--->");
if (videoView.getCurrentPosition() > 4) {
videoView.seekTo(videoView.getCurrentPosition() - 5 * 1000);
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT: //向右键
Log.d(TAG, "right--->");
videoView.seekTo(videoView.getCurrentPosition() + 5 * 1000);
break;
//info键
case KeyEvent.KEYCODE_INFO:
Log.d(TAG,"info--->");
break;
//向上翻页键
case KeyEvent.KEYCODE_PAGE_DOWN:
case KeyEvent.KEYCODE_MEDIA_NEXT:
Log.d(TAG,"page down--->");
break;
//向下翻页键
case KeyEvent.KEYCODE_PAGE_UP:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
Log.d(TAG,"page up--->");
break;
case KeyEvent.KEYCODE_VOLUME_UP: //调大声音键
Log.d(TAG, "voice up--->");
break;
case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键
Log.d(TAG, "voice down--->");
break;
case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音
Log.d(TAG, "voice mute--->");
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
@OnClick(R.id.btn_test)
public void onViewClicked() {
//Toast 提示
Toast.makeText(this,tvTest.getText().toString(), Toast.LENGTH_SHORT).show();
}
protected void onDestroy(){
super.onDestroy();
if (homeReceiver!=null){
unregisterReceiver(homeReceiver);
}
}
}