android代码控制组件的移动,Android可拖动控件(一)

本文介绍了如何在Android开发中实现控件拖动功能,包括View和ViewGroup的拖动实现。通过重写onTouchEvent和onInterceptTouchEvent方法获取用户手势,动态更新控件位置,并在ACTION_UP时更新LayoutParams以保持位置。同时,文章提到了防止控件超出屏幕边界的处理方法,以及获取屏幕尺寸来限制拖动范围。
摘要由CSDN通过智能技术生成

需求说明

在开发过程中,遇到了这样一个需求,要求能够将一个控件设置为可以拖动的。当用户长按一个控件时,这个控件能够随着用户的手势进行移动,涉及的控件包括view及viewgroup.

设计思路

由于控件需要根据用户的手势进行移动,因此不可避免的就是要获取到用户的手势行为,因此可以通过事件分发机制中的touch方法中获取到用户的手势行为,进而重新更新当前控件的位置即可。

代码示例

View控件

对于像继承自TextView或者ImageView这种不涉及到viewgroup的view,可以通过继承实现一个新的类,然后重写其onTouchEvent方法即可。

需要控件能够根据手势实时移动,因此需要在ACTION_MOVE移动状态中更新view控件的位置,在其中获取具体的位置,调用layout方法更新位置。

为了防止当用户抬起手指之后父控件刷新导致的已经滑动的View回到原位,因此需要在手势抬起ACTION_UP时重新更新View的layoutParam保证View的位置发生移动。

控件需要被设置为clickable = true,否则可能无法获取到ACTION_MOVE或者ACTION_UP手势。

示例代码如下:

package com.example.floatingwindowdemo.custom.floatview

import android.content.Context

import android.util.AttributeSet

import android.view.MotionEvent

import android.widget.LinearLayout

import androidx.appcompat.widget.AppCompatTextView

import com.example.floatingwindowdemo.LogUtils

class FloatTextView : AppCompatTextView {

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

constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {}

constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int)

: super(context, attributeSet, defStyleAttr) {

}

init {

}

//记录最后的位置

private var lastX: Int = 0

private var lastY: Int = 0

override fun onTouchEvent(event: MotionEvent?): Boolean {

var action = event?.action ?: return super.onTouchEvent(event)

var isLongTouch = event

when (action) {

MotionEvent.ACTION_DOWN -> {

lastX = event.rawX.toInt()

lastY = event.rawY.toInt()

}

MotionEvent.ACTION_MOVE -> {

//偏移距离

var dx = (event.rawX.toInt()) - lastX

var dy = (event.rawY.toInt()) - lastY

var l = left + dx

var r = right + dx

var b = bottom + dy

var t = top + dy

//利用layout方法重新更新view的位置

layout(l, t, r, b)

lastX = event.rawX.toInt()

lastY = event.rawY.toInt()

}

MotionEvent.ACTION_UP -> {

//此处重新设置LayoutParams,防止当父布局重新刷新时导致控件回归原处

var lp = LinearLayout.LayoutParams(width, height)

lp.setMargins(left, top, 0, 0)

LogUtils.instance.getLogPrint("$left $top $right $bottom")

layoutParams = lp

}

}

return super.onTouchEvent(event)

}

}

Viewgroup

对于需要有多个控件同时拖动的Viewgroup,可以通过继承实现一个Viewgroup新类,然后通过拦截touch事件完成对手势的获取。实现拖动的思路类似于上文。

示例代码如下:

package com.example.floatingwindowdemo.custom.floatview

import android.content.Context

import android.util.AttributeSet

import android.view.MotionEvent

import android.view.View

import android.widget.LinearLayout

import android.widget.TextView

import androidx.appcompat.widget.AppCompatImageView

import com.example.floatingwindowdemo.R

class FloatWindow : LinearLayout {

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

constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {}

constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) :

super(context, attributeSet, defStyleAttr) {

}

private var image: AppCompatImageView? = null

private var text: TextView? = null

private var lastX: Int = 0

private var lastY: Int = 0

init {

View.inflate(context, R.layout.float_window_layout, this)

image = findViewById(R.id.img_float_window)

text = findViewById(R.id.text_float_window)

}

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {

var action = ev?.action ?: return super.onInterceptTouchEvent(ev)

when (action) {

MotionEvent.ACTION_DOWN -> {

lastX = ev.rawX.toInt()

lastY = ev.rawY.toInt()

}

MotionEvent.ACTION_MOVE -> {

var dx = ev.rawX.toInt() - lastX

var dy = ev.rawY.toInt() - lastY

var l = left + dx

var r = right + dx

var t = top + dy

var b = bottom + dy

layout(l, t, r, b)

lastX = ev.rawX.toInt()

lastY = ev.rawY.toInt()

}

MotionEvent.ACTION_UP -> {

var lp = LinearLayout.LayoutParams(width, height)

lp.setMargins(left, top, right, bottom)

layoutParams = lp

}

}

return super.onInterceptTouchEvent(ev)

}

}

添加边界设置

上述代码中没有控件位置对屏幕边缘的限制,因此当滑动时很有可能会滑出屏幕边缘,如果要解决这个问题,需要对当前位置做些判断,可以通过如下几步来解决此问题:

获取到屏幕的高和宽

在重新确定控件位置时判断是否已经超出了屏幕边界,如果超出了屏幕边界,需要重置其位置。

1. 获取屏幕宽高

可以通过DisplayMetrics来获取屏幕的宽和高,,示例代码如下:

private var displayMetrics: DisplayMetrics = resources.displayMetrics

//宽度

private var screenWidth = displayMetrics.widthPixels

//高度

private var screenHeight = displayMetrics.heightPixels

2. 即时判断边缘

由于是在MotionEvent.ACTION_MOVE时重置的控件的位置,所以需要在此时判断当前控件的位置是否已经超过了边界,如果超过了边界,需要重置其位置,示例代码如下:

MotionEvent.ACTION_MOVE -> {

var dx = ev.rawX.toInt() - lastX

var dy = ev.rawY.toInt() - lastY

var l = left + dx

var r = right + dx

var t = top + dy

var b = bottom + dy

//当滑动出边界时需要重新设置位置

if (l < 0) {

l = 0

r = width

}

if (t < 0) {

t = 0

b = height

}

if (r > screenWidth) {

r = screenWidth

l = screenWidth - width

}

if (b > screenHeight) {

b = screenHeight

t = screenHeight - height

}

layout(l, t, r, b)

lastX = ev.rawX.toInt()

lastY = ev.rawY.toInt()

}

后续文章

其他

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值