项目入口是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 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() {
@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对象开始播放,就不详述了~