(1)Android view的直角座標系
需要注意的是,view的直角座標系和數學的直角座標系不同,view的x軸方向和數學的直接座標系一致,但是y軸方向卻相反。
(2)基本方法
在介紹Scroller
之前,我們先來看一下view移動的基礎方法。
-
getScrollX()
和getScrollY()
獲取x軸方向和y軸方向偏移量
,如view直角座標系所示,view的左上角在原點
處,也就是說,初始狀態下 getScrollX()
和 getScrollY()
的值爲0,往左移動時偏移量x爲負數,往右移動時偏移量x爲正數,往上移動時偏移量y爲負數,往下移動時偏移量y爲正數。
-
scrollTo(x, y)
(無滾動特效,所以叫移動)
將view的內容
移動到指定位置。
這裏需要注意的是,不是移動view,而是移動view的內容。
這個方法往往結合onTouchEvent
一起使用,我們看以下代碼
public class TestView extends View {
private float mLastX = 0;
private float mLastY = 0;
private Paint mPaint;
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
mPaint = new Paint();
mPaint.setTextSize(80);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("我是中國人!", 0, 100, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//標誌着第一個手指按下
mLastX = x;//獲取按下時x座標值
mLastY = y;//獲取按下時y座標值
break;
case MotionEvent.ACTION_MOVE:
//按住一點手指開始移動
float move_x = mLastX - x;//計算當前已經移動的x軸方向的距離
float move_y = mLastY - y;//計算當前已經移動的y軸方向的距離
float oldScollX = getScrollX();//計算之前已經偏移的x軸方向的距離
float oldScollY = getScrollY();//計算之前已經偏移的y軸方向的距離
scrollTo((int) move_x, (int) move_y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//表示手勢被取消了,不再接受後續事件
scrollTo(0, 0);
break;
}
return true;
}
}
我們自定義一個view,view僅僅繪製一個文本,這個文本就是view的內容,代碼的邏輯是:移動view,在移動的過程中view的內容也隨之移動,當結束移動時,view的內容位置恢復。
圖片效果:
-
scrollBy(x, y)
(無滾動特效,所以叫移動)
我們先來看一下scrollTo,假如我們去掉觸摸時間的處理
public class TestView extends View {
private Paint mPaint;
public TestView(Context context) {
super(context);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init(){
mPaint = new Paint();
mPaint.setTextSize(80);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("我是中國人!", 0, 100, mPaint);
}
}
這樣的話文本的位置是:
現在我們使用代碼控制view內容的位置
tv_text = findViewById(R.id.tv_text);
tv_text.scrollTo(-100, -100);
移動之後的效果如下:
當我們多次使用scrollTo
後
tv_text = findViewById(R.id.tv_text);
tv_text.scrollTo(-100, -100);
tv_text.scrollTo(-100, -100);
tv_text.scrollTo(-100, -100);
tv_text.scrollTo(-100, -100);
tv_text.scrollTo(-100, -100);
效果如下:
我們發現不管我們使用多少次scrollTo
,view的移動都是以最開始的位置開始的。
scrollBy(x, y)
可以完美解決這個問題,我們看一下源碼就知道它的具體作用了:
/**
* 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(x, y)
使每次移動都是以當前位置開始。
tv_text = findViewById(R.id.tv_text);
tv_text.scrollBy(-100, -100);
tv_text.scrollBy(-100, -100);
tv_text.scrollBy(-100, -100);
tv_text.scrollBy(-100, -100);
tv_text.scrollBy(-100, -100);
tv_text.scrollBy(-100, -100);
tv_text.scrollBy(-100, -100);
效果如下:
(3)Scroller
滑動輔助類的基本方法
Scroller本身不會去移動view,它只是一個移動計算輔助類,用於跟蹤控件滑動的軌跡,只相當於一個滾動軌跡記錄工具,最終還是通過View的scrollTo、scrollBy方法完成View的移動的。
getCurrX()
獲取mScroller當前水平滾動的位置
getCurrY
獲取mScroller當前豎直滾動的位置
getFinalX
獲取mScroller最終停止的水平位置
getFinalY
獲取mScroller最終停止的豎直位置
startScroll()
開始滾動動畫:
startX:滾動的x方向起始點
startY:滾動的y方向起始點
dx:x方向的偏移量
dy:y方向的偏移量
duration:滾動所消耗的時間,默認爲250毫秒
startScroll(int startX, int startY, int dx, int dy)
startScroll(int startX, int startY, int dx, int dy, int duration)
computeScrollOffset()
判斷滾動動畫是否結束:
true:滾動尚未完成
false:滾動已經完成
(4)基本代碼實現
public class TestView extends View {
private float mDownX = 0;
private float mDonwY = 0;
private Paint mPaint;
private Scroller mScroller;
public TestView(Context context) {
super(context);
init(context);
}
public TestView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context mContext){
mPaint = new Paint();
mPaint.setTextSize(80);
mScroller = new Scroller(mContext);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText("我是中國人!", 0, 100, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//標誌着第一個手指按下
mDownX = x;//獲取按下時x座標值
mDonwY = y;//獲取按下時y座標值
break;
case MotionEvent.ACTION_MOVE:
//按住一點手指開始移動
float move_x = mDownX - x;//計算當前已經移動的x軸方向的距離
float move_y = mDonwY - y;//計算當前已經移動的y軸方向的距離
float oldScollX = getScrollX();//計算之前已經偏移的x軸方向的距離
float oldScollY = getScrollY();//計算之前已經偏移的y軸方向的距離
//開始滾動動畫
//第一個參數:x軸開始位置
//第二個參數:y軸開始位置
//第三個參數:x軸偏移量
//第四個參數:y軸偏移量
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), (int) move_x, (int) move_y, 3000);
invalidate();//目的是重繪view,是的執行computeScroll方法
break;
}
return true;
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){//判斷滾動是否完成,true說明滾動尚未完成,false說明滾動已經完成
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//將view直接移動到當前滾動的位置
invalidate();//觸發view重繪
}
}
}
效果如下:
看到這三秒鐘的滾動動畫了吧,默認情況下就是這個效果,默認情況下Scroller
使用的插值器是ViscousFluidInterpolator
,從字面意義上看是一個粘性流體插值器。
(5)構造方法
//默認插值器是ViscousFluidInterpolator
Scroller mScroller = new Scroller(mContext);
//指定一個插值器
Scroller mScroller = new Scroller(mContext, new AccelerateDecelerateInterpolator());
//指定一個插值器,第三個參數表示是否開啓“飛輪”效果,也就是多次滾動時速度疊加
Scroller mScroller = new Scroller(mContext, new AccelerateDecelerateInterpolator(), false);
(6)插值器
Scroller其實就是在scrollTo(x, y)
和scrollBy(x, y)
的基礎上添加滾動效果,滾動效果是一個動畫,當我們new
一個Scroller對象時,就已經指定了一個插值器,下面來說明一下各種插值器:
ViscousFluidInterpolator
這是一個默認插值器,當構造Scroller
時,如果不傳遞插值器或者插值器爲null時,系統默認使用ViscousFluidInterpolator
插值器。
AccelerateDecelerateInterpolator
在動畫開始與結束的時候速率改變比較慢,在中間的時候速率較快。
AccelerateInterpolator
在動畫開始的地方速率改變比較慢,然後開始加速。
AnticipateInterpolator
開始的時候向後然後向前甩。
如圖所示
AnticipateOvershootInterpolator
開始的時候向後然後向前甩一定值後返回最後的值。
如圖所示:
BounceInterpolator
反彈插值器。
如圖所示:
CycleInterpolator
動畫循環播放特定的次數,速率改變沿着正弦曲線。
DecelerateInterpolator
在動畫開始的地方快然後慢。
LinearInterpolator
以常量速率改變。
OvershootInterpolator
向前甩一定值後再回到原來位置.
PathInterpolator
路徑插值器,我們可以按照自己想要的軌跡滾動。
PathInterpolator(Path path)
PathInterpolator(float controlX, float controlY)
PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2)
如果學習Path使用的話,這篇博客是個不錯的選擇Android開發之Path詳解
FastOutLinearInInterpolator
MaterialDesign基於貝塞爾曲線的插補器效果:依次慢慢快。
FastOutSlowInInterpolator
基於貝塞爾曲線的插補器效果:依次慢快慢
LinearOutSlowInInterpolator
基於貝塞爾曲線的插補器效果:依次快慢慢
以上的插值器運用比較廣泛,在Scroller
中設置一個插值器可以優化滾動的效果。