Android技术分享| 【Android 自定义View】多人视频通话控件

语言: CN / TW / HK

【Android 自定义View】多人视频通话控件

f703738da9773912db5a2002e8198618367ae251.jpg

*以上图片截自wechat等待中界面

等待中界面

上图是wechat多人视频通话时未接通的界面状态,可见每个人的 View 中大致需包含了以下元素。

  1. 头像
  2. 昵称
  3. Loading View
  4. 视频 View
  5. 音频状态 icon

所以,我们先写好每个人的布局。如下

```xml

<!--视频View-->
<TextureView
    android:id="@+id/video_view"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
</TextureView>

<!--头像-->
<ImageView
    android:id="@+id/iv_avatar"
    android:src="@drawable/avatar"
    android:layout_height="match_parent"
    android:layout_width="match_parent">
</ImageView>

<!--名字-->
<TextView
    android:id="@+id/tv_user_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_margin="20dp"
    tools:text="UserName"
    android:background="@android:color/transparent"
    android:textColor="@android:color/white"
    android:textSize="14sp" />

<!--音频状态Icon-->
<ImageView
    android:id="@+id/iv_audio_enable"
    android:layout_height="20dp"
    android:layout_width="20dp"
    android:layout_alignParentRight="true"
    android:layout_alignParentBottom="true"
    android:layout_margin="20dp"
    android:src="@drawable/mic_enable">
</ImageView>

<!--Loading-->
<ImageView
    android:id="@+id/iv_loading"
    android:layout_height="30dp"
    android:layout_width="30dp"
    android:layout_centerInParent="true"
    android:src="@drawable/loading">
</ImageView>

```

GroupUserVideoLayout

接着定义自定义View类,GroupUserVideoLayout 添加一些基本的方法。

```kotlin /* * 多人视频通话中每个用户的布局 / class GroupUserVideoLayout @JvmOverloads constructor(mContext:Context): RelativeLayout(mContext) {

private var videoView:TextureView
private var ivAvatar:ImageView
private var ivAudio:ImageView
private var tvName:TextView
private var ivLoading:ImageView

init {
    val root = LayoutInflater.from(mContext).inflate(R.layout.layout_gv_layout,this)
    videoView = root.findViewById(R.id.video_view)
    ivAvatar = root.findViewById(R.id.iv_avatar)
    ivAudio = root.findViewById(R.id.iv_audio_enable)
    tvName = root.findViewById(R.id.tv_user_name)
    ivLoading = root.findViewById(R.id.iv_loading)
}

//设置昵称
fun setUserName(userName:String){
    tvName.text = userName
}
//设置头像
fun setAvatar(avatarUrl:String){
    ivAvatar.loadUrl(avatarUrl)
}
//设置音频图标状态
fun enableAudio(enable:Boolean){
    ivAudio.visibility = if (enable) VISIBLE else GONE
}
//设置LoadingView状态
fun setLoadingState(open:Boolean){
    ivLoading.visibility = if (open) VISIBLE else GONE
}

} ```

接听后

​ 接听后,对应的设置每个人的头像昵称,去掉 Loading,显示视频。接下来就是要定义多个人进出时,布局的变化了。

- 2个人的时候,左右对齐均分显示
- 3个人的时候品字型显示
- 4个人的时候上下2个均分显示
- 5个人以上则九宫格显示
GroupVideoLayoutManager

定义 GroupVideoLayoutManager ,这个是在外部直接使用的,里面应当有查找、添加用户,移除用户,根据人数更新布局位置等功能。

```kotlin class GroupVideoLayoutManager constructor(mContext: Context): RelativeLayout(mContext) {

//自己的ID
var selfId:String=""
private val userLayoutList = mutableListOf<LayoutEntity>()
private var userCount = 0
private val MAX_USER = 8

private val oneUserParamList by lazy { LayoutUtils.get1UserParam(mContext,width,height) }
private val twoUserParamList by lazy { LayoutUtils.get2UserParam(mContext,width,height) }
private val threeUserParamList by lazy { LayoutUtils.get3UserParam(mContext,width,height) }
private val fourUserParamList by lazy { LayoutUtils.get4UserParam(mContext,width,height) }
private val nineUserParamList by lazy { LayoutUtils.get9UserParam(mContext,width,height) }

/**
 * 根据uid 查找对应的View
 */
fun findUser(uid:String):GroupUserVideoLayout?{
    userLayoutList.find { it.userId==uid }?.let { layoutEntity->
        layoutEntity.layout?.let {
            return it
        }?:let{
            return null
        }
    }?:let{
        return null
    }
}

/**
 * 根据uid 添加对应的View
 */
fun addUser(uid:String):GroupUserVideoLayout?{
    if (userCount>MAX_USER){
        return null
    }
    val layout = GroupUserVideoLayout(context)
    userLayoutList.add(LayoutEntity(layout,uid))
    userCount++
    post {
        updateLayout()
    }
    return layout
}

/**
 * 根据uid 移除对应View
 */
fun removeUser(uid:String){
    userLayoutList.find { it.userId==uid }?.let {
        userLayoutList.remove(it)
        userCount--
    }
}

//更新布局位置
private fun updateLayout(){
    if (userLayoutList.isNullOrEmpty()){
        return
    }
    val paramsList:ArrayList<LayoutParams>
    when(userCount){
        1->{
            paramsList = oneUserParamList
            userLayoutList[0].layout?.layoutParams = paramsList[0]
            return
        }
        2->{
            paramsList = twoUserParamList
        }
        3->{
            paramsList = threeUserParamList
        }
        4->{
            paramsList = fourUserParamList
        }
        else->{
            paramsList = nineUserParamList
        }
    }
    var layoutIndex = if (selfId.isEmpty()) 0 else 1
    userLayoutList.forEach {
        if (it.userId == selfId){
            it.layout?.layoutParams = paramsList[0]
        }else if (layoutIndex<paramsList.size){
            it.layout?.layoutParams = paramsList[layoutIndex++]
        }
    }

}

private inner class LayoutEntity {
    var layout: GroupUserVideoLayout? = null
    var userId = ""

    constructor(layout: GroupUserVideoLayout?, userId: String) {
        this.layout = layout
        this.userId = userId
    }
}


override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    if (widthSize == 0 && heightSize == 0) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val minSize = Math.min(measuredWidth, measuredHeight)
        setMeasuredDimension(minSize, minSize)
        return
    }
    val size: Int
    size = if (widthSize == 0 || heightSize == 0) {
        Math.max(widthSize, heightSize)
    } else {
        Math.min(widthSize, heightSize)
    }
    val newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
    super.onMeasure(newMeasureSpec, newMeasureSpec)
}

} ```

​ 以上就实现了类似wechat视频通话界面的自定义View,具体使用效果会在下一期的文章所介绍的demo中体现~敬请期待!

在这里插入图片描述