在之前的文章中句携,已經(jīng)介紹過View的坐標系,滑動等相關(guān)內(nèi)容允乐。今天結(jié)合一個具體的例子來演示一下务甥。
首先介紹一下Scroller。
Scroller
Scroller是一個滑動的輔助類喳篇,它主要包含以下內(nèi)容
startScroll(int startX, int startY, int dx, int dy, int duration)
startX: 滑動開始的X位置
dx: 將要滑動的距離
duration:在多長時間內(nèi)完成滑動getScrollX(),getScrollY()
獲取滑動距離
需要注意的是敞临,在Scroll相關(guān)的方法中,包括scrollTo麸澜,scrollBy挺尿,getScrollX(),startScroll炊邦,他們的參數(shù)是有方向的编矾,與View的坐標系不同,向右馁害,向下為負值窄俏,反之為正值。
舉個例子:
首先通過scrollTo(100,50)將View的內(nèi)容滑動碘菜,View的位置會向左凹蜈,上移動 100,50 個像素。此時忍啸,通過getScrollX/Y()獲取到的值仰坦,就是 100 , 50
然后通過scrollBy(-20, -10)再移動一下View计雌,此時View會向右悄晃,下移動 20,10 個像素,然后通過getScrollX/Y()獲取到的值為100-20=80,50-10=40凿滤,View的內(nèi)容位于初始位置的左上方妈橄。
簡單的總結(jié)一下:
getScrollX/Y()得到的是當(dāng)前View的內(nèi)容與View的初始位置之間的距離,右下為負翁脆,反之為正眷蚓。
startScroll(int startX, int startY, int dx, int dy, int duration):
startX/Y表示滑動開始的位置(通常由getScrollX/Y()得到)
dX/Y表示將要滑動的距離,右下為負鹃祖,反之為正溪椎。
稍后會在具體的例子中演示。
invalidate();
invalidate();是View中的方法恬口,會調(diào)用draw校读,進行View重繪,跟在startScroll方法之后祖能。startScroll實際上只是告訴View怎樣滑動歉秫,invalidate()之后才會開始滑動。重寫computeScroll()
這個方法才是實現(xiàn)彈性滑動的關(guān)鍵养铸。彈性滑動雁芙,可以理解為平滑的移動,我們通過scrollTo/By钞螟,實現(xiàn)的滑動是一下就完成的兔甘,彈性滑動是緩慢平滑的移動。computeScroll()是怎樣實現(xiàn)彈性滑動的呢鳞滨?
看一下代碼就明白了:
@Override
public void computeScroll() {
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
computeScroll是在draw方法中調(diào)用的洞焙,startScroll之后的invalidate會調(diào)用draw方法,draw又調(diào)用了computeScroll拯啦,if里的條件mScroller.computeScrollOffset()是判斷滑動是否完成澡匪,如果沒有完成,就會調(diào)用scrollTo褒链,將view滑動到當(dāng)前位置唁情,然后再postInvalidate繼續(xù)調(diào)用draw方法,最終將View一點一點移動到目標位置甫匹。
實例演示
我們要實現(xiàn)的是一個很簡單的例子甸鸟,自定義一個圓形,當(dāng)手指按下的時候兵迅,圓形移動到手指的位置哀墓,手指移動的時候,圓形會跟隨手指移動喷兼,抬起手指的時候篮绰,圓形慢慢的回復(fù)到初始位置。
代碼如下:
public class FollowFingerView extends View{
//繪制圓形
private Paint mPaint;
//繪制背景
private Paint mBackGroundPaint;
//View的寬和高
private int mWidth;
private int mHeight;
//定義Scroller
private Scroller mScroller;
//存儲上次View的位置參數(shù)
private int mLastX;
private int mLastY;
public FollowFingerView(Context context) {
this(context,null);
// TODO Auto-generated constructor stub
}
public FollowFingerView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
// TODO Auto-generated constructor stub
}
private void init(){
mPaint = new Paint();
//設(shè)置圓形顏色
mPaint.setColor(0x22ff0000);
mBackGroundPaint=new Paint();
//設(shè)置背景顏色
mBackGroundPaint.setColor(0xfff8efe0);
mScroller = new Scroller(getContext());
//設(shè)置默認寬高季惯,wrap_content時使用
mWidth = 400;
mHeight = 400;
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//繪制背景
canvas.drawPaint(mBackGroundPaint);
//繪制半徑為30像素的圓形
int radius = 30;
canvas.drawCircle(30, 30, radius, mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//處理wrap_content失效
setMeasuredDimension(measureWidth(widthMode,width), measureHeight(heightMode,height));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
//獲取觸發(fā)事件時吠各,手指的觸碰位置
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//手指按下時,移動View到觸碰位置
scrollTo(-x, -y);
break;
case MotionEvent.ACTION_MOVE:
//事件發(fā)生時手指的位置與上一個事件結(jié)束時手指的位置勉抓,之間的距離
//注意方向贾漏,可能這樣寫更容易理解:
//int dx = -(x - mLastX);
//int dy = -(y - mLastY);
int dy = mLastY - y;
int dx = mLastX - x;
int dy = mLastY - y;
//跟隨手指移動
scrollBy(dx, dy);
break;
case MotionEvent.ACTION_UP:
//手指抬起的時候,開始返回初始位置
mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX(), -getScrollY(), 500);
Log.d("Follow", "getScrollX is " + getScrollX());
invalidate();
break;
default:
break;
}
//記錄事件結(jié)束時手指的位置
mLastX = x;
mLastY = y;
//該View不是clickable的藕筋,返回值默認為false纵散,應(yīng)該手動改為true,否則不能消費事件,只能執(zhí)行down
//return super.onTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
// TODO Auto-generated method stub
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
private int measureWidth(int widthMode,int widthSize){
switch (widthMode) {
case MeasureSpec.EXACTLY:
mWidth=widthSize;
//Log.d("ViewConstructor", "mode is exactly");
break;
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
break;
default:
break;
}
return mWidth;
}
private int measureHeight(int heightMode,int heightSize){
switch (heightMode) {
case MeasureSpec.EXACTLY:
mHeight=heightSize;
break;
case MeasureSpec.UNSPECIFIED:
case MeasureSpec.AT_MOST:
break;
default:
break;
}
return mHeight;
}
}