在Android中滑动是实现自定义控件的基础,同时在开发中我们也会难免遇到View的滑动处理,不管哪种滑动处理,其基本思想都是类似的。当点击事件传到View时,系统记下触摸点的坐标,手指移动时系统记下移动后触摸的坐标并算出偏移量,并通过偏移量来修改View的坐标。下面来总结下android系统中常见的滑动方案。
View的坐标系
在介绍View的滑动时候,先要介绍下View的坐标系
View的滑动就是通过手指的滑动偏移量,来动态设置View的位移,从而达到滑动的效果,而滑动偏移量的获取就是通过上图方法获取坐标差值来达到的。
MotionEvent
View的滑动就涉及到事件MotionEvent,这个类中提供了很多常量来区分相关动作状态,也提供了很多方法来获取焦点坐标各种方法。
- getX() 获取点击事件距离控件左边的距离,即视图坐标
- getY() 获取点击事件距离控件顶部的距离,即视图坐标
- getRawX() 获取点击事件距离整个屏幕左边的距离,即绝对坐标
- getRawY() 获取点击事件距离整个屏幕顶边的距离,即绝对坐标
View的滑动
下面来介绍下android中的各种滑动方案
- 1 . layout()
通过修改View的left, top, right, bottom四种属性来控制View的坐标,接下来通过自定义View来看下用法,代码如下
class CustomView: View {
constructor(contex: Context): super(contex)
constructor(contex: Context, attrs: AttributeSet): super(contex, attrs)
var lastX: Int? = null
var lastY: Int? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
var x = Math.round(event.x)
var y = Math.round(event.y)
when(event.action){
MotionEvent.ACTION_DOWN -> {
lastX = x
lastY = y
}
MotionEvent.ACTION_MOVE -> {
var offsetX = x - lastX!!
var offsetY = y - lastY!!
layout(left + offsetX, top + offsetY, right + offsetX, bottom + offsetY)
}
}
return true
}
}
- 2 .offsetLeftAndRight()和offsetTopAndBottom()
用法和layout()相关
class CustomView2: View {
constructor(contex: Context): super(contex)
constructor(contex: Context, attrs: AttributeSet): super(contex, attrs)
var lastX: Int? = null
var lastY: Int? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
var x = Math.round(event.x)
var y = Math.round(event.y)
when(event.action){
MotionEvent.ACTION_DOWN -> {
lastX = x
lastY = y
}
MotionEvent.ACTION_MOVE -> {
var offsetX = x - lastX!!
var offsetY = y - lastY!!
offsetLeftAndRight(offsetX)
offsetTopAndBottom(offsetY)
}
}
return true
}
}
- 3 .LayoutParams(改变布局相关参数)
通过获取View的布局参数,设置里面的leftMargin,topMargin来使View滑动
class CustomView3: View {
constructor(contex: Context): super(contex)
constructor(contex: Context, attrs: AttributeSet): super(contex, attrs)
var lastX: Int? = null
var lastY: Int? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
var x = Math.round(event.x)
var y = Math.round(event.y)
when(event.action){
MotionEvent.ACTION_DOWN -> {
lastX = x
lastY = y
}
MotionEvent.ACTION_MOVE -> {
var offsetX = x - lastX!!
var offsetY = y - lastY!!
var layoutParams = layoutParams as ViewGroup.MarginLayoutParams
layoutParams.leftMargin = left + offsetX
layoutParams.topMargin = top + offsetY
setLayoutParams(layoutParams)
}
}
return true
}
}
注意:这里的根布局如果是constraintlayout,还需要设置相应的边界约束才会有作用。
- 4 .动画
在android动画实现View的滑动有好几种,其中View动画只会改变View的投影,但是并不能改变View的位置,所以一般不会使用,可以使用属性动画,或者view自带的animate()
//1
customView.animate().translationX(300F).setDuration(4000).start()
//2
ObjectAnimator.ofFloat(customView, "translationX", 0F, 300F).setDuration(4000).start()
- 5 scrollTo与scrollBy
scrollTo(x, y)表示移动一个具体的坐标点,而scrollBy(dx, dy)则表示移动的增量为dx,dy。其中scrollBy最终也是要调用scrollTo的。
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
这两种滑动方式移动的是View的内容,如果在ViewGroup中使用,则是移动其所有的子View。ACTION_MOVE中的代码替换成如下
((View)getParent()).scrollBy(-offsetX, -offsetY)
注意,通过设置偏移量这里需要设置成负值,这里的滑动是瞬间完成的。
- 6 .Scroller
通过Scroller()设置的滑动是有过渡性的,代码如下
class CustomView5 : View{
private var mScroll: Scroller? = null
constructor(context: Context): this(context, null)
constructor(context: Context, attr: AttributeSet?): super(context, attr){
mScroll = Scroller(context)
}
override fun computeScroll() {
super.computeScroll()
if(mScroll!!.computeScrollOffset()){
(parent as View).scrollTo(mScroll!!.currX, mScroll!!.currY)
invalidate()
}
}
fun smoothScrollTo(destX: Int, destY: Int){
var scrollX = scrollX
var scrollY = scrollY
var deltaX = destX - scrollX
var deltaY = destY - scrollY
mScroll!!.startScroll(scrollX, scrollY, deltaX, deltaY, 2000)
invalidate()
}
}
Scroller本身不能实现View的滑动,它需要与View的computeScroll()方法配合才能实现弹性滑动,在computeScroll()方法中通过scroller来不断获取当前的滑动值,每滑动一小段距离我们就调用invalidate()方法不断进行重绘。从而实现一个平滑效果。
总结
滑动方式大概就这几种,真正在项目中使用,肯定不会这么简单,还是需要结合项目需求做具体调整。