基于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名字去找到对应的图标,再将它加载进去,这样就不会乱了

原生android tv 桌面 android原生电视桌面_原生android tv 桌面