视频聊天+全局浮窗

原文章:Android 直播中的悬浮小窗以及封装
git地址:FloatWindow
原博主开发的是直播中的浮窗,本项目为im的视频聊天浮窗,略作修改。

FloatView

class FloatView(context: Context) : FrameLayout(context) {

    private var lastX: Float = 0f
    private var clickLastX: Float = 0f
    private var lastY: Float = 0f
    private var clickLastY: Float = 0f
    private var minOffsetX = 0 // 最小偏移量
    private var minOffsetY = 0
    private var maxOffsetX = 0 // 最大偏移量
    private var maxOffsetY = 0
    private var offsetX: Int = minOffsetX // 当前偏移量
    private var offsetY: Int = minOffsetY
    private var windowManager: WindowManager? = null
    private var layoutParams: WindowManager.LayoutParams = WindowManager.LayoutParams()
    private val displayMetrics: DisplayMetrics = context.resources.displayMetrics
    private val gestureListener = GestureListener()
    private val gestureDetector = GestureDetectorCompat(context, gestureListener)
    private val scroller = OverScroller(context)
    private val flingRunnable = FlingRunnable()
    private var mOnItemClickListener: OnItemClickListener? = null


    init {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        } else {
            layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
        }
        layoutParams.format = PixelFormat.TRANSPARENT
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        layoutParams.gravity = Gravity.START or Gravity.TOP
        layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT
        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
    }

    fun setAdapter(adapter: FloatViewAdapter) {
        removeAllViews()
        adapter.onCreateView(LayoutInflater.from(context), this)
        adapter.onBindView(this)
    }

    fun addWindow(windowManager: WindowManager) {
        this.windowManager = windowManager
        this.windowManager?.addView(this, layoutParams)
        //因为浮窗默认在左上角,重新设置一个位置
        onTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, SizeUtils.dp2px(12f).toFloat(),
            SizeUtils.dp2px(82f).toFloat(), 0))
    }

    fun remove() {
        if (isAttachedToWindow) {
            windowManager?.removeView(this)
            windowManager = null
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                lastX = event.rawX
                clickLastX = event.rawX
                lastY = event.rawY
                clickLastY = event.rawY
            }
            MotionEvent.ACTION_MOVE -> {
                val currentX = event.rawX
                val currentY = event.rawY
                val dx = (currentX - lastX).toInt()
                val dy = (currentY - lastY).toInt()
                fixOffset(dx, dy)
                move()
                lastX = currentX
                lastY = currentY
            }
            MotionEvent.ACTION_UP->{
                if (clickLastX == lastX && clickLastY == lastY){
                    mOnItemClickListener?.onItemClick()
                }
            }
        }
        return gestureDetector.onTouchEvent(event)
    }

    private fun move() {
        layoutParams.x = offsetX
        layoutParams.y = offsetY
        windowManager?.updateViewLayout(this, layoutParams)
    }

    private fun fixOffset(dx: Int, dy: Int) {
        // 控制偏移量  0<=offset<=displayMetrics.widthPixels - width
        val currentOffsetX = max(offsetX + dx, 0)
        val currentOffsetY = max(offsetY + dy, 0)
        maxOffsetX = if (maxOffsetX > 0) maxOffsetX else displayMetrics.widthPixels - width
        maxOffsetY = if (maxOffsetY > 0) maxOffsetY else displayMetrics.heightPixels - height
        offsetX = min(currentOffsetX, maxOffsetX)
        offsetY = min(currentOffsetY, maxOffsetY)
    }

    inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
        override fun onDown(e: MotionEvent?): Boolean {
            return true
        }

        override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
            performClick()
            return true
        }

        override fun onFling(
            e1: MotionEvent?,
            e2: MotionEvent?,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            maxOffsetX = if (maxOffsetX > 0) maxOffsetX else displayMetrics.widthPixels - width
            maxOffsetY = if (maxOffsetY > 0) maxOffsetY else displayMetrics.heightPixels - height
            scroller.fling(
                offsetX,
                offsetY,
                velocityX.toInt(),
                velocityY.toInt(),
                minOffsetX,
                maxOffsetX,
                minOffsetY,
                maxOffsetY
            )
            postOnAnimation(flingRunnable)
            return true
        }
    }

    inner class FlingRunnable : Runnable {
        override fun run() {
            if (scroller.computeScrollOffset()) {
                offsetX = scroller.currX
                offsetY = scroller.currY
                move()
                postOnAnimation(this)
            }
        }
    }

    interface FloatViewAdapter {

        fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View

        fun onBindView(itemView: View)
    }

    fun setOnItemClickListener(onItemClickListener: OnItemClickListener) {
        mOnItemClickListener = onItemClickListener
    }

    interface OnItemClickListener {
        fun onItemClick()
    }
}

FloatWindow

class FloatWindow(context: Context) {
    companion object {
        @Volatile
        private var instance: FloatWindow? = null

        fun getInstance(context: Context) = instance ?: synchronized(this) {
            instance ?: FloatWindow(context.applicationContext).also { instance = it }
        }
    }

    private var windowManager: WindowManager =
        context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    private var floatView: FloatView? = null

    fun bindView(view: FloatView) {
        this.floatView = view
        floatView?.addWindow(windowManager)
    }

    fun removeView() {
        floatView?.remove()
        floatView = null
    }
}

SimpleAdapter

class SimpleAdapter(var mLocalView: SurfaceView) : FloatView.FloatViewAdapter {

    var mFrameLayout : FrameLayout ?= null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
        return inflater.inflate(R.layout.layout_test, container)
    }

    override fun onBindView(itemView: View) {
        itemView.findViewById<FrameLayout>(R.id.local_video_view_container).addView(mLocalView)
        mFrameLayout = itemView.findViewById<FrameLayout>(R.id.local_video_view_container)
    }

    fun getFrameLayout(): FrameLayout? {
        return mFrameLayout;
    }


}

SimpleAdapter原来文章并无SurfaceView参数,本项目需要,所以略作修改,在FloatView中的OnItemClickListener监听也是新加的,需求是在视频页面点击切换视图,在其他页面点击回到视频页面。

记得在清单中给activity设置

android:launchMode="singleInstance"
android:taskAffinity=".taskCall"
android:excludeFromRecents ="true"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值