androidtv-Leanback开源项目解析

androidtv-Leanback开源项目解析项目展示类图 待添加 项目流程主界面项目入口是 MainActivity 我们发现 MainActivity 非常简单 publicclassM Calledwhenth Overridepubl

项目展示

这里写图片描述
这里写图片描述
这里写图片描述

类图(待添加)

项目流程

主界面

这里写图片描述
项目入口是MainActivity,我们发现MainActivity非常简单。

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

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

 
    <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中进行初始化

 @Override public 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 
  
    list = MovieList.setupMovies(); 
   //总适配器 mRowsAdapter = 
   new ArrayObjectAdapter( 
   new ListRowPresenter()); 
   //每一行的Presenter 把每一项数据装进item的view CardPresenter 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() { @Override public 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 { 
    @Override public 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 { 
    @Override public void run() { mHandler.post(new Runnable() { @Override public 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 
  
    (width, height) { 
   @Override 
   public 
   void 
   onResourceReady(GlideDrawable resource, GlideAnimation 
   super GlideDrawable> glideAnimation) { mBackgroundManager.setDrawable(resource); } }); mBackgroundTimer.cancel(); } 
  

以上是MainFragment所展示的内容


详情界面

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

 @Override public 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基于item mPresenterSelector = 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 
   
     (width, height) { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation 
     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() { @Override public 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 
   
     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; @Override public 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 { 
     } @Override public void onCreate(Bundle savedInstanceState) {} @Override public 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() {} @Override public void onStop() {} static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {} protected void updateVideoImage(String uri) {}

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


分解模块

项目中Fragment的继承结构

BackgroundManager分析

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

CardPresenter继承结构以及使用方法

Glide图片开源库简单介绍

疑问

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

CardView动画效果在哪里实现的

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224301.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午12:14
下一篇 2026年3月17日 下午12:15


相关推荐

  • Spring的基本配置和Spring与Mybatis的整合

    Spring的基本配置和Spring与Mybatis的整合Spring:        Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由RodJohnson在其著作ExpertOne-On-OneJ2EEDevelopmentandDesign中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪…

    2022年4月29日
    43
  • 台式计算机网线插哪里,电脑主机网线插哪里?

    台式计算机网线插哪里,电脑主机网线插哪里?问:电脑主机网线插在哪里?答:这个需要根据你家的上网情况来决定,主要是看有没有用到路由器上网,下面进行详细说明。1、如果你家里没有用路由器,那么电脑主机上的网线,需要插在猫的网口/LAN口。温馨提示:没有用路由器的情况下,电脑要上网的话,你需要打开电脑中的“宽带连接”程序,然后填写你家的宽带账号、宽带密码,就能连接上网了。如果你不知道如何用“宽带连接”程序拨号上网,可以点击阅读下面的文章,查看详…

    2022年6月26日
    36
  • linux复制/剪切文件到另一个文件夹「建议收藏」

    linux复制/剪切文件到另一个文件夹「建议收藏」复制/拷贝:cp文件名路径cphello.csv./python/ml:把当前目录的hello.csv拷贝到当前目的python文件夹里的ml文件夹里cp源文件名新文件名cphello.txtworld.txt:复制并改名,并存放在当前目录下cpfile1file2复制一个文件cpdir/*.复制一个目录下的所有文件…

    2022年8月23日
    21
  • python3·7创建虚拟环境_python激活虚拟环境

    python3·7创建虚拟环境_python激活虚拟环境Centos7安装Python3,创建python3虚拟环境

    2025年11月12日
    4
  • Linux挂载磁盘(扩展根目录)

    Linux挂载磁盘(扩展根目录)1.df命令查看自己的/目录属于哪个逻辑卷(我们要做的是对逻辑卷扩展)[root@redhat6-3~]#df-h查看未挂载的磁盘:[root@redhat6-3~]#lsblk2.新加一块硬盘,我的是vdb先创建物理卷[root@redhat6-3~]#fdisk/dev/vdb在分别输入n、p、1、2048、1048575999、w如果报错,执行(类似刷新磁盘变化,如果磁盘无法格式化和无法创建物理卷):partprobe再执行:fdisk/dev/vdb

    2022年6月19日
    48
  • 2014Esri全球用户大会——亮点系列之精彩应用案例

    2014Esri全球用户大会——亮点系列之精彩应用案例

    2021年12月8日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号