最近在看自定義view相關的內容 在Scroller 這裏的確是卡了一些時間 故寫下來自己的心得總結
聲明:此文章demo轉載於guolin大神的Scroller完全解析 詳情請戳這裏
廢話不多說
Scroller 的作用:是一個專門用於處理滾動效果的工具類
Scroller的使用方法 (代碼中也會將詳細的使用步驟進行註釋)
1.創建Scroller實例
2.調用startScroller()方法初始化滾動數據並刷新界面
3.重寫computescroll()方法,並在內部完成平滑滾動邏輯
其中的scrollTo() ,scrollBy()這裏就不多做介紹 詳情可以看guolin作者的小demo
好 現在開始一點點的摳代碼
效果圖:
public class ScrollerLayout extends ViewGroup {
private Scroller mScroller;
private int mTouchSlop;//爲拖動的最小移動像素數
public ScrollerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 第一步,創建Scroller的實例
mScroller = new Scroller(context);
// 獲取TouchSlop值
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
Log.i("minnum","@"+mTouchSlop);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 爲ScrollerLayout中的每一個子控件測量大小
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
}
這裏比較簡單 就是對於我們自定義view的初始化 在ScrollerLayout 中我們先創建了Scroller的實例 又拿到最小偏移量 然後再onMeasure 方法中定義了每一個子容器的寬高 這裏就是父容器的寬高
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
// 爲ScrollerLayout中的每一個子控件在水平方向上進行佈局
//childview.layout(l,t,r,b)
childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
// 初始化左右邊界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDown = ev.getRawX();
mXLastMove = mXDown;
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float diff = Math.abs(mXMove - mXDown);
Log.i("diff","@"+diff);
mXLastMove = mXMove;
Log.i("inter","@"+mXLastMove);
// 當手指拖動值大於TouchSlop值時,認爲應該進行滾動,攔截子控件的事件
if (diff > mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
每一個子容器的寬高定義好了 下一步就是確定孩子顯示的位置 分別拿到每一個孩子 然後使用child Viewlayout()確定孩子顯示的位置 然後再onInterceptTouchEvent()中對事件進行攔截 不讓事件傳遞到子控件上
event.getRawX:表示的是觸摸點距離屏幕左邊界的距離 當移動的距離大於最小偏移量時 就開始攔截 就會將事件傳遞到onTouchEvent中
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
mXMove = event.getRawX();
Log.i("inters","@"+mXMove+"last"+mXLastMove);
int scrolledX = (int) (mXLastMove - mXMove);
Log.i("imports","scrllx"+getScrollX()+"juli"+scrolledX);
if (getScrollX() + scrolledX < leftBorder) {
scrollTo(leftBorder, 0);
return true;
} else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
scrollTo(rightBorder - getWidth(), 0);
return true;
}
scrollBy(scrolledX, 0);
mXLastMove = mXMove;
break;
case MotionEvent.ACTION_UP:
// 當手指擡起時,根據當前的滾動值來判定應該滾動到哪個子控件的界面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
Log.i("import","scrllx"+getScrollX()+"width"+getWidth());
int dx = targetIndex * getWidth() - getScrollX();
// 第二步,調用startScroll()方法來初始化滾動數據並刷新界面
mScroller.startScroll(getScrollX(), 0, dx, 0);
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
// 第三步,重寫computeScroll()方法,並在其內部完成平滑滾動的邏輯
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
本文的難點就來了 其中getScrollX()確實是我捉摸不透 先來看下圖 假設我們的屏幕在當前1的位置上
那getScrollX()值是如何獲取的呢
公式:當前view視圖x的座標減去初始位置X座標
其中當前view視圖x的座標時是隨着滑動當前x座標發生改變的 初始位置x,y座標是不變的 在我們這個demo中就是(0,0)
當我們屏幕現在在第二個圖形中 當我們向右移動100 我們的視圖x的座標就會從該開始的400 變成現在的500
getScrollX() = 500-0 = 500
假設我們在第二個圖形上 不管你的手指放在圖形的那個位置 他的左上角x座標就是400 同理 當我們在第1個或者第三個時他的初始getScrollX()默認就是當前view視圖的x座標
然後在onTouchEvent 的移動事件中 int scrolledX = (int) (mXLastMove - mXMove); 用上從次的值減去當前移動的值 因爲scrollTo scrollBy他們移動時取反的 這點需要牢記 值爲正代表向右移動
然後用當前位置和 移動的位置進行判斷 是否小於左邊界和大於右邊界 如果條件成立 則說明已超出邊界然後讓其移動到左右邊界的位置 不超出的話就在當前位置上進行距離的增量
在手指擡起的事件中 算出移動的位置dx 然後調用Scroller.startScroll();進行移動
重寫computeScroll()拿到當前mScroller 的x,y值 一點點的移動到最後目的值