注册

Android 控件自动贴边实现

最近接到个需求,需要在用户与App交互时,把SDK中之前实现过的悬浮控件贴边隐藏,结束交互后延迟一段时间再自动显示。本篇文章介绍一下实现的思路。


判断交互


用户与App交互、结束交互可以通过监听触摸事件来实现。建议使用的ActivitydispatchTouchEventActivity下的所有触摸事件分发时都会回调此方法,代码如下:


class AutoEdgeHideActivity : BaseGestureDetectorActivity() {

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
// 手指按下,开始本次交互
// 在此实现隐藏逻辑
}
MotionEvent.ACTION_UP -> {
// 手指抬起,结束本次交互
// 在此实现延迟显示功能
}
}
return super.dispatchTouchEvent(ev)
}
}

隐藏与显示


想要实现的效果是当用户与App交互时,悬浮控件平移贴边,但保留一部分显示。结束交互延迟一段时间后,悬浮控件平移回原来的位置。


此处通过ValueAnimator来实现,计算好控件的起始和结束位置,然后改变控件的x坐标,代码如下:


private fun xCoordinateAnimator(view: View, startX: Float, endX: Float) {
val animator = ValueAnimator.ofFloat(startX, endX)
animator.addUpdateListener {
// 不断更改控件的X坐标
view.x = it.animatedValue as Float
}
// 设置插值器,速度由快变慢
animator.interpolator = DecelerateInterpolator()
// 设置动画的持续时间
animator.duration = 500
animator.start()
}

示例


整合之后做了个示例Demo,完整代码如下:


class AutoEdgeHideActivity : BaseGestureDetectorActivity() {

private lateinit var binding: LayoutAutoEdgeHideActivityBinding

private var widthPixels: Int = 0

private val autoShowInterval = 2
private var interacting = false
private var hidden = false
private var lastPositionX: Float = 0f

private val handler = Handler(Looper.myLooper() ?: Looper.getMainLooper())
private val autoShowRunnable = Runnable { autoShow() }

@SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.layout_auto_edge_hide_activity)
widthPixels = resources.displayMetrics.widthPixels
binding.includeTitle.tvTitle.text = "AutoEdgeHideExample"
binding.vFloatView.setOnClickListener {
if (hidden) {
// 当前为隐藏状态,先显示
// 把之前的延迟线程先取消
handler.removeCallbacks(autoShowRunnable)
autoShow()
Toast.makeText(this, "手动显示控件", Toast.LENGTH_SHORT).show()
} else {
// 相应正常的事件
Toast.makeText(this, "点击了浮标控件", Toast.LENGTH_SHORT).show()
}
}
}

override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
if (!checkIsTouchFloatView(ev, binding.vFloatView)) {
// 起始ACTION_DOWN事件在浮标控件外,自动隐藏浮标控件,标记正在交互
interacting = true
handler.removeCallbacks(autoShowRunnable)
autoHide()
}
}
MotionEvent.ACTION_UP -> {
if (interacting) {
// 交互结束,一定时间后自动显示,时间可以自由配置
interacting = false
handler.postDelayed(autoShowRunnable, autoShowInterval * 1000L)
}
}
}
return super.dispatchTouchEvent(ev)
}

/**
* 检查是否触摸浮标控件
*/
private fun checkIsTouchFloatView(ev: MotionEvent, view: View): Boolean {
val screenLocation = IntArray(2)
view.getLocationOnScreen(screenLocation)
val viewX = screenLocation[0]
val viewY = screenLocation[1]
return (ev.x >= viewX && ev.x <= (viewX + view.width)) && (ev.y >= viewY && ev.y <= (viewY + view.height))
}

private fun autoShow() {
if (hidden) {
hidden = false
binding.vFloatView.let {
xCoordinateAnimator(it, it.x, lastPositionX)
}
}
}

private fun autoHide() {
if (!hidden) {
hidden = true
binding.vFloatView.let {
// 记录一下显示状态下的x坐标
lastPositionX = it.x
// 隐藏时的x坐标,留一点控件的边缘显示(示例中默认控件在屏幕右侧)
val endX = widthPixels - it.width * 0.23f
xCoordinateAnimator(it, lastPositionX, endX)
}
}
}

private fun xCoordinateAnimator(view: View, startX: Float, endX: Float) {
val animator = ValueAnimator.ofFloat(startX, endX)
animator.addUpdateListener {
view.x = it.animatedValue as Float
}
animator.interpolator = DecelerateInterpolator()
animator.duration = 500
animator.start()
}
}

效果如图:


device-2022-11-26-105111.gif

作者:ChenYhong
链接:https://juejin.cn/post/7170191911284637727
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册