目的
這篇文章主要對 View 的滑動進行學習倔既,主要包括滑動的實現(xiàn)方式和彈性滑動提高用戶體驗
(一)View 的滑動
滑動的方式基本思想都是類似的:
當觸摸事件傳到View時,系統(tǒng)記下觸摸點的坐標威恼,手指移動時系統(tǒng)記下移動后的觸摸的坐標并算出偏移量摧茴,并通過偏移量來修改View的坐標印机。
View 實現(xiàn)滑動的幾種方式:
- 第一種,layout()方法
- 第二種瓶殃,offsetLeftAndRight() 與 offsetTopAndBottom()
- 第三種,通過改變 View 的 LayoutParams 使得 View 重新布局從而實現(xiàn)滑動
- 第四種副签,通過動畫給 View 施加平移效果來實現(xiàn)滑動
- 第五種遥椿,通過 View 本身提供的 scrollTo/scrollBy 方法來實現(xiàn)滑動
1. layout() 方法
View 進行繪制的時候會調(diào)用 onLayout() 方法來設(shè)置顯示的位置基矮,因此我們同樣也可以通過修改 View 的屬性來控制 View 的坐標
public class RedView extends View {
int lastX, lastY;
public RedView(Context context) {
super(context);
}
public RedView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@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;
//調(diào)用layout方法重新放置它的位置
layout(offsetX + getLeft(), offsetY + getTop(), offsetX + getRight(), offsetY + getBottom());
break;
}
return true;
}
}
2. offsetLeftAndRight() 與 offsetTopAndBottom()
將 ACTION_MOVE 中的代碼替換
case MotionEvent.ACTION_MOVE:
//計算移動的距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//對 left 和 right 進行偏移
offsetLeftAndRight(offsetX);
//對 top 和 bottom 進行偏移
offsetTopAndBottom(offsetY);
break;
3. LaytouParams(改變布局參數(shù))
LayoutParams 主要保存了一個 View 的布局參數(shù),因此我們可以通過改變參數(shù)從而達到改變 View 位置的效果
case MotionEvent.ACTION_MOVE:
//計算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
需要注意的是 LinearLayout 是 View 的父容器修壕,如果父容器是 RelativeLayout愈捅,則要使用 RelativeLayout.LayoutParams
當然我們可以使用 ViewGroup.MarginLayoutParams 來實現(xiàn),一勞永逸
case MotionEvent.ACTION_MOVE:
//計算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
4. 動畫
可以采用 View 動畫來移動慈鸠,在 res 目錄新建 anim 文件夾并創(chuàng)建 translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300" />
</set>
- android:fillAfter="true" View 平移后停留在最后的位置蓝谨,如果為 false 則會返回起始位置
- android:duration="1000" 動畫的時間為 1000 ms
- android:toXDelta="300" 平移的距離,300 像素
mView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));
在 Java 代碼中執(zhí)行動畫青团,這里所用的是補間動畫譬巫,雖然 View 位置發(fā)生改變,但是對系統(tǒng)來說督笆,View 并沒有發(fā)生改變芦昔,點擊事件并不會發(fā)生。Android3.0 之后娃肿,可以通過屬性動畫來解決這個問題
ObjectAnimator.ofFloat(mView, "translationX", 0, 300).setDuration(1000).start();
5. scrollTo 與 scrollBy
scollTo(x, y) 表示移動到一個具體的坐標點咕缎,而 scollBy(dx, dy) 則表示移動的增量為 dx、dy料扰。
兩個方法區(qū)別: scrollBy() 相對移動凭豪,scrollTo() 絕對移動
其中 scollBy 最終也是要調(diào)用 scollTo 的。scollTo晒杈、scollBy 移動的是View的內(nèi)容嫂伞,如果在 ViewGroup 中使用則是移動他所有的子 View。我們將 ACTION_MOVE 中的代碼替換成如下代碼:
((View) getParent()).scrollBy(-offsetX, -offsetY);
這里要實現(xiàn) mView 隨著我們手指移動的效果的話拯钻,我們就需要將偏移量設(shè)置為負值帖努。
在View內(nèi)部有兩個屬性 mScrllX 和 mScrollY,分別可以通過 getScrollX() 和 getScrollY() 方法得到
在滑動過程中粪般,mScrollX 總是等于 View 左邊緣和 View 內(nèi)容左邊緣在水平方方向的距離拼余;mScrollY 總是等于 View 上邊緣和 View 中內(nèi)容上邊緣在豎直方向的距離。View 邊緣是指 View 的位置也就是 View 的四個頂點到父容器的距離亩歹,View 內(nèi)邊緣是內(nèi)容距離 View 四邊的距離姿搜。
無論是 scrollTo() 還是 scrollBy() 都只能改變 View 內(nèi)容的位置而不能改變 View 在布局中的位置
mScrollX/Y 單位為像素 px。當 View 左邊緣在 View 內(nèi)容左邊緣右邊時捆憎,mScrollX 為正值舅柜,反之為負值;同理躲惰,當 View 上邊緣在 View 內(nèi)容上邊緣下邊時致份,mScrollX 為正值,反之為負值础拨。也就是說氮块,View 從左向右滑動绍载,mScrollX 為負值,反之為正值滔蝉;從上往下滑動击儡,mScrollY 為負值,反之為正值
各種滑動方式的對比
- 改變布局參數(shù):操作復雜蝠引,適用于有交互的 View
- 屬性動畫:操作簡單阳谍,適用于沒交互 View 和實現(xiàn)復雜的動畫效果
- scrollTo/By:操作簡單,適合對 View 內(nèi)容的滑動
(二)彈性滑動
在我們進行滑動時螃概,這個過程是瞬間完成的矫夯,所以用戶體驗不大好,這里我們可以添加有過渡的滑動
View 彈性滑動的幾種方式
- 第一種:Scroller
- 第二種:通過動畫
這幾種的方式有一個共同的思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成
1. Scroller
Scroller 本身是不能實現(xiàn) View 的滑動的吊洼,它需要與 View 的 computeScroll() 方法配合才能實現(xiàn)彈性滑動的效果
- 初始化Scroller對象
- 重寫View的computeScroll()方法
- 調(diào)用mScroller.startScroll()方法
public RedView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new ScrollView(context);
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) { //判斷Scroller是否執(zhí)行完畢
((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
//緩慢的滑到指定位置
public void smoothScrollTo(int destX, int destY) {
int deltaX = destX - getScrollX();
int deltaY = destY - getScrollY();
//2000ms 內(nèi)滑到向(deltaY训貌,deltaY)的位置,效果就是慢慢滑動
mScroller.startScroll(0, 0, deltaX, deltaY, 2000);
invalidate();
}
在Activity中調(diào)用:
mView.smoothScrollTo(-400, -400);
Scroller 本身并不能實現(xiàn) View 的滑動冒窍,它需要配合 View 的 computeScroll 方法才能完成彈性滑動的效果递沪,它通過不斷地讓 View 重繪,而每一次重繪距滑動起始時間間隔综液,通過這個時間間隔 Scroller 就可以得出 View 當前的滑動位置款慨,知道了滑動位置就可以通過 scrollTo 方法來完成 View 的滑動。就這樣意乓,View 的每一次重繪都會導致 View 進行小幅度的滑動(彈性滑動的核心思想:將一次大的滑動分成若干次小的滑動并在一個時間段內(nèi)完成)樱调,而多次的小幅度滑動就組成了彈性滑動约素,這就是 Scroller 的工作機制届良。
2. 通過動畫
動畫本身就是一種漸進的過程,因此通過它來實現(xiàn)的滑動天然就是具有彈性效果:
ObjectAnimator.ofFloat(mView, "translationX", 0, 100).setDuration(1000).start();
總結(jié)
滑動做為 View 最基本的行為圣猎,一切的華麗的士葫、絢麗的 UI,歸根結(jié)底都是建立在其基礎(chǔ)上
最后
學習資料主要來源于《Android開發(fā)藝術(shù)探索》和《Android進階之光》送悔,對進階學習的一個記錄與總結(jié)慢显,如果有幸能對大家有所幫助,那將榮幸之至欠啤。