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 = f / (float) mDuration;
// This controls the viscous fluid effect (how much of it)
mViscousFluidScale = f;
// must be set to 1.0 (used in viscousFluid())
mViscousFluidNormalize = f;
mViscousFluidNormalize = f / viscousFluid(f);
}
然後是其 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 / f;
float distance = (mVelocity * timePassedSeconds)
- (mDeceleration * timePassedSeconds * timePassedSeconds / f);
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 ;
}
// 處理水準滑動
@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, , );
ViewCompat.postInvalidateOnAnimation(DragViewGroup.this);
}
};
這樣,我們就完成了想要的滑動了。之後,會分析一下它的源碼,看看到底如何實作的。