解析6種常用View 的滑動方法
> View 的滑動是Android 實現自定義控件的基礎,實現View 滑動有很多種方法,在這裏主要講解6 種滑動方法,分別是layout()、offsetLeftAndRight()與offsetTopAndBottom()、LayoutParams、動畫、scollTo 與scollBy,以及Scroller。
View 的滑動是Android 實現自定義控件的基礎,同時在開發中我們也難免會遇到View 的滑動處理。其實不管是哪種滑動方式,其基本思想都是類似的:當點擊事件傳到View 時,係統記下觸摸點的坐標,手指移動時係統記下移動後觸摸的坐標並算出偏移量,並通過偏移量來修改View 的坐標。實現View 滑動有很多種方法,在這裏主要講解6 種滑動方法,分別是layout()、offsetLeftAndRight()與offsetTopAndBottom()、LayoutParams、動畫、scollTo 與scollBy,以及Scroller。
1 layout()方法
View 進行繪製的時候會調用onLayout()方法來設置顯示的位置,因此我們同樣也可以通過修改View 的left、top、right、bottom 這4 種屬性來控製View 的坐標。首先我們要自定義一個View,在onTouchEvent()方法中獲取觸摸點的坐標,代碼如下所示:
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;
接下來我們在ACTION_MOVE 事件中計算偏移量,再調用layout()方法重新放置這個自定義View 的位置即可。
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//調用layout 方法來重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY);
break;
在每次移動時都會調用layout()方法對屏幕重新布局,從而達到移動View 的效果。自定義View,CustomView 的全部代碼如下所示:
public class CustomView extends View {
private int lastX;
private int lastY;
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context) {
super(context);
}
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;
//調用layout 方法來重新放置它的位置
layout(getLeft()+offsetX, getTop()+offsetY,getRight()+offsetX , getBottom()+offsetY);
break;
}
return true;
}
}
隨後,我們在布局中引用自定義View 就可以了。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:andro
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.liuwangshu.moonviewslide.CustomView
android:
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="50dp"
android:background="@android:color/holo_red_light" />
</LinearLayout>
運行程序,效果如圖1 所示。圖1 中的方塊就是我們自定義的CustomView,它會隨著我們手指的滑動改變自己的位置。
圖1 View 的滑動
2 offsetLeftAndRight()與offsetTopAndBottom()
這兩種方法和layout()方法的效果差不多,其使用方式也差不多。我們將ACTION_MOVE中的代碼替換成如下代碼:
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//對left 和right 進行偏移
offsetLeftAndRight(offsetX);
//對top 和bottom 進行偏移
offsetTopAndBottom(offsetY);
break;
3 LayoutParams(改變布局參數)
LayoutParams 主要保存了一個View 的布局參數,因此我們可以通過LayoutParams 來改變View 的布局參數從而達到改變View 位置的效果。同樣,我們將ACTION_MOVE 中的代碼替換成如下代碼:
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams)getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
因為父控件是LinearLayout,所以我們用了LinearLayout.LayoutParams。如果父控件是RelativeLayout,則要使用RelativeLayout.LayoutParams。除了使用布局的LayoutParams 外,我們還可以用ViewGroup.MarginLayoutParams 來實現:
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)
getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
4 動畫
可以采用View 動畫來移動,在res 目錄新建anim 文件夾並創建translate.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:andro>
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300" />
</set>
接下來在Java 代碼中調用就好了,代碼如下所示:
mCustomView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
運行程序,我們設置的小方塊會向右平移300 像素,然後又會回到原來的位置。為了解決這個問題,我們需要在translate.xml 中加上fillAfter="true",代碼如下所示。運行代碼後會發現,方塊向右平移300 像素後就停留在當前位置了。
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:andro android:fillAfter="true">
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300" />
</set>
需要注意的是,View 動畫並不能改變View 的位置參數。如果對一個Button 進行如上的平移動畫操作,當Button 平移300 像素停留在當前位置時,我們點擊這個Button 並不會觸發點擊事件,但在我們點擊這個Button 的原始位置時卻觸發了點擊事件。對於係統來說這個Button 並沒有改變原有的位置,所以我們點擊其他位置當然不會觸發這個Button 的點擊事件。在Android3.0 時出現的屬性動畫解決了上述問題,因為它不僅可以執行動畫,還能夠改變View 的位置參數。當然,這裏使用屬性動畫移動那就更簡單了,我們讓CustomView 在1000ms 內沿著X 軸向右平移300 像素,代碼如下所示。
ObjectAnimator.ofFloat(mCustomView,"translationX",0,300).setDuration(100
0).start();
5 scrollTo 與scollBy
scrollTo(x,y)表示移動到一個具體的坐標點,而scrollBy(dx,dy)則表示移動的增量為dx、dy。其中,scollBy 最終也是要調用scollTo 的。View.java 的scollBy 和scollTo 的源碼如下所示:
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();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
scollTo、scollBy 移動的是View 的內容,如果在ViewGroup 中使用,則是移動其所有的子View。我們將ACTION_MOVE 中的代碼替換成如下代碼:
((View)getParent()).scrollBy(-offsetX,-offsetY);
這裏若要實現CustomView 隨手指移動的效果,就需要將偏移量設置為負值。為什麼要設置為負值呢?下麵具體講解一下。假設我們正用放大鏡來看報紙,放大鏡用來顯示字的內容。同樣我們可以把放大鏡看作我們的手機屏幕,它們都是負責顯示內容的;而報紙則可以被看作屏幕下的畫布,它們都是用來提供內容的。放大鏡外的內容,也就是報紙的內容不會隨著放大鏡的移動而消失,它一直存在。同樣,我們的手機屏幕看不到的視圖並不代表其不存在,如圖2 所示。
圖2 初始情況
畫布上有3 個控件,即Button、EditText 和SwichButton。隻有Button 在手機屏幕中顯示,它的Android 坐標為(60,60) 。現在我們調用scrollBy(50,50),按照字麵的意思,這個Button 應該會在屏幕右下側,可是事實並非如此。如果我們調用scrollBy(50,50),裏麵的參數都是正值,我們的手機屏幕向X 軸正方向,也就是向右邊平移50,然後手機屏幕向Y 軸正方向,也就是向下方平移50,平移後的效果如圖3所示。
圖3 調用scrollBy(50,50)後
雖然我們設置的數值是正數並且在X 軸和Y 軸的正方向移動,但Button 卻向相反方向移動了,這是參考對象不同導致的差異。所以我們用scrollBy 方法的時候要設置負數才會達到自己想要的效果。
6 Scroller
我們在用scollTo/scollBy 方法進行滑動時,這個過程是瞬間完成的,所以用戶體驗不大好。這裏我們可以使用Scroller 來實現有過渡效果的滑動,這個過程不是瞬間完成的,而是在一定的時間間隔內完成的。Scroller 本身是不能實現View 的滑動的,它需要與View 的computeScroll()
方法配合才能實現彈性滑動的效果。在這裏我們實現CustomView 平滑地向右移動。首先我們要初始化Scroller,代碼如下所示:
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
接下來重寫computeScroll()方法,係統會在繪製View 的時候在draw()方法中調用該方法。在這個方法中,我們調用父類的scrollTo()方法並通過Scroller 來不斷獲取當前的滾動值,每滑動一小段距離我們就調用invalidate()方法不斷地進行重繪,重繪就會調用computeScroll()方法,這樣我們通過不斷地移動一個小的距離並連貫起來就實現了平滑移動的效果。
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View) getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
invalidate();
}
}
我們在CustomView 中寫一個smoothScrollTo 方法,調用Scroller 的startScroll()方法,在2000ms 內沿X 軸平移delta 像素,代碼如下所示:
public void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int delta=destX-scrollX;
mScroller.startScroll(scrollX,0,delta,0,2000);
invalidate();
}
最後我們在ViewSlideActivity.java 中調用CustomView 的smoothScrollTo()方法。這裏我們設定CustomView 沿著X 軸向右平移400 像素。
mCustomView.smoothScrollTo(-400,0);
本文選自《Android進階之光》,點此鏈接可在博文視點官網查看此書。
想及時獲得更多精彩文章,可在微信中搜索“博文視點”或者掃描下方二維碼並關注。
最後更新:2017-08-13 22:42:20