自定义 UI (V2)

更新时间: 2023/11/07 07:50:49

本文介绍如何在集成含 UI 呼叫组件后,定制界面界面风格,包括界面布局、界面样式等。

本文适用于呼叫组件 V2.1.0 及之后版本,如果您集成的是呼叫组件 V1 版本,请参见自定义 UI(V1)

自定义 UI 的步骤说明

  1. 呼叫组件包含六种类型的 Fragment,分别针对不同的呼叫状态(呼叫、被叫、通话中)和呼叫类型(音频、视频),具体类型如下表所示。

    P2PCallFragmentType 类型枚举:

    代码 具体值 对应 Fragment 页面 说明
    VIDEO_CALLER 1 VideoCallerFragment 视频呼叫页面
    VIDEO_CALLEE 2 VideoCalleeFragment 视频被叫页面
    VIDEO_ON_THE_CALL 3 VideoOnTheCallFragment 视频通话页面
    AUDIO_CALLER 4 AudioCallerFragment 音频呼叫页面
    AUDIO_CALLEE 5 AudioCalleeFragment 音频被叫页面
    AUDIO_ON_THE_CALL 6 AudioOnTheCallFragment 音频通话页面
  2. 您如果需要修改相关 UI,请按照如下步骤实现:

    1. 创建 Fragment。

    例如,创建一个名为 TestAudioCallerFragment 的 Fragment,用于修改音频呼叫页面的样式。该 Fragment 继承目标 Fragment AudioCallerFragment,也可以直接继承 BaseP2pCallFragment,但这种情况下需要自己完全负责 UI。示例代码如下:

    javapublic class TestAudioCallerFragment extends AudioCallerFragment {
    }
    
    1. 创建页面 Activity。

    创建一个名为TestActivity的页面,该页面应继承自P2PCallFragmentActivity类。在TestActivity中重写provideUIConfig方法,并注册修改后的页面。示例代码如下:

    javapublic class TestActivity extends P2PCallFragmentActivity {
      @NonNull
      @Override
      protected P2PUIConfig provideUIConfig(CallParam param) {
        return new P2PUIConfig.Builder()
          // 此处通过 P2PCallFragmentType 类型以及具体的 Fragment 完成页面注册
          .customCallFragmentByKey(P2PCallFragmentType.AUDIO_CALLER, new TestAudioCallerFragment())
          .build();
      }
    }
    
    1. 在初始化呼叫组件时,将刚刚创建的 Activity 在组件中注册,示例代码如下:

      javaCallKitUIOptions options = new CallKitUIOptions.Builder()
        ......
        // 设置自定义的音频通话界面
        .p2pAudioActivity(TestActivity.class)
        .build();
      // 组件初始化
      CallKitUI.init(this, options);
      
  3. 以上步骤已完成组件UI的自定义,如需进行具体的 UI 修改,请参考下文。

基于 Fragment 修改 UI 示例

修改 UI 可以分为两种情况:

  • 对界面进行简单的修改,例如更换图标、隐藏控制按钮等。这些修改可以通过代码实现,而无需重新编写布局。
  • 对界面进行较大的修改,需要进行大量的 UI 修改。对于这种情况,可以考虑通过自定义 XML 文件来实现。

本文以自定义语音呼叫界面为例,展示如何修改背景图片、增加或去除图标、增加标题和副标题、将默认的对方头像样式从方形改为圆形。

效果对比

默认样式 自定义样式

|自定义UI原图 | 自定义UI修改后 |

方式1: 通过 layout.xml 修改 UI

示例代码如下:

xml<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/clRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black"
    tools:ignore="ContentDescription,SpUsage">

    <ImageView
        android:id="@+id/ivBg"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/ivUserInnerAvatar"
        android:layout_width="90dp"
        android:layout_height="90dp"
        android:layout_marginTop="160dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <TextView
        android:id="@+id/tvOtherCallTip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="自定义提示信息内容"
        android:textColor="@color/colorWhite"
        android:textSize="14dp"
        app:layout_constraintStart_toStartOf="@id/ivUserInnerAvatar"
        app:layout_constraintTop_toBottomOf="@id/ivUserInnerAvatar"
        app:layout_constraintEnd_toEndOf="@id/ivUserInnerAvatar" />
    
    <ImageView
        android:id="@+id/ivCancel"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:layout_marginBottom="75dp"
        android:src="@drawable/call_reject"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

TestAudioCallerFragment 代码如下:

javapublic class TestAudioCallerFragment extends AudioCallerFragment {
  @Nullable
  @Override
  protected View toCreateRootView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    // 创建根布局
    return inflater.inflate(R.layout.fragment_test_audio_caller, container, false);
  }
  
  @Override
  protected void toBindView() {
    View rootView = getRootView();
    if (rootView == null) {
      return;
    }
    // 绑定取消按钮
    bindView(getViewKeyImageCancel(), rootView.findViewById(R.id.ivCancel));
  }
  
  @Override
  protected void toRenderView(@NonNull CallParam callParam, @Nullable P2PUIConfig uiConfig) {
    // 需要保留,否则上面绑定的默认点击事件无效
    super.toRenderView(callParam, uiConfig);
    View rootView = getRootView();
    if (rootView == null) {
      return;
    }
    // 渲染对方昵称
    TextView nameView = rootView.findViewById(R.id.tvUserName);
    nameView.setText("18012345678");
    
    // 渲染对方头像
    ImageView avatarView = rootView.findViewById(R.id.ivUserAvatar);
    String avatarUrl = "https://yx-web-nosdn.netease.im/quickhtml/assets/yunxin/default/g2-demo-avatar-imgs/86117845552861184.jpg";
    Glide.with(requireContext()).load(avatarUrl)
      .circleCrop()
      .into(avatarView);
  }
}

方式2: 通过代码修改 UI

示例代码如下:

javapublic class TestAudioCallerFragment extends VideoCallerFragment {
  @Override
  protected void toRenderView(@NonNull CallParam callParam, @Nullable P2PUIConfig uiConfig) {
    // 获取到背景大图
    ImageView backgroundView = getView(getViewKeyImageBigBackground());
    // 避免被组件内部异步加载图片
    removeView(getViewKeyImageBigBackground());
    // 获取到用户头像
    ImageView avatarView = getView(getViewKeyImageUserInnerAvatar());
    removeView(getViewKeyImageUserInnerAvatar());
    // 父类渲染
    super.toRenderView(callParam, uiConfig);
    // 修改背景大图
    if (backgroundView != null) {
      backgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
      backgroundView.setImageResource(R.drawable.bg_custom_for_audio);
    }
    // 修改用户头像为圆形
    String avatarUrl = "https://yx-web-nosdn.netease.im/quickhtml/assets/yunxin/default/g2-demo-avatar-imgs/86117845552861184.jpg";
    if (avatarView != null) {
      Glide.with(requireContext()).load(avatarUrl)
        .circleCrop()
        .into(avatarView);
    }
    // 添加自定义 View
    View rootView = getRootView();
    if (rootView instanceof ConstraintLayout) {
      ViewGroup viewGroup = (ViewGroup) rootView;
      TextView newAddedView = new TextView(getContext());
      newAddedView.setText("每个人都会遇到那个TA");
      ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
      params.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID;
      params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID;

      View tipView = getView(getViewKeyTextOtherCallTip());
      if (tipView != null) {
        params.topToBottom = tipView.getId();
      }
      newAddedView.setLayoutParams(params);
      viewGroup.addView(newAddedView);
    }
    // 隐藏麦克风图片按钮
    View muteAudioImage = getView(getViewKeyMuteImageAudio());
    if (muteAudioImage != null) {
      muteAudioImage.setVisibility(View.GONE);
    }
    // 隐藏麦克风文本说明
    View muteAudioText = getView(getViewKeyTextMuteAudioDesc());
    if (muteAudioText != null) {
      muteAudioText.setVisibility(View.GONE);
    }
    // 隐藏扬声器图片按钮
    View speakerImage = getView(getViewKeyImageSpeaker());
    if (speakerImage != null) {
      speakerImage.setVisibility(View.GONE);
    }
    // 隐藏扬声器文本说明
    View speakerText = getView(getViewKeyTextSpeakerDesc());
    if (speakerText != null) {
      speakerText.setVisibility(View.GONE);
    }
  }
}

BaseP2pCallFragment 提供的主要方法如下表所示。

方法
参数
返回
说明
toUpdateUIState Integer - 页面状态更新,执行时机在 onCreateAction 方法执行后,用户可根据更新类型执行不同的行为,目前支持 P2PUIUpdateType.INITP2PUIUpdateType.CHANGE_CALL_TYPE,页面创建完成后都会触发此方法的 INIT 类型调用。
toCreateRootView LayoutInflater,ViewGroup,Bundle View 创建 Fragment 的 View,等同于 onCreateView 方法,注意,若重写此方法后需要同时重写 toBindView否则会出现崩溃
toBindView - - 用户可重写此方法,在此方法内调用 bindView 来进行 View 的绑定
toRenderView - - 此方法在 toBindView 执行后被调用,通过调用 getView 方法获取组件依赖的控件进行点击时间以及图标等设置,用户可重写此方法实现覆盖父类设置
onCreateAction - - 页面创建动作,对应 onCreateView方法,在 toRenderView 方法后执行
onDestroyAction - - 页面销毁动作,对应 onDestroyView方法
bindView String,View - 将 key 和对应的View绑定,绑定后,组件会通过相应的 key 获取 view 并实现点击或者显/隐控制等
getView String View 通过 key 获取对应绑定的 View ,若未绑定返回 null
removeView String - 移除对应 key 绑定的View

现有布局控件 key 说明如下表所示。所有定义均在 Fragment 父类 BaseP2pCallFragment 中。

key 控件默认类型 说明
viewKeyVideoViewPreview NERtcVideoView 视频呼叫时本地视频预览控件
viewKeyVideoViewBig NERtcVideoView 视频通话中大视频展示控件
viewKeyVideoViewSmall NERtcVideoView 视频通话中小视频展示控件
viewKeyImageVideoShadeBig ImageView 视频通话关闭视频时大视频的覆盖控件
viewKeyImageVideoShadeSmall ImageView 视频通话关闭视频时小视频的覆盖控件
viewKeyTextRemoteVideoCloseTip TextView 视频通话中远端视频关闭时文本提示控件
viewKeyImageBigBackground ImageView 语音通话时大的背景图控件
viewKeyTextUserInnerAvatar TextView 对端用户没有头像时展示文本头像控件
viewKeyImageUserInnerAvatar ImageView 对端用户头像展示控件
viewKeyFrameLayoutUserAvatar FrameLayout 头像整体父布局控件
viewKeyTextUserName TextView 对端用户昵称文本控件
viewKeyTextOtherCallTip TextView 呼叫/被叫/语音通话时文本提示控件
viewKeyImageCancel ImageView 取消呼叫图片控件
viewKeyTextCancelDesc TextView 取消呼叫文本说明控件
viewKeyImageReject ImageView 拒绝接听图片控件
viewKeyTextRejectDesc TextView 拒绝接听文本说明控件
viewKeyImageAccept ImageView 接听图片控件
viewKeyTextAcceptDesc TextView 接听文本说明控件
viewKeyImageHangup ImageView 通话中挂断图片控件
viewKeyImageSwitchCamera ImageView 切换摄像头方向图片控件
viewKeyImageSwitchType ImageView 切换通话类型图片控件
viewKeyTextSwitchTypeDesc TextView 切换通话类型文本说明控件
viewKeyTextSwitchTip TextView 切换通话过程中提示文本控件
viewKeyImageSwitchTipClose ImageView 切换通话过程中提示关闭图片控件
viewKeySwitchTypeTipGroup Group 切换通话过程中整体提示组控件,用户控制 viewKeyTextSwitchTip]viewKeyImageSwitchTipClose 的展示/隐藏
viewKeyMuteImageVideo ImageView 摄像头开关图片控件
viewKeyMuteImageAudio ImageView 本地麦克风开关图片控件
viewKeyTextMuteAudioDesc TextView 本地麦克风开关文本说明控件
viewKeyImageSpeaker ImageView 扬声器开关图片控件
viewKeyTextSpeakerDesc TextView 扬声器开关文本说明控件
viewKeyTextConnectingTip TextView 接听成功前,点击接听后的提示文本控件
viewKeyTextTimeCountdown TextView 通话中倒计时文本控件

完全自实现 UI,不基于 Fragment

如果您不想使用 Fragment 来修改自定义 UI,可以直接继承 CommonCallActivity 类来实现呼叫通话 UI 的修改。您可以使用该类的方法来执行呼叫相关的操作,并且可以根据自己的逻辑来定义所有的 UI 和行为。

在初始化呼叫组件时,不要忘记注册自定义的 Activity。

操作步骤如下:

  1. 创建页面 Activity。

    创建 TestActivity 页面,并让它继承 CommonCallActivity。在这个页面中,实现自己的业务逻辑,并调用呼叫流程方法。

    此时 P2PUIConfig 中关于UI部分参数会失效。但前台服务开/关及配置仍然有效。

    示例代码如下:

    javapublic class TestActivity extends CommonCallActivity {
      @Override
      protected int provideLayoutId() {
        return 0;
      }
    }
    
  2. 在初始化呼叫组件时,将刚刚创建的 Activity 在组件中注册,示例代码如下:

    javaCallKitUIOptions options = new CallKitUIOptions.Builder()
      ......
      // 设置自定义的视频通话界面
      .p2pVideoActivity(TestActivity.class)
      .build();
    // 组件初始化
    CallKitUI.init(this, options);
    

UI 层组件提供的方法和参数

CommonCallActivity 提供的方法:

方法
参数
返回
说明
getCallParam - CallParam 呼叫/被叫时的必要参数
getCallEngine - NECallEngine 呼叫组件实例
getUiConfig - P2PUIConfig 获取 provideUIConfig 方法返回的实例
isLocalMuteAudio - Boolean 本地音频是否关闭采集
isLocalMuteVideo - Boolean 本地视频是否关闭采集
isRemoteMuteVideo - Boolean 对端用户是否关闭视频
doOnCreate Bundle - 对应页面 onCreate 方法
configWindow - - 可重写方法配置 window 相关配置
provideLayoutId - Int 返回页面布局 id
provideUIConfig CallParam P2PUIConfig 根据呼叫参数返回对应的页面配置,用于配置前台服务开启/关闭、大小窗点击切换等
doMuteAudio - - 修改本地当前音频采集状态,若当前为开启则执行后为关闭
doMuteVideo - - 修改本地当前视频采集状态,若当前为开启则执行后为关闭
doSwitchCamera - - 切换摄像头方向,默认为正向
doCall NEResultObserver<CommonResult<NECallInfo>> - 主叫呼叫
doAccept NEResultObserver<CommonResult<NECallInfo>> - 被叫接受
doHangup NEResultObserver<CommonResult<Void>>, String, String - 终止通话
showOverlayPermissionDialog View.OnClickListener - 展示申请浮窗权限弹窗
doShowFloatingWindow - - 具有浮窗权限后,展示浮窗
startVideoPreview - - 开启视频预览
stopVideoPreview - - 关闭视频预览

CallParam 参数字段:

字段 类型 说明
isCalled Boolean 通话中是否为被叫
callType Int 通话类型详见 NECallType
callerAccId String 通话主叫方 accId
calledAccId String 通话被叫方 accId
callExtraInfo String 扩展信息,透传到到被叫 onReceiveInvited
globalExtraCopy String 自定义的呼叫全局抄送信息,用户服务端接收抄送时设置自己的业务标识
rtcChannelName String 呼叫时确定的 rtc 房间名称
pushConfig NECallPushConfig 呼叫的推送配置
extras Map<String, Object> 呼叫的 UI 页面传递扩展信息,类似 Intent

P2PUIConfig支持的配置内容:

配置项 说明
showAudio2VideoSwitchOnTheCall 是否展示通话中音频转视频按钮
默认 true
showVideo2AudioSwitchOnTheCall 是否展示通话中视频转音频按钮
默认 true
closeVideoLocalUrl 本端用户关闭摄像头时的图片 url 链接
closeVideoLocalTip 本端用户关闭摄像头时的文本提示
closeVideoRemoteUrl 对端用户关闭摄像头时的本端的图片 url 链接
closeVideoRemoteTip 对端用户关闭摄像头时的本端文本提示
closeVideoType 关闭视频模式,默认 CLOSE_TYPE_MUTE 详细见下文
enableCanvasSwitch 是否支持通话中点击小视频预览进行大小画面点击切换
默认 true
enableTextDefaultAvatar 当没有头像时是否展示文字头像(取传入昵称的最后两个字)
默认 true
enableForegroundService 是否通话中开启前台服务,可提高应用的优先级,避免在后台时被系统回收资源
默认 false
foregroundNotificationConfig 通话中前台服务绑定的通知配置(图标,标题,内容,以及通道 id)
customCallFragmentByKey 配置自定义通话中 fragment 页面
enableFloatingWindow 配置是否开启小窗功能,默认 false 关闭
enableVideoCalleePreview 配置是否开启被叫视频预览,默认 false 关闭,当配置 rtc 初始化模式为NECallInitRtcMode.IN_NEED_DELAY_TO_ACCEPT时,此功能不生效
enableVirtualBlur 配置是否支持通话视频虚化,默认 false 关闭
虚化功能说明,若需要开启虚化,在配置此开关 true 情况下,同时需要引入,com.netease.yunxin:nertc-nenncom.netease.yunxin:nertc-segment 库,具体请参考集成音视频SDK

您可以设置当用户在界面上单击关闭摄像头按钮时,摄像头数据采集的模式。包括 3 种模式,区别和说明如下表所示。

模式 说明
Constants.CLOSE_TYPE_MUTE 用户在界面上单击关闭摄像头按钮时,不发布本地摄像头数据,但仍然采集摄像头数据。该模式下,用户单击开启摄像头后,界面能快速展示摄像头采集的数据。
默认为该模式。
Constants.CLOSE_TYPE_DISABLE 用户在界面上单击关闭摄像头按钮时,关闭本地摄像头数据采集及发布。
Constants.CLOSE_TYPE_COMPAT 兼容此前使用 CLOSE_TYPE_MUTE 模式后续希望更换为CLOSE_TYPE_DISABLE
此文档是否对你有帮助?
有帮助
去反馈
  • 自定义 UI 的步骤说明
  • 基于 Fragment 修改 UI 示例
  • 方式1: 通过 layout.xml 修改 UI
  • 方式2: 通过代码修改 UI
  • 完全自实现 UI,不基于 Fragment
  • UI 层组件提供的方法和参数