项目展示

类图(待添加)
项目流程
主界面
项目入口是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
