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

你可能感兴趣的:(LeanBack框架介绍)