前言java
自定義View做爲Android進階的基礎,是咱們開發者不得不學習的知識,而酷炫的自定義View效果,都離不開View的滑動,因此接下來咱們來一塊兒探究View的滑動方式,看看View是如何滑動的,爲Android進階的道路打下基礎。android
瞭解View的滑動方式,首先咱們得了解View在什麼位置,咱們能夠把手機屏幕區域當作是像數學中座標系同樣的區域,只不過是手機屏幕座標系的Y軸和數學中的座標系的Y軸正方向相反git
肯定View的位置主要是根據View的left、top、right、bottom四個屬性來決定,須要注意的是View的這四個屬性是相對於它的父容器來講的,因此對應爲left是View的左上角相對於父容器的橫座標,top爲縱座標,right爲View右下角相對於父容器的橫座標,bottom爲縱座標。(具體能夠看下方示意圖A)github
//獲取view位置的值
left = View.getLeft();
top = View.getTop();
right = View.getRight();
bottom = View.getBottom();
複製代碼
除了上面肯定View位置的參數,還有x,y,translationX,translationY這四個參數,x和y表明View的左上角的座標值,而translationX,translationY是左上角座標相對於View的父容器的偏移量,默認爲零,也就是view不移動,則x和y等於left和top,他們換算關係可看下面示意圖A,在View的滑動過程當中,left和top表示的是View原始位置的值,這是不會改變的,因此改變的是滑動偏移量加上原始值獲得新的左上角座標。bash
layout方法改變View位置滑動Viewapp
首先咱們看看layout()方法源碼ide
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
.......
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
........
}
}
複製代碼
瞭解過自定義View的各位應該都知道,onLayout()是View繪製過程當中的一個方法,能夠經過它肯定View的位置,也就是說咱們經過layout()方法能夠改變View的位置,下面咱們經過onLayout方法作一個能夠隨意滑動 view的例子佈局
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取觸屏時候的座標
Log.e("毛麒添","getLeft:"+getLeft()+"getTop:"+getTop()+"getRight:"+getRight()+"getBottom:"+getBottom());
x = event.getRawX();
y = event.getRawY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
//手指移動偏移量
int offsetX = (int) (x-lastX);
int offsetY = (int) (y-lastY);
layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
break;
case MotionEvent.ACTION_UP:
Log.e("毛麒添","getLeft:"+getLeft()+"getTop:"+getTop()+"getRight:"+getRight()+"getBottom:"+getBottom());
break;
}
lastX=x;
lastY=y;
return super.onTouchEvent(event);
}
複製代碼
offsetLeftAndRight()與offsetTopAndBottom() 方法改變View的位置讓其滑動post
case MotionEvent.ACTION_MOVE:
//手指移動偏移量
int offsetX = (int) (x-lastX);
int offsetY = (int) (y-lastY);
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
break;
複製代碼
使用scrollTo()和scrollBy()滑動View學習
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
複製代碼
經過源碼咱們能夠看到scrollBy()的實現實際上是調用了scrollTo()方法。這裏有個mScrollX和mScrollY的規則咱們須要明白:scrollTo()中mScrollX的值等於view左邊緣和view內容左邊緣在水平方向的距離,而且當view的左邊緣在view的內容左邊緣右邊時,mScrollX爲正,反之爲負;同理mScrollY等於view上邊緣和view內容上邊緣在豎直方向的距離,而且當view的上邊緣在view的內容上邊緣下邊時,mScrollY爲正,反之爲負。當View沒有使用scrollTo()和scrollBy()進行滑動的時候,mScrollX和mScrollY默認等於零,也就是view的左邊緣與內容左邊緣重合。
根據上面的規則,咱們假設將view內容右下滑動,獲得下圖
結合上面的知識,咱們將上面滑動的例子改寫一下,若是使用scrollTo()則只是滑動到咱們手指滑動偏移量的距離的點,達不到要求,而scrollBy()是在scrollTo()的基礎上偏移滑動的位置,正好符合咱們自由滑動的要求,而且根據上面的分析mScrollX和mScrollY爲負值,則滑動偏移也應該爲負值才能達到咱們想要的自由滑動效果(這個你們須要本身好好想明白可能纔會更加清楚理解)
case MotionEvent.ACTION_MOVE:
//手指移動偏移量
int offsetX = (int) (x-lastX);
int offsetY = (int) (y-lastY);
//滑動方式1
((View)getParent()).scrollBy(-offsetX,-offsetY);
break;
複製代碼
根據滑動打印的日誌咱們能夠看出,scrollBy()和scrollTo()在滑動的過程當中只是改變了View內容的位置,而沒有改變初始的left,right,top,bottom的值
使用動畫讓View滑動
xml補間動畫的方式讓View滑動
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:duration="500"
>
<translate android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="500"
android:toYDelta="500"
/>
</set>
複製代碼
startAnimation(AnimationUtils.loadAnimation(mContext, R.anim.testscroll));
複製代碼
屬性動畫讓View滑動
ObjectAnimator.ofFloat(testScroll,"translationX",0,300).setDuration(2000).start();
複製代碼
改變佈局參數 LayoutParams 滑動View
......
case MotionEvent.ACTION_MOVE:
//手指移動偏移量
int offsetX = (int) (x-lastX);
int offsetY = (int) (y-lastY);
//滑動方式5
moveView(offsetX,offsetY);
break;
......
private void moveView(int offsetX, int offsetY) {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
}
複製代碼
Scroller彈性滑動
首先咱們要明白什麼是Scroller?
Scroller mScroller=new Scroller(context);
public void smoothScrollTo(int desx,int desy){
int scaleX = (int) getScaleX();
int scaleY = (int) getScaleY();
int deltaX = desx-scaleX;
int deltaY = desy-scaleY;
//3秒內彈性滑到desx desy 位置
mScroller.startScroll(scaleX,scaleY,deltaX,deltaY,3000);
//從新繪製界面 會調用computeScroll方法
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){//還沒滑動到指定位置
((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
//拿到自定View的實例對象調用smoothScrollTo實現右下方向3秒
//內到指定位置的彈性滑動
//爲何是-300 請看scrollTo()或scrollBy()滑動解析
testScroll.smoothScrollTo(-300,-300);
複製代碼
下面咱們從源碼角度來分析一下Scroller是如何實現彈性滑動的
/**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
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;
}
//View 中 computeScroll()方法沒有實現內容,須要子View 自行實現
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
複製代碼
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
.......
}
}
else {
.......
}
return true;
}
複製代碼
到此,View的滑動方式就已經瞭解完了。若是文章中有寫得不對的地方,請給我留言指出,你們一塊兒學習進步。若是以爲個人文章給予你幫助,也請給我一個喜歡和關注。
參考連接
參考書籍