一、layou方法
代码示例如下,自定义一个view,在onTouchEvent()方法中计算手指滑动时的偏移量,调用view的layout()方法,在当前left、top、right、bottom上加上偏移量,实现view的滑动。
public class TestView extends View {
private static final String TAG = "TestView";
public TestView(Context context) {
super(context);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
float startX;
float startY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录初始触摸点坐标
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//手指滑动过程中经过的点坐标
float lastX = event.getRawX();
float lastY = event.getRawY();
//计算偏移量
int offsetX = (int) (lastX - startX);
int offsetY = (int) (lastY - startY);
//在当前的left、top、right、bottom上加上偏移量
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
Log.e(TAG, "onTouchEvent: layout: " + offsetX + "/" + offsetY);
//重新设置初始坐标 ,这一步很重要
startX = lastX;
startY = lastY;
break;
}
return true;
}
}
二、offsetLeftAndRight()与offsetTopAndBottom()方法
这两个方法相当于系统提供的一个对左右、上下移动的API的封装。使用方法同样先在onTouchEvent()方法中计算出手指滑动的偏移量,然后将偏移量作为方法参数传入。关键代码如下:
float startX;
float startY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录初始触摸点坐标
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//手指滑动过程中经过的点坐标
float lastX = event.getRawX();
float lastY = event.getRawY();
//计算偏移量
int offsetX = (int) (lastX - startX);
int offsetY = (int) (lastY - startY);
//调用方法,参数为计算出的偏移量
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
Log.e(TAG, "onTouchEvent: offsetLeftAndRight_offsetTopAndBottom: " + offsetX + "/" + offsetY);
//重新设置初始坐标 ,这一步很重要
startX = lastX;
startY = lastY;
break;
}
return true;
}
三、修改view的LayoutParams
LayoutParams保存着一个View的布局参数。可以通过改变LayoutParams来动态修改一个View的位置参数,以此达到改变View位置的效果。关键代码如下:
float startX;
float startY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录初始触摸点坐标
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//手指滑动过程中经过的点坐标
float lastX = event.getRawX();
float lastY = event.getRawY();
//计算偏移量
int offsetX = (int) (lastX - startX);
int offsetY = (int) (lastY - startY);
//修改LayoutParams参数,这里要改变位置主要是修改LayoutParams的margin属性,因此这里直接使用
// ViewGroup.MarginLayoutParams以求方便,不必考虑view的父布局类型。在一般应用中,如若有需要
// 修改LayoutParams的其他属性,要注意getLayoutParams()必须获取的是父布局类型的LayoutParams。
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
Log.e(TAG, "onTouchEvent: layoutParams: " + offsetX + "/" + offsetY);
//重新设置初始坐标,这一步很重要
startX = lastX;
startY = lastY;
break;
}
return true;
}
四、scrollTo与scrollBy
1.scrollTo(int x,int y):移动到一个坐标点
2.scrollBy(int x,int y):按照x轴增量x,y轴增量y的方式移动
这两个方法实质上移动的是调用方法的对象所包含的内容。即:如果对一个ViewGroup(如LinearLayout)调用这两个方法,会发现其内部所有的子view会一起发生移动;而如果对一个view(如TextView)调用,则会发现看不到移动的效果。要对view使用这两个方法,实际上是需要对该view 的parent使用。代码示例如下:
1.在ViewGroup中,可以直接调用
float startX;
float startY;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float lastX = event.getRawX();
float lastY = event.getRawY();
int offsetX = (int) (lastX - startX);
int offsetY = (int) (lastY - startY);
//注意这里的参数要变成相反数。由于作用对象是parent,所以实际发生
//移动的应该是parent,但我们的目标是子view。因此,以相对视图关系
//来看,要让子view产生所期望的移动效果,parent应该要向反方向移动。
scrollBy(-offsetX, -offsetY);
startX = lastX;
startY = lastY;
break;
}
return true;
}
2.在view中,需要由view的parent来调用
((View) getParent()).scrollBy(-offsetX, -offsetY);
五、使用Scroller实现带过度动画的滑动
使用前面几种方法实现view位置的改变,如果不是通过手指触摸事件,而是通过如按钮点击事件调用,view的位置变化是瞬间完成的。如果要让位置变化有一个过渡动画的过程,可以使用Scroller来实现。Scroller的使用可以分为3步骤:
1.初始化Scroller: Scroller scroller=new Scroller(context);
2.重写computeScroll()方法,实现模拟滑动
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY());
invalidate();
}
}
3.startScroll()方法开启模拟过程。可以在自定义view内部合适的位置调用下列方法,也可以在自定义view内部声明一个调用该方法的方法,然后在外部Activity等需要调用的位置调用。
scroller.startScroll(startX, startY, dx, dy, duration);
代码示例:
自定义View
public class TestView extends View {
private static final String TAG = "TestView";
private Scroller mScroller;
public TestView(Context context) {
super(context);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
public void scrollAnimation(int startX, int startY, int dx, int dy, int duration) {
mScroller.startScroll(startX, startY, dx, dy, duration);
invalidate();
}
}
在Activity中调用:
@Override
public void onClick(View v) {
mTestView.scrollAnimation(0,0,-300,-400,2000);
}