Android TV之谷歌android leanback框架详解

google leanback 库简介

“Leanback” 就是靠着看的意思。是指以放松的姿势倒在沙发上.谷歌推出 android.support.v17.leanback 软件包提供的 API 支持在电视设备上构建用户界面。它为电视应用提供了一些重要的小部件。这个库只支持到api 17以上的版本,也就是andorid 4.2,而一些效果也只是在api-21以上支持。

Demo 介绍

这是两个关于比较官方的Demo地址。有需要的可以下载

以下是Demo的部分截图
Android TV之谷歌android leanback框架详解_第1张图片
Android TV之谷歌android leanback框架详解_第2张图片

接下来就来说说代码结构
MVP的构建模式

Leanback 提供了model-view-presenter mvp的方式来构建应用。

  • model 是由应用开发者来提供,leanback对于model的实现没有加额外的限制,任何对象都是可以的。
  • view 还是由原来的android.view包下的类来实现。
  • Presenter 是基于现在的Adapter的该概念,并扩充为更具的灵活性和组合性。特别的是,绑定数据到view上的操作已经将adapter中分离出去,这部分逻辑由presenter去承担。

不信可以看看它的代码结构
Android TV之谷歌android leanback框架详解_第3张图片
至于MVP的好处这里就不用说了。

视图结构
  • 首先用android Tv的例子来介绍。运行程序时,整体内容被对齐在一个网格布局里。左侧的每一个标题header,都有右侧对应的一个内容行row,他们是一一对应的。header+content row由一个类 ListRow来表示。页面的整体其实是ListRow的集合

Android TV之谷歌android leanback框架详解_第4张图片

  • 整体是一个大的ArrayObjectAdapter 由一系列的ListRow来填充。view的呈现方式由ListRowPresenter来定义。
  • 一个ListRow 由HeaderItem 和一个小的ArrayObjectAdapter组成,这个一行中的ArrayObjectAdapter中放置我们定义的view,呈现方式由CardPresenter来定义。
    典型的代码如下:
List<Movie> list = MovieList.setupMovies();
       mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
       CardPresenter cardPresenter = new CardPresenter();
       for (int 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));
       }
        setAdapter(mRowsAdapter);
  • 基本关系:
    ArrayObjectAdapter (RowsAdapter) ← A set of ListRow
    ListRow = HeaderItem + ArrayObjectAdapter (RowAdapter)
    ArrayObjectAdapter (RowAdapter) ← A set of Object (CardInfo/Item)

Android TV之谷歌android leanback框架详解_第5张图片

一些关键点

一版来说,谷歌的leanback 是(如上图)左边的菜单对应后面的一行。但是其实实际在开发中应用中,是左边的一个菜单对应右边一整个页面。第一种情况基本上上面已经说了。下面来说说第二种情况:

public class MainFragment extends BaseBrowseFragment {
    private static final String TAG = "MainFragment";
    private static final long HEADER_ID_DISCOVERY = 0;
    private static final long HEADER_ID_TV_SHOW = 1;
    private static final long HEADER_ID_FAV = 2;
    private static final long HEADER_ID_MEDIA = 3;
    private static final long HEADER_ID_SETTINGS = 4;  // 这些都是对应的左边菜单的id

    @Override
    protected int getHeaderTitleArrayRes() {
        return R.array.main_header_title_array;
    }

    @Override
    protected int getHeaderIconArrayRes() {
        return R.array.main_header_icon;
    }

    @Override
    protected FragmentFactory getBrowseFragmentFactory() {
        return new PageRowFragmentFactory(mBackgroundManager);
    }

    protected void onEntranceTransitionEnd() {
        Log.d(TAG, "onEntranceTransitionEnd: ");
    }

    private static class PageRowFragmentFactory extends BrowseFragment.FragmentFactory {
        private final BackgroundManager mBackgroundManager;

        PageRowFragmentFactory(BackgroundManager backgroundManager) {
            this.mBackgroundManager = backgroundManager;
        }

        @Override
        public Fragment createFragment(Object rowObj) {
            Row row = (Row) rowObj;
            long id = row.getHeaderItem().getId();         //每当点击后就会显示出对应的fragment
            mBackgroundManager.setDrawable(null);
            if (id == HEADER_ID_TV_SHOW) {
                return new TvShowFragment();
            } else if (id == HEADER_ID_DISCOVERY) {
                return new DiscoveryFragment();
            } else if (id == HEADER_ID_MEDIA) {
                return new MediaFragment();
            }else if (id == HEADER_ID_FAV) {
                return new FavoriteFragment();
            }
            else if (id == HEADER_ID_SETTINGS) {
                return new SettingsFragment();
            }

            throw new IllegalArgumentException(String.format("Invalid row %s", rowObj));
        }
    }

}

在leanback 中,右边页面显示的种类往往不同,例如,有视频列表,图片列表,音乐列表。那么这些在leanback中都是怎么处理的呢?其实在那些列表中的每一个item都是一个Card。然后其实就是在recycleview 中设置不同类型的item。

通过如下的selector 去选择不同的card

public class CardPresenterSelector extends PresenterSelector {

    private final Context mContext;
    private final HashMap<Card.Type, Presenter> presenters = new HashMap<Card.Type, Presenter>();

    public CardPresenterSelector(Context context) {
        mContext = context;
    }

    @Override
    public Presenter getPresenter(Object item) {
        if (!(item instanceof Card)) throw new RuntimeException(
                String.format("The PresenterSelector only supports data items of type '%s'",
                        Card.class.getName()));
        Card card = (Card) item;
        Presenter presenter = presenters.get(card.getCardType());

        if (presenter == null) {
            switch (card.getCardType()) {
                case SINGLE_LINE:
                    presenter = new SingleLineCardPresenter(mContext);
                    break;
                case VIDEO_GRID:
                    presenter = new VideoCardViewPresenter(mContext, R.style.VideoGridCardTheme);
                    break;
                case MOVIE:
                    presenter = new CardPresenter();
                    break;
                case MOVIE_COMPLETE:
                    /**
                     * {@link com.smartdevice.multimediaplayer.utils.Constants.ITEM_TYPE_MUSIC}
                     * add for music which use in search and discovery model ,ordinal  =  0
                     */
                    presenter = new MusicCardPresenter(mContext, false);
                    break;
                case MUSIC_SMALL:
                    presenter = new MusicCardPresenter(mContext, true);
                    break;
                case MOVIE_BASE:
                case SQUARE_BIG:
                case ICON:
                    presenter = new GridItemPresenter(mContext);
                    break;
                case GRID_SQUARE:
                    presenter = new GridItemPresenter(mContext);
                    break;
                case MUSIC_ARTIST:
                    presenter = new ArtistsCirclePresenter(mContext);
                    break;
                case MUSIC_ALBUM:
                    presenter = new AlbumCardPresenter(mContext);
                    break;
                case CIRCLE_ICON:
                    presenter = new CircleIconPresenter(mContext);
                    break;
                case TV_SHOW_CURRENT:
                case TV_SHOW_UPCOMING:
                case TV_SHOW_RECOMMENDATION:
                case TV_SHOW_APP:
                case SPOTIFY_SEARCH:
                    presenter = new TVShowPresenter(mContext);
                    break;
                case POPULAR_MUSIC:
                    presenter = new PopularMusicPresenter(mContext);
                    break;
                case RECOMMEND_VIDEO:
                case CHILD_CHANNEL:
                    presenter = new RecommendVideoPresenter(mContext);
                    break;
                case FAVOITE_TVSHOW:
                    presenter = new FavoriteTVShowPresenter(mContext);
                    break;
                case DEVICE:
                    presenter = new DeviceItemPresenter(mContext);
                    break;
                case SETTINGS:
                    presenter = new SettingsItemPresenter(mContext);
                    break;
                case LOCALDEVICE:
                    presenter = new LocalDeviceItemPresenter(mContext);
                    break;

                case DLNA_DEVICE:
                    presenter = new DlnaDeviceItemPresenter(mContext);
                    break;
                case LOADING_ICON:
                    presenter = new LoadingCardPresenter(false);
                    break;
                case LOADING_ICON_ERROR:
                    presenter = new LoadingCardPresenter(true);
                    break;
                case YOUTUBE_VIDEO_ICON:
                    presenter = new DiscoveryCardViewPresent(mContext);
                    break;
                default:
                    presenter = new ImageCardViewPresenter(mContext);
                    break;
            }
        }
        presenters.put(card.getCardType(), presenter);
        return presenter;
    }

选择好card后,然后对应的card再去往里面填充数据,下面是一种card 类型。

public class MusicCardPresenter extends MusicAbstractCardPresenter<ImageCardView> {
    public static final String TAG = MusicCardPresenter.class.getSimpleName();

    public MusicCardPresenter(Context context, int cardThemeResId) {
        super(new ContextThemeWrapper(context, cardThemeResId));
    }

    public MusicCardPresenter(Context context, boolean isSmall) {
        this(context, isSmall ? R.style.MusicSmallCardStyle : R.style.MusicCardStyle);
    }

    @Override
    protected ImageCardView onCreateView() {
        ImageCardView imageCardView = new ImageCardView(getContext());
        imageCardView.setFocusable(true);
        imageCardView.setFocusableInTouchMode(true);
        imageCardView.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP);
        return imageCardView;
    }

    @Override
    public void onBindViewHolder(Card card, ImageCardView cardView) {
        cardView.setTitleText(card.getTitle());
        if (TextUtils.isEmpty(card.getFilePath())) {//spotify music
            Glide.with(getContext())
                    .load(card.getImageUrl())
                    .into(cardView.getMainImageView());   这里使用Glide 框架来填充图片

        } else {//local music files
            mImageFetcher.loadImage(card.getFilePath(), cardView.getMainImageView());
        }

    }

    @Override
    public void onUnbindViewHolder(ImageCardView cardView) {
        super.onUnbindViewHolder(cardView);
        ImageWorker.cancelWork(cardView.getMainImageView());
        cardView.setBadgeImage(null);
        cardView.setMainImage(null);
    }
如何使用

build.gradle中导入

    implementation 'com.android.support:recyclerview-v7:27.1.0'
    implementation 'com.android.support:leanback-v17:27.1.0'   //主要是这个
    implementation 'com.android.support:recommendation:27.1.0'
    implementation 'com.android.support:preference-leanback-v17:27.1.0'

然后就可以正常调用了。

感谢:Android TV Leanback 简介

你可能感兴趣的:(Android,系统,第三方库)