學(xué)習(xí)谷歌的小弟自定義View01--常用工具介紹

谷歌的小弟說 先看看這幾個東西 懵逼啊 堅持看下去吧

Configuration

ViewConfiguration

GestureDetector

VelocityTracker

Scroller

ViewDragHelper

嗯哼,它們都已經(jīng)躺在這里了,我們就來挨個瞅瞅

Configuration

This class describes all device configuration information that can?impact the resources the application retrieves.

Configuration用來描述設(shè)備的配置信息。

比如用戶的配置信息:locale和scaling等等

比如設(shè)備的相關(guān)信息:輸入模式,屏幕大小, 屏幕方向等等

我們經(jīng)常采用如下方式來獲取需要的相關(guān)信息:

Configuration configuration=getResources().getConfiguration();

//獲取國家碼

int countryCode=configuration.mcc;

//獲取網(wǎng)絡(luò)碼

int networkCode=configuration.mnc;

//判斷橫豎屏

if(configuration.orientation==Configuration.ORIENTATION_PORTRAIT){?

?}else{

}

ViewConfiguration

看完Configuration再來瞅ViewConfiguration。這兩者的名字有些像,差了一個View;咋一看,還以為它倆是繼承關(guān)系,其實不然。

官方對于ViewConfiguration的描述是:

Contains methods to standard constants used in the UI for timeouts,sizes, and distances.

ViewConfiguration提供了一些自定義控件用到的標(biāo)準(zhǔn)常量,比如尺寸大小,滑動距離,敏感度等等。

可以利用ViewConfiguration的靜態(tài)方法獲取一個實例

ViewConfiguration viewConfiguration=ViewConfiguration.get(context);

在此介紹ViewConfiguration的幾個對象方法。

ViewConfiguration? viewConfiguration=ViewConfiguration.get(context);

//獲取touchSlop。

該值表示系統(tǒng)所能識別出的被認(rèn)為是滑動的最小距離

int touchSlop = viewConfiguration.getScaledTouchSlop();

//獲取Fling速度的最小值和最大值

int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();

int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();

//判斷是否有物理按鍵

boolean isHavePermanentMenuKey=viewConfiguration.hasPermanentMenuKey();

ViewConfiguration

還提供了一些非常有用的靜態(tài)方法,比如:

//雙擊間隔時間.在該時間內(nèi)是雙擊,否則是單擊

int doubleTapTimeout=ViewConfiguration.getDoubleTapTimeout();

//按住狀態(tài)轉(zhuǎn)變?yōu)殚L按狀態(tài)需要的時間

int longPressTimeout=ViewConfiguration.getLongPressTimeout();

//重復(fù)按鍵的時間

int keyRepeatTimeout=ViewConfiguration.getKeyRepeatTimeout();

GestureDetector

大家都知道,我們可以在onTouchEvent()中自己處理手勢。其實Android系統(tǒng)也給我們提供了一個手勢處理的工具,這就是GestureDetector手勢監(jiān)聽類。利用GestureDetector可以簡化許多操作,輕松實現(xiàn)一些常用的功能。

嗯哼,來吧,一起瞅瞅它是怎么使用的。

第一步:實現(xiàn)OnGestureListener

private class GestureListenerImpl implements GestureDetector.OnGestureListener{

//觸摸屏幕時均會調(diào)用該方法

@Override

public boolean onDown(MotionEvent e) { ?

System.out.println("---> 手勢中的onDown方法");

return false;? ??

? ? }

//手指在屏幕上拖動時會調(diào)用該方法

@Override

public boolean onFling(MotionEvent e1,MotionEvent e2,floatvelocityX,floatvelocityY) {?

System.out.println("---> 手勢中的onFling方法");

return false;?

?}

//手指長按屏幕時均會調(diào)用該方法

@Override

public void onLongPress(MotionEvent e) {

??System.out.println("---> 手勢中的onLongPress方法");?

? }

//手指在屏幕上滾動時會調(diào)用該方法

@Override

public boolean onScroll(MotionEvent e1,MotionEvent e2,floatdistanceX,floatdistanceY) {

?System.out.println("---> 手勢中的onScroll方法");

return false;?

?}

//手指在屏幕上按下,且未移動和松開時調(diào)用該方法

@Override

public void onShowPress(MotionEvent e) {?

System.out.println("---> 手勢中的onShowPress方法"); ?

}

//輕擊屏幕時調(diào)用該方法

@Override?

public boolean onSingleTapUp(MotionEvent e) {?

?System.out.println("---> 手勢中的onSingleTapUp方法");

return false;?

? ? ? } ?

?}

第二步:生成GestureDetector對象

GestureDetector gestureDetector =new GestureDetector(context,new GestureListenerImpl());

這里的GestureListenerImpl就是GestureListener監(jiān)聽器的實現(xiàn)。


第三步:將Touch事件交給GestureDetector處理

比如將Activity的Touch事件交給GestureDetector處理

@Override

public boolean onTouchEvent(MotionEvent event) {

return mGestureDetector.onTouchEvent(event);

? }

比如將View的Touch事件交給GestureDetector處理

mButton=(Button) findViewById (R.id.button);

mButton.setOnTouchListener(newOnTouchListener() {

@Override

public boolean onTouch(View arg0, MotionEvent event) {

return mGestureDetector.onTouchEvent(event);? ??

? ? }??

});

VelocityTracker

這個玩意兒一看名字,大概就可以猜到意思了。嗯哼,速度追蹤。

VelocityTracker用于跟蹤觸摸屏事件(比如,F(xiàn)linging及其他Gestures手勢事件等)的速率。

簡單說一下它的常用套路。

第一步:開始速度追蹤

private void startVelocityTracker(MotionEvent event) {

if(mVelocityTracker ==null) {? ? ?

? ? mVelocityTracker = Velocity Tracker.obtain();?

? ? }? ?

? mVelocityTracker.addMovement(event);?

}


在這里我們初始化VelocityTracker,并且把要追蹤的MotionEvent注冊到VelocityTracker的監(jiān)聽中。

第二步:獲取追蹤到的速度

privateintgetScrollVelocity() {

// 設(shè)置VelocityTracker單位.1000表示1秒時間內(nèi)運動的像素mVelocityTracker.computeCurrentVelocity(1000);

// 獲取在1秒內(nèi)X方向所滑動像素值

int xVelocity = (int) mVelocityTracker.getXVelocity();

returnMath.abs(xVelocity);? ? ?

}

同理可以獲取1秒內(nèi)Y方向所滑動像素值

第三步:解除速度追蹤

private void stopVelocityTracker() {

if(mVelocityTracker !=null) {? ?

? ? ? ? mVelocityTracker.recycle();? ? ? ?

? ? mVelocityTracker =null;? ? ?

? ? ? ?}

? }

以上就是VelocityTracker的常用使用方式。

Scroller

Scroller挺常見的,用的比較多了。在此只強調(diào)幾個重要的問題,別的就不再贅述了。

第一點:scrollTo()和scrollBy()的關(guān)系

先看scrollBy( )的源碼

publicvoidscrollBy(intx,inty) {? ? ? ? ? scrollTo(mScrollX + x, mScrollY + y);? }


這就是說scrollBy( )調(diào)用了scrollTo( ),最終起作用的是scrollTo( )方法。

第二點:scroll的本質(zhì)

scrollTo( )和scrollBy( )移動的只是View的內(nèi)容,而且View的背景是不移動的。

第三點:scrollTo( )和scrollBy( )方法的坐標(biāo)說明

比如我們對于一個TextView調(diào)用scrollTo(0,25) ;那么該TextView中的content(比如顯示的文字:Hello)會怎么移動呢?

向下移動25個單位?不!恰好相反!!這是為什么呢?

因為調(diào)用該方法會導(dǎo)致視圖重繪,即會調(diào)用

public void invalidate(int l, int t, int r, int b)

此處的l,t,r,b四個參數(shù)就表示View原來的坐標(biāo).

在該方法中最終會調(diào)用:

tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);

p.invalidateChild(this, tmpr);

其中tmpr是一個Rect,this是原來的View;通過這兩行代碼就把View在一個Rect中重繪。

請注意第一行代碼:

原來的l和r均減去了scrollX

原來的t和b均減去了scrollY

就是說scrollX如果是正值,那么重繪后的View的寬度反而減少了;反之同理

就是說scrollY如果是正值,那么重繪后的View的高度反而減少了;反之同理

所以,TextView調(diào)用scrollTo(0,25)和我們的理解相反

scrollBy(int x,int y)方法與上類似,不再多說了.

ViewDragHelper

在項目中很多場景需要用戶手指拖動其內(nèi)部的某個View,此時就需要在onInterceptTouchEvent()和onTouchEvent()這兩個方法中寫不少邏輯了,比如處理:拖拽移動,越界,多手指的按下,加速度檢測等等。

ViewDragHelper可以極大的幫我們簡化類似的處理,它提供了一系列用于處理用戶拖拽子View的輔助方法和與其相關(guān)的狀態(tài)記錄。比較常見的:QQ側(cè)滑菜單,Navigation Drawer的邊緣滑動,都可以由它實現(xiàn)。

ViewDragHelper的使用并不復(fù)雜,在此通過一個示例展示其常用的用法。

packagecom.tencent.test.utils;

importandroid.content.Context;

importandroid.support.v4.widget.ViewDragHelper;

importandroid.util.AttributeSet;

importandroid.view.MotionEvent;

importandroid.view.View;

importandroid.widget.LinearLayout;

public classIPlayGameextendsLinearLayout {

privateViewDragHelpermViewDragHelper;

publicIPlayGame(Context context,AttributeSet attrs) {

super(context,attrs);

initViewDragHelper();

}

//初始化ViewDragHelper

private voidinitViewDragHelper() {

mViewDragHelper= ViewDragHelper.create(this,1.0f, newViewDragHelper.Callback() {

@Override

public booleantryCaptureView(View child, intpointerId) {

return true;

}

//處理水平方向的越界

@Override

public intclampViewPositionHorizontal(View child, intleft, intdx) {

intfixedLeft;

View parent = (View) child.getParent();

intleftBound = parent.getPaddingLeft();

intrightBound = parent.getWidth() - child.getWidth() - parent.getPaddingRight();

if(left < leftBound) {

fixedLeft = leftBound;

}else if(left > rightBound) {

fixedLeft = rightBound;

}else{

fixedLeft = left;

}

returnfixedLeft;

}

//處理垂直方向的越界

@Override

public intclampViewPositionVertical(View child, inttop, intdy) {

intfixedTop;

View parent = (View) child.getParent();

inttopBound = getPaddingTop();

intbottomBound = getHeight() - child.getHeight() - parent.getPaddingBottom();

if(top < topBound) {

fixedTop = topBound;

}else if(top > bottomBound) {

fixedTop = bottomBound;

}else{

fixedTop = top;

}

returnfixedTop;

}

//監(jiān)聽拖動狀態(tài)的改變

@Override

public voidonViewDragStateChanged(intstate) {

super.onViewDragStateChanged(state);

switch(state) {

caseViewDragHelper.STATE_DRAGGING:

System.out.println("STATE_DRAGGING");

break;

caseViewDragHelper.STATE_IDLE:

System.out.println("STATE_IDLE");

break;

caseViewDragHelper.STATE_SETTLING:

System.out.println("STATE_SETTLING");

break;

}

}

}

//捕獲View

@Override

public voidonViewCaptured(View capturedChild, intactivePointerId) {

super.onViewCaptured(capturedChild,activePointerId);

System.out.println("ViewCaptured");

}

//釋放View

@Override

public voidonViewReleased(View releasedChild, floatxvel, floatyvel) {

super.onViewReleased(releasedChild,xvel,yvel);

System.out.println("ViewReleased");

}

});

}

//將事件攔截交給ViewDragHelper處理

@Override

public booleanonInterceptTouchEvent(MotionEvent ev) {

returnmViewDragHelper.shouldInterceptTouchEvent(ev);

}

//將Touch事件交給ViewDragHelper處理

@Override

public booleanonTouchEvent(MotionEvent ev) {

mViewDragHelper.processTouchEvent(ev);

return true;

}


從這個例子可以看出來ViewDragHelper是作用在ViewGroup上的(比如LinearLayout)而不是直接作用到某個被拖拽的子View。其實這也不難理解,因為子View在布局中的位置是其所在的ViewGroup決定的。

在該例中ViewDragHelper做了如下主要操作:

(1) ViewDragHelper接管了ViewGroup的事件攔截,請參見代碼第91-94行

(2) ViewDragHelper接管了ViewGroup的Touch事件,請參見代碼第98-102行

(3) ViewDragHelper處理了拖拽子View時的邊界越界,請參見代碼第22-55行

(4) ViewDragHelper監(jiān)聽拖拽子View時的狀態(tài)變化,請參見代碼第58-72行

除了這些常見的操作,ViewDragHelper還可以實現(xiàn):抽屜拉伸,拖拽結(jié)束松手后子View自動返回到原位等復(fù)雜操作。

好了,了解完這些非常有用的工具,我們就正式進(jìn)入自定義View。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容