Android View的三種移動方式
Android開發中,View一直是Android開發人員的一塊心病,一方面想要進階,一方面又害怕進階,可以說Android的View是進階路上的最大絆腳石,因為它涉及的東西太多了,比如本次我們此次要寫的View移動,另外還包括View的觸摸事件的傳遞,創建自定義View,這些都是極其重要且不得不面對的難題。但是無論如何,現在不克服的困難將來就會被困難克服。
Let's begin!
在此之前,我們還是先了解Android坐標系的定義規則以及View的一些位置參數。
Android坐標系
- View的位置及大小是由四個參數決定,即left、top、right、bottom,并且這四個參數都是相對于其父View的。
int width = right-left;
int height = bottom-top;
在Activity中布局完成后,我們可以通過View一些方法獲取這些參數信息:
//left,top,right,bottom值的獲取
int left = getLeft();
int top = getTop();
int right = getRight();
int bottom = getBottom();
- 另外Android 3.0以后加入x,y,translationX,translationY等參數。(x,y)表示為View在ViewGroup中左上角的x,y的值,translationX,translationY在用于平移一個View。默認是都為0,在調用了View的setTranslationX()/setTranslationY()之后發生改變。
//x,y,translationX,translationY參數的獲取
int x = getX();
int y = getY();
int translationX = getTranslationX();
int translationY = getTranslationY();
PS:調用View的setTranslationX()和setTranslationY()方法雖然可以使得View平移指定距離,但是這一過程是瞬間完成的。為了使View的移動使得更為平滑,因此可以使用View的屬性動畫來指定translationX和translationY。
ObjectAnimator valueAnimator = ObjectAnimator.ofFloat(textView, "translationX", 200);
valueAnimator.setDuration(2000);
valueAnimator.start();
另外,如果給View設置setTranslationX()和setTranslationY()后,如果設置的值沒有發生變化,那么其只會移動一次,即首次指定的移動距離。查看源碼后我們發現原因:原來在設置值之后其會將設置進去的值和當前的translationX,translationY進行對比,不一致時才進行移動。
Paste_Image.png
了解了View的一些基本參數之后,我們看關于View的三種移動方式。
一、使用Android系統提供的scrollTo()/scrollBy()方法實現View的移動。
-
不管是scrollTo()還是scrollBy()其移動的本質都是View/ViewGroup中的內容。并且其移動的過程是瞬間完成的,因此,為了實現更好的移動效果,他需要與Scroller類結合使用。另外,它不同于上面的Translation,移動的是View本身,這一點需要好好理解一下。
-
scrollTo()和scrollBy()都是View中的方法, 不是Scroller中的方法 ,但是控制View的平滑移動與Scroller類密不可分。
scrollTo() :指是的移動的絕對位置,如果位置沒有變化,多次調用則不會起作用。
scrollTo移動過程示意圖
scrollBy() :其本質依然是調用的scrollTo(),指的的移動當前位置的相對距離(每次都是先將當前的位置和設置的距離相加之和調用scrollTo(),這樣如果你多次調用,你就會發現其每次都會移動一段距離,這是和scrollTo()的本質區別)
scrollBy移動過程示意圖
PS:關于上面兩張圖,其實一直以來,我自己都沒完全搞明白什么相對絕對,所以兩張手圖可能會讓人更容易理解。還有就是scrollTo()和scrollBy()移動方向問題,上面我們已經畫過Android的坐標系,x軸左→右為正,y軸從上→下為正。但是這并不適用于scrollTo和scrollBy,scrollTo和scrollBy剛好相反,即x軸左→右為負,y軸從上→下為負,簡直是有點坑爹啊。O__O "…
- Scroller類分析:而為什么使用Scroller類中的方法可以對View/ViewGroup的內容進行移動呢?下面我們試著分析一下。
首先
我們創建一個Scroller類的對象mScroller。
然后
要使View在規定的時間中移動到指定的位置,我們會調用startScroll()方法,startScroll()是Scroller類中的方法,另外Scroller類中還有一個filing()方法也是很常用的,它主要是處理平滑的移動,一般營造滑動之后的慣性效果,使得View的移動更逼真。下面我們看startScroll()的源碼:
//其接收四個/五個參數。如果duration不設置,則為默認。這四個參數都不難理解,這里不再做解釋。
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
...
}
而一般我們調用這個方法后都要去調View的 invalidate(),這個方法可以觸發View的draw()方法。而draw()中調用了 computeScroll(),源碼中我們發現computeScroll()是個空方法,這也是為什么我們需要重寫 computeScroll()方法的原因。因為正在的移動操作就是在computeScroll()中進行的。
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必須調用View的postInvalidate()/invalidate(),如果不加會導致View的移動只會第一幀。
postInvalidate();
}
super.computeScroll();
}
上面我們看到Scroller類中還有一個computeScrollOffset()方法,它又是干啥的呢?它的主要作用就是判斷mCurrX,和mCurrY是否有改變,有則返回true,無則返回false。通過這個方法的判斷可以指點是否需要持續的調用scrollTo()去移動View。這里再給出一個示例,使用scrollTo()讓View跟著手指移動:
public class CuView extends LinearLayout {
private float mStartX;
private float mStartY;
private Scroller mScroller;
/**
* 第一次滑動是否完成
*/
private boolean isFirstFinish;
public CuView(Context context) {
super(context);
init(context);
}
public CuView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
mScroller = new Scroller(context);
}
public CuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CuView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
/**
* 讓View跟著你的手指走吧
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
/**
* 第一次移動完成后,我們不需要再去拿開始的位置了,否則造成View重新移動的最起始的位置。
*/
if (!isFirstFinish) {
mStartX = event.getRawX();
mStartY = event.getRawY();
}
break;
case MotionEvent.ACTION_MOVE:
scrollTo((int) (mStartX - event.getRawX()), (int) (mStartY - event.getRawY()));
break;
case MotionEvent.ACTION_UP:
//第一次移動完成
isFirstFinish = true;
break;
}
return true;
}
/**
* 測試startScroll
*/
public void startScroll() {
/**
* 注意Scroller移動方向,
*/
mScroller.startScroll(20, 20, -500, -500, 5000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
super.computeScroll();
}
}
二、使用動畫實現View的移動。
- 這里包括View的Tween Animation/Frame Animation,以及3.0之后加入的Property Animation。其移動的是View的一個映像,View本身的位置及大小并沒有發生任何改變。
三、設置View的LayoutParams來移動View
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) textView.getLayoutParams();
layoutParams.leftMargin = 50;
textView.requestLayout();
來自:http://www.jianshu.com/p/4d69caebf718