在讨论滑动效果之前,必须先理解两个重要的坐标体系概念。第一个是屏幕坐标系,这是以设备屏幕左上角为原点(0,0)的绝对坐标系。第二个是视图坐标系,这是相对于父容器的相对坐标系,以视图自身左上角为原点。
View在绘制时通过onLayout()方法确定其在父容器中的位置。要实现View的滑动效果,我们可以调用layout()方法传入新的坐标参数。这些坐标参数可以在触摸事件中进行实时获取和处理,为后续的滑动实现奠定基础。
下面演示一个View随手指滑动的简单实现方案:
1. 在触摸事件ACTION_DOWN中记录手指按下时的初始坐标(startX, startY)
public class DragView extends View {
private int mLastX;
private int mLastY;
public DragView(Context context) {
this(context, null);
}
public DragView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int lastX = 0, lastY = 0;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;

layout(getLeft() + offsetX, getTop() + offsetY,
getRight() + offsetX, getBottom() + offsetY);
break;
}
return true;
}
}
2. 在触摸事件ACTION_MOVE中计算X和Y方向上的偏移量(deltaX, deltaY)
3. 调用layout()方法根据偏移量更新View位置
4. 更新完成后必须重置初始坐标为当前坐标,以确保下次计算偏移量的准确性
private int mLastX;
private int mLastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - mLastX;
int offsetY = y - mLastY;
layout(getLeft() + offsetX, getTop() + offsetY,
getRight() + offsetX, getBottom() + offsetY);
//重新设置初始坐标
mLastX = x;
mLastY = y;
break;
}
return true;
}
第二种滑动实现方法与上述方法步骤相似,主要区别在于移动View的方式。第二种方法使用View.offsetLeftAndRight()和View.offsetTopAndBottom()代替layout()方法,代码更为简洁。
视图移动最难以理解的部分在于坐标系的转换。可以这样形象理解:
手机屏幕是一个透明盖板
offsetLeftAndRight(offsetX); offsetTopAndBottom(offsetY);
屏幕下方是一个无限大的画布
只有与屏幕重合的画布部分才可见
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams(); layoutParams.leftMargin = getLeft() + offsetX; layoutParams.topMargin = getTop() + offsetY; setLayoutParams(layoutParams);
调用移动方法实际上是在移动这个透明盖板
视图坐标变化相当于改变盖板与画布的相对位置
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams(); marginLayoutParams.leftMargin = getLeft() + offsetX; marginLayoutParams.topMargin = getTop() + offsetY; setLayoutParams(marginLayoutParams);
在基本滑动基础上增加自动回弹功能:
1. 保留手指移动时的滑动逻辑
2. 在手指抬起(ACTION_UP)时初始化Scroller对象
3. 调用startScroll()方法设置回弹动画
4. 在computeScroll()中实现平滑移动逻辑
使用Scroller时需注意:
startScroll()参数需要起始坐标
通过getScrollX()和getScrollY()获取子View在父容器中的偏移量
computeScroll()不会自动调用,需要通过postInvalidate()触发重绘
在computeScroll()中不断获取当前滚动位置并更新View
((View)getParent()).scrollBy(-offsetX, -offsetY);
直到Scroller滚动完成才停止更新

工作时间:8:00-18:00
电子邮件
扫码二维码
获取最新动态
