Android自訂控制項來襲(Scroller)

來源:互聯網
上載者:User

Android自訂控制項來襲(Scroller)

先看看

實現方法繼承自ViewGroup需要我們自己來測量,布局,實現滑動的效果,處理滑動衝突,
自訂ViewGroup的一般思路是重寫onMeasure方法,在onMeasure方法中調用measureChild來測量子View,然後調用setMeasuredDimension來測量自己的大小。然後重寫onLayout方法,在onLayout中調用子View的layout方法來確定子View的位置,下面我們先來做好這兩件工作

初始時候我們的Content應該是顯示在螢幕中的,而Menu應該是顯示在螢幕外的。當Menu開啟時,應該是這種樣子的<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="http://www.bkjia.com/uploads/allimg/160414/0426255018-2.png" title="\" />

mMenuRightPadding是Menu距螢幕右側的一個距離,因為我們Menu開啟後,Content還是會留一部分,而不是完全隱藏的

public class MySlidingMenu extends ViewGroup {public MySlidingMenu(Context context) {        this(context, null, 0);    }    public MySlidingMenu(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MySlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        DisplayMetrics metrics = new DisplayMetrics();        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        wm.getDefaultDisplay().getMetrics(metrics);         //擷取螢幕的寬和高        mScreenWidth = metrics.widthPixels;        mScreenHeight = metrics.heightPixels;            //設定Menu距離螢幕右側的距離,convertToDp是將代碼中的100轉換成100dp        mMenuRightPadding = convertToDp(context,100);         } @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //拿到Menu,Menu是第0個孩子        mMenu = (ViewGroup) getChildAt(0);        //拿到Content,Content是第1個孩子        mContent = (ViewGroup) getChildAt(1);        //設定Menu的寬為螢幕的寬度減去Menu距離螢幕右側的距離        mMenuWidth = mMenu.getLayoutParams().width = mScreenWidth - mMenuRightPadding;        //設定Content的寬為螢幕的寬度        mContentWidth = mContent.getLayoutParams().width = mScreenWidth;        //測量Menu        measureChild(mMenu,widthMeasureSpec,heightMeasureSpec);        //測量Content        measureChild(mContent, widthMeasureSpec, heightMeasureSpec);        //測量自己,自己的寬度為Menu寬度加上Content寬度,高度為螢幕高度        setMeasuredDimension(mMenuWidth + mContentWidth, mScreenHeight);    }@Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        //擺放Menu的位置,根據上面圖可以確定上下左右的座標        mMenu.layout(-mMenuWidth, 0, 0, mScreenHeight);        //擺放Content的位置        mContent.layout(0, 0, mScreenWidth, mScreenHeight);    }/**     * 將傳進來的數轉化為dp     */    private int convertToDp(Context context , int num){        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,num,context.getResources().getDisplayMetrics());    }}

目前我們的側滑菜單中的兩個子View的位置應該是這個樣子

接下來我們編寫xml布局檔案
left_menu.xml 左側菜單的布局檔案,是一個ListView

      

其中ListView的Item布局為left_menu_item.xml

        

我們再來編寫內容地區的布局檔案 content.xml 其中有一個header,header中有一個ImageView,這個ImageView是menu的開關,我們點擊他的時候可以自動開關menu,然後header下面也是一個istview

<code class=" hljs xml"><!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--><linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">    <linearlayout android:layout_width="match_parent" android:layout_height="65dp" android:background="#000000" android:gravity="center_vertical" android:orientation="horizontal">        <imageview android:id="@+id/menu_toggle" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/toggle" android:paddingleft="10dp">    </imageview></linearlayout>        <listview android:id="@+id/content_listview" android:layout_width="match_parent" android:layout_height="wrap_content" android:dividerheight="0dp" android:divider="@null" android:scrollbars="none"></listview></linearlayout></code>

content的item的布局檔案為 content_item.xml

<code class=" hljs xml"><!--{cke_protected}{C}%3C!%2D%2D%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%2D%2D%3E--><linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:gravity="center_vertical" android:background="#ffffff" android:layout_height="match_parent">    <imageview android:id="@+id/content_imageview" android:layout_width="80dp" android:layout_height="80dp" android:src="@drawable/content_1" android:layout_margin="20dp">    <textview android:id="@+id/content_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Content - 1" android:textcolor="#000000" android:textsize="20sp"></textview></imageview></linearlayout></code>

在activity_main.xml中,我們將menu和content添加到我們的slidingMenu中

<code class=" hljs avrasm"><relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aaaaaa"><com.example.user.slidingmenu.myslidingmenu android:id="@+id/slidingmenu" android:layout_width="wrap_content" android:layout_height="match_parent">        <include android:id="@+id/menu" layout="@layout/left_menu">        <include android:id="@+id/content" layout="@layout/content"></include></include></com.example.user.slidingmenu.myslidingmenu></relativelayout></code>

現在應該是這種效果

左側菜單是隱藏在螢幕左側外部的,但是現在還不能滑動,如果想要實現滑動功能,我們可以使用View的scrollTo和scrollBy方法,這兩個方法的區別是scrollTo是直接將view移動到指定的位置,scrollBy是相對於當前的位置移動一個位移量,所以我們應該重寫onTouchEvent方法,用來計算出當前手指的一個位移量,然後使用scrollBy方法一點一點的移動,就形成了一個可以跟隨手指移動的view的動畫效果了

在寫代碼之前,我們先掃清一下障礙,我們先來弄清楚這些座標是怎麼回事

好了,把這些座標弄清楚後,我們就簡單多了,下面直接看onTouchEvent方法

@Override    public boolean onTouchEvent(MotionEvent event) {        int action = event.getAction();        switch (action){            case MotionEvent.ACTION_DOWN:                mLastX = (int) event.getX();                mLastY = (int) event.getY();                break;            case MotionEvent.ACTION_MOVE:                int currentX = (int) event.getX();                int currentY = (int) event.getY();                //拿到x方向的位移量                int dx = currentX - mLastX;                if (dx < 0){//向左滑動                    //邊界控制,如果Menu已經完全顯示,再滑動的話                    //Menu左側就會出現白邊了,進行邊界控制                    if (getScrollX() + Math.abs(dx) >= 0) {                        //直接移動到(0,0)位置,不會出現白邊                        scrollTo(0, 0);                    } else {//Menu沒有完全顯示呢                        //其實這裡dx還是-dx,大家不用刻意去記                        //大家可以先使用dx,然後運行一下,發現                        //移動的方向是相反的,那麼果斷這裡加個負號就可以了                        scrollBy(-dx, 0);                    }                }else{//向右滑動                    //邊界控制,如果Content已經完全顯示,再滑動的話                    //Content右側就會出現白邊了,進行邊界控制                    if (getScrollX() - dx <= -mMenuWidth) {                        //直接移動到(-mMenuWidth,0)位置,不會出現白邊                        scrollTo(-mMenuWidth, 0);                    } else {//Content沒有完全顯示呢                        //根據手指移動                        scrollBy(-dx, 0);                    }                }                mLastX = currentX;                mLastY = currentY;                break;        }        return true;    }

現在我們的SlidingMenu依然是不能夠水平滑動的,但是listview可以豎直滑動,原因是我們的SlidingMenu預設是不攔截事件的,那麼事件會傳遞給他的子View去執行,也就是說傳遞給了Content的ListView去執行了,所以listview是可以滑動的,為了簡單,我們先重寫onInterceptTouchEvent方法,我們返回true,讓SlidingMenu攔截事件,我們的SlidingMenu就能夠滑動了,但是ListView是不能滑動的,等下我們會進行滑動衝突的處理,現在先實現SlidingMenu的功能

@Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        return true;    }

我們在構造方法中初始化一個Scroller

public MySlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        ...        mScroller = new Scroller(context);        ...    }

然後重寫computeScroll方法,這個方法是保證Scroller自動滑動的必須方法,這是一個模板方法,到哪裡都這麼些就好了

@Override    public void computeScroll() {        if (mScroller.computeScrollOffset()){            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());            invalidate();        }    }

接著我們在onTouchEvent的ACTION_UP中進行判斷,判斷當前menu開啟了多少

case MotionEvent.ACTION_UP:                if (getScrollX() < -mMenuWidth / 2){//開啟Menu                    //調用startScroll方法,第一個參數是起始X座標,第二個參數                    //是起始Y座標,第三個參數是X方向位移量,第四個參數是Y方向位移量                    mScroller.startScroll(getScrollX(), 0, -mMenuWidth - getScrollX(), 0, 300);                    //設定一個已經開啟的標識,當實現點擊開關自動開啟關閉功能時會用到                    isOpen = true;                    //一定不要忘了調用這個方法重繪,否則沒有動畫效果                    invalidate();                }else{//關閉Menu                    //同上                    mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 300);                    isOpen = false;                    invalidate();                }                break;

關於startScroll中的startX和startY好判斷,那麼dx和dy怎麼計算呢?其實也非常簡單,比如我們startX座標為30,我們想移動到-100,那麼startX+dx = -100 –> dx = -100 - startX –> dx = -130

好了現在我們就可以實現鬆開手指後自動滑動的動畫效果了
現在我們還需要點擊content中左上方的一個三角,如果當前menu沒有開啟,則自動開啟,如果已經開啟,則自動關閉的功能,自動滑動的效果我們要藉助Scroller.startScroll方法

/**     * 點擊開關,開閉Menu,如果當前menu已經開啟,則關閉,如果當前menu已經關閉,則開啟     */    public void toggleMenu(){        if (isOpen){            closeMenu();        }else{            openMenu();        }    }    /**     * 關閉menu     */    private void closeMenu() {        //也是使用startScroll方法,dx和dy的計算方法一樣        mScroller.startScroll(getScrollX(),0,-getScrollX(),0,500);        invalidate();        isOpen = false;    }    /**     * 開啟menu     */    private void openMenu() {        mScroller.startScroll(getScrollX(),0,-mMenuWidth-getScrollX(),0,500);        invalidate();        isOpen = true;    }

處理滑動衝突

由於我們的menu和content是listview,listview是支援豎直滑動的,而我們的slidingMenu是支援水平滑動的,因此會出現滑動的衝突。剛才我們直接在onInterceptTouchEvent中返回了true,因此SlidingMenu就會攔截所有的事件,而ListView接收不到任何的事件,因此ListView不能滑動了,我們要解決這個滑動衝突很簡單,只需要判斷當前是水平滑動還是豎直滑動,如果是水平滑動的話則讓SlidingMenu攔截事件,如果是豎直滑動的話就不攔截事件,把事件交給子View的ListView去執行

@Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        boolean intercept = false;        int x = (int) ev.getX();        int y = (int) ev.getY();        switch (ev.getAction()){            case MotionEvent.ACTION_DOWN:                intercept = false;                break;            case MotionEvent.ACTION_MOVE:                int deltaX = (int) ev.getX() - mLastXIntercept;                int deltaY = (int) ev.getY() - mLastYIntercept;                if (Math.abs(deltaX) > Math.abs(deltaY)){//橫向滑動                    intercept = true;                }else{//縱向滑動                    intercept = false;                }                break;            case MotionEvent.ACTION_UP:                intercept = false;                break;        }        mLastX = x;        mLastY = y;        mLastXIntercept = x;        mLastYIntercept = y;        return intercept;    }

現在就可以了,

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.