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

数字逸影客
• 阅读 241

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

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

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

等待中界面

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

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

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

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#2C3033"
    tools:parentTag="android.widget.RelativeLayout">
    
    <!--视频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>

</merge>
GroupUserVideoLayout

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

/**
 * 多人视频通话中每个用户的布局
 */
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 ,这个是在外部直接使用的,里面应当有查找、添加用户,移除用户,根据人数更新布局位置等功能。

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)
    }

}

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这