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中體現~敬請期待!

在這裡插入圖片描述