LiveCoreView 是一个跨平台的视频直播核心组件,提供开播、观看、连麦和主播 PK 等关键能力。该组件通过挂件机制,支持在视频区域实时显示用户名、等级、PK 进度条等自定义信息。本文档指导开发者通过实现接口,在 Android 平台上快速定制专属的视频挂件 UI 。效果展示
连线视频挂件 | PK 视频挂件 |
![]() | ![]() |
准备工作
核心原理
LiveCoreView 通过 VideoViewAdapter 代理支持自定义视图渲染。当业务场景发生变化(例如有人上麦、开始 PK )时,LiveCoreView 会调用代理方法询问应该显示的视图。开发者实现对应的接口方法并返回自定义的 View 实例即可。方法 | 描述 | 对应业务场景 |
createCoGuestView | 创建观众连麦的挂件视图。 | 观众连麦、邀请上麦 |
createCoHostView | 创建跨房连麦(主播连线)的挂件视图。 | 主播连线 |
createBattleView | 创建 PK 场景中单个用户的挂件视图(例如头像、分数)。 | 主播 PK |
createBattleContainerView | 创建 PK 场景的整体容器视图(例如背景、分数 PK 条)。 | 主播 PK |
调整连线场景挂件
当观众与主播进行连线或者主播间跨房连线时,直播间界面会从单人模式切换为多人布局。此时,需要在连线的视频窗口上展示特定的用户信息,例如昵称、等级或“静音”状态提示,以便区分不同麦位。
适用场景
主播和观众视频连麦时,修改麦位上的用户信息(例如昵称、等级、静音图标)。
主播与主播跨房连线时,修改对方主播的显示样式(例如昵称、等级、静音图标)。
修改无视频画面时的默认背景(例如占位头像)。
修改空麦位时的视图实现。
视图层级示意

实现步骤
说明:
步骤 1. 准备自定义挂件 View
定义三个基础视图类,分别用于展示用户信息、空麦位提示和无视频流时的背景。
创建自定义的用户信息视图:
import android.content.Contextimport android.view.Viewimport android.widget.FrameLayoutimport android.widget.ImageViewimport android.widget.TextViewclass CustomInfoView(context: Context, name: String, isMuted: Boolean) : FrameLayout(context) {private val nameTextView = TextView(context)private val muteIcon = ImageView(context)init {nameTextView.text = namemuteIcon.visibility = if (isMuted) View.VISIBLE else View.GONEaddView(nameTextView)addView(muteIcon)// 此处省略具体布局参数设置代码}}
创建自定义的空麦位视图:
import android.content.Contextimport android.widget.FrameLayoutimport android.widget.ImageViewimport android.widget.TextViewclass EmptySeatView(context: Context) : FrameLayout(context) {private val addIcon = ImageView(context)private val addLabel = TextView(context)init {addLabel.text = "邀请连线"// 此处省略图片资源加载和布局参数设置代码}}
创建自定义无视频流时的头像占位视图:
import android.content.Contextimport android.widget.FrameLayoutimport android.widget.ImageViewclass CustomAvatarView(context: Context, avatarURL: String) : FrameLayout(context) {private val avatarImageView = ImageView(context)init {// 实际项目中可使用 Glide 等图片加载库加载 avatarURLaddView(avatarImageView)// 此处省略具体布局参数设置代码}}
步骤 2. 实现 Adapter 逻辑
创建一个适配器类,实现
VideoViewAdapter 接口的方法 createCoGuestView (观众连线)和 createCoHostView (主播连线),返回刚才创建的自定义视图。实现
VideoViewAdapter 中的 createCoGuestView 方法,返回观众连线视频挂件。import android.content.Contextimport android.view.Viewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStatusimport io.trtc.tuikit.atomicxcore.api.live.SeatInfoimport io.trtc.tuikit.atomicxcore.api.view.VideoViewAdapterimport io.trtc.tuikit.atomicxcore.api.view.ViewLayerclass VideoWidgetAdapter(private val context: Context) : VideoViewAdapter {override fun createCoGuestView(seatInfo: SeatInfo, viewLayer: ViewLayer): View? {val isUserOnSeat = seatInfo.userInfo.userID.isNotEmpty()return when (viewLayer) {ViewLayer.FOREGROUND -> {if (isUserOnSeat) {// 非空麦位,返回自定义的前景视图CustomInfoView(context, seatInfo.userInfo.userName, seatInfo.userInfo.microphoneStatus == DeviceStatus.OFF)} else {// 空麦位,返回自定义空麦位视图EmptySeatView(context)}}ViewLayer.BACKGROUND -> {if (isUserOnSeat) {// 返回自定义的背景视图(未开摄像头时显示)CustomAvatarView(context, seatInfo.userInfo.avatarURL)} else {null}}}}}
实现
VideoViewAdapter 中的 createCoHostView 方法,返回主播连线的视频挂件。import android.content.Contextimport android.view.Viewimport io.trtc.tuikit.atomicxcore.api.device.DeviceStatusimport io.trtc.tuikit.atomicxcore.api.live.SeatInfoimport io.trtc.tuikit.atomicxcore.api.view.VideoViewAdapterimport io.trtc.tuikit.atomicxcore.api.view.ViewLayerclass VideoWidgetAdapter(private val context: Context) : VideoViewAdapter {override fun createCoHostView(seatInfo: SeatInfo, viewLayer: ViewLayer): View? {val isUserOnSeat = seatInfo.userInfo.userID.isNotEmpty()return when (viewLayer) {ViewLayer.FOREGROUND -> {if (isUserOnSeat) {// 返回自定义的前景视图,可以返回与观众连线不同的样式CustomInfoView(context, seatInfo.userInfo.userName, seatInfo.userInfo.microphoneStatus == DeviceStatus.OFF)} else {// 返回自定义空麦位视图,可以返回与观众连线不同的样式EmptySeatView(context)}}ViewLayer.BACKGROUND -> {if (isUserOnSeat) {// 返回自定义的背景视图(未开摄像头时显示),可以返回与观众连线不同的样式CustomAvatarView(context, seatInfo.userInfo.avatarURL)} else {null}}}}}
参数说明:
参数 | 类型 | 说明 |
seatInfo | SeatInfo | 麦位信息对象,包含麦上用户的详细信息。 |
seatInfo.userInfo.userName | String | 麦上用户的昵称。 |
seatInfo.userInfo.avatarURL | String | 麦上用户的头像 URL。 |
seatInfo.userInfo.microphoneStatus | DeviceStatus | 麦上用户的麦克风状态。 |
seatInfo.userInfo.cameraStatus | DeviceStatus | 麦上用户的摄像头状态。 |
viewLayer | ViewLayer | 视图层级枚举。 .foreground 表示前景挂件视图,始终显示在视频画面的最上层。.background 表示背景挂件视图,位于前景视图下层,仅在对应用户没有视频流(例例如未开摄像头)的情况下显示,通常用于展示用户的默认头像或占位图。 |
调整主播 PK 场景挂件
PK 是直播中互动性最强的环节。在 PK 过程中,画面通常根据参与人数被分割为几部分。开发者需要在画面上方或中间区域添加 PK 专用的 UI ,分数 PK 条、每个麦位上的分数显示、倒计时动画或“VS”特效图标,以营造紧张的竞技氛围。视图层级示意

说明:
PK 功能依赖于连线功能,所以需要先在主播连线的情况下才能发起 PK 请求。
实现步骤
步骤 1. 准备自定义 UI 组件
PK 场景通常需要两类视图:单人挂件:显示在每个主播窗口上(例如分数胶囊)。
全局容器:覆盖在整体画面上(例如 VS 动画、倒计时)。
说明:
import android.content.Contextimport android.widget.FrameLayoutimport android.widget.ImageView// 示例:单人分数条class MyBattleScoreView(context: Context) : FrameLayout(context) {// 内部实现分数显示逻辑}// 示例:全局 VS 面板class MyBattleContainer(context: Context) : FrameLayout(context) {private val battleTimeView = ImageView(context)// 内部实现倒计时和 VS 动画逻辑}
步骤 2. 实现 Adapter 逻辑
在之前创建的
VideoWidgetAdapter 中,实现剩余的 PK 视图方法。class VideoWidgetAdapter(private val context: Context) : VideoViewAdapter {// 省略 createCoGuestView / createCoHostView 实现...// 1. 创建【PK 单人信息】挂件 (显示在每个主播视频上方)override fun createBattleView(seatInfo: SeatInfo): View? {return MyBattleScoreView(context)}// 2. 创建【PK 全局容器】挂件 (显示在整个视频区域上方)override fun createBattleContainerView(): View? {return MyBattleContainer(context)}}
集成与生效(重要)
这是最关键的一步。您需要将实现了代理逻辑的
VideoWidgetAdapter 注入到直播核心流程中。主播端集成:需在初始化外层容器前,先初始化
LiveCoreView 并通过 setVideoViewAdapter 设置适配器。import android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport android.view.ViewGroupimport com.trtc.uikit.livekit.features.anchorview.AnchorViewimport io.trtc.tuikit.atomicxcore.api.live.LiveInfoimport io.trtc.tuikit.atomicxcore.api.view.LiveCoreViewclass AnchorActivity : AppCompatActivity() {private lateinit var anchorView: AnchorViewprivate lateinit var widgetAdapter: VideoWidgetAdapteroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化适配器对象widgetAdapter = VideoWidgetAdapter(this)// 假设此处已获取 liveInfoval liveInfo = LiveInfo()// 1. 仅传入 Context 初始化 LiveCoreViewval videoView = LiveCoreView(this)// 2. 调用 setVideoViewAdapter 传入自定义适配器videoView.setVideoViewAdapter(widgetAdapter)// 3. 将核心视频组件传入外层容器anchorView = AnchorView(this, liveInfo, videoView)setContentView(anchorView, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT))}}
观众端集成:观众端通过
AudienceContainerViewDelegate 来回调核心视图的创建时机。import android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport io.trtc.tuikit.atomicxcore.api.live.LiveInfoimport io.trtc.tuikit.atomicxcore.api.view.LiveCoreViewclass AudienceActivity : AppCompatActivity(), AudienceContainerViewDelegate {private lateinit var audienceView: AudienceContainerViewprivate lateinit var widgetAdapter: VideoWidgetAdapteroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)widgetAdapter = VideoWidgetAdapter(this)val roomId = "your_room_id"audienceView = AudienceContainerView(this, roomId)audienceView.setDelegate(this)setContentView(audienceView)}override fun onCreateCoreView(liveInfo: LiveInfo): LiveCoreView? {val view = LiveCoreView(this)// 注入适配器view.setVideoViewAdapter(widgetAdapter)return view}}
进阶:获取实时业务数据
在开发
PK 等复杂场景时,您会发现 SeatInfo 只提供了基础麦位信息。如果您需要获取实时倒计时、PK 分数等业务数据,就需要在您的自定义视图中对接 AtomicXCore 中的核心数据。常见问题
设置了适配器,但自定义视图没有显示?
请检查设置时机是否太晚。在
Android 的组件装载链路中,LiveCoreView 的适配器赋值必须在它被传入 AnchorView 之前完成。如果 AnchorView 已经完成了初始化,它内部会直接加载 SDK 的默认挂件视图。解决:请务必遵循严格的初始化顺序:先
new LiveCoreView > 再调用 setVideoViewAdapter 注入自定义 UI > 最后实例化 AnchorView 并传入配置好的 CoreView。我只想修改“连麦”视图,“PK”视图想保留默认的,怎么办?
VideoViewAdapter 是一种完全接管模式。一旦您设置了自定义的代理,SDK 默认的 AnchorView/AudienceView 内部实现的代理逻辑就会完全失效。解决:您需要在您的代理类中实现所有相关方法。对于您不想修改的部分(例如
PK 视图),您可以:1. 复制源码:找到
LiveKit 源码中默认的实现类,在您的代理中返回这些默认类的实例。2. 手动实现:参考默认样式,快速写一个类似的简单视图返回。
自定义挂件显示了,但无法点击?
这是
Android 经典的事件分发冲突问题。由于前景视图(.FOREGROUND)叠加在视频渲染层之上:1. 请确保您返回的
CustomView 设置了 isClickable = true 或注册了 setOnClickListener。2. 如果您的挂件是一个嵌套的
ViewGroup(例如 FrameLayout),请检查其父容器是否错误地拦截了触摸事件(例如被外层控件触发了 onInterceptTouchEvent 且返回了 true)。

