LeanBack框架介绍

Leanback库是Google开源的一个高效开发的支持库,它包含了一套完整的电视应用开发Api资源和组件。

Leanback库是基于Model --> Presenter --> View 的样式设计的(MVP)

Google官方DEMO: https://github.com/android/tv-samples

标准页面

以VerticalGridFragment页面为例:

页面拆解:

image
VerticalGridFragment -->(
  VerticalGridPresenter -->(
    VerticalGridView -->(
      ItemBridgeAdapter{
        CursorObjectAdapter(
          CardPresenter --> ImageCardView,
          Object
        )
      }
    )
  )
)

界面背后的迷宫

阶段一

setAdapter流程:

阶段二

View Bind流程:

//ItemBridgeAdapter.java
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
    Presenter presenter = mPresenters.get(viewType);
    Presenter.ViewHolder presenterVh;
    View view;
    if (mWrapper != null) {
        view = mWrapper.createWrapper(parent);
        presenterVh = presenter.onCreateViewHolder(parent);
        mWrapper.wrap(view, presenterVh.view);
    } else {
        presenterVh = presenter.onCreateViewHolder(parent);
        view = presenterVh.view;
    }
    ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
    onCreate(viewHolder);
    if (mAdapterListener != null) {
        mAdapterListener.onCreate(viewHolder);
    }
    View presenterView = viewHolder.mHolder.view;
    if (presenterView != null) {
        viewHolder.mFocusChangeListener.mChainedListener =
                presenterView.getOnFocusChangeListener();
        presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
    }
    if (mFocusHighlight != null) {
        mFocusHighlight.onInitializeView(view);
    }
    return viewHolder;
}
//ItemBridgeAdapter.java
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
    ViewHolder viewHolder = (ViewHolder) holder;
    viewHolder.mItem = mAdapter.get(position);

    viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);

    onBind(viewHolder);
    if (mAdapterListener != null) {
        mAdapterListener.onBind(viewHolder);
    }
}

在tryGetViewHolderForPositionByDeadline时根据position获取对应的Presenter的type,在调用CreatViewHolder时通过type创建对应的ViewHolder,可以理解为getItemViewType实现了position到ViewHolder的映射

//ItemBridgeAdapter.java
@Override
public int getItemViewType(int position) {
    PresenterSelector presenterSelector = mPresenterSelector != null
            ? mPresenterSelector : mAdapter.getPresenterSelector();
    Object item = mAdapter.get(position);
    Presenter presenter = presenterSelector.getPresenter(item);
    int type = mPresenters.indexOf(presenter);
    if (type < 0) {
        mPresenters.add(presenter);
        type = mPresenters.indexOf(presenter);
        if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
        onAddPresenter(presenter, type);
        if (mAdapterListener != null) {
            mAdapterListener.onAddPresenter(presenter, type);
        }
    }
    return type;
}

在VerticalGridFragment页面有如下定义:

//VerticalGridFragment.java
private final CursorObjectAdapter mVideoCursorAdapter =
        new CursorObjectAdapter(new CardPresenter());
    //CardPresenter.java
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        ...
        ImageCardView cardView = new ImageCardView(parent.getContext()) {
            @Override
            public void setSelected(boolean selected) {
                updateCardBackgroundColor(this, selected);
                super.setSelected(selected);
            }
        };

        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        updateCardBackgroundColor(cardView, false);
        return new ViewHolder(cardView);
    }

    //CardPresenter.java
    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        Video video = (Video) item;

        ImageCardView cardView = (ImageCardView) viewHolder.view;
        cardView.setTitleText(video.title);
        cardView.setContentText(video.studio);

        if (video.cardImageUrl != null) {
            // Set card size from dimension resources.
            Resources res = cardView.getResources();
            int width = res.getDimensionPixelSize(R.dimen.card_width);
            int height = res.getDimensionPixelSize(R.dimen.card_height);
            cardView.setMainImageDimensions(width, height);

            Glide.with(cardView.getContext())
                    .load(video.cardImageUrl)
                    .apply(RequestOptions.errorOf(mDefaultCardImage))
                    .into(cardView.getMainImageView());
        }
    }

阶段三

View Recycle流程:

 //CardPresenter.java
    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
        ImageCardView cardView = (ImageCardView) viewHolder.view;
        // Remove references to images so that the garbage collector can free up memory.
        cardView.setBadgeImage(null);
        cardView.setMainImage(null);
    }

在View Recycling策略下,tryGetViewHolderForPositionByDeadline先通过postion查找对应的type,然后确定mAttachScrapCache、mChangeScrapCache、mCacheItem(和postion相关)、mRecyclerPool(和type相关)中是否有可复用的ViewHolder,减少重复创建带来的性能影响。

以上分析可知:要了解当前页面View的Bind、Recycler整体流程,需要重点分析getItemViewType是如何实现position到ViewHolder映射的

如何写一个基本页面:

  • Fragment
  • Presenter
  • PresenterSelecter
  • ItemBridgeAdapter

复杂页面

以复杂的十字交错页面为例:

拆解如下:

ItemBridgeAdapter {
  PresenterSelector{
    Presenter
  }
  ObjectAdapter{
    Object
  }
}
Presenter -- ViewHolder -- ObjectAdapter
HeadersSupportFragment -->(
  VerticalGridView -->(
    ItemBridgeAdapter{
       IconHeaderItemPresenter --> HeaderView,
       ArrayObjectAdapter(
        ListRow(HeaderItem + CursorObjectAdapter)
       )
    }
  )
)
RowsSupportFragment --> ( 
  VerticalGridView --> (
    ItemBridgeAdapter {
      ListRowPresenter --> ListRowView,
      ListRowDataAdapter(
        ArrayObjectAdapter (
          ListRow (HeaderItem + CursorObjectAdapter(CardPresenter))
       )
     ) 
   }
 )
)
ListRowView -->(
  HorizontalGridView -->(
    ItemBridgeAdapter {
       CardPresenter --> ImageCardView,
       CursorObjectAdapter
    }
  )
)

阶段一

左侧导航栏

右侧内容页

阶段二、三

左侧导航栏

根据标准页面的分析结论,同理可以分析getItemViewType中position到ViewHolder的映射关系:

//ItemBridgeAdapter.java
@Override
public int getItemViewType(int position) {
    PresenterSelector presenterSelector = mPresenterSelector != null
            ? mPresenterSelector : mAdapter.getPresenterSelector();
    Object item = mAdapter.get(position);
    Presenter presenter = presenterSelector.getPresenter(item);
    int type = mPresenters.indexOf(presenter);
    if (type < 0) {
        mPresenters.add(presenter);
        type = mPresenters.indexOf(presenter);
        if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
        onAddPresenter(presenter, type);
        if (mAdapterListener != null) {
            mAdapterListener.onAddPresenter(presenter, type);
        }
    }
    return type;
}

而HeadersSupportFragment的PresenterSelector声明如下

setHeaderPresenterSelector(new PresenterSelector() {
    @Override
    public Presenter getPresenter(Object o) {
        return new IconHeaderItemPresenter();
    }
});

因此HeadersSupportFragment页面的Bind、Recycler的流程就在IconHeaderItemPresenter内实现:

//IconHeaderItemPresenter.java
public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
    mUnselectedAlpha = viewGroup.getResources()
            .getFraction(R.fraction.lb_browse_header_unselect_alpha, 1, 1);
    LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    View view = inflater.inflate(R.layout.icon_header_item, null);
    view.setAlpha(mUnselectedAlpha); // Initialize icons to be at half-opacity.

    return new ViewHolder(view);
}

@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
    HeaderItem headerItem = ((ListRow) item).getHeaderItem();
    View rootView = viewHolder.view;
    rootView.setFocusable(true);

    ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
    Drawable icon = rootView.getResources().getDrawable(R.drawable.android_header, null);
    iconView.setImageDrawable(icon);

    TextView label = (TextView) rootView.findViewById(R.id.header_label);
    label.setText(headerItem.getName());
}

@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
    // no op
}

右侧内容页

同理,分析getItemViewType的position到ViewHolder的映射关系

//MainFragment.java
mCategoryRowAdapter = new ArrayObjectAdapter(new ListRowPresenter());
setAdapter(mCategoryRowAdapter);

因此右侧内容栏页面的Bind、Recycler的流程在ListRowPresenter内实现

//ListRowPresenter.java
@Override
protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
    initStatics(parent.getContext());
    ListRowView rowView = new ListRowView(parent.getContext());
    setupFadingEffect(rowView);
    if (mRowHeight != 0) {
        rowView.getGridView().setRowHeight(mRowHeight);
    }
    return new ViewHolder(rowView, rowView.getGridView(), this);
}

@Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
    super.onBindRowViewHolder(holder, item);
    ViewHolder vh = (ViewHolder) holder;
    ListRow rowItem = (ListRow) item;
    vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
    vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
    vh.mGridView.setContentDescription(rowItem.getContentDescription());
}

@Override
protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
    ViewHolder vh = (ViewHolder) holder;
    vh.mGridView.setAdapter(null);
    vh.mItemBridgeAdapter.clear();
    super.onUnbindRowViewHolder(holder);
}

createRowViewHolder创建了一个ListRowView。HorizontalGridView和VerticalGridView实现方式一致,仅在LayoutManager上有区别

public final class ListRowView extends LinearLayout {

    private HorizontalGridView mGridView;
    ...
}

onBindRowViewHolder设置mGridView的Adapter, onUnbindRowViewHolder时释放。

再看页面源数据部分:

//MainFragment.java
CursorObjectAdapter videoCursorAdapter =
        new CursorObjectAdapter(new CardPresenter());
videoCursorAdapter.setMapper(new VideoCursorMapper());
mVideoCursorAdapters.put(videoLoaderId, videoCursorAdapter);

ListRow row = new ListRow(header, videoCursorAdapter);
mCategoryRowAdapter.add(row);

rowItem.getAdapter()返回的是videoCursorAdapter。

此后的流程和标准页面分析的流程一样。因为在onBindRowViewHolder阶段,设置ListRowView中HorizontalGridView的Adapter为CursorObjectAdapter ,对应的Presenter为CardPresenter,因此getItemViewType中position到ViewHolder的映射时,HorizontalGridView的每个子View的Bind、Recycler的流程在CardPresenter内实现

public class CardPresenter extends Presenter {
    ...
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        mDefaultBackgroundColor =
            ContextCompat.getColor(parent.getContext(), R.color.default_background);
        mSelectedBackgroundColor =
                ContextCompat.getColor(parent.getContext(), R.color.selected_background);
        mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie, null);

        ImageCardView cardView = new ImageCardView(parent.getContext()) {
            @Override
            public void setSelected(boolean selected) {
                updateCardBackgroundColor(this, selected);
                super.setSelected(selected);
            }
        };

        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        updateCardBackgroundColor(cardView, false);
        return new ViewHolder(cardView);
    }

@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
    Video video = (Video) item;

    ImageCardView cardView = (ImageCardView) viewHolder.view;
    cardView.setTitleText(video.title);
    cardView.setContentText(video.studio);

    if (video.cardImageUrl != null) {
        // Set card size from dimension resources.
        Resources res = cardView.getResources();
        int width = res.getDimensionPixelSize(R.dimen.card_width);
        int height = res.getDimensionPixelSize(R.dimen.card_height);
        cardView.setMainImageDimensions(width, height);

        Glide.with(cardView.getContext())
                .load(video.cardImageUrl)
                .apply(RequestOptions.errorOf(mDefaultCardImage))
                .into(cardView.getMainImageView());
    }
}

@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
    ImageCardView cardView = (ImageCardView) viewHolder.view;

    // Remove references to images so that the garbage collector can free up memory.
    cardView.setBadgeImage(null);
    cardView.setMainImage(null);
}

以上分析可知:一个Row布局的Bind、Recycler流程,是先对RowPresnter的处理,然后对子Presnter的处理。其中都是通过getItemViewType实现position到ViewHolder映射的。

如何写一个包含Row结构页面:

  • Fragment
  • Presenter
  • PresenterSelecter
  • ItemBridgeAdapter
  • RowPresenter
  • ArrayObjectAdapter
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 一、RecyclerView的四级缓存 (1)mChangedScrap、mAttachedScrap: 用于屏幕...
    zzq_nene阅读 1,906评论 1 2
  • 【Android 控件 RecyclerView】 概述 RecyclerView是什么 从Android 5.0...
    Rtia阅读 307,547评论 27 439
  • 上一篇文章分析RecyclerView刷新机制知道LayoutManager在布局子View时会向Recycler...
    susion哒哒阅读 17,241评论 13 45
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 125,233评论 2 7
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,055评论 0 4