自定義View系列教程01--常用工具介紹

在自定義View的時(shí)候颗胡,常常會(huì)用到一些Android系統(tǒng)提供的工具毫深。這些工具封裝了我們經(jīng)常會(huì)用到的方法,比如拖拽View毒姨,計(jì)算滑動(dòng)速度哑蔫,View的滾動(dòng),手勢處理等等。如果我們自己去實(shí)現(xiàn)這些方法會(huì)比較繁瑣闸迷,而且容易出一些bug嵌纲。所以,作為自定義View系列教程的開端腥沽,先介紹一下這些常用的工具逮走,以便在后續(xù)的學(xué)習(xí)和工作中使用。

  • Configuration
  • ViewConfiguration
  • GestureDetector
  • VelocityTracker
  • Scroller
  • ViewDragHelper

嗯哼今阳,它們都已經(jīng)躺在這里了师溅,我們就來挨個(gè)瞅瞅
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。這兩者的名字有些像矿筝,差了一個(gè)View起便;咋一看,還以為它倆是繼承關(guān)系窖维,其實(shí)不然榆综。 官方對于ViewConfiguration的描述是:

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

ViewConfiguration提供了一些自定義控件用到的標(biāo)準(zhǔn)常量,比如尺寸大小铸史,滑動(dòng)距離鼻疮,敏感度等等。 可以利用ViewConfiguration的靜態(tài)方法獲取一個(gè)實(shí)例
ViewConfiguration viewConfiguration=ViewConfiguration.get(context);

在此介紹ViewConfiguration的幾個(gè)對象方法琳轿。

ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
//獲取touchSlop判沟,該值表示系統(tǒng)所能識別出的被認(rèn)為是滑動(dòng)的最小距離
int touchSlop = viewConfiguration.getScaledTouchSlop();
//獲取Fling速度的最小值和最大值
int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
//判斷是否有物理按鍵
boolean isHavePermanentMenuKey=viewConfiguration.hasPermanentMenuKey();

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

//雙擊間隔時(shí)間.在該時(shí)間內(nèi)是雙擊崭篡,否則是單擊
int doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
//按住狀態(tài)轉(zhuǎn)變?yōu)殚L按狀態(tài)需要的時(shí)間
int longPressTimeout = ViewConfiguration.getLongPressTimeout();
//重復(fù)按鍵的時(shí)間
int keyRepeatTimeout = ViewConfiguration.getKeyRepeatTimeout();

GestureDetector 大家都知道挪哄,我們可以在onTouchEvent()中自己處理手勢。其實(shí)Android系統(tǒng)也給我們提供了一個(gè)手勢處理的工具琉闪,這就是GestureDetector手勢監(jiān)聽類迹炼。利用GestureDetector可以簡化許多操作,輕松實(shí)現(xiàn)一些常用的功能颠毙。 嗯哼斯入,來吧,一起瞅瞅它是怎么使用的蛀蜜。
第一步:實(shí)現(xiàn)OnGestureListener

private class GestureListenerImpl implements GestureDetector.OnGestureListener {
        //觸摸屏幕時(shí)均會(huì)調(diào)用該方法
        @Override
        public boolean onDown(MotionEvent e) {
            System.out.println("---> 手勢中的onDown方法");
            return false;
        }

        //手指在屏幕上拖動(dòng)時(shí)會(huì)調(diào)用該方法
        @Override
        public boolean onFling(MotionEvent e1,MotionEvent e2, float velocityX,float velocityY) {
            System.out.println("---> 手勢中的onFling方法");
            return false;
        }

        //手指長按屏幕時(shí)均會(huì)調(diào)用該方法
        @Override
        public void onLongPress(MotionEvent e) {
            System.out.println("---> 手勢中的onLongPress方法");
        }

        //手指在屏幕上滾動(dòng)時(shí)會(huì)調(diào)用該方法
        @Override
        public boolean onScroll(MotionEvent e1,MotionEvent e2, float distanceX,float distanceY) {
            System.out.println("---> 手勢中的onScroll方法");
            return false;
        }

        //手指在屏幕上按下,且未移動(dòng)和松開時(shí)調(diào)用該方法
        @Override
        public void onShowPress(MotionEvent e) {
            System.out.println("---> 手勢中的onShowPress方法");
        }

        //輕擊屏幕時(shí)調(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)聽器的實(shí)現(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(new OnTouchListener() {            
   @Override  
   public boolean onTouch(View arg0, MotionEvent event) {  
          return mGestureDetector.onTouchEvent(event);  
      }  
});  

VelocityTracker 這個(gè)玩意兒一看名字,大概就可以猜到意思了滴某。嗯哼磅摹,速度追蹤滋迈。 VelocityTracker用于跟蹤觸摸屏事件(比如,F(xiàn)linging及其他Gestures手勢事件等)的速率户誓。 簡單說一下它的常用套路杀怠。
第一步:開始速度追蹤

private void startVelocityTracker(MotionEvent event) {  
    if (mVelocityTracker == null) {  
         mVelocityTracker = VelocityTracker.obtain();  
     }  
     mVelocityTracker.addMovement(event);  
}  

在這里我們初始化VelocityTracker,并且把要追蹤的MotionEvent注冊到VelocityTracker的監(jiān)聽中厅克。
第二步:獲取追蹤到的速度

private int getScrollVelocity() {  
     // 設(shè)置VelocityTracker單位.1000表示1秒時(shí)間內(nèi)運(yùn)動(dòng)的像素  
     mVelocityTracker.computeCurrentVelocity(1000);  
     // 獲取在1秒內(nèi)X方向所滑動(dòng)像素值  
     int xVelocity = (int) mVelocityTracker.getXVelocity();  
     return Math.abs(xVelocity);  
    } 

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

第三步:解除速度追蹤

private void stopVelocityTracker() {  
      if (mVelocityTracker != null) {  
          mVelocityTracker.recycle();  
          mVelocityTracker = null;  
      }  
}  

以上就是VelocityTracker的常用使用方式赔退。
Scroller
Scroller挺常見的,用的比較多了证舟。在此只強(qiáng)調(diào)幾個(gè)重要的問題硕旗,別的就不再贅述了。
第一點(diǎn):scrollTo()和scrollBy()的關(guān)系 先看scrollBy( )的源碼

public void scrollBy(int x, int y) {   
       scrollTo(mScrollX + x, mScrollY + y);   
} 

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

第二點(diǎn):scroll的本質(zhì) scrollTo( )和scrollBy( )移動(dòng)的只是View的內(nèi)容,而且View的背景是不移動(dòng)的抵知。
第三點(diǎn):scrollTo( )和scrollBy( )方法的坐標(biāo)說明
比如我們對于一個(gè)TextView調(diào)用scrollTo(0,25) 墙基;那么該TextView中的content(比如顯示的文字:Hello)會(huì)怎么移動(dòng)呢? 向下移動(dòng)25個(gè)單位?不刷喜!恰好相反2兄啤!這是為什么呢? 因?yàn)檎{(diào)用該方法會(huì)導(dǎo)致視圖重繪掖疮,即會(huì)調(diào)用

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

此處的l,t,r,b四個(gè)參數(shù)就表示View原來的坐標(biāo). 在該方法中最終會(huì)調(diào)用:
tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY); p.invalidateChild(this, tmpr);

其中tmpr是一個(gè)Rect初茶,this是原來的View;通過這兩行代碼就把View在一個(gè)Rect中重繪浊闪。
請注意第一行代碼:
原來的l和r均減去了scrollX
原來的t和b均減去了scrollY
就是說scrollX如果是正值,那么重繪后的View的寬度反而減少了;
反之同理 就是說scrollY如果是正值,那么重繪后的View的高度反而減少了;
反之同理 所以恼布,TextView調(diào)用scrollTo(0,25)和我們的理解相反
scrollBy(int x,int y)方法與上類似,不再多說了.

ViewDragHelper 在項(xiàng)目中很多場景需要用戶手指拖動(dòng)其內(nèi)部的某個(gè)View,此時(shí)就需要在onInterceptTouchEvent() 和 onTouchEvent()這兩個(gè)方法中寫不少邏輯了搁宾,比如處理:拖拽移動(dòng)折汞,越界,多手指的按下盖腿,加速度檢測等等爽待。

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

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

/**
 * ViewDragHelper使用示例
 * 原創(chuàng)作者:谷哥的小弟
 * 原創(chuàng)地址:http://blog.csdn.net/lfdfhl
 */
public class MyLinearLayout extends LinearLayout {
    private ViewDragHelper mViewDragHelper;

    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViewDragHelper();
    }

    //初始化ViewDragHelper
    private void initViewDragHelper() {
        mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                return true;
            }

    22        //處理水平方向的越界
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                int fixedLeft;
                View parent = (View) child.getParent();
                int leftBound = parent.getPaddingLeft();
                int rightBound = parent.getWidth() - child.getWidth() - parent.getPaddingRight();

                if (left < leftBound) {
                    fixedLeft = leftBound;
                } else if (left > rightBound) {
                    fixedLeft = rightBound;
                } else {
                    fixedLeft = left;
                }
                return fixedLeft;
            }

            //處理垂直方向的越界
            @Override
            public int clampViewPositionVertical(View child, int top, int dy) {
                int fixedTop;
                View parent = (View) child.getParent();
                int topBound = getPaddingTop();
                int bottomBound = getHeight() - child.getHeight() - parent.getPaddingBottom();
                if (top < topBound) {
                    fixedTop = topBound;
                } else if (top > bottomBound) {
                    fixedTop = bottomBound;
                } else {
                    fixedTop = top;
                }
                return fixedTop;
    55        }

            //監(jiān)聽拖動(dòng)狀態(tài)的改變
    58       @Override
            public void onViewDragStateChanged(int state) {
                super.onViewDragStateChanged(state);
                switch (state) {
                    case ViewDragHelper.STATE_DRAGGING:
                        System.out.println("STATE_DRAGGING");
                        break;
                    case ViewDragHelper.STATE_IDLE:
                        System.out.println("STATE_IDLE");
                        break;
                    case ViewDragHelper.STATE_SETTLING:
                        System.out.println("STATE_SETTLING");
                        break;
                }
     72       }

            //捕獲View
            @Override
            public void onViewCaptured(View capturedChild, int activePointerId) {
                super.onViewCaptured(capturedChild, activePointerId);
                System.out.println("ViewCaptured");
            }

            //釋放View
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                System.out.println("ViewReleased");
            }
        });
    }

    //將事件攔截交給ViewDragHelper處理
 91   @Override
 92   public boolean onInterceptTouchEvent(MotionEvent ev) {
 93     return mViewDragHelper.shouldInterceptTouchEvent(ev);
 94    }


    //將Touch事件交給ViewDragHelper處理
 98   @Override
 99   public boolean onTouchEvent(MotionEvent ev) {
 100       mViewDragHelper.processTouchEvent(ev);
 101      return true;
 102   }
}

從這個(gè)例子可以看出來ViewDragHelper是作用在ViewGroup上的(比如LinearLayout)而不是直接作用到某個(gè)被拖拽的子View疙筹。其實(shí)這也不難理解富俄,因?yàn)樽覸iew在布局中的位置是其所在的ViewGroup決定的。
在該例中ViewDragHelper做了如下主要操作:
(1) ViewDragHelper接管了ViewGroup的事件攔截而咆,請參見代碼第91-94行
(2) ViewDragHelper接管了ViewGroup的Touch事件霍比,請參見代碼第98-102行
(3) ViewDragHelper處理了拖拽子View時(shí)的邊界越界,請參見代碼第22-55行
(4) ViewDragHelper監(jiān)聽拖拽子View時(shí)的狀態(tài)變化暴备,請參見代碼第58-72行
除了這些常見的操作悠瞬,ViewDragHelper還可以實(shí)現(xiàn):抽屜拉伸,拖拽結(jié)束松手后子View自動(dòng)返回到原位等復(fù)雜操作涯捻。
好了浅妆,了解完這些非常有用的工具,我們就正式進(jìn)入自定義View障癌。


who is the next one凌外? ——> onMeasure源碼分析

自定義View系列教程01--常用工具介紹
自定義View系列教程02--onMeasure源碼詳盡分析
自定義View系列教程03--onLayout源碼詳盡分析
自定義View系列教程04--Draw源碼分析及其實(shí)踐
自定義View系列教程05–示例分析
自定義View系列教程06–詳解View的Touch事件處理
自定義View系列教程07–詳解ViewGroup分發(fā)Touch事件
自定義View系列教程08–滑動(dòng)沖突的產(chǎn)生及其處理

原文地址:http://blog.csdn.net/lfdfhl/article/details/51324275

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涛浙,隨后出現(xiàn)的幾起案子康辑,更是在濱河造成了極大的恐慌,老刑警劉巖轿亮,帶你破解...
    沈念sama閱讀 223,207評論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疮薇,死亡現(xiàn)場離奇詭異,居然都是意外死亡我注,警方通過查閱死者的電腦和手機(jī)惦辛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評論 3 400
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仓手,“玉大人胖齐,你說我怎么就攤上這事炫加∧担” “怎么了絮宁?”我有些...
    開封第一講書人閱讀 170,031評論 0 366
  • 文/不壞的土叔 我叫張陵奴曙,是天一觀的道長导披。 經(jīng)常有香客問我贰您,道長刁赖,這世上最難降的妖魔是什么逛钻? 我笑而不...
    開封第一講書人閱讀 60,334評論 1 300
  • 正文 為了忘掉前任贬蛙,我火速辦了婚禮雨女,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阳准。我一直安慰自己氛堕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評論 6 398
  • 文/花漫 我一把揭開白布野蝇。 她就那樣靜靜地躺著讼稚,像睡著了一般括儒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锐想,一...
    開封第一講書人閱讀 52,895評論 1 314
  • 那天帮寻,我揣著相機(jī)與錄音,去河邊找鬼赠摇。 笑死固逗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藕帜。 我是一名探鬼主播抒蚜,決...
    沈念sama閱讀 41,300評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耘戚!你這毒婦竟也來了嗡髓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,264評論 0 277
  • 序言:老撾萬榮一對情侶失蹤收津,失蹤者是張志新(化名)和其女友劉穎饿这,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撞秋,經(jīng)...
    沈念sama閱讀 46,784評論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡长捧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吻贿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片串结。...
    茶點(diǎn)故事閱讀 40,989評論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舅列,靈堂內(nèi)的尸體忽然破棺而出肌割,到底是詐尸還是另有隱情,我是刑警寧澤帐要,帶...
    沈念sama閱讀 36,649評論 5 351
  • 正文 年R本政府宣布把敞,位于F島的核電站,受9級特大地震影響榨惠,放射性物質(zhì)發(fā)生泄漏奋早。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評論 3 336
  • 文/蒙蒙 一赠橙、第九天 我趴在偏房一處隱蔽的房頂上張望耽装。 院中可真熱鬧,春花似錦期揪、人聲如沸掉奄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挥萌。三九已至,卻和暖如春枉侧,著一層夾襖步出監(jiān)牢的瞬間引瀑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評論 1 275
  • 我被黑心中介騙來泰國打工榨馁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留憨栽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,452評論 3 379
  • 正文 我出身青樓翼虫,卻偏偏與公主長得像屑柔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子珍剑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評論 2 361

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