教你10行代碼寫側滑菜單,10行代碼滑菜單

來源:互聯網
上載者:User

教你10行代碼寫側滑菜單,10行代碼滑菜單

原帖最初發表於傳智播客黑馬程式員論壇,地址:http://bbs.itheima.com/thread-167442-1-1.html

先來看個側滑菜單效果:

    

 

上面分別為:初始狀態->滑動中->鬆開開啟菜單

你造嗎?看完本文章,寫這種側滑菜單,so easy for you!

你造嗎?其實寫這個側滑菜單,只需要10行手寫代碼就能搞定!

你造嗎?看完本文章,當你再聽到產品說"我要這個效果"時,再也不會底氣不足!

 

在Android開發中,自訂View效果是屬於進階的部分。因為常用邏輯上的東西,經過1年開發後,你會的大家都會。而對於如何?特殊效果的view的技能上,就可以分個高低了。所以說,無論是面子上的事兒(可以得到程式員妹紙的崇拜),還是職業技能的提高,我們都非常有必要去熟諳view特效之技巧。

接下來,我將用原理分析加程式碼範例的方式說明讓view移動的一些常用技巧。

 

在實際開發中,我們一般會在2種情況下需要讓view移動:

1.觸摸過程中,隨著手指拖動讓view移動

2.手指抬起後,讓view平滑緩慢移動到某個位置

 

View移動的本質是什嗎?

試想一下,view無論出現在螢幕中任何一個位置都是有其座標點,假如沒有座標,那麼view的出現就是不可控的,從而也無法進行繪製了。

所謂view移動,無非是讓view從一個座標點移動到另外一個座標點。

 

View在什麼座標系裡面移動?

先想想座標系的概念:

螢幕座標系:原點在螢幕左上方,x軸正方向朝右,y軸正方向朝下;

view座標系:原點在view的左上方,同樣x軸正方向朝右,y軸正方向朝下;

 

(如果對上面2種座標系的說明有疑惑,可以在activity中放置一個簡單的TextView,最好讓TextView距離父view有些距離,對TextView進行觸摸監聽,然後在onTouchEvent中列印event.getX()和event.getRawX(),前者是觸摸點相對於view左上方的座標,後者是觸摸點相對於螢幕左上方的座標)

 

View移動可以細分為2類:

a.view本身在父view中移動,即整體移動,同時該分類也可看做是父view的內容在移動

b.view的內容在移動,即內部移動

 

View移動有哪些具體的API?

下面結合上面的分析來看移動view的具體方法:

 

1.通過更改view的left,top,right,bottom來移動view

    該方法只適用於在ViewGroup中移動子view,即移動分類中的a類。我們知道left,top,right,bottom4個值是在onLayout方法執行的時候初始化的。同時對4個參數的意思再做一次簡單說明,只拿left和top為例:

left:表示當前view的左邊距父view原點的距離

top:表示當前view的頂部距父view原點的距離

view提供了3個方法來更改這些值:

layout(l,r,t,b);offsetLeftAndRight(offset);//同時對left和right進行位移offsetTopAndBottom(offset);//同時對top和bottom進行位移

    那麼假如我們對一個view添加觸摸監聽,在TouchMove的時候得到觸摸移動的值,分別使用這3個方法就可以達到讓view隨我們手指移動而在父view中移動的目的.

程式碼範例:

public boolean onTouch(View v, MotionEvent event) {         float x =  event.getX();         float y =  event.getY();         switch(event.getAction()) {         case MotionEvent.ACTION_DOWN:                 break;         case MotionEvent.ACTION_MOVE:                 float deltaX = x -lastX;                 float deltaY = y -lastY;//                text.layout(text.getLeft()+deltaX,text.getTop()+deltaY, text.getRight()+deltaX, text.getBottom()+deltaY);                 text.offsetLeftAndRight((int) deltaX);                 text.offsetTopAndBottom((int) deltaY);                 break;         }                 lastX = x;                 lastY = y;                 return true;}

2.通過更改scrollX和scrollY來移動view,即scrollTo和scrollBy方法,該方法是用來移動view內容的,用官方的話說就是:internally scroll theircontent.

根據對view移動的分類,如果是在ViewGroup中使用,則可以讓當前view的所有子view同時移動,如果是對單個view使用,則可以讓view的內容移動。

注意:view的background不屬於view的內容,

下面摘自一段view的draw方法中的注釋,來看view繪製的步驟:

/*         *Draw traversal performs several drawing steps which must be executed         *in the appropriate order:         *  1. Draw the background         *  2. If necessary, save the canvas' layers toprepare for fading         *  3. Draw view's content         *  4. Draw children         *  5. If necessary, draw the fading edges and restorelayers         *  6. Draw decorations (scrollbars forinstance)         */

對於這裡的view的content,對於TextView,content就是它的文本,對於ImageView,則是它的drawable對象。

 

下面解釋scrollX和scrollY:

首先如果直接使用scrollTo和scrollBy這2個方法,你會發現當你scrollTo(10,0)的時候,view的內容卻向左移動10,同樣scrollBy(10,0),view的內容會繼續向左移動10,竟然不是向右移動,這很讓人迷惑。

先來看下官方對scrollX的解釋:

/**         *Return the scrolled left position of this view. This is the left edge of         *the displayed part of your view. You do not need to draw any pixels         *farther left, since those are outside of the frame of your view on screen.         * @return The leftedge of the displayed part of your view, in pixels.         */

首先Android繪製view的時候都有一個bounds,可通過view.getDrawingRect()擷取,其實是個Rect對象,這個Rect的地區就是Canvas真正繪製的地區,超過這個bounds就不會再繪製。其實上邊注釋中的the left edge of the displayed part if yourview,就是指bounds的left。Rect的left,top,right,bottom值分別為scrollX,scrollY,scrollX+width,scrollY+height;顯然Rect的left和top最初都是0,因為沒有scroll,當scrollTo(10,0)後,Rect的left為10,即bounds向右移動了10,那麼這時候再在移動後的bounds範圍內繪製的時候,會看到是view的內容向左移動了,因為view的位置是不變的,bounds右移,會造成內容向左移動的視覺效果。這也是我們疑惑的地方。究其原因,就是scrollTo和scrollBy是指view的bounds移動,並不是直接指view內容的移動。同時scroll所在座標系是當前view座標系。

scrollTo和scrollBy方法移動view過程:

 

那麼假如我們對一個view添加觸摸監聽,在TouchMove的時候得到觸摸移動的值,分別使用scrollTo和scrollBy方法就可以達到讓view的內容隨我們手指移動而移動的目的。

程式碼範例:

public boolean onTouch(View v, MotionEvent event) {         float x =  event.getX();         float y =  event.getY();         switch(event.getAction()) {         case MotionEvent.ACTION_DOWN:                 break;         case MotionEvent.ACTION_MOVE:                 float deltaX = x -lastX;                 float deltaY = y -lastY;                                                            /*為什麼是減去delatX:newScrollX:手指往右移動,deltaX會是正值,scrollX會更小,bounds的left更小,則bounds往左移動,view的內容會往右移動*/                 float newScrollX =text.getScrollX()- deltaX;                 float newScrollY =text.getScrollY()- deltaY;                 text.scrollTo((int)newScrollX, (int)newScrollY);                 //scrollBy傳負值的原理同上text.scrollBy((int)-deltaX,(int)-deltaY);                 break;         }                 lastX = x;                 lastY = y;                 return true;}

如何讓View平滑移動?

上面2種方法一般用在對View進行TouchMove的時候讓view移動的,而且layout和scroll都是瞬間移動過去,那麼問題來了,當我們手指抬起後,想讓View移動平滑移動到指定位置該怎麼辦?既然layout和scroll都能移動view,那我們在一段時間內迴圈調用這些方法,每次移動一點,不就能夠平滑移動了嗎。

對於這個需求,一般有2種做法:

1.Scroller實現

2.自訂動畫實現

 

一.用Scroller實現View平滑移動

Scroller封裝了對view移動的操作,但它是一個類比移動,並不是真正去移動view。由於它類比了view的整個移動過程,所以我們可以在類比過程中,迴圈擷取當前view真實移動的時候的scrollX,scrollY;那麼拿到scrollX和scrollY後,再調用scrollTo就達到平滑移動的目的了。

使用Scroller一般有3個固定步驟:

a.初始化Scroller

Scroller scroller =new Scroller(getContext());

b.開啟類比過程

scroller.startScroll(startX, startY, dx, dy,500);invalidate();

c.在類比過程中擷取view真實移動時的值,並調用scrollTo去真正移動view

public void computeScroll() {         super.computeScroll();         if(scroller.computeScrollOffset()){                  scrollTo(scroller.getCurrX(),scroller.getCurrY());                 invalidate();         }}

說明:在b,c步驟中,需要說明的是必須調用invalidate()方法,因為我們只能在computeScroll方法中擷取類比過程中的scrollX和scrollY,但是computeScroll不會自動調用,而invalidate->draw->computeScroll;所以需要調用invalidate,從而間接調用computeScroll,達到迴圈擷取scrollX和scrollY的目的。當類比過程結束後,scroller.computeScrollOffset()會返回false,從而中斷迴圈。

 

二.用自訂動畫實現View平滑移動

    由於系統定義的TranslateAnimation只能移動view本身,而不能移動view的內容,所以需要自訂。系統定義好的4種動畫,本質都是在一段時間內改變view的某個屬性,所以我們也可以自訂動畫,繼承自Animation類,去在一段時間內改變我們想改變的屬性,比如scrollX,scrollY。

    Animation同樣是類比了一個執行過程,它與Scroller很相似,不同的是Scroller為我們計算出了view真實移動情況下的scrollX和scrollY,而Animation沒有。另外Scroller需要我們去主動調用computeScroll,而Animation不需要,它在執行過程中會迴圈調用applyTransformation方法,直到動畫結束為止。所以我們需要在applyTransformation方法中計算當前的scrollX和scrollY,然後調用view.scrollTo(x,y);

    由於applyTransformation的第一個interpolatedTime為我們標識了動畫執行的進度和百分比,所以我們可以根據這個參數擷取執行過程中任意時刻的scrollX和scrollY。

下面寫一個通用的讓view在一段時間內緩慢scroll到指定位置的動畫,代碼如下:

public class SmoothScrollAnimation extends Animation{         private View view;          private int originalScrollX;         private int totalScrollDistance;         public SmoothScrollAnimation(View view,int targetScrollX){                 super();                 this.view= view;                    originalScrollX = view.getScrollX();                   totalScrollDistance = targetScrollX- originalScrollX;                    setDuration(400);         }    @Override         protected void applyTransformation(float interpolatedTime, Transformation t){                 super.applyTransformation(interpolatedTime, t);                 int newScrollX =(int)(originalScrollX+                 totalScrollDistance*interpolatedTime);                                view.scrollTo(newScrollX,0);         }}

使用強大的ViewDragHelper

    最後,介紹一個更強大的類ViewDragHelper,在上面的過程中我們需要監視觸摸,計算手指移動距離,在去不斷調用layout方法去移動view,在手指抬起時去自己緩慢移動view,有了這個類,你統統不需要做了。

    ViewDragHelper一般用於在ViewGroup中對子view的拖拽移動,不過它需要接收一個TouchEvent事件。

    它封裝了對觸摸位置(是否在邊緣和當前觸摸的是哪個子view),手指移動距離,移動速度,移動方向的檢測,以及Scroller.只需要我們指定什麼時候開始檢測,具體移動多少。

    那麼我們現在來做這個文章開頭的側滑菜單的效果,是真正的10行手寫代碼實現的,可見ViewDragHelper的強大。

方法的說明都在代碼中,代碼如下:

public class SlideMenu extends FrameLayout{         public SlideMenu(Contextcontext, AttributeSet attrs) {                 super(context,attrs);                 init();         }           private View menuView,mainView;         private int menuWidth;//菜單寬度         private ViewDragHelperviewDragHelper;         private void init(){                 viewDragHelper =ViewDragHelper.create(this,callback);         }         @Override         protected void onFinishInflate() {                 super.onFinishInflate();                 if(getChildCount()!=2){                          throw newIllegalArgumentException("Youlayout must have only 2 children!");                 }                 menuView =getChildAt(0);                 mainView = getChildAt(1);         }         @Override         protected void onSizeChanged(int w,int h, int oldw,int oldh) {                 super.onSizeChanged(w,h, oldw, oldh);                 menuWidth = menuView.getMeasuredWidth();         }         @Override         public boolean onInterceptTouchEvent(MotionEvent ev) {                 return viewDragHelper.shouldInterceptTouchEvent(ev);         }         @Override         public boolean onTouchEvent(MotionEvent event) {                 //將觸摸事件傳遞給ViewDragHelper,此操作必不可少                 viewDragHelper.processTouchEvent(event);                 return true;         }         //ViewDragHelper對觸摸監聽的回調         private ViewDragHelper.Callbackcallback = new Callback() {                 /**                  * 什麼時候開始監測觸摸                  */                 @Override                 public boolean tryCaptureView(View child,int pointerId) {                          return mainView==child;//如果當前觸摸的child是mainView時開始檢測                 }                 /**                  * 當觸摸到childView時回調                  */                 @Override                 public void onViewCaptured(View capturedChild,int activePointerId) {                          super.onViewCaptured(capturedChild,activePointerId);                 }                 /**                  * 當拖拽狀態改變,比如idle,dragging                  */                 @Override                 public void onViewDragStateChanged(int state) {                          super.onViewDragStateChanged(state);                 }                 /**                  * 在這裡決定真正讓view在垂直方向移動多少,預設實現則不會移動                  * 移動原理:最初top為0,當檢測到手指向下拖動了10,則dy=10,top=child.getTop()+dy;                  * 如果返回0,則top一直為0,那麼view就在垂直方向就不會移動                  * 如果返回top,則view會一直跟隨手指拖動而移動                  * @param top  top為它認為你想移動到最新的top值                  * @param dy垂直方向移動了多少                  */                 @Override                 public int clampViewPositionVertical(View child,int top, int dy) {                          return 0;                 }                 /**                  * 在這裡決定真正讓view在水平方向移動多少,預設實現則不會移動                  * 移動原理:最初left為0,當檢測到手指向右拖動了10,則dx=10,left=child.getLeft()+dx;                  * 如果返回0,則left一直為0,那麼view就在水平方向就不會移動                  * 如果返回left,則view會一直跟隨手指拖動而移動                  * @param left  left為它認為你想移動到最新的left值                  * @param dx水平方向移動了多少                  */                 @Override                 public int clampViewPositionHorizontal(View child,int left, int dx) {                          return left;                 }                 /**                  * view移動後的回調                  * @param left移動後的view最新的left值                  * @param top移動後的view最新的top值                  * @param dx x方向移動了多少                  * @param dy y方向移動了多少                  */                 @Override                 public void onViewPositionChanged(View changedView,int left, int top,                                   int dx,int dy) {                          super.onViewPositionChanged(changedView,left, top, dx, dy);                 }                 /**                  * 手指抬起回調                  * @param xvel x方向滑動的速度                  * @param yvel y方向滑動的速度                  */                 @Override                 public void onViewReleased(View releasedChild,float xvel, float yvel) {                         super.onViewReleased(releasedChild,xvel, yvel);                         //手指抬起後緩慢移動到指定位置                         if(mainView.getLeft()<menuWidth/2){                           //關閉菜單                           viewDragHelper.smoothSlideViewTo(mainView, 0,                           0);//相當於Scroller的startScroll方法                           ViewCompat.postInvalidateOnAnimation(SlideMenu.this);                         }else {                           //開啟菜單                           viewDragHelper.smoothSlideViewTo(mainView,menuWidth, 0);                           ViewCompat.postInvalidateOnAnimation(SlideMenu.this);                         }                 }         };         /**          * 對Scroller的封裝          */         public void computeScroll() {                 if(viewDragHelper.continueSettling(true)){                          ViewCompat.postInvalidateOnAnimation(this);                 }         }}
View移動總結

綜上所述,當你明白了移動view的原理和api後,不用再去在TouchMove的時候去自己手動移動view,如果對layout方法和scrollTo,scrollBy方法理解不深,就將上面對應代碼複製到自己的demo中去感受下。

由於更多的移動veiw的情況是在ViewGroup中去移動子view,所以一般都用ViewDragHelper去做,這個類的介紹由於篇幅有限,可能對各個方法的理解還不夠透徹,將代碼運行起來並試著去改改效果,多感受一下就明白了。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.