项目展示



类图(待添加)

项目流程

主界面


项目入口是MainActivity,我们发现MainActivity非常简单。

public class MainActivity extends Activity {/*** Called when the activity is first created.*/@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}
}

实际上他是在布局activity_main中设置android:name的方式直接加载Fragment的

<?xml version="1.0" encoding="utf-8"?>
<fragment 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:id="@+id/main_browse_fragment"android:name="com.leanback.MainFragment"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"tools:deviceIds="tv"tools:ignore="MergeRootFrame" />

那么我们转到MainFragment,MainFragment继承于BrowseFragment,在重写方法onActivityCreated中进行初始化

    @Overridepublic void onActivityCreated(Bundle savedInstanceState) {Log.i(TAG, "onCreate");super.onActivityCreated(savedInstanceState);//准备背景管理器prepareBackgroundManager();//设置UI元素setupUIElements();//创建适配器并加载数据loadRows();//设置事件监听setupEventListeners();}

初始化背景管理器

    private void prepareBackgroundManager() {mBackgroundManager = BackgroundManager.getInstance(getActivity());mBackgroundManager.attach(getActivity().getWindow());mDefaultBackground = getResources().getDrawable(R.drawable.default_background);mMetrics = new DisplayMetrics();getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);}

设置UI元素,实际上所有方法都在底层fragment封装好了,所以不会有findviewbyid存在

    private void setupUIElements() {//设置标题setTitle(getString(R.string.browse_title));//展示在标题栏上的图片(图片会隐藏标题)setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));//设置侧边栏显示状态 enabled 可见setHeadersState(HEADERS_ENABLED);//暂不知道方法具体作用setHeadersTransitionOnBackEnabled(true);//设置快速通道(侧边栏)背景setBrandColor(getResources().getColor(R.color.fastlane_background));//搜索图标颜色setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));}

创建适配器并加载数据,这里面核心内容是运用了Presenter作为中间层去处理业务,它是从Model中获取数据并提供给View的层

    private void loadRows() {//获取假数据List<Movie> list = MovieList.setupMovies();//总适配器mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());//每一行的Presenter 把每一项数据装进item的viewCardPresenter cardPresenter = new CardPresenter();int i;for (i = 0; i < NUM_ROWS; i++) {if (i != 0) {//列表里的数据重新随机排序Collections.shuffle(list);}//每一行数据适配器ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);//生成每一行的数据for (int j = 0; j < NUM_COLS; j++) {listRowAdapter.add(list.get(j % 5));}//添加左侧标题HeaderItem header = new HeaderItem(i, MovieList.MOVIE_CATEGORY[i]);mRowsAdapter.add(new ListRow(header, listRowAdapter));}HeaderItem gridHeader = new HeaderItem(i, "PREFERENCES");//生成最后一行额外数据GridItemPresenter mGridPresenter = new GridItemPresenter();ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);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);}

设置事件监听

    private void setupEventListeners() {setOnSearchClickedListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Toast.makeText(getActivity(), "Implement your own in-app search", Toast.LENGTH_LONG).show();}});setOnItemViewClickedListener(new ItemViewClickedListener());setOnItemViewSelectedListener(new ItemViewSelectedListener());}

ItemViewClickedListener监听点击跳转到DetailsActivity,在此不赘述。ItemViewSelectedListener这个监听很有意思,选择到的view会改变当前界面的背景图片,记录mBackgroundURI并开启一个计时器去改变背景,以下核心代码

    private final class ItemViewSelectedListener implements OnItemViewSelectedListener {@Overridepublic void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,RowPresenter.ViewHolder rowViewHolder, Row row) {if (item instanceof Movie) {mBackgroundURI = ((Movie) item).getBackgroundImageURI();startBackgroundTimer();}}}
    private void startBackgroundTimer() {if (null != mBackgroundTimer) {mBackgroundTimer.cancel();}mBackgroundTimer = new Timer();mBackgroundTimer.schedule(new UpdateBackgroundTask(), BACKGROUND_UPDATE_DELAY);}private class UpdateBackgroundTask extends TimerTask {@Overridepublic void run() {mHandler.post(new Runnable() {@Overridepublic void run() {if (mBackgroundURI != null) {updateBackground(mBackgroundURI.toString());}}});}}

更新背景图片方法里用了google推荐的图片开源库Glide

    protected void updateBackground(String uri) {int width = mMetrics.widthPixels;int height = mMetrics.heightPixels;Glide.with(getActivity()).load(uri).centerCrop().error(mDefaultBackground).into(new SimpleTarget<GlideDrawable>(width, height) {@Overridepublic void onResourceReady(GlideDrawable resource,GlideAnimation<? super GlideDrawable>glideAnimation) {mBackgroundManager.setDrawable(resource);}});mBackgroundTimer.cancel();}

以上是MainFragment所展示的内容


详情界面


DetailAcitvity加载VideoDetailsFragment方式与MainActivity一致。VideoDetailsFragment继承于DetailsFragment。我们先看初始化方法,获取到传过来的Movie对象后判断状态,非空的情况初始化内容,否则回跳MainActivity,以下针对重要的方法说明一下~

    @Overridepublic void onCreate(Bundle savedInstanceState) {Log.d(TAG, "onCreate DetailsFragment");super.onCreate(savedInstanceState);//准备背景管理器prepareBackgroundManager();//获取Movie对象mSelectedMovie = (Movie) getActivity().getIntent().getSerializableExtra(DetailsActivity.MOVIE);if (mSelectedMovie != null) {setupAdapter();setupDetailsOverviewRow();setupDetailsOverviewRowPresenter();setupMovieListRow();setupMovieListRowPresenter();updateBackground(mSelectedMovie.getBackgroundImageUrl());setOnItemViewClickedListener(new ItemViewClickedListener());} else {Intent intent = new Intent(getActivity(), MainActivity.class);startActivity(intent);}}

setupAdapter方法设置适配器

    private void setupAdapter() {// A ClassPresenterSelector 选择一个Presenter基于itemmPresenterSelector = new ClassPresenterSelector();mAdapter = new ArrayObjectAdapter(mPresenterSelector);setAdapter(mAdapter);}

setupDetailsOverviewRow方法设置一个详细片段的概述Row。包括一个图像,一个说明视图,以及可选的一系列的Action

    private void setupDetailsOverviewRow() {Log.d(TAG, "doInBackground: " + mSelectedMovie.toString());final DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie);row.setImageDrawable(getResources().getDrawable(R.drawable.default_background));int width = Utils.convertDpToPixel(getActivity().getApplicationContext(), 500);int height = Utils.convertDpToPixel(getActivity().getApplicationContext(), 500);Glide.with(getActivity()).load(mSelectedMovie.getCardImageUrl()).centerCrop().error(R.drawable.default_background).into(new SimpleTarget<GlideDrawable>(width, height) {@Overridepublic void onResourceReady(GlideDrawable resource,GlideAnimation<? super GlideDrawable>glideAnimation) {Log.d(TAG, "details overview card image url ready: " + resource);row.setImageDrawable(resource);mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size());}});row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString(R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2)));row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1),getResources().getString(R.string.rent_2)));row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1),getResources().getString(R.string.buy_2)));mAdapter.add(row);}

setupDetailsOverviewRowPresenter方法创建RowPresenter作为一个中间层,设置背景和风格,以及通过Action的id来监听点击的Action进行跳转,跳转到了播放控制界面

    private void setupDetailsOverviewRowPresenter() {// Set detail background and style.DetailsOverviewRowPresenter detailsPresenter =new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());detailsPresenter.setBackgroundColor(getResources().getColor(R.color.selected_background));detailsPresenter.setStyleLarge(true);// Hook up transition element.detailsPresenter.setSharedElementEnterTransition(getActivity(),DetailsActivity.SHARED_ELEMENT_NAME);//播放Action监听detailsPresenter.setOnActionClickedListener(new OnActionClickedListener() {@Overridepublic void onActionClicked(Action action) {if (action.getId() == ACTION_WATCH_TRAILER) {Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class);intent.putExtra(DetailsActivity.MOVIE, mSelectedMovie);startActivity(intent);} else {Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show();}}});mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter);}

setupMovieListRow()和setupMovieListRowPresenter()这两个方法是设置下方view列表的~

    private void setupMovieListRow() {String subcategories[] = {getString(R.string.related_movies)};List<Movie> list = MovieList.list;Collections.shuffle(list);ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());for (int j = 0; j < NUM_COLS; j++) {listRowAdapter.add(list.get(j % 5));}HeaderItem header = new HeaderItem(0, subcategories[0]);mAdapter.add(new ListRow(header, listRowAdapter));}private void setupMovieListRowPresenter() {mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());}

剩下的更新背景方法和监听就不细说了,以上就是详情界面的全部内容~


播放界面


PlaybackOverlayActivity播放页先看看xml的构造,可见是帧布局中包着一个VideoView和一个Fragment,VideoView一开始是不可见的,直接展示的是PlaybackOverlayFragment里面的内容,

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent" android:layout_height="match_parent"><VideoView android:id="@+id/videoView" android:layout_width="match_parent"android:layout_alignParentRight="true" android:layout_alignParentLeft="true"android:layout_alignParentTop="true" android:layout_alignParentBottom="true"android:layout_height="match_parent" android:layout_gravity="center"android:layout_centerInParent="true"></VideoView><fragment android:id="@+id/playback_controls_fragment"android:name="com.leanback.PlaybackOverlayFragment" android:layout_width="match_parent"android:layout_height="match_parent" /></FrameLayout>

我们再来看一下PlaybackOverlayActivity的内容,声明VideoView对象,LeanbackPlaybackState这个枚举包含四种状态:正在播放、暂停、缓冲、空闲。MediaSession对象是用于播放器与控制器之间进行交互。在初始化方法里加载videoView对象,设置回调,设置MediaSession对象。其最初界面的显示主要在PlaybackOverlayFragment中。

    private VideoView mVideoView;private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE;// Allows interaction with media controllers, volume keys, media buttons, and transport controls.private MediaSession mSession;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.playback_controls);loadViews();setupCallbacks();mSession = new MediaSession(this, "LeanbackSampleApp");mSession.setCallback(new MediaSessionCallback());mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);mSession.setActive(true);}/** List of various states that we can be in*/public static enum LeanbackPlaybackState {PLAYING, PAUSED, BUFFERING, IDLE;}

这里我把主要方法代码列出来,其他的仅展示方法名~

public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment {public interface OnPlayPauseClickedListener {}@Overridepublic void onCreate(Bundle savedInstanceState) {}@Overridepublic void onAttach(Activity activity) {}//设置行内容private void setupRows() {ClassPresenterSelector ps = new ClassPresenterSelector();PlaybackControlsRowPresenter playbackControlsRowPresenter;if (SHOW_DETAIL) {playbackControlsRowPresenter = new PlaybackControlsRowPresenter(new DescriptionPresenter());} else {playbackControlsRowPresenter = new PlaybackControlsRowPresenter();}playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {public void onActionClicked(Action action) {if (action.getId() == mPlayPauseAction.getId()) {togglePlayback(mPlayPauseAction.getIndex() == PlayPauseAction.PLAY);} else if (action.getId() == mSkipNextAction.getId()) {next();} else if (action.getId() == mSkipPreviousAction.getId()) {prev();} else if (action.getId() == mFastForwardAction.getId()) {Toast.makeText(getActivity(), "TODO: Fast Forward", Toast.LENGTH_SHORT).show();} else if (action.getId() == mRewindAction.getId()) {Toast.makeText(getActivity(), "TODO: Rewind", Toast.LENGTH_SHORT).show();}if (action instanceof PlaybackControlsRow.MultiAction) {((PlaybackControlsRow.MultiAction) action).nextIndex();notifyChanged(action);}}});playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS);ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);ps.addClassPresenter(ListRow.class, new ListRowPresenter());mRowsAdapter = new ArrayObjectAdapter(ps);addPlaybackControlsRow();addOtherRows();setAdapter(mRowsAdapter);}//播放方法public void togglePlayback(boolean playPause) {}//获取进度条private int getDuration() {}//增加控制行,这里有上下两行,上边一行是控制播放相关,下边一行是点赞相关private void addPlaybackControlsRow() {}//更新适配器private void notifyChanged(Action action) {}//更新播放行内容private void updatePlaybackRow(int index) {}//增加其他的行内容,这个把底下一排视频列表又加上来了private void addOtherRows() {}//获取更新周期private int getUpdatePeriod() {}//启动进度条滚动private void startProgressAutomation() {}//播放下一个private void next() {}//播放上一个private void prev() {}//停止进度条滚动private void stopProgressAutomation() {}@Overridepublic void onStop() {}static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {}protected void updateVideoImage(String uri) {}

点击播放按钮就走togglePlayback从而走到了PlaybackOverlayActivity中的onFragmentPlayPause调用VideoView对象开始播放,就不详述了~


分解模块

项目中Fragment的继承结构

BackgroundManager分析

ArrayObjectAdapter的继承结构以及使用方法

CardPresenter继承结构以及使用方法

Glide图片开源库简单介绍

疑问

Presenter重写方法onCreateViewHolder在哪里加载的

CardView动画效果在哪里实现的

androidtv-Leanback开源项目解析相关推荐

  1. Github项目解析(九)--实现Activity跳转动画的五种方式

    2019独角兽企业重金招聘Python工程师标准>>> 版权声明:本文为博主原创文章,未经博主允许不得转载. 转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了在Activity ...

  2. html获取此次点击的id,github项目解析(八)--Activity启动过程中获取组件宽高的三种方式...

    转载请标明出处:1片枫叶的专栏 上1个github小项目中我们介绍了避免按钮重复点击的小框架,其实现的核心逻辑是重写OnClickListener的onClick方法,添加避免重复点击的逻辑,即为第2 ...

  3. Github项目解析(五)--Android日志框架

    转载请标明出处:一片枫叶的专栏 本文主要讲解的是自己实现一个简单的Android日志框架,可以在Android studio控制台中输入日志信息,其简单的使用方式: L.v("...&quo ...

  4. java eventbus 原理_本文为 Android 开源项目实现原理解析 EventBus 部分,从源码分析 EventBus 的实现原理...

    之前太忙导致 Android 开源项目实现原理解析 一度搁浅,目前一期进行中,我也完成了 EventBus 分析的初稿,大家可以稍微看看后面会继续润色下. PS:本文直接复制 Markdown,格式有 ...

  5. 项目解析jsx文件_仅含一个源文件:这5个开源项目值得一看

    全文共1199字,预计学习时长3分钟 图源:unsplash 程序员常常会根据自己的喜好和项目类型用不同的方式写代码.如果一个软件项目规模很大且发展迅速,他们通常会把整个任务分解成好几个文件,从而实现 ...

  6. [高光谱] 开源项目Hyperspectral-Classification Pytorch解析之main

    开源项目Hyperspectral-Classification Pytorch解析之main.py 编码方式: # -*- coding: utf-8 -*- 项目简介: ""& ...

  7. [高光谱] GitHub开源项目Hyperspectral-Classification的解析

    GitHub链接:Hyperspectral-Classification Pytorch. 画重点!!! 完整版看这里嗷:GitHub开源项目Hyperspectral-Classification ...

  8. 美团动态线程池实践思路开源项目(DynamicTp),线程池源码解析及通知告警篇

    大家好,这篇文章我们来聊下动态线程池开源项目(DynamicTp)的通知告警模块.目前项目提供以下通知告警功能,每一个通知项都可以独立配置是否开启.告警阈值.告警间隔时间.平台等,具体代码请看core ...

  9. Android Hawk数据库的源码解析,Github开源项目,基于SharedPreferences的的存储框架

    今天看了朋友一个项目用到了Hawk,然后写了这边文章 一.了解一下概念 Android Hawk数据库github开源项目 Hawk是一个非常便捷的数据库.操作数据库只需一行代码,能存任何数据类型. ...

最新文章

  1. int/double/string使用
  2. Linux 小知识翻译 - 「端口限制」
  3. android dialog横屏,解决dialog在横竖屏切换时消失
  4. Linux思维导图之shell脚本编程基础、习题
  5. 图解Linux的Socket
  6. cmenu 隐藏子项中的一个子项_QML中的模型-视图-代理
  7. PAT1096 Consecutive Factors (20)(逻辑)
  8. 见实私域流量运营词汇手册(1.0版)
  9. 深入探讨SDN拓扑发现机制:新的攻击及实践对策【SDN拓扑】(上)
  10. 圣诞节平面设计师排版|首选手写字体素材,简单但非常好用。
  11. TeraTerm自动登录(Login)
  12. 火狐浏览器安装有道翻译插件
  13. python生成词云图、特殊图形_Python模块---Wordcloud生成词云图
  14. html个人单页代码,个人单页HTML模板
  15. 最小熵原理(四):“物以类聚”之从图书馆到词向量
  16. Mysql中使用sql语句查询前一个天、前一周、前一月时间的方法
  17. 利用八爪鱼爬取关键词搜索的微博数据
  18. Java白盒测试三角形函数_白盒测试实验报告-三角形形状-山东大学
  19. 美信监控易:石油销售公司全省IT一体化运维解决方案
  20. MySQL数据库基础——强大的select之二,更加精彩的功能

热门文章

  1. 阿帕奇骆驼#3-RabbitMq
  2. 绿金业绩评价居行业“第一梯队” ,农业银行绿色金融实践入选《中国银行业绿色金融发展报告》
  3. 中国银行业存在三大金融风险隐患?
  4. Ubuntu使用技巧:WinQQ自动隐藏解决
  5. Inconel 718实际有多厚 锻造注意事项
  6. 1.4313锻件锻件密度 特殊长度可定做
  7. SequoiaDB 巨杉数据库-MongoDB协议兼容
  8. 【Embedded System】裸机接口开发
  9. [附源码]java毕业设计火车票预订系统论文2022
  10. iOS中获取相册、相机、定位、以及麦克风权限设置