View 滑动实现
复习一下view滑动的几种实现方式
1.通过layout实现
通过不断重新layout view 达到滑动的效果。
@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;
}
这里需要注意,我们使用的getX 和getY 方法,如果使用 getRawX 和getRawY 方法,那么代码就要做些许改变。就是在重新layout后,我们需要重新赋值 lastX和lastY的值。 否则就会出现意想不到的结果哦。 有兴趣的朋友可以试一试,可以加深对于这两个方法的认识。
–
2.通过改变LayoutParams
layoutparams 保存了view的布局参数,我们可以通过不断修改其参数,重新设置,达到滑动效果。需要修改的参数就是自己与父view的边距。注意通过getLayotParams 方法获取layoutparams时类型要根据其父view的类型来确定。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点坐标
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
// 转换成这个类型,就不用关心父view的类型了
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
//LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
–
3.通过view自己的scrollTo和scrollBy 来实现
srollTo(x,y) 表示移动到位置(x,y)处。
srollBy(dx,dy) 表示在当前位置的基础上移动 dx和dy的距离。dx和dy为偏移量。
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
同时需要注意,他们移动的都是view自己的内容。对应 viewgroup移动的就是自己的子view。所以父viewgroup中的素有view都会一起移动哦。
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
//这里要注意,offsetx为我们相对于父viewgroup移动的距离,但是,这里我们调用的是让父viewgroup移动的方法,所以参考系,相对于反转了,所以要在前面加上负号。
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
}
return true;
}
–
4.scroller
与上面的不同,scroller主要是用于当没有用户手动操作的时候,我们用来连续移动 view 的方式。需要配合view 的computeScroll 和scrollTo方法配合使用。该方法会在view 的draw方法中被调用。
@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller是否执行完毕
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 通过重绘来不断调用computeScroll
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 = (int) event.getX();
lastY = (int) event.getY();
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;
}
流程如下:
说的通俗一点就是:scroller 每次提供一个值 mScroller.getCurrX(),然后view scrollTo这个位置,然后view 重新绘制,然后又调用 computeScroll 方法,然后scroler 又提供一个值mScroller.getCurrX() 又重绘。 相当于一个死循环,不停地移动。 循环的出口就是mScroller.computeScrollOffset() 方法。 这个方法有两个作用,一个是返回是否滑动到了指定的位置,二是每次调用时会不断的改变 mScroller.getCurrX()的值,让这个越来越接近滑动结束的值,知道达到滑动结束的值时,该方法的返回值也就变成了false。
看看scroller的部分代码,首先是其构造函数,其实也就是初始化了一些滑动的位置和时间值:
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
// This controls the viscous fluid effect (how much of it)
mViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
mViscousFluidNormalize = 1.0f;
mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
}
然后是其 computeScrollOffset()方法
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = (float)timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
float timePassedSeconds = timePassed / 1000.0f;
float distance = (mVelocity * timePassedSeconds)
- (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
mCurrX = mStartX + Math.round(distance * mCoeffX);
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distance * mCoeffY);
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
5.ViewDragHelper
ViewDragHelper
是v4
包中Google 为我们封装了Scroller
的一个工具类。为我们实现不同的滑动等效果提供的一种方案。
其一般使用在ViewGroup
中,通过其工厂方法创建其实例,通过其实例来处理View
的事件和移动。 相当于我们把View
(子View
)都交给她来处理了。
- 首先当然是先创建其实例对象
mViewDragHelper = ViewDragHelper.create(this, callback);
- 将
ViewGroup
的事件交给mViewDragHelper
处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
- 因为同样是 通过
Scroller
实现滑动的,所以我们也需要重写computeScroll
方法
@Override
public void computeScroll() {
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
这些也都是固定写法了。
- 最为关键的就是 在创建
mViewDragHelper
时我们传入的callback
对象,这个是我们要自己去实现的。通过这个回调,我们可以实现我们自己需要的滑动效果。
private ViewDragHelper.Callback callback =
new ViewDragHelper.Callback() {
//是否处理 该view也就是参数 child。返回 true 处理,否则这忽略噶子view。
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mchild == child;
}
// 触摸到View后回调
@Override
public void onViewCaptured(View capturedChild,
int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
// 当拖拽状态改变时回调 如:STATE_IDLE、STATE_DRAGGING、STATE_SETTLING
@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);
//手指抬起后缓慢移动到指定位置
//相当于Scroller的startScroll方法
mViewDragHelper.smoothSlideViewTo(mchild, 0, 0);
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
};
这样,我们就完成了想要的滑动了。之后,会分析一下它的源码,看看到底如何实现的。