自制控件2 —— 自制控件 仿qq側(cè)滑菜單

自定義控件——初識(shí)自定義控件里面飘哨,我們已經(jīng)對(duì)自定義控件進(jìn)行描述和分類统屈。其分類分別是

  • 自制控件
  • 組合控件
  • 拓展控件

這篇博文里面孽拷,我們繼續(xù)進(jìn)行自制控件膜宋。

我們想要繼續(xù)的是一個(gè)簡(jiǎn)單的仿造qq側(cè)滑菜單秋茫。

先來(lái)看一下效果圖

效果圖.gif

在(初識(shí)自定義控件)中乃秀,我們知道了自定義控件分為三種

  • 自制控件
  • 組合控件
  • 拓展控件

在(自制控件1)中,我們自制了一個(gè)開(kāi)關(guān)按鈕View跺讯,這次,我們來(lái)做自制的ViewGroup抬吟,一個(gè)簡(jiǎn)單的仿qq策劃菜單。

在(自制控件1)我們利用View.layout(l,t,r,b)這個(gè)api讓View動(dòng)起來(lái)火本。在本次的側(cè)滑菜單里面擎析,我們使用
ScrollTo和ScrollBy讓View動(dòng)起來(lái)
而且使用Scroller做彈性滑動(dòng)桨醋。

如果對(duì)自制繼承自ViewGroup的控件還沒(méi)有一個(gè)大概的概念现斋,可以通過(guò)(初識(shí)自定義控件)這篇博文里面的demo喜最,進(jìn)行一個(gè)大概的了解。

一庄蹋、造起來(lái)一個(gè)ViewGroup

新建一個(gè)類瞬内,比如叫做SlideMenu迷雪,繼承自ViewGroup

public class SlideMenu extends ViewGroup{
    public SlideMenu(Context context) {
        super(context);
    }
    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    }
}

.
.
然后我們?cè)?activity_main 利用控件的全路徑名引入這個(gè)控件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.amqr.diyslidemenu.MainActivity">
    <com.amqr.diyslidemenu.view.SlideMenu
        android:id="@+id/mSmenu"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        >
    </com.amqr.diyslidemenu.view.SlideMenu>
</RelativeLayout>

.
.

二、弄兩個(gè)布局文件虫蝶,一個(gè)左側(cè)菜單的章咧,一個(gè)是主頁(yè)部分的。SlideView里面把這兩個(gè)布局加載出來(lái)

.
1能真、準(zhǔn)備兩個(gè)布局文件赁严,左側(cè)菜單的布局文件需要控制寬度,這里我們?cè)O(shè)置為200dp

左側(cè)菜單 slide_left.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="match_parent"
    android:background="#ff0000"
    >
    <!--左側(cè)的菜單限定為200dp-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:text="本地"
            android:textSize="26sp"
            android:gravity="center"
            android:layout_marginTop="10dp"
            android:background="#689342"
            />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:text="體育"
            android:textSize="26sp"
            android:gravity="center"
            android:layout_marginTop="10dp"
            android:background="#689342"
            />
    </LinearLayout>
    
</ScrollView>
左側(cè)菜單.png

主頁(yè)部分 slide_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="主頁(yè)內(nèi)容區(qū)域"
        android:layout_gravity="center"
        android:textSize="30dp"
        android:layout_centerInParent="true"
        />
</RelativeLayout>
主頁(yè)部分的.png

2舟陆、引用xml的布局代碼里面include進(jìn)來(lái)左側(cè)菜單和布局文件

這個(gè)include的先后順序需要嚴(yán)格區(qū)分
因?yàn)榇龝?huì)需要結(jié)合SlideView的onFinishInflate相互結(jié)合

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.amqr.diyslidemenu.MainActivity">
    <com.amqr.diyslidemenu.view.SlideMenu
        android:id="@+id/mSmenu"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        >
        <!--這個(gè)include的先后順序需要嚴(yán)格區(qū)分误澳,
       因?yàn)榇龝?huì)需要結(jié)合SlideView的onFinishInflate相互結(jié)合 -->
        <!--左側(cè)菜單-->
        <include layout="@layout/slide_left"/>
        <!--主頁(yè)部分-->
        <include layout="@layout/slide_main"/>
    </com.amqr.diyslidemenu.view.SlideMenu>
</RelativeLayout>

三耻矮、利用SlideView的onFinishInflate方法加載view

利用SlideView的onFinishInflate方法加載view

public class SlideMenu extends ViewGroup{
    private View mLeftMenu;
    private View mMainPage;
    public SlideMenu(Context context) {
        super(context);
    }
    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    // 當(dāng)SlideView被xml引用加載之后完成秦躯,這個(gè)方法就會(huì)調(diào)用。
    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate(); // 裆装?踱承??
        //  getChildAt 作用 Returns the view at the specified position in the group.
        //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
        mLeftMenu = getChildAt(0); // 左側(cè)菜單
        mMainPage = getChildAt(1);
    }
}

看一下onFinishInflate這個(gè)方法哨免,屬于View類下一個(gè)方法

    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     *
     * <p>Even if the subclass overrides onFinishInflate, they should always be
     * sure to call the super method, so that we get called.
     */
    @CallSuper
    protected void onFinishInflate() {
    }

getChildAt的作用是 精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate(); // 茎活??琢唾?
        //  getChildAt 作用 Returns the view at the specified position in the group.
        //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
        mLeftMenu = getChildAt(0); // 左側(cè)菜單
        mMainPage = getChildAt(1);
    }

四载荔、利用onMeasure來(lái)孩子測(cè)量大小

首先記住,不管干嘛采桃,首先先把現(xiàn)在onMeasure里面把setMeasuredDimension方法給寫(xiě)上懒熙。

說(shuō)在測(cè)量之前的第一點(diǎn)

我們的ViewGroup也是View這點(diǎn)我們都知道,其實(shí)到最終普办,ViewGroup到最后還是給他的父親調(diào)用工扎,他的父親就是使用使用measure來(lái)測(cè)量ViewGroup的大小的。說(shuō)了這么多衔蹲,我們還是看一下代碼吧肢娘。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.amqr.diyslidemenu.MainActivity">



    <!--這個(gè)ViewGroup,他的父親就是上面的RelativeLayout舆驶,RelativeLayout就是使用measure來(lái)測(cè)量這個(gè)SlideView的大小的-->
    <com.amqr.diyslidemenu.view.SlideMenu
        android:id="@+id/mSmenu"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        >
        <!--這個(gè)include的先后順序需要嚴(yán)格區(qū)分橱健,
       因?yàn)榇龝?huì)需要結(jié)合SlideView的onFinishInflate相互結(jié)合 -->
        <!--左側(cè)菜單-->
        <include layout="@layout/slide_left"/>
        <!--主頁(yè)部分-->
        <include layout="@layout/slide_main"/>
    </com.amqr.diyslidemenu.view.SlideMenu>
</RelativeLayout>

這個(gè)SlideMenu是ViewGroup,他的父親就是上面的RelativeLayout沙廉,RelativeLayout就是使用measure來(lái)測(cè)量這個(gè)SlideView的大小的

明白了onMeasure是給measure調(diào)用的之后畴博,我們就應(yīng)該清楚地知道,onMeasure是父親給孩子用的寬高(父親把自己所能給的都給了蓝仲,也就是最大的俱病,我們可以采用父親的寬高官疲,我們可以自己指定寬高。但是孩子的自由發(fā)揮的空間沒(méi)有辦法超出父親所能給的最大值亮隙,但是可以比父親型举臁)

說(shuō)在測(cè)量之前的第二點(diǎn)

怎么得到一個(gè)View在xml布局文件里面寬?
利用view.getLayoutParams().width,高類似

有了前面的兩點(diǎn)說(shuō)明溢吻,現(xiàn)在我們可以真正式在SlideMenu里面復(fù)寫(xiě)onMeasere方法并且進(jìn)行測(cè)量了

代碼如下:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // makeMeasureSpec 的時(shí)候维费,第一個(gè)參數(shù)是大小,第二個(gè)參數(shù)是模式
        // 寬度我們使用左側(cè)菜單的在xml里面的200dp促王,因?yàn)橹付ù笮∷阅J绞荕easureSpec.EXACTLY
        // 怎么得到一個(gè)View在xml布局文件里面寬,利用view.getLayoutParams().width,高類似
        int leftViewMeasureSpecWidth = MeasureSpec.
                makeMeasureSpec(mLeftMenu.getLayoutParams().width, MeasureSpec.EXACTLY);
        //左側(cè)菜單的的高度我們希望填充父窗體颤专,而當(dāng)前onMeasure里面的heightMeasureSpec根據(jù)我們的布局顯然就是填充父窗體
        // 所以一直接用父親傳過(guò)來(lái)的這個(gè)32位參數(shù)就好
        mLeftMenu.measure(leftViewMeasureSpecWidth,heightMeasureSpec);
        // 至于主頁(yè)頁(yè)面,我們的希望他寬高都是填充父窗體簇捍,所以直接用onMeasure里面?zhèn)鬟^(guò)來(lái)的參數(shù)就好啦
        mMainPage.measure(widthMeasureSpec,heightMeasureSpec);
        // onMeasure一開(kāi)始什么都不管就應(yīng)該復(fù)寫(xiě)setMeasuredDimension,不然報(bào)錯(cuò)。
        // 除非我們的自定義控件是寬高都是填充父窗體么鹤,那么我們就留著下面這句super的代碼就可以
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
    }

關(guān)于測(cè)量的View的measure方法的參數(shù)可以參考(初識(shí)自定義控件)

五、利用onLayout給自孩子擺放位置

在onMeasure里面給孩子布局采用的方法是 child.measure方法
利用onLayout給自己孩子擺放位置相應(yīng)來(lái)說(shuō)用的是 child.layout方法

擺放之前,了解getMeasuredWidth();和getWidth()的區(qū)別

我們下面說(shuō)的layout的前提是已經(jīng)進(jìn)行onMeasure被執(zhí)行之后(onMeasure里面必須執(zhí)行setMeasuredDimension)
getWidth()必須在控件的 layout(l,t,r,b) 被執(zhí)行過(guò)后才能獲取到有效的值瓤荔,也就在View被繪制好之后才有效
getMeasuredWidth(); 是 layout(l,t,r,b)執(zhí)行之前就會(huì)獲取到View的寬度的腔丧,也就是在View被繪制好之前就可以生效的衣厘。

先測(cè)量型宙,后擺放搁嗓。

也就是 先onMeasure棍矛,后onLayout慨绳。

有了這些了解战秋,我們可以來(lái)很好地?cái)[放位置了

代碼如下

 @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int leftViewWidth = mLeftMenu.getMeasuredWidth();
        int leftViewHeight = mLeftMenu.getMeasuredHeight();
        // l t r b  左上右下  左上一點(diǎn)疯搅,右下一點(diǎn)礁蔗,兩點(diǎn)確定了一個(gè)矩形的大小
        mLeftMenu.layout(-leftViewWidth,0,0,leftViewHeight);
        int mainViewWidth = mMainPage.getMeasuredWidth();
        int mainViewHeight = mMainPage.getMeasuredHeight();
        mMainPage.layout(0,0,mainViewWidth,mainViewHeight);
    }

到此為止先停一下,看一下SlideView里面目前的代碼:

SlideMenu

public class SlideMenu extends ViewGroup{
    private View mLeftMenu;
    private View mMainPage;
    public SlideMenu(Context context) {
        super(context);
    }
    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    // 當(dāng)SlideView被xml引用加載之后完成呵曹,這個(gè)方法就會(huì)調(diào)用。
    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate(); // 民假?宰衙?考余?
        //  getChildAt 作用 Returns the view at the specified position in the group.
        //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
        mLeftMenu = getChildAt(0); // 左側(cè)菜單
        mMainPage = getChildAt(1);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // makeMeasureSpec 的時(shí)候,第一個(gè)參數(shù)是大小酥筝,第二個(gè)參數(shù)是模式
        // 寬度我們使用左側(cè)菜單的在xml里面的200dp滚躯,因?yàn)橹付ù笮∷阅J绞荕easureSpec.EXACTLY
        // 怎么得到一個(gè)View在xml布局文件里面寬,利用view.getLayoutParams().width,高類似
        int leftViewMeasureSpecWidth = MeasureSpec.
                makeMeasureSpec(mLeftMenu.getLayoutParams().width, MeasureSpec.EXACTLY);
        //左側(cè)菜單的的高度我們希望填充父窗體,而當(dāng)前onMeasure里面的heightMeasureSpec根據(jù)我們的布局顯然就是填充父窗體
        // 所以一直接用父親傳過(guò)來(lái)的這個(gè)32位參數(shù)就好
        mLeftMenu.measure(leftViewMeasureSpecWidth,heightMeasureSpec);
        // 至于主頁(yè)頁(yè)面,我們的希望他寬高都是填充父窗體掸掏,所以直接用onMeasure里面?zhèn)鬟^(guò)來(lái)的參數(shù)就好啦
        mMainPage.measure(widthMeasureSpec,heightMeasureSpec);
        // onMeasure一開(kāi)始什么都不管就應(yīng)該復(fù)寫(xiě)setMeasuredDimension茁影,不然報(bào)錯(cuò)。
        // 除非我們的自定義控件是寬高都是填充父窗體丧凤,那么我們就留著下面這句super的代碼就可以
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int leftViewWidth = mLeftMenu.getMeasuredWidth();
        int leftViewHeight = mLeftMenu.getMeasuredHeight();
        Log.d("Slide","getWidth:"+mLeftMenu.getWidth()+"   mLeftMenu.getHeight():"+mLeftMenu.getHeight());
        Log.d("Slide","getMeasuredWidth:"+mLeftMenu.getMeasuredWidth()+"   getMeasuredHeight:"+mLeftMenu.getMeasuredHeight());
        // l t r b  左上右下  左上一點(diǎn)募闲,右下一點(diǎn),兩點(diǎn)確定了一個(gè)矩形的大小
        mLeftMenu.layout(-leftViewWidth,0,0,leftViewHeight);
        int mainViewWidth = mMainPage.getMeasuredWidth();
        int mainViewHeight = mMainPage.getMeasuredHeight();
        mMainPage.layout(0, 0, mainViewWidth, mainViewHeight);
    }
}

MainActivity

package com.amqr.diyslidemenu;

import android.app.Activity;

import android.os.Bundle;

public class MainActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }

}

運(yùn)行效果:

當(dāng)前還無(wú)法拉動(dòng)愿待,但是我們已經(jīng)過(guò)能讓界面顯示出來(lái)了浩螺。

暫時(shí)效果.png

六、開(kāi)始做移動(dòng)效果

移動(dòng)的方式有很多種仍侥,這次我們采用的是ScrollTo+ScrollBy方式

說(shuō)明1: ScrollTo和ScrollBy 的了解

ScrollTo和ScrollBy就是手機(jī)屏幕的左上角動(dòng)年扩,而不是View或者ViewGroup動(dòng)。

區(qū)別是访圃,ScrollTo每次都是都是想比較于最開(kāi)始的左上角(0,0)
ScrollBy每次的移動(dòng)是累計(jì)的

比如厨幻,調(diào)用ScollTo(20,0),的時(shí)候矛市,那么手機(jī)屏幕回向右移動(dòng)20個(gè)單位悟衩,但是再次調(diào)用ScollTo(20,0),的時(shí)候是不動(dòng)的怠蹂,因?yàn)槊看味际歉铋_(kāi)始的(0剑梳,0)做比較节预;然后我們調(diào)用ScrollTo(-20,0)的時(shí)候赡艰,就回到最開(kāi)的原點(diǎn)棉胀。

ScrollBy是累計(jì)的挤巡,第一次調(diào)用ScrollBy(20,0),向右移動(dòng)20 個(gè)單位徽鼎,再次調(diào)用ScrollBy(20,0)盛末,那么屏幕的左上角就會(huì)移動(dòng)到(40,0)的位置,因?yàn)槔塾?jì)嘛否淤。

說(shuō)明2: getX和getRawX的了解

getX()是觸摸的點(diǎn)與控件自身的距離
getRawX()是觸摸的點(diǎn)與屏幕的距離
結(jié)論:當(dāng)你觸到按鈕時(shí)悄但,x,y是相對(duì)于該按鈕左上點(diǎn)(控件本身)的相對(duì)位置。而rawx,rawy始終是相對(duì)于屏幕的位置石抡。

圖片摘自csdn

說(shuō)明3:getScrollX()和getScrollY()的了解

getScrollX(): 手機(jī)屏幕顯示區(qū)域左上角 與 你指定的View的左上角的橫向距離getScrollY(): 手機(jī)屏幕顯示區(qū)域左上角 與 你指定的View的左上角的垂直距離(因?yàn)樽右晥D的高度和手機(jī)屏幕高度一樣)

六.1檐嚣、簡(jiǎn)單的移動(dòng),不會(huì)產(chǎn)生越界現(xiàn)象

public class SlideMenu extends ViewGroup{
    private View mLeftMenu;
    private View mMainPage;
    private int downX;
    public SlideMenu(Context context) {
        super(context);
    }
    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    // 當(dāng)SlideView被xml引用加載之后完成啰扛,這個(gè)方法就會(huì)調(diào)用嚎京。
    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate(); // ?隐解?鞍帝?
        //  getChildAt 作用 Returns the view at the specified position in the group.
        //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
        mLeftMenu = getChildAt(0); // 左側(cè)菜單
        mMainPage = getChildAt(1);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // makeMeasureSpec 的時(shí)候,第一個(gè)參數(shù)是大小煞茫,第二個(gè)參數(shù)是模式
        // 寬度我們使用左側(cè)菜單的在xml里面的200dp帕涌,因?yàn)橹付ù笮∷阅J绞荕easureSpec.EXACTLY
        // 怎么得到一個(gè)View在xml布局文件里面寬岩臣,利用view.getLayoutParams().width,高類似
        int leftViewMeasureSpecWidth = MeasureSpec.
                makeMeasureSpec(mLeftMenu.getLayoutParams().width, MeasureSpec.EXACTLY);
        //左側(cè)菜單的的高度我們希望填充父窗體,而當(dāng)前onMeasure里面的heightMeasureSpec根據(jù)我們的布局顯然就是填充父窗體
        // 所以一直接用父親傳過(guò)來(lái)的這個(gè)32位參數(shù)就好
        mLeftMenu.measure(leftViewMeasureSpecWidth,heightMeasureSpec);
        // 至于主頁(yè)頁(yè)面,我們的希望他寬高都是填充父窗體宵膨,所以直接用onMeasure里面?zhèn)鬟^(guò)來(lái)的參數(shù)就好啦
        mMainPage.measure(widthMeasureSpec,heightMeasureSpec);
        // onMeasure一開(kāi)始什么都不管就應(yīng)該復(fù)寫(xiě)setMeasuredDimension架谎,不然報(bào)錯(cuò)。
        // 除非我們的自定義控件是寬高都是填充父窗體辟躏,那么我們就留著下面這句super的代碼就可以
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int leftViewWidth = mLeftMenu.getMeasuredWidth();
        int leftViewHeight = mLeftMenu.getMeasuredHeight();
        Log.d("Slide","getWidth:"+mLeftMenu.getWidth()+"   mLeftMenu.getHeight():"+mLeftMenu.getHeight());
        Log.d("Slide","getMeasuredWidth:"+mLeftMenu.getMeasuredWidth()+"   getMeasuredHeight:"+mLeftMenu.getMeasuredHeight());
        // l t r b  左上右下  左上一點(diǎn)谷扣,右下一點(diǎn),兩點(diǎn)確定了一個(gè)矩形的大小
        mLeftMenu.layout(-leftViewWidth,0,0,leftViewHeight);
        int mainViewWidth = mMainPage.getMeasuredWidth();
        int mainViewHeight = mMainPage.getMeasuredHeight();
        mMainPage.layout(0, 0, mainViewWidth, mainViewHeight);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //return super.onTouchEvent(event);
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                downX = (int)(event.getX()+0.5f);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int)(event.getX()+0.5f);
                // 用減法捎琐,比如按下是0会涎,最終移動(dòng)到了20,那么屏幕向左邊移動(dòng)20個(gè)單位瑞凑,左側(cè)菜單就可顯示出來(lái)了
                int distanceX =  downX - moveX;
                int scrollX = getScrollX();  // 注意getScaleX()不要寫(xiě)成getScaleX()
                // 解決越界問(wèn)題
                if(scrollX+distanceX < (-mLeftMenu.getMeasuredWidth())){ // 左側(cè)越界臨界點(diǎn)
                    scrollTo(-mLeftMenu.getMeasuredWidth(),0);
                }else if(scrollX+distanceX>0){ // 右側(cè)越界臨界點(diǎn)
                    scrollTo(0,0);
                }else{  // 在兩個(gè)臨界點(diǎn)之間的可移動(dòng)范圍
                    scrollBy(distanceX,0);
                }
                downX = moveX;
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        //關(guān)鍵一步末秃,返回true,代表消費(fèi)當(dāng)前的偷吃事件
        return true;
    }
}
簡(jiǎn)單的移動(dòng)籽御,不會(huì)產(chǎn)生越界現(xiàn)象.gif

六.2练慕、判斷松手后應(yīng)該停留在哪一個(gè)界面

原理圖.png

做的事情很簡(jiǎn)單,其實(shí)也就是在 case MotionEvent.ACTION_UP: 里面添加幾行代碼

   case MotionEvent.ACTION_UP:
        int upScrollX = getScrollX();
        if(upScrollX<-(mLeftMenu.getMeasuredWidth()/2)){
            scrollTo(-mLeftMenu.getMeasuredWidth(),0);
        }else{
            scrollTo(0,0);
        }
        break;
判斷松手后應(yīng)該停留在哪一個(gè)界面.gif

六.3技掏、使用Scoller彈性滑動(dòng)铃将,讓滑動(dòng)產(chǎn)生過(guò)渡效果

自定義View做動(dòng)畫(huà)有很多做法,Scroller是其中一種哑梳,也是我們這次要采用的做法劲阎。

Scoller彈性滑動(dòng)是使用過(guò)程:
1、實(shí)例化Scroller
實(shí)例化一個(gè)Scroller

@Override
protected void onFinishInflate() {
    super.onFinishInflate(); 
    //  getChildAt 作用 Returns the view at the specified position in the group.
    //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
    mLeftMenu = getChildAt(0); // 左側(cè)菜單
    mMainPage = getChildAt(1);

    scroller = new Scroller(getContext()); // 實(shí)例化一個(gè)Scroll鸠真,需要context
}

2悯仙、調(diào)用startScroll(startX, startY, dx, dy, durationTime);模擬數(shù)據(jù)變化,接著調(diào)用invalidate(); 觸發(fā)computeScroll()

注意點(diǎn):startScroll 只是模擬數(shù)據(jù)的變化吠卷,想要看到效果還需要調(diào)用invalidate重新刷新UI锡垄,其實(shí)就是調(diào)用onDraw

注意點(diǎn):invalidate();經(jīng)過(guò)輾轉(zhuǎn)會(huì)去computeScroll();
invalidate(); ---> draw()-->onDraw()--> computeScroll();

3、在computeScroll()里面復(fù)寫(xiě)真正讓讓模擬數(shù)據(jù)生效的代碼,調(diào)用invalidate()

注意點(diǎn):
scroller.computeScrollOffset()
computeScrollOffset()為true代表模擬數(shù)據(jù)還沒(méi)有完成

注意點(diǎn):scroller.getCurrX()當(dāng)前時(shí)刻正模擬到的數(shù)據(jù)

代碼如下(其實(shí)這幾乎是模板代碼):

@Override
    public void computeScroll() { // 如果數(shù)據(jù)模擬沒(méi)有完成撤嫩,那么繼續(xù)更新
        //super.computeScroll();
        // computeScrollOffset
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),0);
            invalidate(); // 注意這里還調(diào)用了invalidate偎捎,這樣才會(huì)產(chǎn)生效果
        }
    }

.
.
上面幾點(diǎn)已經(jīng)說(shuō)完,具體看看在代碼中怎么使用吧

computeScroll();和computeScrollOffset()的結(jié)合使用

        case MotionEvent.ACTION_UP:
            int upScrollX = getScrollX();
            choosePage(upScrollX<-(mLeftMenu.getMeasuredWidth()/2));
            break;
    }
    //關(guān)鍵一步序攘,返回true,代表消費(fèi)當(dāng)前的偷吃事件
    return true;
}
private void choosePage(boolean isMainPage){
    if(isMainPage){
        startScrollNow(-mLeftMenu.getMeasuredWidth());
        //scrollTo(0,0); 可以實(shí)現(xiàn)但是無(wú)動(dòng)畫(huà)過(guò)渡
    }else{
        startScrollNow(0);
        //scrollTo(-mLeftMenu.getMeasuredWidth(),0);  可以實(shí)現(xiàn)但是無(wú)動(dòng)畫(huà)過(guò)渡
    }
}
private void startScrollNow(int endX){
    int startX = getScrollX();  // 起始X
    int startY = 0;  // 起始Y
    int dx = endX - startX;   // X方向的增量值寻拂,可以理解為距離
    int dy = 0;   // Y方向的增量值程奠,可以理解為距離
    int time = Math.abs(dx) * 10;
    int durationTime = (time>600)?600:time;
    //startScroll(int startX, int startY, int dx, int dy, int duration)
    // 注意: startScroll 只是模擬數(shù)據(jù)的變化,想要看到效果還需要調(diào)用invalidate重新刷新UI祭钉,其實(shí)就是調(diào)用onDraw
    scroller.startScroll(startX, startY, dx, dy, durationTime);
    invalidate();  // 關(guān)鍵代碼瞄沙,invalidate和computeScroll才會(huì)有動(dòng)畫(huà)效果,scroller.startScroll只是模擬數(shù)據(jù)
}
@Override
public void computeScroll() { // 如果數(shù)據(jù)模擬沒(méi)有完成,那么繼續(xù)更新
    //super.computeScroll();
    // computeScrollOffset
    if(scroller.computeScrollOffset()){
        scrollTo(scroller.getCurrX(),0);
        invalidate(); // 注意這里還調(diào)用了invalidate,這樣才會(huì)產(chǎn)生效果
    }
}
彈性滑動(dòng).gif

六距境、4申尼、添加是 否處于主頁(yè)的方法 和 展示那個(gè)頁(yè)面的方法

其實(shí)也就是添加了isAtMainPage、showMainPage()和showLeftPage()這三個(gè)方法


public class SlideMenu extends ViewGroup{
    private static final int MAIN_PAGE = 0;
    private static final int LEFT_PAGE = 1;
    private View mLeftMenu;
    private View mMainPage;
    private int downX;
    private Scroller scroller;
    private int pageIndex;
    public SlideMenu(Context context) {
        super(context);
    }
    public SlideMenu(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    // 當(dāng)SlideView被xml引用加載之后完成垫桂,這個(gè)方法就會(huì)調(diào)用师幕。
    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate(); // ?诬滩?霹粥?
        //  getChildAt 作用 Returns the view at the specified position in the group.
        //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
        mLeftMenu = getChildAt(0); // 左側(cè)菜單
        mMainPage = getChildAt(1);
        scroller = new Scroller(getContext()); // 實(shí)例化一個(gè)Scroll,需要context
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // makeMeasureSpec 的時(shí)候疼鸟,第一個(gè)參數(shù)是大小后控,第二個(gè)參數(shù)是模式
        // 寬度我們使用左側(cè)菜單的在xml里面的200dp,因?yàn)橹付ù笮∷阅J绞荕easureSpec.EXACTLY
        // 怎么得到一個(gè)View在xml布局文件里面寬空镜,利用view.getLayoutParams().width,高類似
        int leftViewMeasureSpecWidth = MeasureSpec.
                makeMeasureSpec(mLeftMenu.getLayoutParams().width, MeasureSpec.EXACTLY);
        //左側(cè)菜單的的高度我們希望填充父窗體浩淘,而當(dāng)前onMeasure里面的heightMeasureSpec根據(jù)我們的布局顯然就是填充父窗體
        // 所以一直接用父親傳過(guò)來(lái)的這個(gè)32位參數(shù)就好
        mLeftMenu.measure(leftViewMeasureSpecWidth,heightMeasureSpec);
        // 至于主頁(yè)頁(yè)面,我們的希望他寬高都是填充父窗體,所以直接用onMeasure里面?zhèn)鬟^(guò)來(lái)的參數(shù)就好啦
        mMainPage.measure(widthMeasureSpec, heightMeasureSpec);
        // onMeasure一開(kāi)始什么都不管就應(yīng)該復(fù)寫(xiě)setMeasuredDimension吴攒,不然報(bào)錯(cuò)馋袜。
        // 除非我們的自定義控件是寬高都是填充父窗體,那么我們就留著下面這句super的代碼就可以
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int leftViewWidth = mLeftMenu.getMeasuredWidth();
        int leftViewHeight = mLeftMenu.getMeasuredHeight();
        Log.d("Slide", "getWidth:" + mLeftMenu.getWidth() + "   mLeftMenu.getHeight():" + mLeftMenu.getHeight());
        Log.d("Slide", "getMeasuredWidth:" + mLeftMenu.getMeasuredWidth() + "   getMeasuredHeight:" + mLeftMenu.getMeasuredHeight());
        // l t r b  左上右下  左上一點(diǎn)舶斧,右下一點(diǎn)欣鳖,兩點(diǎn)確定了一個(gè)矩形的大小
        mLeftMenu.layout(-leftViewWidth,0,0,leftViewHeight);
        int mainViewWidth = mMainPage.getMeasuredWidth();
        int mainViewHeight = mMainPage.getMeasuredHeight();
        mMainPage.layout(0, 0, mainViewWidth, mainViewHeight);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //return super.onTouchEvent(event);
        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                downX = (int)(event.getX()+0.5f);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int)(event.getX()+0.5f);
                // 用減法,比如按下是0茴厉,最終移動(dòng)到了20泽台,那么屏幕向左邊移動(dòng)20個(gè)單位,左側(cè)菜單就可顯示出來(lái)了
                int distanceX =  downX - moveX;
                int scrollX = getScrollX();  // 注意getScaleX()不要寫(xiě)成getScaleX()
                // 解決越界問(wèn)題
                if(scrollX+distanceX < (-mLeftMenu.getMeasuredWidth())){ // 左側(cè)越界臨界點(diǎn)
                    scrollTo(-mLeftMenu.getMeasuredWidth(),0);
                }else if(scrollX+distanceX>0){ // 右側(cè)越界臨界點(diǎn)
                    scrollTo(0,0);
                }else{  // 在兩個(gè)臨界點(diǎn)之間的可移動(dòng)范圍
                    scrollBy(distanceX,0);
                }
                downX = moveX;
                break;
            case MotionEvent.ACTION_UP:
                int upScrollX = getScrollX();
                choosePage(upScrollX<-(mLeftMenu.getMeasuredWidth()/2));
                break;
        }
        //關(guān)鍵一步矾缓,返回true怀酷,代表消費(fèi)當(dāng)前的touch事件
        return true;
    }
    private void choosePage(boolean isMainPage){
        if(isMainPage){
            pageIndex = MAIN_PAGE;
            startScrollNow(-mLeftMenu.getMeasuredWidth());
            //scrollTo(0,0); 可以實(shí)現(xiàn)但是無(wú)動(dòng)畫(huà)過(guò)渡
        }else{
            pageIndex = LEFT_PAGE;
            startScrollNow(0);
            //scrollTo(-mLeftMenu.getMeasuredWidth(),0);  可以實(shí)現(xiàn)但是無(wú)動(dòng)畫(huà)過(guò)渡
        }
    }
    private void startScrollNow(int endX){
        int startX = getScrollX();  // 起始X
        int startY = 0;  // 起始Y
        int dx = endX - startX;   // X方向的增量值,可以理解為距離
        int dy = 0;   // Y方向的增量值嗜闻,可以理解為距離
        int time = Math.abs(dx) * 10;
        int durationTime = (time>600)?600:time;
        //startScroll(int startX, int startY, int dx, int dy, int duration)
        // 注意: startScroll 只是模擬數(shù)據(jù)的變化蜕依,想要看到效果還需要調(diào)用invalidate重新刷新UI,其實(shí)就是調(diào)用onDraw
        scroller.startScroll(startX, startY, dx, dy, durationTime);
        invalidate();  // 關(guān)鍵代碼琉雳,invalidate和computeScroll才會(huì)有動(dòng)畫(huà)效果,scroller.startScroll只是模擬數(shù)據(jù)
    }
    @Override
    public void computeScroll() { // 如果數(shù)據(jù)模擬沒(méi)有完成样眠,那么繼續(xù)更新
        //super.computeScroll();
        // computeScrollOffset
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),0);
            invalidate(); // 注意這里還調(diào)用了invalidate,這樣才會(huì)產(chǎn)生效果
        }
    }
    public boolean isAtMainPage(){
        return (pageIndex == MAIN_PAGE)?true:false;
    }
    public void showMainPage(){
        choosePage(true);
    }
    public void showLeftPage(){
        choosePage(false);
    }
}
頁(yè)面判斷.gif

六翠肘、5檐束、我們發(fā)現(xiàn)當(dāng)我們滑動(dòng)左側(cè)菜單的時(shí)候,無(wú)法拉動(dòng)菜單束倍。

這就涉及到一個(gè)View的傳遞機(jī)制了被丧。

關(guān)于View的傳遞機(jī)制盟戏,可以參考文章 ——————————

為什么拉動(dòng)左側(cè)的菜單無(wú)法拖動(dòng),這肯定是屬于SlideMenu的左側(cè)菜單在拉動(dòng)的時(shí)候甥桂,SlideMenu的onTouchEvent沒(méi)有被執(zhí)行柿究,為了確保我們的SlideMenu不管是在左側(cè)菜單還是主頁(yè)菜單的都能夠順利拖動(dòng),我們就在SlideMenu里面復(fù)寫(xiě) onInterceptTouchEvent 方法黄选,然后判斷一下蝇摸,如果是橫向滑動(dòng),就攔截下來(lái)糕簿,自己就消費(fèi)掉這個(gè)touch

其實(shí)也就只是添加這么一小段代碼

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    switch (action){
        case MotionEvent.ACTION_DOWN:
            interDownX = (int)(ev.getX()+0.5f);
            interDownY = (int)(ev.getY()+0.5f);
            break;
        case MotionEvent.ACTION_MOVE:
            interMoveX = (int)(ev.getX()+0.5f);
            interMoveY = (int)(ev.getY()+0.5f);
            int diatanceInterX = Math.abs(interMoveX - interDownX);
            int distanceInterY = Math.abs(interMoveY - interDownY);
            if(diatanceInterX > distanceInterY){ // 代表是水平滑動(dòng)探入,(水平滑動(dòng)的距離比垂直滑動(dòng)的距離大)
                return true; // 攔截之后就肯定執(zhí)行onTouchEvent
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
    return super.onInterceptTouchEvent(ev); // 如果不是水平滑動(dòng)就不攔截
}
完成.gif

好啦,該做的差不多都做啦懂诗。

最后附上所有代碼:
SlideMenu完整代碼

public class SlideMenu extends ViewGroup{


    private static final int MAIN_PAGE = 0;
    private static final int LEFT_PAGE = 1;
    private View mLeftMenu;
    private View mMainPage;
    private int downX;
    private Scroller scroller;
    private int pageIndex;
    private int interDownX;
    private int interDownY;
    private int interMoveX;
    private int interMoveY;

    public SlideMenu(Context context) {
        super(context);
    }

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

    public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    // 當(dāng)SlideView被xml引用加載之后完成蜂嗽,這個(gè)方法就會(huì)調(diào)用。
    /**
     * Finalize inflating a view from XML.  This is called as the last phase
     * of inflation, after all child views have been added.
     */

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate(); // 殃恒?植旧??

        //  getChildAt 作用 Returns the view at the specified position in the group.
        //  精確地返回在ViewGroup里面的View的位置 所以我們的include順序很重要
        mLeftMenu = getChildAt(0); // 左側(cè)菜單
        mMainPage = getChildAt(1);
        scroller = new Scroller(getContext()); // 實(shí)例化一個(gè)Scroll离唐,需要context

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // makeMeasureSpec 的時(shí)候病附,第一個(gè)參數(shù)是大小,第二個(gè)參數(shù)是模式
        // 寬度我們使用左側(cè)菜單的在xml里面的200dp亥鬓,因?yàn)橹付ù笮∷阅J绞荕easureSpec.EXACTLY

        // 怎么得到一個(gè)View在xml布局文件里面寬完沪,利用view.getLayoutParams().width,高類似
        int leftViewMeasureSpecWidth = MeasureSpec.
                makeMeasureSpec(mLeftMenu.getLayoutParams().width, MeasureSpec.EXACTLY);

        //左側(cè)菜單的的高度我們希望填充父窗體,而當(dāng)前onMeasure里面的heightMeasureSpec根據(jù)我們的布局顯然就是填充父窗體
        // 所以一直接用父親傳過(guò)來(lái)的這個(gè)32位參數(shù)就好
        mLeftMenu.measure(leftViewMeasureSpecWidth,heightMeasureSpec);

        // 至于主頁(yè)頁(yè)面,我們的希望他寬高都是填充父窗體嵌戈,所以直接用onMeasure里面?zhèn)鬟^(guò)來(lái)的參數(shù)就好啦
        mMainPage.measure(widthMeasureSpec, heightMeasureSpec);

        // onMeasure一開(kāi)始什么都不管就應(yīng)該復(fù)寫(xiě)setMeasuredDimension覆积,不然報(bào)錯(cuò)。
        // 除非我們的自定義控件是寬高都是填充父窗體熟呛,那么我們就留著下面這句super的代碼就可以
        // super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int leftViewWidth = mLeftMenu.getMeasuredWidth();
        int leftViewHeight = mLeftMenu.getMeasuredHeight();

        Log.d("Slide", "getWidth:" + mLeftMenu.getWidth() + "   mLeftMenu.getHeight():" + mLeftMenu.getHeight());
        Log.d("Slide", "getMeasuredWidth:" + mLeftMenu.getMeasuredWidth() + "   getMeasuredHeight:" + mLeftMenu.getMeasuredHeight());

        // l t r b  左上右下  左上一點(diǎn)宽档,右下一點(diǎn),兩點(diǎn)確定了一個(gè)矩形的大小
        mLeftMenu.layout(-leftViewWidth,0,0,leftViewHeight);


        int mainViewWidth = mMainPage.getMeasuredWidth();
        int mainViewHeight = mMainPage.getMeasuredHeight();

        mMainPage.layout(0, 0, mainViewWidth, mainViewHeight);

    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        int action = ev.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:
                interDownX = (int)(ev.getX()+0.5f);
                interDownY = (int)(ev.getY()+0.5f);

                break;
            case MotionEvent.ACTION_MOVE:
                interMoveX = (int)(ev.getX()+0.5f);
                interMoveY = (int)(ev.getY()+0.5f);

                int diatanceInterX = Math.abs(interMoveX - interDownX);
                int distanceInterY = Math.abs(interMoveY - interDownY);

                if(diatanceInterX > distanceInterY){ // 代表是水平滑動(dòng)庵朝,(水平滑動(dòng)的距離比垂直滑動(dòng)的距離大)
                    return true; // 攔截之后就肯定執(zhí)行onTouchEvent
                }
                break;

            case MotionEvent.ACTION_UP:

                break;

        }
        return super.onInterceptTouchEvent(ev); // 如果不是水平滑動(dòng)就不攔截
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //return super.onTouchEvent(event);

        int action = event.getAction();
        switch (action){
            case MotionEvent.ACTION_DOWN:

                downX = (int)(event.getX()+0.5f);

                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int)(event.getX()+0.5f);

                // 用減法吗冤,比如按下是0,最終移動(dòng)到了20九府,那么屏幕向左邊移動(dòng)20個(gè)單位椎瘟,左側(cè)菜單就可顯示出來(lái)了
                int distanceX =  downX - moveX;

                int scrollX = getScrollX();  // 注意getScaleX()不要寫(xiě)成getScaleX()

                // 解決越界問(wèn)題
                if(scrollX+distanceX < (-mLeftMenu.getMeasuredWidth())){ // 左側(cè)越界臨界點(diǎn)
                    scrollTo(-mLeftMenu.getMeasuredWidth(),0);
                }else if(scrollX+distanceX>0){ // 右側(cè)越界臨界點(diǎn)
                    scrollTo(0,0);
                }else{  // 在兩個(gè)臨界點(diǎn)之間的可移動(dòng)范圍
                    scrollBy(distanceX,0);
                }

                downX = moveX;
                break;

            case MotionEvent.ACTION_UP:
                int upScrollX = getScrollX();
                choosePage(upScrollX<-(mLeftMenu.getMeasuredWidth()/2));
                break;
        }
        //關(guān)鍵一步,返回true昔逗,代表消費(fèi)當(dāng)前的touch事件
        return true;

    }


    private void choosePage(boolean isMainPage){
        if(isMainPage){
            pageIndex = MAIN_PAGE;
            startScrollNow(-mLeftMenu.getMeasuredWidth());
            //scrollTo(0,0); 可以實(shí)現(xiàn)但是無(wú)動(dòng)畫(huà)過(guò)渡
        }else{
            pageIndex = LEFT_PAGE;
            startScrollNow(0);
            //scrollTo(-mLeftMenu.getMeasuredWidth(),0);  可以實(shí)現(xiàn)但是無(wú)動(dòng)畫(huà)過(guò)渡
        }


    }

    private void startScrollNow(int endX){
        int startX = getScrollX();  // 起始X
        int startY = 0;  // 起始Y
        int dx = endX - startX;   // X方向的增量值降传,可以理解為距離
        int dy = 0;   // Y方向的增量值,可以理解為距離

        int time = Math.abs(dx) * 10;
        int durationTime = (time>600)?600:time;

        //startScroll(int startX, int startY, int dx, int dy, int duration)
        // 注意: startScroll 只是模擬數(shù)據(jù)的變化勾怒,想要看到效果還需要調(diào)用invalidate重新刷新UI婆排,其實(shí)就是調(diào)用onDraw
        scroller.startScroll(startX, startY, dx, dy, durationTime);
        invalidate();  // 關(guān)鍵代碼,invalidate和computeScroll才會(huì)有動(dòng)畫(huà)效果,scroller.startScroll只是模擬數(shù)據(jù)
    }


    @Override
    public void computeScroll() { // 如果數(shù)據(jù)模擬沒(méi)有完成笔链,那么繼續(xù)更新
        //super.computeScroll();

        // computeScrollOffset
        if(scroller.computeScrollOffset()){
            scrollTo(scroller.getCurrX(),0);
            invalidate(); // 注意這里還調(diào)用了invalidate段只,這樣才會(huì)產(chǎn)生效果
        }

    }



    public boolean isAtMainPage(){
        return (pageIndex == MAIN_PAGE)?true:false;
    }

    public void showMainPage(){
        choosePage(true);
    }
    public void showLeftPage(){
        choosePage(false);
    }

}

.
.
MainActivity

public class MainActivity extends Activity {

    private SlideMenu slideMenu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        slideMenu = (SlideMenu) findViewById(R.id.mSmenu);

        findViewById(R.id.mIvBack).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(slideMenu.isAtMainPage()){
                    slideMenu.showLeftPage();
                }else{
                    slideMenu.showMainPage();
                }
            }
        });


        findViewById(R.id.mTvBd).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this,"本地",Toast.LENGTH_SHORT).show();
                if(slideMenu.isAtMainPage()){
                    slideMenu.showLeftPage();
                }else{
                    slideMenu.showMainPage();
                }
            }
        });


    }
}

組合控件仿qq側(cè)滑菜單至此結(jié)束。

組合控件1—— 設(shè)置框一文中鉴扫,我們將進(jìn)行組合控件的demo編寫(xiě)赞枕。

本篇完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坪创,一起剝皮案震驚了整個(gè)濱河市炕婶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莱预,老刑警劉巖柠掂,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異依沮,居然都是意外死亡涯贞,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)危喉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宋渔,“玉大人,你說(shuō)我怎么就攤上這事辜限』始穑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵薄嫡,是天一觀的道長(zhǎng)氧急。 經(jīng)常有香客問(wèn)我,道長(zhǎng)岂座,這世上最難降的妖魔是什么态蒂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮费什,結(jié)果婚禮上钾恢,老公的妹妹穿的比我還像新娘。我一直安慰自己鸳址,他們只是感情好瘩蚪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著稿黍,像睡著了一般疹瘦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巡球,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天言沐,我揣著相機(jī)與錄音邓嘹,去河邊找鬼。 笑死险胰,一個(gè)胖子當(dāng)著我的面吹牛汹押,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播起便,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼棚贾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了榆综?” 一聲冷哼從身側(cè)響起妙痹,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鼻疮,沒(méi)想到半個(gè)月后怯伊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陋守,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年震贵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片水评。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猩系,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出中燥,到底是詐尸還是另有隱情寇甸,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布疗涉,位于F島的核電站拿霉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咱扣。R本人自食惡果不足惜绽淘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闹伪。 院中可真熱鬧沪铭,春花似錦、人聲如沸偏瓤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厅克。三九已至赔退,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硕旗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工窗骑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卵渴。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓慧域,卻偏偏與公主長(zhǎng)得像鲤竹,于是被迫代替她去往敵國(guó)和親浪读。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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

  • 一辛藻、Android開(kāi)發(fā)初體驗(yàn) 二碘橘、Android與MVC設(shè)計(jì)模式模型對(duì)象存儲(chǔ)著應(yīng)用的數(shù)據(jù)和業(yè)務(wù)邏輯。模型類通常用來(lái)...
    為夢(mèng)想戰(zhàn)斗閱讀 886評(píng)論 0 3
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,098評(píng)論 25 707
  • 什么是View View 是 Android 中所有控件的基類吱肌。 View的位置參數(shù) View 的位置由它的四個(gè)頂...
    acc8226閱讀 1,170評(píng)論 0 7
  • 20170505之察己 1痘拆、主題是什么? -- 誰(shuí)在學(xué)習(xí) 2氮墨、事件是什么纺蛆? -- 臨在讀書(shū)會(huì) 3、即時(shí)反應(yīng)是什么?...
    tianyouyou9閱讀 304評(píng)論 0 0
  • 最近熱播的我的前半生 规揪,子君從一個(gè)富家太太一下子跌落到無(wú)依無(wú)靠 一切都要靠自己的普通打工者桥氏。這部劇很真實(shí),真實(shí)...
    怡寧_f566閱讀 262評(píng)論 7 2