androidtv-Leanback开源项目解析

项目展示

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

类图(待添加)

项目流程

主界面

这里写图片描述
项目入口是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的

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

    @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<Movie> 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<GlideDrawable>(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<GlideDrawable>(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<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;

    @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动画效果在哪里实现的

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值