在讲解滑动之前,要先熟悉一下安卓的坐标系。安卓视图有两个坐标系,一个是Android坐标系,一个是视图坐标系。前者以屏幕的最左上角为原点,向右为X轴正方向,向下为Y轴正方向。后者以父视图的左上角为原点,其它与前者一致。
而获取坐标的方法也可以分为两类,View提供的获得坐标的方法和MotionEvent提高的方法。View提供的方法有getTop(),getLeft(),getBottom(),getRight(),而MotionEvent提供的方法有getX(),getY(),getRawX(),getRawY()。
如上图所示,getLeft()方法为View自身左边到父布局左边的距离,getTop()方法为View自身的顶边到父布局顶边的距离,getRight()为View自身的右边到父布局左边的距离,getBottom()为View自身底边到父布局顶边的距离。
而getX()为为触摸点到View左边的距离,getY()为触摸点到View顶边的距离。getRawX()为触摸事件到屏幕左边的距离,getRawY()为触摸事件到屏幕顶边的距离。
下面进入正题:
方法一:layout方法
注意:layout方法的参数是(getLeft()+offsetX,getTop()+offsetY.getRight()+offsetX,getBottom()+offsetY),offsetX为水平方向偏移量,offsetY为竖直方向偏移量
关于偏移量的计算可以采用getX()和getRawX()两种方法
首先看使用getX()的方法
import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class DragView1 extends View { private int lastX; private int lastY; public DragView1(Context context) { super(context); ininView(); } public DragView1(Context context, AttributeSet attrs) { super(context, attrs); ininView(); } public DragView1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ininView(); } private void ininView() { // 给View设置背景颜色,便于观察 setBackgroundColor(Color.BLUE); } // 视图坐标方式 @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录触摸点坐标 lastX = x; lastY = y; break; case MotionEvent.ACTION_MOVE: // 计算偏移量 int offsetX = x - lastX; int offsetY = y - lastY; // 在当前left、top、right、bottom的基础上加上偏移量 layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); // offsetLeftAndRight(offsetX); // offsetTopAndBottom(offsetY); break; } return true; } }
如果将上面代码
int x = (int) event.getX();
int y = (int) event.getY();
直接改成getRawX()和getRawY()的话,就会发现图形移动的幅度远大于触摸点移动的幅度(以我的经验,图形很容易就飞出屏幕),而解决这个问题的方法如下。
@Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) (event.getRawX()); int rawY = (int) (event.getRawY()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录触摸点坐标 lastX = rawX; lastY = rawY; break; case MotionEvent.ACTION_MOVE: // 计算偏移量 int offsetX = rawX - lastX; int offsetY = rawY - lastY; // 在当前left、top、right、bottom的基础上加上偏移量 layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); // 重新设置初始坐标 lastX = rawX; lastY = rawY; break; } return true; }
以下是我在调试过程中获得的数据,首先是使用getX()方法时获得的数据(排版有些问题,大家见谅)
08-16 02:01:15.493 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y148offsetX0offsetY2
08-16 02:01:15.551 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y146offsetX4offsetY0
08-16 02:01:15.690 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X220Y149offsetX3offsetY3
08-16 02:01:15.740 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y146offsetX4offsetY0
08-16 02:01:15.778 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y150offsetX4offsetY4
08-16 02:01:15.823 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X226Y153offsetX9offsetY7
08-16 02:01:15.861 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X215Y151offsetX-2offsetY5
08-16 02:01:15.910 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y147offsetX4offsetY1
08-16 02:01:15.924 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X219Y152offsetX2offsetY6
08-16 02:01:15.962 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X218Y149offsetX1offsetY3
08-16 02:01:15.975 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y154offsetX4offsetY8
08-16 02:01:16.041 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X224Y156offsetX7offsetY10
08-16 02:01:16.079 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y155offsetX0offsetY9
08-16 02:01:16.111 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y154offsetX4offsetY8
08-16 02:01:16.124 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X222Y145offsetX5offsetY-1
08-16 02:01:16.177 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X216Y151offsetX-1offsetY5
08-16 02:01:16.242 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y148offsetX0offsetY2
08-16 02:01:16.273 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y152offsetX4offsetY6
08-16 02:01:16.311 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X220Y151offsetX3offsetY5
08-16 02:01:16.346 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y151offsetX0offsetY5
08-16 02:01:16.400 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y150offsetX0offsetY4
08-16 02:01:16.472 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X227Y152offsetX10offsetY6
08-16 02:01:16.507 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y150offsetX0offsetY4
08-16 02:01:16.522 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X218Y157offsetX1offsetY11
08-16 02:01:16.544 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y154offsetX0offsetY8
08-16 02:01:16.578 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y150offsetX4offsetY4
08-16 02:01:16.608 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X222Y145offsetX5offsetY-1
08-16 02:01:16.627 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X216Y151offsetX-1offsetY5
08-16 02:01:16.667 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X217Y150offsetX0offsetY4
08-16 02:01:16.700 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X222Y152offsetX5offsetY6
08-16 02:01:16.750 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X223Y154offsetX6offsetY8
08-16 02:01:16.793 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X221Y154offsetX4offsetY8
08-16 02:01:16.822 10322-10322/com.example.tai.dragviewtest D/DEBUG: lastX217lastY146X222Y145offsetX5offsetY-1
可以很清楚地看到,由于在触摸点移动时相应的控件也跟着移动,所以效果类似于自动充值,X,Y偏移量都处于一个正常的范围内。
下面看一下getRawX()和getRawY()的情况
08-16 01:28:58.550 10322-10322/com.example.tai.dragviewtest D/Debug/t: LastX250LastY237X269Y278
08-16 01:28:58.562 10322-10322/com.example.tai.dragviewtest D/Debug/t: LastX250LastY237X284Y307
08-16 01:28:58.611 10322-10322/com.example.tai.dragviewtest D/Debug/t: LastX250 LastY237 X412 Y48508-16 01:28:58.924 10322-10322/com.example.tai.dragviewtest D/Debug/t: LastX250LastY237X629Y781
虽然没有打印offsetX和offsetY的值,但可以清楚的看到,由于没有坐标重置,导致offsetX和offsetY的实际值远大于本身的偏移量,因此导致控件唯一幅度远大于触摸点位移的幅度。至于解决方法就是上面所说的。方法二 offsetLeftAndRight()和offsetTopAndBottom
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
直接用以上代码来替代layout方法,原理是一样的。
方法三 LayoutParams
我觉得原理是和以上两种一样的,只不过换一下写法,下面是代码。
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DragView3 extends View{
int lastX;
int lastY;
public DragView3(Context context)
{
super(context);
initView();
}
public DragView3(Context context,AttributeSet attributeSet)
{
super(context,attributeSet);
initView();
}
public DragView3(Context context,AttributeSet attributeSet,int defStyleAttr)
{
super(context,attributeSet,defStyleAttr);
initView();
}
private void initView()
{
setBackgroundColor(Color.BLUE);
}
@Override
public boolean onTouchEvent(MotionEvent event){
int x=(int)event.getX();
int y=(int)event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
lastX=x;
lastY=y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX=x-lastX;
int offsetY=y-lastY;
LinearLayout.LayoutParams layoutParams=(LinearLayout.LayoutParams)getLayoutParams();
layoutParams.topMargin=getTop()+offsetY;
layoutParams.leftMargin=getLeft()+offsetX;
setLayoutParams(layoutParams);
break;
}
return true;
}
}
关于
LinearLayout.LayoutParams layoutParams=(LinearLayout.LayoutParams)getLayoutParams();
你的父布局是什么,这个“LinearLayout”就写什么(前提是有父布局)
此外还可以这么写
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
效果是一样的。
方法四 scrollTo 和scrollBy
这两个方法的区别很明显,一个是To,是到某位置;一个是By,是在原有基础上加上一个偏移量,先说后者。
原理依旧是计算楚偏移量,具体的改变如下。
((View) getParent()).scrollBy(-offsetX, -offsetY);
只有这么一点改变,至于为什么是负的,这就涉及视图移动的原理了,简单地说一下。
可以这么想象,所有的视图都画在一张画布上,而上面盖了一块木板,木板上有屏幕那么大的一个空缺,我们看到的实际上是透过木板看到的画布上的景象,你看不到,不代表画布上没画。而scroll两个方法其实都是在移动木板而不是画布,因此方向是相反的(可以自己比划一下)。
然后是scrollTo方法,要注意的是该方法需要用getRawX()和getRawY()获得坐标,另外别忘了重置坐标,否则会出现之前的问题。
((View)getParent()).scrollTo(-x,-y);
使用这个方法的重点在于重写computeScroll()方法(系统在绘制View时会在onDraw()方法中调用此方法)
public class DragView5 extends View {
int lastX;
int lastY;
Scroller mscroller;
public DragView5(Context context)
{
super(context);
initView(context);
}
public DragView5(Context context, AttributeSet attributeSet)
{
super(context,attributeSet);
initView(context);
}
public DragView5(Context context,AttributeSet attributeSet,int defStyleAttr)
{
super(context,attributeSet,defStyleAttr);
initView(context);
}
private void initView(Context context)
{
setBackgroundColor(Color.BLUE);
mscroller=new Scroller(context);
}
@Override
public void computeScroll(){
super.computeScroll();
if(mscroller.computeScrollOffset())
{
((View)getParent()).scrollTo(mscroller.getCurrX(),mscroller.getCurrY());
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
int x=(int )event.getX();
int y=(int)event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
lastX=x;
lastY=y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX=x-lastX;
int offsetY=y-lastY;
((View)getParent()).scrollBy(-offsetX,-offsetY);
break;
case MotionEvent.ACTION_UP:
View viewGroup=(View)getParent();
mscroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());
invalidate();
break;
}
return true;
}
}
方法七 ViewDragHelper
这个方法我并不熟悉,能看懂,但自己用就很吃力了,也就不多说了,上代码(这个的代码实现了类似于QQ侧边栏效果)
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
public class DragViewGroup extends FrameLayout {
private ViewDragHelper mViewDragHelper;
private View mMenuView, mMainView;
private int mWidth;
public DragViewGroup(Context context) {
super(context);
initView();
}
public DragViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public DragViewGroup(Context context,
AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将触摸事件传递给ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}
private void initView() {
mViewDragHelper = ViewDragHelper.create(this, callback);
}
private ViewDragHelper.Callback callback =
new ViewDragHelper.Callback() {
// 何时开始检测触摸事件
@Override
public boolean tryCaptureView(View child, int pointerId) {
//如果当前触摸的child是mMainView时开始检测
return mMainView == child;
}
// 触摸到View后回调
@Override
public void onViewCaptured(View capturedChild,
int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
// 当拖拽状态改变,比如idle,dragging
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
// 当位置改变的时候调用,常用与滑动时更改scale等
@Override
public void onViewPositionChanged(View changedView,
int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
// 处理垂直滑动
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
// 处理水平滑动
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
// 拖动结束后调用
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
//手指抬起后缓慢移动到指定位置
if (mMainView.getLeft() < 500) {
//关闭菜单
//相当于Scroller的startScroll方法
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
} else {
//打开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
}
};
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
这是我第一次写博客,这篇博客是我在学习《Android群英录》的时候写的总结,本来算是一个私人的笔记,但我觉得可能对同时初学者的其他人有用,所以最后还是发了出来。
这些代码基本上都是我从《Android群英录》个github上弄下来的,自己看明白后把原来的注释掉自己写一遍,然后碰到了一些问题和解决办法记了下来。
这篇博客除了代码都是纯手打,我个人觉得勉强算是原创,如果我弄错了,请联系我,我会及时删除。