基于Android Tv制作一个Tv桌面(三)
接下来我们就开始分析一下代码部分,这里我就转载其他人写的吧,我一开始也是参考这篇文章的。
当然,我自己也添加了我自己在开发过程中写的代码和注释,对于新手来说没有这些注释还真的消耗头发
转载地址:
(AndroidManifest.xml)
<uses-permission android:name="android.permission.INTERNET" /> //这是申请网络的权限
<uses-permission android:name="android.permission.RECORD_AUDIO" /> //这是申请开发音频的权限
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" /> //因为电视一般不支持触屏,所以需要声明一下权限
<uses-feature
android:name="android.software.leanback"
android:required="true" /> //使用一些TV下面的控件时,需要的声明
<activity
android:name=".MainActivity"
android:banner="@drawable/app_icon_your_company"
android:icon="@drawable/app_icon_your_company"
android:label="@string/app_name"
android:logo="@drawable/app_icon_your_company"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
//因为我做的是桌面,所以加上了这两句
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
//这个地方与手机端开发区别较大,这个主要是声明了这是一个TV项目,如果不加这个,那么运行在TV上时,是找不到这个应用的运行图标的
//但是我依然找不到图标哈哈,以后发现了问题所在再写下来吧
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
然后我就打开了MainActivity.xml,然后我发现里面竟然是空的。随后我就打开了MainActivity的资源文件。
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_browse_fragment"
android:name="com.example.administrator.myfirst.MainFragment" //这里是关键点,原来里面是引用了一个碎片。
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.administrator.myfirst.MainActivity"
tools:deviceIds="tv"
tools:ignore="MergeRootFrame" />
然后就可以打开MainFragment.java。里面有四个方法,我们一一来分析一下。
//这些也写出来吧,不然看起来耗头发
private static final String TAG = "MainFragment";
private static final int BACKGROUND_UPDATE_DELAY = 300;
private static final int GRID_ITEM_WIDTH = 200;
private static final int GRID_ITEM_HEIGHT = 200;
private final Handler mHandler = new Handler();
private Drawable mDefaultBackground;
private DisplayMetrics mMetrics;
private Timer mBackgroundTimer;
private String mBackgroundUri;
private BackgroundManager mBackgroundManager;
private List<ResolveInfo> apps_list = MainActivity.apps;
private List<String> sys_apps_name = MainActivity.system_app_name;
private List<String> my_apps_name = MainActivity.my_app_name;
private List<String> apps_name = MainActivity.name;
private List<String> apps_pak = MainActivity.pak;
private List<String> apps_cla = MainActivity.cla;
public void onActivityCreated(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onActivityCreated(savedInstanceState);
prepareBackgroundManager();
setupUIElements();
loadRows();
setupEventListeners();
}
这里大家一看就明白这个方法的作用,初始化。
private void prepareBackgroundManager() {
mBackgroundManager = BackgroundManager.getInstance(getActivity());
mBackgroundManager.attach(getActivity().getWindow());
//设置右边应用区域的背景颜色,被这个东西坑惨了ContextCompat.getColor(),看了下面的代码才反应过来
//其实我也不知道它的原理是什么,参考百度吧
mBackgroundManager.setColor(ContextCompat.getColor(getActivity(), R.color.search_opaque));
mDefaultBackground = getResources().getDrawable(R.drawable.default_background);
mMetrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
}
private void setupUIElements() {
// setBadgeDrawable(getActivity().getResources().getDrawable(
// R.drawable.videos_by_google_banner));
setTitle(getString(R.string.browse_title)); // Badge, when set, takes precedent
// over title
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(false); //这里是设置左边引导的显示与否。
// set fastLane (or headers) background color
setBrandColor(getResources().getColor(R.color.background_gradient_start)); //设置左边引导的背景色
// set search icon color
setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
}
这个方法主要作用是产生页面数据
private void loadRows() {
List<Movie> list = MovieList.setupMovies(); //Movie一个实现了序列化的Bean。setupMovies()是产生movie数据的方法
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); //页面数据的适配器
CardPresenter cardPresenter = new CardPresenter(); //这个比较重要下面贴上源码
int sys_app_layer = 0;
int my_app_layer = 0;
if((sys_apps_name.size() % 8) == 0)
{
sys_app_layer = (sys_apps_name.size() / 8);
}
else
{
sys_app_layer = (sys_apps_name.size() / 8) +1;
}
//(字怎么变黄了???)
if((my_apps_name.size() % 8) == 0)
{
my_app_layer = (sys_apps_name.size() / 8);
}
else
{
my_app_layer = (sys_apps_name.size() / 8) +1;
}
//应领导要求,将app分成系统应用和第三方应用
int i;
HeaderItem header1 = new HeaderItem(0, MovieList.MOVIE_CATEGORY[0]);
for (i = 0; i < sys_app_layer; i++) {
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
for (int j = 0; j < 8 && ((8*i + j) < sys_apps_name.size()); j++) {
//Log.d(TAG,"list.get["+(8*i+j)+"]:"+list.get(8*i + j));
listRowAdapter.add(list.get(8*i + j));
}
if(i == 0)
{
rowsAdapter.add(new ListRow(header1, listRowAdapter));
}
else
{
rowsAdapter.add(new ListRow(listRowAdapter));
}
}
HeaderItem header2 = new HeaderItem(i, MovieList.MOVIE_CATEGORY[1]);
for (i = 0; i < my_app_layer; i++) {
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
for (int j = 0; j < 8 && ((8*i + j) < my_apps_name.size()); j++) {
//Log.d(TAG,"list.get["+(8*i+j)+"]:"+list.get(8*i + j));
listRowAdapter.add(list.get(8*i + j + sys_apps_name.size()));
}
if(i == 0)
{
rowsAdapter.add(new ListRow(header2, listRowAdapter));
}
else
{
rowsAdapter.add(new ListRow(listRowAdapter));
}
}
HeaderItem gridHeader = new HeaderItem(i, "PREFERENCES");
GridItemPresenter mGridPresenter = new GridItemPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter); //里面传了一个Presenter
gridRowAdapter.add(getResources().getString(R.string.grid_view));
gridRowAdapter.add(getString(R.string.error_fragment));
gridRowAdapter.add(getResources().getString(R.string.personal_settings));
mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter)); //看了这个应该很清晰了
setAdapter(mRowsAdapter); //最后将mRowsAdapter设置}
主要就是实现了每个控件的绑定,设置数据
public class CardPresenter extends Presenter {
private static final String TAG = "CardPresenter";
private static final int CARD_WIDTH = 313;
private static final int CARD_HEIGHT = 176;
private static int sSelectedBackgroundColor;
private static int sDefaultBackgroundColor;
private Drawable mDefaultCardImage;
private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
// Both background colors should be set because the view's background is temporarily visible
// during animations.
view.setBackgroundColor(color);
view.findViewById(R.id.info_field).setBackgroundColor(color);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
Log.d(TAG, "onCreateViewHolder");
sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background);
sSelectedBackgroundColor = parent.getResources().getColor(R.color.selected_background);
/*
* This template uses a default image in res/drawable, but the general case for Android TV
* will require your resources in xhdpi. For more information, see
* https://developer.android.com/training/tv/start/layouts.html#density-resources
*/
//下面这个mDefaultCardImage我将它移到了onBindViewHolder方法里面
//mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie);
ImageCardView cardView = new ImageCardView(parent.getContext()) {
@Override
public void setSelected(boolean selected) {
updateCardBackgroundColor(this, selected); //这句很关键,它实现了当选择改变的时候,选中效果的出现
super.setSelected(selected);
}
};
cardView.setFocusable(true);
cardView.setFocusableInTouchMode(true);
updateCardBackgroundColor(cardView, false);
return new ViewHolder(cardView);
}
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
Movie movie = (Movie) item;
//这里是根据item的Title与列表apps_name做对比,目的是获得i,获得准确的apps_icon
//至于为什么这样做,我会在文章后面画个图解释
for(int i=0; i<apps_name.size(); i++)
{
if(((Movie) item).getTitle() == apps_name.get(i))
{
mDefaultCardImage = apps_icon.get(i);
}
}
ImageCardView cardView = (ImageCardView) viewHolder.view;
Log.d(TAG, "onBindViewHolder");
if (movie.getCardImageUrl() != null) {
cardView.setTitleText(movie.getTitle());
//cardView.setContentText(movie.getStudio());
//这里是设置layout与父组件边框的距离,我没有找到调节父组件之间的距离的方法
//领导又觉得它们挤着慌,只好改一下这里了
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
layout.setMargins(30, 15, 30, 15);
//设置图标与父控件的距离,这里可以调节图标大小和形状
//说到这不得不承认我真的是菜,我没有找到将图标设置成原图的方法,只能这样把它硬挤成型
ImageView imageView = cardView.getMainImageView();
imageView.setPadding(75, 25, 75, 25);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
Glide.with(viewHolder.view.getContext())
.load(movie.getCardImageUrl())
.centerCrop()
.error(mDefaultCardImage)
.into(layout);
}
}
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
Log.d(TAG, "onUnbindViewHolder");
ImageCardView cardView = (ImageCardView) viewHolder.view;
// Remove references to images so that the garbage collector can free up memory
cardView.setBadgeImage(null);
cardView.setMainImage(null);
}
}
这个方法设置了监听
private void setupEventListeners() {
setOnSearchClickedListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//左上角搜索点击事件
Toast.makeText(getActivity(), "Implement your own in-app search", Toast.LENGTH_LONG)
.show();
}
});
//item的监听
setOnItemViewClickedListener(new ItemViewClickedListener());
setOnItemViewSelectedListener(new ItemViewSelectedListener());
}
解释一下之前的那个循环吧,下面图中的大黑框是屏幕,黑框外面的小框则是超出屏幕外面的图标,图标下面都有app的名字我这里是把app的各种信息分别存在不同的列表中,位置也是对应好的;但是一打开应用我就傻眼了,app名字和图标对应不上,原因就在于它加载图标时先把屏幕内的填上,再搞屏幕外面的。所以我就只能根据app名字去找到对应的图标,再将它加载进去,这样就不会乱了