AudienceView 观众端核心 UI 组件。通过该组件,开发者可以快速搭建基础的观众直播界面。本文档将按照从基础按钮调整到复杂视图替换的顺序,指导开发者完成观众端界面的按需定制。
AudienceView 是一个滑动容器。AudienceView 上进行的,而是作用于它内部的单房间视图 AudienceLiveView。滑动容器在工作时会动态创建和展示这些单房间视图,开发者需要通过 AudienceViewListener 协议,在以下核心生命周期回调中获取到对应房间的视图实例(即回调参数中的 liveView ),并在正确的时机对其进行定制与资源管理:方法 | 描述 |
onCreateLiveView(audienceView: AudienceLiveView, liveInfo: LiveInfo) | 视图创建回调。此回调会向外传递刚刚实例化的 AudienceLiveView。适用于提前确定的静态样式定制。由于容器会提前预加载下个房间,该回调触发时视图可能尚未显示在屏幕上。通常在这里通过 liveView 执行替换内置组件、配置固定按钮等一次性布局操作。 |
onLiveViewDidAppear(audienceView: AudienceLiveView, liveInfo: LiveInfo) | 视图展示回调。此回调会向外传递当前真正显示在屏幕上的 AudienceLiveView。适用于依赖实时状态或信令的动态调整。通常在这里记录当前活跃的 liveView 实例,以便在收到业务信令(如商品上架通知)时,精准定向更新当前屏幕上的 UI;或在此处开启房间专属的定时器与动效。 |
onLiveViewDidDisappear(audienceView: AudienceLiveView, liveInfo: LiveInfo) | 视图隐藏回调。当观众滑出该房间或关闭界面时触发。 适用于状态重置与资源清理。通常在这里清空活跃的 liveView 记录,销毁在该房间内弹出的自定义业务面板、停止动效或清理定时器。 |
AudienceLiveView 提供的核心自定义接口与属性如下:方法/属性 | 描述 |
topRightItems | 用于灵活配置直播间右上角的按钮集合,支持自由添加自定义按钮或调整内置按钮的布局。 |
bottomItems | 用于灵活配置直播间底部的按钮集合,支持自由添加自定义按钮或调整内置按钮的布局。 |
replace(node: AudienceNode, view: View?) | 用于将指定位置的默认组件(如顶部信息区、底部操作栏),替换为开发者自定义的全新视图。 |
overlayView | 专属挂件图层,方便开发者自由添加需要在视频画面上方悬浮展示的全局业务 UI。 |
perform(action:AudienceAction) | 用于在自定义视图中直接触发内置默认逻辑,例如显示默认观众列表,显示默认连麦管理面板等。 |
import android.os.Bundleimport android.view.Gravityimport android.widget.FrameLayoutimport android.widget.ImageViewimport androidx.appcompat.app.AppCompatActivityimport com.trtc.uikit.livekit.features.audienceview.AudienceLiveViewimport com.trtc.uikit.livekit.features.audienceview.AudienceViewimport com.trtc.uikit.livekit.features.audienceview.AudienceViewDefineimport com.trtc.uikit.livekit.features.audienceview.AudienceViewDefine.AudienceBottomItemimport io.trtc.tuikit.atomicx.common.util.ScreenUtil.dip2pximport io.trtc.tuikit.atomicxcore.api.live.LiveInfoclass AudienceActivity : AppCompatActivity(), AudienceViewDefine.AudienceViewListener {private var currentLiveView: AudienceLiveView? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化容器 Viewval audienceView = AudienceView(this)setContentView(audienceView)// 注册监听并初始化audienceView.addListener(this)audienceView.init(this, "your_room_id")}// 业务方法:展示商品列表页private fun showProductListPanel() {println("展示商品列表页...")}// 业务方法:模拟收到 IM 商品推送通知fun onReceiveProductPushMessage() {// 必须作用于当前正在展示的 active 视图val liveView = currentLiveView ?: returnval productCard = AudienceProductCardView(this)productCard.setOnClickListener { showProductListPanel() }val params = FrameLayout.LayoutParams(dip2px(150f), FrameLayout.LayoutParams.WRAP_CONTENT).apply {gravity = Gravity.BOTTOM or Gravity.ENDrightMargin = dip2px(12f)bottomMargin = dip2px(100f)}liveView.overlayView.addView(productCard, params)}// --- AudienceViewListener 回调实现 ---override fun onCreateLiveView(liveView: AudienceLiveView, liveInfo: LiveInfo) {// 配置底部操作栏:隐藏连麦功能,并添加自定义“购物车”按钮val shopCartBtn = ImageView(this).apply {setOnClickListener { showProductListPanel() }}liveView.bottomItems = listOf(AudienceBottomItem.Gift,AudienceBottomItem.Like,AudienceBottomItem.Custom(shopCartBtn))}override fun onLiveViewDidAppear(liveView: AudienceLiveView, liveInfo: LiveInfo) {currentLiveView = liveView}override fun onLiveViewDidDisappear(liveView: AudienceLiveView, liveInfo: LiveInfo) {if (currentLiveView === liveView) {currentLiveView = null}}override fun onLiveEnded(roomId: String, ownerName: String, ownerAvatarUrl: String) {}override fun onClickFloatWindow() {}}class AudienceProductCardView(context: android.content.Context) : android.view.View(context) {// 自定义的商品卡片}
bottomItems 属性灵活增删内置功能,或通过 AudienceBottomItem.Custom(View) 插入自定义按钮。
AudienceViewListener 回调,在相应的回调方法中把组装好的包含 AudienceBottomItem 枚举的数组重新赋值给视图属性。override fun onCreateLiveView(audienceView: AudienceLiveView, liveInfo: LiveInfo) {// 步骤 1:准备自定义按钮视图val shopButton = ImageView(audienceView.context).apply {setImageResource(R.drawable.shop_cart)}// 步骤 2:更新底部按钮数组,保留送礼,隐藏连麦,新增商品按钮audienceView.bottomItems = listOf(AudienceBottomItem.Gift,AudienceBottomItem.Custom(shopButton))}
topRightItems 属性精简或增加控制按钮。
AudienceViewListener 回调,在相应的回调方法中将枚举项或自定义视图赋值给 topRightItems 属性。override fun onCreateLiveView(audienceView: AudienceLiveView, liveInfo: LiveInfo) {// 步骤 1:准备自定义按钮视图val reportButton = ImageView(audienceView.context).apply {setImageResource(R.drawable.report_btn)}// 步骤 2: 更新顶部按钮数组(保留人数和关闭,新增举报)audienceView.topRightItems = listOf(AudienceViewDefine.AudienceTopRightItem.AudienceCount,AudienceViewDefine.AudienceTopRightItem.Custom(reportButton),AudienceViewDefine.AudienceTopRightItem.Close)}
replace 接口整体替换指定区域的视图。内部通过 AudienceNode 枚举定义了 5 个支持替换的区域,可以结合开头的“页面结构示意图”理解:AudienceNode | 说明 |
LIVE_INFO | 左上角的主播与房间信息展示区域。 |
TOP_RIGHT_BUTTONS | 右上角的系统控制按钮区域。 |
NETWORK_INFO | 网络状态指示区域。 |
BOTTOM_RIGHT_BAR | 右下角的业务操作栏区域。 |
BARRAGE_INPUT | 左下角的弹幕输入框触发区域。 |
replace 接口会将自定义视图放入指定的区域。视图的位置由框架控制,开发者无需设置。视图的尺寸由其自身决定,推荐使用以下两种方式之一声明尺寸:class MyInfoView(context: Context) : FrameLayout(context) {init {val label = TextView(context).apply {text = "直播中"setPadding(dip2px(12f), dip2px(12f), dip2px(12f), dip2px(12f))}val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)addView(label, params)}}
View 的 onMeasure 方法,或者在初始化时赋予明确宽高的 LayoutParams 来决定视图尺寸。class MyInfoView(context: Context) : FrameLayout(context) {init {// 赋予固定的宽高属性layoutParams = LayoutParams(dip2px(200f), dip2px(44f))setBackgroundColor(Color.DKGRAY)}}
AudienceViewListener 回调,在相应的回调方法中调用 AudienceLiveView 组件的 replace 接口,传入需要替换的节点枚举和新建的自定义视图。override fun onCreateLiveView(liveView: AudienceLiveView, liveInfo: LiveInfo) {// 步骤 1:初始化符合布局规则的自定义视图val customInfoView = MyInfoView(liveView.context)// 步骤 2:调用替换接口更新特定节点liveView.replace(AudienceViewDefine.AudienceNode.LIVE_INFO, customInfoView)}
perform 方法快速触发内置逻辑。支持的 AudienceAction 包括:展示礼物面板(SHOW_GIFT_PANEL)、展示观众列表(SHOW_AUDIENCE_LIST)等。setOnClickListener 或手势识别器为视图添加点击事件。perform 方法传入 AudienceAction 枚举,或执行其他业务代码。override fun onCreateLiveView(audienceView: AudienceLiveView, liveInfo: LiveInfo) {val btn = MyGiftButton(audienceView.context, audienceView)audienceView.bottomItems = listOf(AudienceBottomItem.Custom(btn))}class MyGiftButton(context: Context, private val liveView: AudienceLiveView) : androidx.appcompat.widget.AppCompatImageView(context) {init {setImageResource(R.drawable.custom_gift)setOnClickListener {// 弹出默认礼物面板liveView.perform(AudienceViewDefine.AudienceAction.SHOW_GIFT_PANEL)// 或者触发自定义逻辑// presentCustomPanel()}}}
perform 触发的内置默认面板无法满足具体的业务需求时,开发者可以彻底接管这部分逻辑,基于底层数据 AtomicXCore 构建全新的业务面板。AtomicXCore 获取房间、用户及状态数据,完全自主地完成自定义视图的搭建与交互绑定。在完成自定义控制器的构建后,建议使用内部封装的 AtomicPopover 组件将其弹出,以确保您的自定义面板也能获得与 SDK 内置面板一致的丝滑手势拦截与平滑动画。View,并在其内部引入底层核心数据接口,用于拉取或监听业务数据以驱动 UI 更新。AtomicPopover 对象,并指定其弹出位置(BOTTOM 或 CENTER)。setContent 传入容器,最后调用 show() 进行展示。import android.content.Contextimport android.graphics.Colorimport android.widget.FrameLayoutimport io.trtc.tuikit.atomicx.widget.basicwidget.popover.AtomicPopover// 步骤1:构建自定义业务视图(如:观众列表)class CustomAudienceListView(context: Context) : FrameLayout(context) {init {setBackgroundColor(Color.WHITE)setupUI()bindLiveData()}private fun setupUI() {// 在此添加您的自定义 UI 控件,例如展示观众头像、用户等级等列表}private fun bindLiveData() {// 使用 AtomicXCore 提供的核心接口获取当前房间状态或用户数据// 例如:LiveAudienceStore.create(liveID)...// 拿到核心数据后刷新上方构建的自定义 UI}}// 示例场景:从屏幕底部向上滑出一个高度占屏幕 50% 的完全自定义面板fun presentBusinessPanel(context: Context) {// 步骤2:实例化弹窗容器,指定从底部弹出val popover = AtomicPopover(context, AtomicPopover.PanelGravity.BOTTOM)// 步骤3:配置并展示自定义视图// 设置面板高度为屏幕高度的 50%(也可以使用 WrapContent 自适应高度)popover.setPanelHeight(AtomicPopover.PanelHeight.Ratio(0.5f))// 实例化自定义业务视图val audienceListView = CustomAudienceListView(context)// 将视图填入 Popover 并展示popover.setContent(audienceListView)popover.show()}
AtomicXCore 接口实现自定义功能面板页onCreateLiveView 回调中将其添加至 overlayView。onLiveViewDidAppear 记录的当前活跃视图实例,再向其 overlayView 中动态添加控件。liveViewDidDisappear 回调中执行视图的 removeFromSuperview 操作。这能有效避免用户滑动切房时,因视图复用导致的 UI 状态异常。setOnClickListener ,用于响应观众的点击操作。overlayView 中,并在点击回调中调用前文定义的自定义弹窗逻辑。class LiveRoomActivity : AppCompatActivity() {// 在 AudienceViewListener 中获取当前的 liveViewprivate var currentLiveView: AudienceLiveView? = null// 示例场景:在画面左上角悬浮显示红包挂件,点击后联动弹出业务面板fun addRedPacketWidget() {val activeView = currentLiveView ?: return// 步骤 1 & 2:创建挂件视图并开启交互val redPacketWidget = ImageView(this).apply {setImageResource(R.drawable.red_packet_icon)setOnClickListener { handleRedPacketClick() }}val params = FrameLayout.LayoutParams(dip2px(60f), dip2px(60f)).apply {gravity = Gravity.TOP or Gravity.STARTtopMargin = dip2px(120f)leftMargin = dip2px(15f)}// 步骤 3:添加至覆盖图层activeView.overlayView.addView(redPacketWidget, params)}private fun handleRedPacketClick() {// 在此处调用前文写好的 presentBusinessPanel 方法,弹出深度定制的业务视图// presentBusinessPanel(this)}}
AudienceBottomItem.Custom() 传入自定义视图后,点击视图却无法触发绑定的事件。Android 中默认的 View 往往不具备点击特性,请确保对传入的控件调用了 setOnClickListener ,或者在 XML 中/代码中将其 isClickable 设为了 true。View 撑起 WRAP_CONTENT),导致父布局尺寸为 0,即便内部子按钮画出了边界外,点击事件也会因为超出父视图 Rect 范围而被系统的 Touch 机制丢弃。onInterceptTouchEvent 中拦截了事件。AudienceLiveView 的 bottomItems 和 topRightItems 属性支持响应式更新。只需组装一个新的按钮数组并重新赋值,SDK 内部会自动触发视图刷新,无需手动重绘。需要注意的是在动态更新视图时,务必确保更新的是当前正在展示的视图实例。请使用 onLiveViewDidAppear 回调中记录的活跃实例,切勿误修改处于“预加载”状态的相邻房间视图。replace(node, customView) 接口替换指定节点后,发现当开始滑动后程序抛出 illegalStateException: The specified child already has a parent... 崩溃,或者屏幕上替换的自定义视图消失了。这通常是因为开发者在不同的直播间视图中,错误地复用了同一个自定义 View 实例。AudienceView 是一个滑动容器,为了保证滑动的流畅性,系统会提前预加载接下来的相邻房间。这意味着 onCreateLiveView 代理方法会被提前调用。在 Android 中,一个 View 实例同一时刻只能拥有一个 Parent。如果您传入了一个共享的全局 View 实例,当预加载下一个房间时,将其 addView 到新房间前,如果不手动 removeView 会直接导致崩溃;即使框架底层处理了强制拔出,也会导致该 View 从当前可视房间被剥离,从而引发显示异常。// ❌ 错误做法:在外部持有一个共享的视图实例val sharedBrandView = MyBrandView(context)override fun onCreateLiveView(audienceView: AudienceLiveView, liveInfo: LiveInfo) {// ⚠️ 警告:当预加载下一个 liveView 时,复用同一个 View 实例会导致 Parent 冲突!audienceView.replace(AudienceNode.LIVE_INFO, sharedBrandView)}
onCreateLiveView 回调中,每次都为新的 AudienceLiveView 创建一个全新的自定义视图实例。override fun onCreateLiveView(audienceView: AudienceLiveView, liveInfo: LiveInfo) {// ✅ 正确做法:每次触发回调时,都实例化一个全新的自定义视图val newBrandView = MyBrandView(audienceView.context)audienceView.replace(AudienceNode.LIVE_INFO, newBrandView)}
文档反馈