悬浮滑动控件

floatwidgetdemo

废话少说,效果图如上。

思路

在onInterceptTouchEvent()判断是否点击在控件上,在长按的情况下拦截后续事件,在onTouchEvent()处理控件移动和 MotionEvent.ACTION_UP事件,手指拿起后执行吸附动画。

1、按下去的时候判断开启线程,执行动画完毕后,将长按标志为true

when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                if (inTouchChild(ev.x, ev.y)) {
                    parent.requestDisallowInterceptTouchEvent(true)
                    postDelayed(checkForTap, CHECK_DELAY_MILLIS)
                }
            }
    }

//放大按钮并设置长按标志
 inner class CheckForTap : Runnable {
        override fun run() {
            if (!inTouchChild(actionMoveX, actionMoveY)) {
                return
            }
            floatWidget.animate()
                .scaleX(MAX_SCALE)
                .scaleY(MAX_SCALE)
                .setDuration(150)
                .setListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator?) {
                        super.onAnimationEnd(animation)
                        if (!inTouchChild(actionMoveX, actionMoveY)) {
                            // 恢复到按压时的位置
                            upOrCancelEvent(actionDownX, 0)
                            return
                        }
                        isLongPressed = true //动画结束才设置为长按
                    }
                }).start()
        }
    }

2、在 MotionEvent.ACTION_MOVE时拦截事件,交由onTouchEvent处理

  override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!touchIntercept) {
            return super.onTouchEvent(event)
        }
        when (event.action) {
            MotionEvent.ACTION_MOVE -> {
                val offsetX = event.x - actionDownX
                val offsetY = event.y - actionDownY
                updateLayoutParams(offsetX.toInt(), offsetY.toInt())
            }
            MotionEvent.ACTION_CANCEL,
            MotionEvent.ACTION_UP -> {
                upOrCancelEvent(event.x)
            }
        }
        actionDownX = event.x
        actionDownY = event.y
        return true
    }

完整代码如下

package com.example.viewdemo.widget

import android.animation.*
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.annotation.RequiresApi
import com.example.viewdemo.R
import kotlin.math.abs


@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
class FloatWidgetLayout : FrameLayout {
    companion object {
        private const val CHECK_DELAY_MILLIS = 100L
        private const val MAX_SCALE = 1.2f
    }

    private var actionDownX = 0f
    private var actionDownY = 0f
    private var actionMoveX = 0f
    private var actionMoveY = 0f
    private var touchSlop = 0
    private var touchIntercept = false //是否拦截
    private var isLongPressed = false  //是否长按
    private var checkForTap = CheckForTap() //
    private lateinit var floatWidget: ImageView

    private val floatWidgetFrame = Rect() //获取宽高

    constructor(context: Context) : super(context) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initView(context)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) :
            super(context, attrs, defStyleAttr) {
        initView(context)
    }

    private fun initView(context: Context) {
        val view = LayoutInflater.from(context).inflate(
            R.layout.layout_float_widget, null, true
        )
        touchSlop = ViewConfiguration.get(context).scaledTouchSlop
        floatWidget = view.findViewById(R.id.iv_float)
        floatWidget.apply {
            isClickable = true
        }
        addView(view)
    }


    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                actionDownX = ev.x
                actionDownY = ev.y
                actionMoveX = ev.x
                actionMoveY = ev.y
                touchIntercept = false
                isLongPressed = false
            }
            MotionEvent.ACTION_MOVE -> {
                actionMoveX = ev.x
                actionMoveY = ev.y
            }
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {

        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                if (inTouchChild(ev.x, ev.y)) {
                    parent.requestDisallowInterceptTouchEvent(true)
                    postDelayed(checkForTap, CHECK_DELAY_MILLIS)
                }
            }
            MotionEvent.ACTION_MOVE -> {
                val offsetX = abs(ev.x - actionDownX)
                val offsetY = abs(ev.y - actionDownY)
                val inTouchChild = inTouchChild(ev.x, ev.y)
                val x = offsetX > touchSlop
                val y = offsetY > touchSlop
                if (isLongPressed && inTouchChild && (x || y)) { //长按而且点击在控件上而且发生了滑动
                    touchIntercept = true
                    actionDownX = ev.x
                    actionDownY = ev.y
                    return true
                }
            }
            MotionEvent.ACTION_CANCEL,
            MotionEvent.ACTION_UP -> {
                removeCallbacks(checkForTap)
                if (isLongPressed) {
                    upOrCancelEvent(ev.x)
                } else {
                    upOrCancelEvent(actionDownX, 0)
                }
            }
        }
        return super.onInterceptTouchEvent(ev)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!touchIntercept) {
            return super.onTouchEvent(event)
        }
        when (event.action) {
            MotionEvent.ACTION_MOVE -> {
                val offsetX = event.x - actionDownX
                val offsetY = event.y - actionDownY
                updateLayoutParams(offsetX.toInt(), offsetY.toInt())
            }
            MotionEvent.ACTION_CANCEL,
            MotionEvent.ACTION_UP -> {
                upOrCancelEvent(event.x)
            }
        }
        actionDownX = event.x
        actionDownY = event.y
        return true
    }

    inner class CheckForTap : Runnable {
        override fun run() {
            if (!inTouchChild(actionMoveX, actionMoveY)) {
                return
            }
            floatWidget.animate()
                .scaleX(MAX_SCALE)
                .scaleY(MAX_SCALE)
                .setDuration(150)
                .setListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator?) {
                        super.onAnimationEnd(animation)
                        if (!inTouchChild(actionMoveX, actionMoveY)) {
                            // 恢复到按压时的位置
                            upOrCancelEvent(actionDownX, 0)
                            return
                        }
                        isLongPressed = true //动画结束才设置为长按
                    }
                }).start()
        }
    }

    private fun inTouchChild(x: Float, y: Float): Boolean {
        floatWidget.getHitRect(floatWidgetFrame)
        return floatWidgetFrame.contains(x.toInt(), y.toInt())
    }

    private fun upOrCancelEvent(x: Float, animationDuration: Long = 500) {

        val targetMarginStart = if (x < width / 2f) 0 else width - floatWidget.width //超过一半靠右,否正靠左
        startSpringBackAnimator(targetMarginStart, animationDuration)
    }

    private fun startSpringBackAnimator(
        targetMarginStart: Int,
        animationDuration: Long
    ) {
        // 缩放动画
        val xScaleAnimation = ObjectAnimator.ofFloat(
            floatWidget,
            "scaleX", floatWidget.scaleX, 1.0f
        )
        xScaleAnimation.duration = 150
        xScaleAnimation.interpolator = DecelerateInterpolator()
        val yScaleAnimation = ObjectAnimator.ofFloat(
            floatWidget,
            "scaleY", floatWidget.scaleY, 1.0f
        )
        yScaleAnimation.duration = 150
        yScaleAnimation.interpolator = DecelerateInterpolator()

        // 吸附动画
        val layoutParams = floatWidget.layoutParams as MarginLayoutParams
        val xTranslationAnimation = ValueAnimator.ofInt(
            layoutParams.marginStart, targetMarginStart
        )
        xTranslationAnimation.addUpdateListener {
            val lp = floatWidget.layoutParams as MarginLayoutParams
            lp.marginStart = it.animatedValue as Int
            floatWidget.layoutParams = lp
        }
        xTranslationAnimation.interpolator = DecelerateInterpolator()
        xTranslationAnimation.duration = animationDuration

        val animatorSet = AnimatorSet()
        animatorSet.play(xScaleAnimation).with(yScaleAnimation)
            .before(xTranslationAnimation)
        animatorSet.addListener(object : AnimatorListenerAdapter() {

            override fun onAnimationCancel(animation: Animator?) {
                super.onAnimationCancel(animation)
                onAnimationEnd(animation)
            }

            override fun onAnimationEnd(animation: Animator?) {
                super.onAnimationEnd(animation)
            }
        })
        animatorSet.start()
    }

    /**
     * 移动widget
     */
    private fun updateLayoutParams(
        marginStartOffset: Int,
        topMarginOffset: Int
    ) {
        (floatWidget.layoutParams as? MarginLayoutParams)?.let { lp ->
            var newTopMargin = lp.topMargin + topMarginOffset

            lp.topMargin = newTopMargin

            var newMarginStart = lp.marginStart + marginStartOffset

            if (newMarginStart < 0) {
                newMarginStart = 0
            } else if (newMarginStart > width - floatWidget.width) {
                newMarginStart = width - floatWidget.width
            }
            lp.marginStart = newMarginStart
            floatWidget.layoutParams = lp
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: QT悬浮控件是一个常用的界面元素,可以用来增强用户界面的交互性和可视化效果。 悬浮控件是一种浮动在界面上的圆形按钮,通常具有醒目的颜色和简单的图标。用户可以通过点击悬浮球来执行特定的操作,比如展开菜单、切换页面或者执行其他自定义的操作。 QT 360悬浮控件具有以下特点: 1. 自定义样式:用户可以根据自己的需求来设置悬浮球的颜色、大小和图标等属性,以达到最佳的视觉效果。 2. 可拖拽:用户可以通过鼠标拖动悬浮球来改变其位置,从而实现界面布局的灵活调整。 3. 功能丰富:悬浮球可以绑定各种不同的功能,比如打开新窗口、显示通知、执行特定的操作等,以提供更好的用户体验。 4. 显示吸引力:由于其醒目的颜色和浮动效果,悬浮球通常能够吸引用户的注意力,提醒用户当前可以进行的操作。 5. 界面整洁:悬浮球可以减少界面上的冗余元素,使界面更加整洁和简洁。 总的来说,QT 360悬浮控件是一个非常实用和方便的界面元素,可以增强用户界面的交互性和可视化效果,提供更好的用户体验。无论是用于桌面应用程序还是移动应用程序,悬浮控件都能够起到很大的作用。 ### 回答2: Qt 360悬浮控件是一种用于Qt开发的图形用户界面控件,可以自定义各种风格和功能的悬浮球,常用于提供快捷操作入口或菜单选项。 悬浮控件可以在主界面或其他窗口上悬浮显示,通过悬浮球上的图标或按钮可以触发相应的动作或弹出特定的功能菜单。用户可以根据自己的需求,定义悬浮球的位置、大小、样式,以及悬浮球上的按钮数量和功能等。 除了常见的按钮功能外,悬浮控件还可以添加拖拽、缩放、旋转等操作,使用户能够更加灵活地进行交互。悬浮控件还支持设置悬浮球的透明度、边框样式、阴影效果等,以满足不同设计需求。 Qt 360悬浮控件的具体实现需要使用Qt框架提供的QPainter、QMouseEvent等相关类进行开发。通过捕获鼠标事件,可以实现悬浮球的拖拽功能;通过绘制函数,可以绘制出符合要求的悬浮球外观。 总之,Qt 360悬浮控件是一种功能强大的图形用户界面控件,可以提供方便快捷的交互方式。开发者可以根据自己的需求进行定制,以实现个性化的界面设计和交互效果。 ### 回答3: QT 360悬浮控件是一款在QT开发环境下使用的可自定义的悬浮控件悬浮控件是一个浮动在窗口上的小球形按钮,它可以用来进行快捷操作或者显示一些辅助信息。 QT 360悬浮控件的特点有以下几点。首先,它可以通过简单的代码设置悬浮球的样式和行为,例如可以定义悬浮球的位置、尺寸、背景色、图标等。其次,悬浮控件还支持鼠标事件,用户可以通过鼠标点击、拖拽、滚轮等方式与悬浮球进行交互,从而实现不同的功能。此外,悬浮控件还可以响应键盘事件,用户可以通过键盘快捷键来控制悬浮球的行为。 悬浮控件可以应用在很多场景中。例如,在一个窗口较大的应用程序中,如果用户需要频繁地进行某个操作,可以将这个操作设置在悬浮球上,这样用户就不需要频繁地切换窗口或者寻找按钮。另外,悬浮控件还可以用来显示一些辅助信息,例如在悬浮球上显示当前系统的网络状态、电池电量等信息。 总之,QT 360悬浮控件是一款方便实用的控件,在QT开发环境下可以灵活地进行设置和使用。无论是用来进行快捷操作还是显示辅助信息,它都能为用户提供便利。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值