A summary

At present, the easiest way to realize the illusion is to let users open the suspension window, but this is equivalent to giving users one more step of operation, and users may not be willing to open it. Is there one that can be used directly without opening permissions? The answer must be some, but a little hook system code, most development scenarios only use the DEBUG package, such as: test environment performance detection tool DoraemonKit, such a global function.

Let’s take a look at the results

Ii. Technical Understanding

2.1 First understand the flow of setContentView

When we call setContentView, we will first call the setContentView of PhoneWindow (the older version may go all the way around and end up calling PhoneWindow), The PhoneWindow class has an inner class called DecorView, which is a View that inherits from FramLayout and then takes a different root layout depending on the Theme. But each of these root layouts uses a View with id Android.r.D.C. tent, and then adds the View generated by our XML to the View in Android.R.D.C. Tent

2.2 ViewDragHelper

ViewDragHelper is a useful set of methods and state tracking for dragging and repositioning views in a ViewGroup. Basically used in custom ViewGroup handling drag, can achieve drag function

3 Finding a solution

  1. We can put our dangling View in a DecorView.
  2. You can put a fancy View in android.r.I.D.C tent
  3. Drag and drop can be done using ViewDragHelper
  4. We can register the Activity of the life cycle monitoring context. RegisterActivityLifecycleCallbacks (this), the Activity of the onStart add our vain View, Because you’re creating a new View inside the ContentView, it feels like there’s no memory leak, so you don’t have to remove it when you onStop

Four start the masturbation code

Define a FloatViewManger to initialize the float View

Add a View to the onStart of the Activity, take android.r.D.C. Tent to the child View, remove it, and add it as a DragViewParent that we can slide. Then add the View we just removed to our DragViewParent as follows

object FloatViewManger : Application.ActivityLifecycleCallbacks {
    var height: Float = 0F
    var width: Float = 0F

    /** * from 0 to 1 */

    var proportionX: Float = 0F

    /** * from 0 to 1 */
    var proportionY: Float = 0F
    private lateinit var context: Context
    var floatView: View? = null


    fun init(context: Application) {
        this.context = context
        // Register callback listeners for the Activity lifecycle
        context.registerActivityLifecycleCallbacks(this)}/** * Add the required View to each Activity's DecorView * since ViewDragHelper is used, find android.r.i.C.ontent * after setContentView, We'll set the view to android.r.D.C. tent * and then we'll get the first child of android.r.D.C tent which is the XML view * and then add the child view to Android.r.D.C tent DragViewParent, DragViewParent adds child View XML */
    private fun addView(activity: Activity) {
        if (floatView == null) {
            return
        }
        val decorView = activity.window.decorView as ViewGroup
        // If you add DragViewParent that can be found in decorView, you don't need to initialize the DragViewParent
        val tagView = decorView.findViewWithTag<View>(TAG)

        var dragViewParent: DragViewParent
        if(tagView ! =null) {
            // Add DragViewParent to ContentIdView
            dragViewParent = tagView as DragViewParent
        } else {
            / / initialization
            dragViewParent = DragViewParent(context)

            val contentView = decorView.findViewById<ViewGroup>(android.R.id.content)

            // This is the View we wrote setContentView with
            val xmlView = contentView.getChildAt(0);

            // Remove the View from the contentView
            contentView.removeView(xmlView)

            // Add this View to our custom View
            dragViewParent.addView(xmlView)


            // Add our custom sliding View to the contentView
            contentView.addView(dragViewParent)

            dragViewParent.tag = TAG
        }



        if(floatView!! .parent ! =null) {
            // Remove the View that the user needs to float first, because the float View is unique, after adding must be removed before adding
            valparentView = floatView!! .parentas ViewGroup
            parentView.removeView(floatView)
        }

        // Add the View that the user needs to float to our custom DragViewParentdragViewParent.setDragViewChild(floatView!! , width, height) }fun show(a){ floatView? .visibility = View.VISIBLE }fun hide(a){ floatView? .visibility = View.GONE }override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?).{}override fun onActivityStarted(activity: Activity) {
        addView(activity)
    }

    override fun onActivityResumed(activity: Activity){}override fun onActivityPaused(activity: Activity){}override fun onActivityStopped(activity: Activity){}override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle){}override fun onActivityDestroyed(activity: Activity){}}Copy the code

Define a draggable ParentView

Use ViewDragHelper to implement drag and drop

支那const val TAG = "tag"

/** * records the last position of the slide */

var lastX = -1f
var lastY = -1f

class DragViewParent(context: Context) : FrameLayout(context) {
    private lateinit var dragHelper: ViewDragHelper

    private lateinit var dragView: View
    private val viewWidth = 0
    private val viewHeight = 0

    fun setDragViewChild(view: View, width: Float, height: Float) {
        isClickable = false
        dragView = view

        var layoutParams = FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.WRAP_CONTENT,
            FrameLayout.LayoutParams.WRAP_CONTENT
        )
        addView(view, layoutParams)
        setViewDragHelper()
    }

    private fun setViewDragHelper(a) {
        dragHelper = ViewDragHelper.create(this.1.0 f.object : ViewDragHelper.Callback() {


            override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
                val leftBound = paddingLeft
                val rightBound: Int = width - dragView.width - leftBound
                return Math.min(Math.max(left, leftBound), rightBound)
            }

            override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
                val leftBound = paddingTop
                val rightBound: Int = height - dragView.height - dragView.paddingBottom
                return Math.min(Math.max(top, leftBound), rightBound)
            }


            // Call back when the boundary drags
            override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) {}

            override fun getViewHorizontalDragRange(child: View): Int {
                return measuredWidth - child.measuredWidth
            }

            override fun getViewVerticalDragRange(child: View): Int {
                return measuredHeight - child.measuredHeight
            }

            override fun tryCaptureView(child: View, pointerId: Int): Boolean {
                return dragView === child
            }


            override fun onViewPositionChanged(
                changedView: View,
                left: Int,
                top: Int,
                dx: Int,
                dy: Int
            ) {
                super.onViewPositionChanged(changedView, left, top, dx, dy)
                lastX = changedView.x
                lastY = changedView.y
            }
        })
        dragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT)
    }

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        return dragHelper.shouldInterceptTouchEvent(event)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        dragHelper.processTouchEvent(event)
        return true
    }

    override fun computeScroll(a) {
        if (dragHelper.continueSettling(true)) {
            invalidate()
        }
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        restorePosition()

    }

    private fun restorePosition(a) {
        if (lastX == -1f && lastY == -1f) { // The initial position
// lastX = (measuredWidth - (if (dragView.measuredWidth > 0) dragView.measuredWidth else viewWidth).toFloat())
// lastY = (measuredHeight - (if (dragView.measuredHeight > 0) dragView.measuredHeight else viewHeight).toFloat())
            lastX = (measuredWidth-dragView.measuredWidth) * FloatViewManger.proportionX
            lastY = (measuredHeight-dragView.measuredHeight) * FloatViewManger.proportionY
        }
        dragView.layout(
            lastX.toInt(),
            lastY.toInt(),
            lastX.toInt() + dragView.measuredWidth,
            lastY.toInt() + dragView.measuredHeight
        )
    }


}**
Copy the code

Next is the tool file used

val Float.px: Float
    get() = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this,
        Resources.getSystem().displayMetrics
    )

fun getScreenHeight(): Int {
    return Resources.getSystem().displayMetrics.heightPixels
}

fun getScreenWeith(): Int {
    return Resources.getSystem().displayMetrics.widthPixels
}

Copy the code

Source locationFloatView