使用传输控件

使用 Compose 构建更优质的应用
使用 Jetpack Compose 为 Android TV OS 创建美观的 UI,代码量更少。

Leanback UI 工具包具有播放控制功能,可提供更佳的用户体验。对于视频应用,传输控制支持使用向前和向后控制进行视频擦除。擦除时,显示屏会显示缩略图以帮助导航视频。

库包含抽象类以及预构建的开箱即用的实现,为开发者提供了更细粒度的控制。使用预构建的实现,您可以快速构建功能丰富的应用,无需编写大量代码。如果您需要更多自定义,可以扩展库中的任何预构建组件。

控件和播放器

Leanback UI 工具包将传输控件 UI 与播放视频的播放器分离。这通过两个组件来实现:播放支持片段用于显示传输控件(以及可选的视频),以及播放器适配器用于封装媒体播放器。

播放片段

您的应用 UI 活动应使用 PlaybackSupportFragmentVideoSupportFragment。两者都包含 Leanback 传输控件

您可以自定义片段的 ObjectAdapter 以增强 UI。例如,使用 setAdapter() 添加“相关视频”行。

PlayerAdapter

PlayerAdapter 是一个抽象类,用于控制底层媒体播放器。开发者可以选择预构建的 MediaPlayerAdapter 实现,或者编写自己的类实现。

将这些部分粘合在一起

您必须使用一些“控制粘合剂”将播放片段连接到播放器。Leanback 库提供两种粘合剂

leanback transport control glue

如果希望应用程序支持视频擦除,则必须使用 PlaybackTransportControlGlue

还需要指定一个“胶水主机”,它将胶水绑定到播放片段,在 UI 中绘制传输控件并维护其状态,并将传输控件事件传回胶水。主机必须与播放片段类型匹配。使用 PlaybackSupportFragmentGlueHostPlaybackFragment 配合使用,使用 VideoSupportFragmentGlueHostVideoFragment 配合使用。

以下是 Leanback 传输控件的各个部分如何组合在一起的示意图

leanback transport control glue

将应用程序粘合在一起的代码应位于定义 UI 的 PlaybackSupportFragmentVideoSupportFragment 中。

在以下示例中,应用程序构造了一个 PlaybackTransportControlGlue 实例,将其命名为 playerGlue,并将其 VideoSupportFragment 连接到新创建的 MediaPlayerAdapter。由于这是一个 VideoSupportFragment,因此设置代码调用 setHost()VideoSupportFragmentGlueHost 附加到 playerGlue。代码包含在扩展 VideoSupportFragment 的类中。

Kotlin

class MyVideoFragment : VideoSupportFragment() {

  fun onCreate(savedInstanceState: Bundle) {
      super.onCreate(savedInstanceState)
      val playerGlue = PlaybackTransportControlGlue(getActivity(),
          MediaPlayerAdapter(getActivity()))
      playerGlue.setHost(VideoSupportFragmentGlueHost(this))
      playerGlue.addPlayerCallback(object : PlaybackGlue.PlayerCallback() {
          override fun onPreparedStateChanged(glue: PlaybackGlue) {
              if (glue.isPrepared()) {
                  playerGlue.seekProvider = MySeekProvider()
                  playerGlue.play()
              }
          }
      })
      playerGlue.setSubtitle("Leanback artist")
      playerGlue.setTitle("Leanback team at work")
      val uriPath = "android.resource://com.example.android.leanback/raw/video"
      playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath))
  }
}

Java

public class MyVideoFragment extends VideoSupportFragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
              new PlaybackTransportControlGlue(getActivity(),
                      new MediaPlayerAdapter(getActivity()));
      playerGlue.setHost(new VideoSupportFragmentGlueHost(this));
      playerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
          @Override
          public void onPreparedStateChanged(PlaybackGlue glue) {
              if (glue.isPrepared()) {
                  playerGlue.setSeekProvider(new MySeekProvider());
                  playerGlue.play();
              }
          }
      });
      playerGlue.setSubtitle("Leanback artist");
      playerGlue.setTitle("Leanback team at work");
      String uriPath = "android.resource://com.example.android.leanback/raw/video";
      playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
  }
}

请注意,设置代码还定义了 PlayerAdapter.Callback 来处理来自媒体播放器的事件。

自定义 UI 胶水

可以自定义 PlaybackBannerControlGluePlaybackTransportControlGlue 来更改 PlaybackControlsRow

自定义标题和描述

要自定义播放控件顶部的标题和描述,请覆盖 onCreateRowPresenter()

Kotlin

override fun onCreateRowPresenter(): PlaybackRowPresenter {
    return super.onCreateRowPresenter().apply {
        (this as? PlaybackTransportRowPresenter)
                ?.setDescriptionPresenter(MyCustomDescriptionPresenter())
    }
}

Java

@Override
protected PlaybackRowPresenter onCreateRowPresenter() {
  PlaybackTransportRowPresenter presenter = (PlaybackTransportRowPresenter) super.onCreateRowPresenter();
  presenter.setDescriptionPresenter(new MyCustomDescriptionPresenter());
  return presenter;
}

添加控件

控制胶水在 PlaybackControlsRow 中显示操作的控件。

PlaybackControlsRow 中的操作被分配给两个组:主要操作辅助操作。主要组的控件显示在进度条上方,辅助组的控件显示在进度条下方。最初,只有播放/暂停按钮的一个主要操作,没有辅助操作。

可以通过覆盖 onCreatePrimaryActions()onCreateSecondaryActions() 将操作添加到主要组和辅助组。

Kotlin

private lateinit var repeatAction: PlaybackControlsRow.RepeatAction
private lateinit var pipAction: PlaybackControlsRow.PictureInPictureAction
private lateinit var thumbsUpAction: PlaybackControlsRow.ThumbsUpAction
private lateinit var thumbsDownAction: PlaybackControlsRow.ThumbsDownAction
private lateinit var skipPreviousAction: PlaybackControlsRow.SkipPreviousAction
private lateinit var skipNextAction: PlaybackControlsRow.SkipNextAction
private lateinit var fastForwardAction: PlaybackControlsRow.FastForwardAction
private lateinit var rewindAction: PlaybackControlsRow.RewindAction

override fun onCreatePrimaryActions(primaryActionsAdapter: ArrayObjectAdapter) {
    // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
    // Will display as follows:
    // play/pause, previous, rewind, fast forward, next
    //   > /||      |<        <<        >>         >|
    super.onCreatePrimaryActions(primaryActionsAdapter)
    primaryActionsAdapter.apply {
        add(skipPreviousAction)
        add(rewindAction)
        add(fastForwardAction)
        add(skipNextAction)
    }
}

override fun onCreateSecondaryActions(adapter: ArrayObjectAdapter?) {
    super.onCreateSecondaryActions(adapter)
    adapter?.apply {
        add(thumbsDownAction)
        add(thumbsUpAction)
    }
}

Java

private PlaybackControlsRow.RepeatAction repeatAction;
private PlaybackControlsRow.PictureInPictureAction pipAction;
private PlaybackControlsRow.ThumbsUpAction thumbsUpAction;
private PlaybackControlsRow.ThumbsDownAction thumbsDownAction;
private PlaybackControlsRow.SkipPreviousAction skipPreviousAction;
private PlaybackControlsRow.SkipNextAction skipNextAction;
private PlaybackControlsRow.FastForwardAction fastForwardAction;
private PlaybackControlsRow.RewindAction rewindAction;

@Override
protected void onCreatePrimaryActions(ArrayObjectAdapter primaryActionsAdapter) {
    // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
    // Will display as follows:
    // play/pause, previous, rewind, fast forward, next
    //   > /||      |<        <<        >>         >|
    super.onCreatePrimaryActions(primaryActionsAdapter);
    primaryActionsAdapter.add(skipPreviousAction);
    primaryActionsAdapter.add(rewindAction);
    primaryActionsAdapter.add(fastForwardAction);
    primaryActionsAdapter.add(skipNextAction);
}

@Override
protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
    super.onCreateSecondaryActions(adapter);
    adapter.add(thumbsDownAction);
    adapter.add(thumbsUpAction);
}

必须覆盖 onActionClicked() 来处理新操作。

Kotlin

override fun onActionClicked(action: Action) {
    when(action) {
        rewindAction -> {
            // Handle Rewind
        }
        fastForwardAction -> {
            // Handle FastForward
        }
        thumbsDownAction -> {
            // Handle ThumbsDown
        }
        thumbsUpAction -> {
            // Handle ThumbsUp
        }
        else ->
            // The superclass handles play/pause and delegates next/previous actions to abstract methods,
            // so those two methods should be overridden rather than handling the actions here.
            super.onActionClicked(action)
    }
}

override fun next() {
    // Skip to next item in playlist.
}

override fun previous() {
    // Skip to previous item in playlist.
}

Java

@Override
public void onActionClicked(Action action) {
    if (action == rewindAction) {
        // Handle Rewind
    } else if (action == fastForwardAction ) {
        // Handle FastForward
    } else if (action == thumbsDownAction) {
        // Handle ThumbsDown
    } else if (action == thumbsUpAction) {
        // Handle ThumbsUp
    } else {
        // The superclass handles play/pause and delegates next/previous actions to abstract methods,
        // so those two methods should be overridden rather than handling the actions here.
        super.onActionClicked(action);
    }
}

@Override
public void next() {
    // Skip to next item in playlist.
}

@Override
public void previous() {
    // Skip to previous item in playlist.
}

在特殊情况下,可能需要实现自己的 PlaybackTransportRowPresenter 来呈现自定义控件并使用 PlaybackSeekUi 响应搜索操作。

视频擦除

如果应用程序使用 VideoSupportFragment 并且希望支持视频擦除。

scrubbing

需要提供 PlaybackSeekDataProvider 的实现。此组件提供滚动时使用的视频缩略图。必须通过扩展 PlaybackSeekDataProvider 来实现自己的提供程序。请参阅 Leanback Showcase 应用程序 中的示例。