Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android TV: Building Apps with Google’s Leanback Library

Joe Birch
August 02, 2016

Android TV: Building Apps with Google’s Leanback Library

In this class, we'll look at how we can create Android TV apps with the help of Google's leanback library. After a brief introduction to the TV platform and an open-source Vine TV app, we'll move straight into how you can begin building applications for yourself using the leanback library, following best practices along the way. We'll take a look at the different components that are provided by the framework and how you can craft custom components of your own to enhance your application's UX. Seeing as Android TV applications are completely testable, we'll also take a brief look at how this can be done to ensure your app functions as expected!

Joe Birch

August 02, 2016
Tweet

More Decks by Joe Birch

Other Decks in Programming

Transcript

  1. <uses-feature android:name="android.hardware.microphone" android:required="false"/> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> <uses-feature android:name="android.software.leanback" android:required="true"/> <activity

    android:name=“com.hitherejoe.vineyard.ui.main.LeanbackActivity” android:label="@string/app_name" android:theme="@style/Theme.Leanback"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> </intent-filter> </activity>
  2. public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup

    viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // free resources } }
  3. public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup

    viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // free resources } }
  4. <android.support.v17.leanback.widget.NonOverlappingLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height=“match_parent" android:orientation="horizontal"> <ImageView android:id="@+id/header_icon" android:layout_width="32dp" android:layout_height="32dp"/> <TextView

    android:id="@+id/header_label" android:layout_marginLeft="6dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/white" android:textSize=“@dimen/header_text”/> </android.support.v17.leanback.widget.NonOverlappingLinearLayout>
  5. public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup

    viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // free resources } }
  6. @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { HeaderItem headerItem

    = ((ListRow) o).getHeaderItem(); setIconDrawable(headerItem.getName(), viewholder.iconImage); TextView label = viewHolder.headerText; label.setText(headerItem.getName()); }
  7. public class IconHeaderItemPresenter extends RowHeaderPresenter { @Override public ViewHolder onCreateViewHolder(ViewGroup

    viewGroup) { // inflate layout } @Override public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) { // set text, icons etc } @Override public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { // release bitmaps if used } }
  8. ArrayObjectAdapter rowAdapter = new ArrayObjectAdapter(this); rowAdapter.add(…); HeaderItem header = new

    HeaderItem(headerPosition, tag); mRowsAdapter.add(new ListRow(header, rowAdapter));
  9. Post Results (Array Object Adapter) Search Results (Array Object Adapter)

    Row Adapter (Array Object Adapter) Focused item triggers Post search
  10. mMediaController = getActivity().getMediaController(); mMediaController.registerCallback(mMediaControllerCallback); private class MediaControllerCallback extends MediaController.Callback {

    @Override public void onPlaybackStateChanged(@NonNull PlaybackState state) { } @Override public void onMetadataChanged(@NonNull MediaMetadata metadata) { } }
  11. ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector(); mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); mSecondaryActionsAdapter

    = new ArrayObjectAdapter(presenterSelector); mPlaybackControlsRow .setPrimaryActionsAdapter(mPrimaryActionsAdapter); mPlaybackControlsRow .setSecondaryActionsAdapter(mPrimaryActionsAdapter);
  12. public class Action { private Drawable mIcons; private CharSequence mLabel1;

    private CharSequence mLabel2; private ArrayList mKeyCodes; … }
  13. mPlayPauseAction = new PlayPauseAction(getActivity()); mRepeatAction = new RepeatAction(getActivity()); mSkipNextAction =

    new SkipNextAction(getActivity()); mSkipPreviousAction = new SkipPreviousAction(getActivity()); mPrimaryActionsAdapter.add(mPlayPauseAction); mPrimaryActionsAdapter.add(mSkipNextAction); mPrimaryActionsAdapter.add(mSkipPreviousAction); mSecondaryActionsAdapter.add(mRepeatAction);
  14. 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(true); } else if (action.getId() == mSkipPreviousAction.getId()) { prev(true); } else if (action.getId() == mRepeatAction.getId()) { loopVideos(); } if (action instanceof PlaybackControlsRow.MultiAction) { notifyChanged(action); } } });
  15. Post item = (Post) mPlaybackControlsRow.getItem(); item.description = description; item.username =

    username; mPlaybackControlsRow.setTotalTime((int) duration); mPlaybackControlsRow.setImageDrawable(resource); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(bufferedT
  16. Post item = (Post) mPlaybackControlsRow.getItem(); item.description = description; item.username =

    username; mPlaybackControlsRow.setTotalTime((int) duration); mPlaybackControlsRow.setImageDrawable(resource); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(bufferedTime);
  17. @Override public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) { String title = getString(…);

    String description = getString(…); Drawable icon = getActivity().getDrawable(…); return new GuidanceStylist.Guidance( title, description, "", icon); }
  18. @Override public void onCreateActions( @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {

    GuidedAction guidedAction = new GuidedAction.Builder() .id(…) .title(…) .description(…) .checkSetId(OPTION_CHECK_SET_ID) .build(); guidedAction.setChecked(isChecked); actions.add(guidedAction); }
  19. Tag Card TagCardView cardView = new TagCardView(parent.getContext()); Tag post =

    (Tag) item; TagCardView cardView = (TagCardView) viewHolder.view; if (post.tag != null) { cardView.setCardText(post.tag); cardView.setCardIcon(R.drawable.ic_tag); }
  20. Icon Card IconCardView cardView = new IconCardView(parent.getContext()); Option option =

    (Option) item; IconCardView cardView = (IconCardView) viewHolder.view; if (option.tag != null) { cardView.setCardIcon(R.drawable.ic_loop); cardView.setTitleText(option.title); cardView.setValueText(option.title); }
  21. Live Card Live Card View Base Card View Preview Card

    View Looping Video View Progress Bar Image View View (Transparent Overlay) Video View
  22. Live Card LiveCardView cardView = new LiveCardView(parent.getContext()); Post post =

    (Post) item; LiveCardView cardView = (LiveCardView) viewHolder.view; if (post.videoUrl != null) { cardView.setTitleText(post.description); cardView.setContentText(post.username); cardView.setVideoUrl(post.videoUrl); Glide.with(cardView.getContext()) .load(post.thumbnailUrl) .centerCrop() .error(mDefaultCardImage) .into(cardView.getMainImageView()); }
  23. @Override public void onPictureInPictureChanged(boolean inPictureInPicture) { if (inPictureInPicture) { //

    Hide the controls in picture-in-picture mode. } else { // Restore the playback UI based on the playback status. } }
  24. @Override public void onPause() { if (mInPictureInPicture) { // Continue

    playback } // If paused but not in PIP, pause playback if necessary }
  25. Resources Official Android TV Documentation github.com/hitherejoe/vineyard Google Plus Android TV

    Community github.com/hitherejoe/AndroidTvBoilerplate github.com/hitherejoe/leanbackcards medium.com/@hitherejoe