SoFunction
Updated on 2025-04-09

Android implements draggable floating view

This article shares with you the example of android to implement draggable floating view for your reference. The specific content is as follows

Source of business

After the page is minimized, a floating view needs to appear to inform the user to prevent the view from being blocked. The view needs to be swiped

Known issues

The layout type of dependency is unknown [for subsequent expansion]

The outside world passes ViewGroup itself inherits LinearLayout [or other ViewGroup]

class FloatChannelView(var mContext: Context?, var viewGroup: ViewGroup) : LinearLayout(mContext){
    private var mIcon: ImageView = ImageView(context)
    private var mName: TextView = TextView(context)
    private var mClose: ImageView = ImageView(context)
    private var iconWH = dip2Px(38)
    private var groupPadding = dip2Px(3)
    private var mViewGroupH = dip2Px(44)
    private var mViewGroupW = dip2Px(152)
    private var mBoundaryLeft: Float
    private var mBoundaryTop: Float
    private var mBoundaryRight: Float
    private var mBoundaryBottom: Float
    private var mScreenWidth = getScreenWidth() // Get screen width and height    private var mScreenHeight = getScreenHeight()
 
    private var mDownEventX: Float = 0f  // x relative to the control    private var mDownEventY: Float = 0f
    private var mDownX: Float = 0f  // The x relative to the screen    private var mDownY: Float = 0f
 
    private var mListener: OnClickListener? = null
    private var mIsStartAnimation: Boolean = false
 
    private val mDefaultMargin = dip2Px(12)
    private var mMarginLeft = mDefaultMargin
    private var mMarginTop = mDefaultMargin
    private var mMarginRight = mDefaultMargin
    private var mMarginBottom = mDefaultMargin
 
    init {
        layoutParams = LayoutParams(mViewGroupW, mViewGroupH)
        setPadding(groupPadding, groupPadding, groupPadding, groupPadding)
        setBackgroundResource() // It is recommended to add some transparency        orientation = HORIZONTAL
        gravity = Gravity.CENTER_VERTICAL
        mBoundaryLeft = ()
        mBoundaryTop = ()
        mBoundaryRight = mScreenWidth - ()
        mBoundaryBottom = (mScreenHeight - mMarginBottom - dip2Px(85)).toFloat()
        setView()
    }
}

2. Drag the event to affect clicks and event distribution processing.

override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (mIsStartAnimation) {  // The animation is in progress without processing onTouch            return true
        }
        if (event == null) {
            return (event)
        }
        mIsOnTouch = true
        //The upper left corner coordinate of the floating area        val x = x
        val y = y
        //The width and height of the suspended area        val width = mViewGroupW
        val height = mViewGroupH
        when () {
            ACTION_DOWN -> {
                //Click on the position coordinate                mDownEventX = 
                mDownEventY = 
                mDownX = x
                mDownY = y
            }
            ACTION_UP -> {
                mUpTime = ()
            
                if (mIsMove && abs(mDownX - x) <= 8f && abs(mDownY - y) <= 8f) {
                    mListener?.onClick(this)
                }
                mIsMove = false
                // Handle boundary overflow issues after lifting                resilienceAnimation(x, y, x + mViewGroupW, y + mViewGroupH)
            }
            ACTION_MOVE -> {
                val changeX = () - mDownEventX
                val changeY = () - mDownEventY
                mIsMove = true
                if (changeX == 0f && changeY == 0f) {
                    return (event)
                }
                val left = (x + changeX).toInt()
                val top = (y + changeY).toInt()
                val right = left + mViewGroupW
                val bottom = top + mViewGroupH
                layout(left, top, right, bottom)
            }
        }
        return true
    }

3. Drag to the boundary issue.

After dragging it out of the boundary, it rebounded

/**
 *  Boom beyond the boundary
 *  @param left Current x direction position
 *  @param right Current y direction position
 */
    private fun resilienceAnimation(left: Float, top: Float, right: Float, bottom: Float) {
       
        var startX = 0f
        var resilienceX = 0f
        if (mBoundaryLeft <= left && right <= mBoundaryRight) {  // x direction is within range            // Not handled        } else if (mBoundaryLeft > left) {  // left overflow            startX = 0f
            resilienceX = mBoundaryLeft - left
        } else {   // right direction overflows at the bottom            startX = 0f
            resilienceX = mBoundaryRight - right
        }
        var startY = 0f
        var resilienceY = 0f
        if (mBoundaryTop <= top && bottom <= mBoundaryBottom) {  // The y direction is within range            // Not handled        } else if (mBoundaryTop > top) {  // top overflow            startY = 0f
            resilienceY = mBoundaryTop - top
        } else {  // bottom overflow            startY = 0f
            resilienceY = mBoundaryBottom - bottom
        }
        if (resilienceX == 0f && resilienceY == 0f) {  // No rebound required in range            return
        }
 
        // Bounce back beyond the boundary        val phaseFirstDuration: Long = 400
        var oAnimPhaseFirstTUpX: ObjectAnimator? = null
        if (resilienceX != 0f) {
            oAnimPhaseFirstTUpX = (this, "translationX", startX, resilienceX)
                    .setDuration(phaseFirstDuration)
        }
        var oAnimPhaseFirstTUpY: ObjectAnimator? = null
        if (resilienceY != 0f) {
            oAnimPhaseFirstTUpY = (this, "translationY", startY, resilienceY)
                    .setDuration(phaseFirstDuration)
        }
        val animatorSet = AnimatorSet()
        if (oAnimPhaseFirstTUpX != null && oAnimPhaseFirstTUpY != null) {
            (oAnimPhaseFirstTUpX).with(oAnimPhaseFirstTUpY)
        } else if (oAnimPhaseFirstTUpX != null) {
            (oAnimPhaseFirstTUpX)
        } else {
            (oAnimPhaseFirstTUpY)
        }
        [ - 1].addListener(object :  {
 
            override fun onAnimationStart(animation: Animator?) {
                mIsStartAnimation = true
            }
 
            override fun onAnimationEnd(animation: Animator?) {
                var l = left
                var t = top
                var r = right
                var b = bottom
                when {
                    mBoundaryLeft > left -> {  // The left side of x overflows                        l = mBoundaryLeft
                        r = mBoundaryLeft + mViewGroupW
                    }
                    mBoundaryRight < right -> {  // The right side of x overflows                        l = mBoundaryRight - mViewGroupW
                        r = mBoundaryRight
                    }
                    else -> {  // The x direction does not overflow                    }
                }
 
                when {
                    mBoundaryTop > top -> {  // y top overflow                        t = mBoundaryTop
                        b = mBoundaryTop + mViewGroupH
                    }
                    mBoundaryBottom < bottom -> {  // y bottom overflow                        t = mBoundaryBottom - mViewGroupH
                        b = mBoundaryBottom
                    }
                    else -> {  // The y direction has not overflowed                    }
                }
                // Only offset is performed, the actual position has not changed, the offset needs to be reset and redrawn                this@ = 0f
                this@ = 0f
                layout((), (), (), ())
                mMarginLeft = ()
                mMarginTop = ()
                mIsStartAnimation = false
            }
 
            override fun onAnimationCancel(animation: Animator?) {}
 
            override fun onAnimationRepeat(animation: Animator?) {}
 
        })
        ()

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.