侧滑菜单功能非常常见,借鉴学习了之后,自己总结记录一下,下面实现一种最简单的侧滑菜单,后面再修改代码实现不同的侧滑菜单效果

首先是第一种效果

第一种效果是继承ViewGroup,需要我们自己来测量、滑动处理等。

一、首先讲解一下思路:

1、继承GroupView重写构造方法

a、我们需要重写三个构造方法
b、在构造方法中初始化宽高值等

2、重写onMeasure方法

a、在onMeasure方法中调用measureChild来测量子view,即我们的menu跟content两个view。
b、调用setMeasureDimension来测量自己的大小。

3、重写onLayout方法。

a、调用menu跟content两个view的layout方法来确定它们的位置。

4、重写onTouchEvent事件

a、记录手指按下时的x跟y值
b、通过手指移动时的x值减去记录好的x值进行判断当前是向左还是向右移动进行页面menu的打开或者关闭
c、当手指松开时,我们判断menu有没有打开超过它自身的一半宽度进行自动打开或者关闭

5、重写onInterceptTouchEvent事件

a、由于我们的主页面中可能有listview或者scrollview那些可以上下滚动的view存在,可能会造成我们的事件冲突,所以需要重写onInterceptTouchEvent事件来区分当前用户是向上(下)还是向右(左)滑动。

6、实例化Scroller类实现滚动打开关闭

a、由于我们需要加入一个按钮,点击后打开或者关闭menu,所以我们需要这个滚动类

二、我们看看我们页面上的布局是什么样的

android 仿qq右侧滑 手机qq右滑菜单栏_ide

如上图片:我们布局应该是content在我们的屏幕中间,而menu超出屏幕在左边,当我们手指往右边滑的时候,menu跟随手指滑动出来

而当我们完全打开了menu之后,页面应该是显示成这样

android 仿qq右侧滑 手机qq右滑菜单栏_xml_02


三、首先是我们的自定义view类

public class SlidingMenu extends ViewGroup {

    private Scroller mScroller;
    private  int mMenuRightPadding;
    private  int heightPixels;
    private  int widthPixels;
    private int mMenuWidth;
    private View mMenu;
    private View mContent;
    private int mLastY;
    private int mLastX;
    private boolean isOpen;
    private int mLastYIntercept;
    private int mLastXIntercept;

    public SlidingMenu(Context context) {
        this(context,null,0);
    }

    public SlidingMenu(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    public SlidingMenu(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        DisplayMetrics metrics=new DisplayMetrics();
        WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);
        //获取屏幕的宽高
        widthPixels = metrics.widthPixels;
        heightPixels = metrics.heightPixels;
        //初始化mMenuRightPadding,作为侧边view到屏幕右边的距离,converToDp方法将100转换为100dp
        mMenuRightPadding=converToDp(context,100);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //侧滑view要放置的位置,左上右下分别是menu的负宽度,因为要超出屏幕的左边,而上边跟右边都是0,下边就是屏幕的高度值,因为要占满全屏,如果不需要,可以改动
        mMenu.layout(-mMenuWidth,0,0,heightPixels);
        //content区域位置,屏幕的中间
        mContent.layout(0,0,widthPixels,heightPixels);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取SlidingMenu的第一个子孩子控件,即我们的侧边栏view
        mMenu = this.getChildAt(0);
        //获取SlidingMenu的第二个子孩子控件,即我们的内容区域
        mContent=this.getChildAt(1);
        //计算menu要使用的宽度为屏幕宽度减去menu要距离屏幕右侧的距离
        mMenuWidth=widthPixels - mMenuRightPadding;
        //设置menu的宽度
        mMenu.getLayoutParams().width = mMenuWidth;
        //设置content的宽度
        mContent.getLayoutParams().width=widthPixels;
        //测量mMenu
        this.measureChild(mMenu, widthMeasureSpec, heightMeasureSpec);
        //测量content
        this.measureChild(mContent, widthMeasureSpec, heightMeasureSpec);
        //测量自己,自己的宽度为侧边栏已经内容区域的宽度总和,高度为全屏
        this.setMeasuredDimension(widthPixels+mMenuWidth,heightPixels);


    }

    /**
     * 转换px为pd
     * @param context 上下文
     * @param px 要转换的px数值
     * @return 返回转换好的结果值
     */
    private int converToDp(Context context,int px){
        int result=0;
        result= (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,px,context.getResources().getDisplayMetrics());
        return result;
    }

   }

按照上面所说的我们的布局的样子,我们开始书写我们的xml

四、首先是view_content.xml,给了一个listview

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#000000">

    <ListView
        android:id="@+id/content_lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </ListView>
</LinearLayout>

五、 view_menu.xml,也是一样的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/menu_lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
</LinearLayout>

六、我们还需要listview的item布局,简单起见,view_content跟view_menu里面的listview我们都用同一个view_item.xml布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"
        android:layout_gravity="center_vertical"
        />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:text="内容"/>
</LinearLayout>

七、最后是我们的main.xml布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <com.mjz.menudemo.view.SlidingMenu
        android:id="@+id/slidingmenu"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        >
        <include
            android:id="@+id/menu"
            layout="@layout/view_menu"
            />
        <include
            android:id="@+id/content"
            layout="@layout/view_content"
            />
    </com.mjz.menudemo.view.SlidingMenu>

</RelativeLayout>

八、注意布局中的menu跟content的顺序要对,因为我们在自定义view——SlidingMenu.java中有这么一段代码,代码中查找子view的位置是用下标的

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取SlidingMenu的第一个子孩子控件,即我们的侧边栏view
        mMenu = this.getChildAt(0);
        //获取SlidingMenu的第二个子孩子控件,即我们的内容区域
        mContent=this.getChildAt(1);

    }

九、此时我们的xml布局写好了,大概效果已经出来了,现在我们要让menu能打开以及关闭

我们需要让view跟着我们的手指滑动而滑动,我们可以使用View的scrollTo和scrollBy方法,这两个方法的区别是scrollTo是直接将view移动到指定的位置,scrollBy是相对于当前的位置移动一个偏移量,所以我们应该重写onTouchEvent方法,用来计算出当前手指的一个偏移量,然后使用scrollBy方法一点一点的移动,就形成了一个可以跟随手指移动的view的动画效果了 

十、现在我们看一下各大坐标的值

android 仿qq右侧滑 手机qq右滑菜单栏_xml_03


当menu滚动到一半的时候,情况是这样的

android 仿qq右侧滑 手机qq右滑菜单栏_xml_04

而menu全部拉出来之后,情况如下

android 仿qq右侧滑 手机qq右滑菜单栏_android_05

十一、弄清楚这些情况之后,我们开始重写onTouchEvent方法

<span style="font-family:microsoft yahei;color:#555555;"><span style="font-size: 14px; line-height: 35px; background-color: rgb(240, 240, 240);">@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action=event.getAction();
        switch (action){
            //手指按下
            case MotionEvent.ACTION_DOWN:
                //记录按下时的x跟y值
                mLastX= (int) event.getX();
                mLastY= (int) event.getY();
                break;
            //手指移动
            case MotionEvent.ACTION_MOVE:
                //时刻捕捉移动的手指位置的x跟y值
                int currentX= (int) event.getX();
                int currentY= (int) event.getY();
                //计算出当前的x值偏移量
                int offsetX=currentX-mLastX;
                if(offsetX>0){//大于0代表着当前手指是往右滑的
                    //首先要判断是不是滑到了最右边(menu已经全部显示出来了),如果全部显示了,再继续往右边滑,会造成超出了menu
                    if(getScrollX()-offsetX<=-mMenuWidth){
                        scrollTo(-mMenuWidth,0);
                    }else{
                        scrollBy(-offsetX,0);
                    }

                }else{
                    //否则是往左边滑动
                    if(getScrollX()+Math.abs(offsetX)>=0){
                        scrollTo(0,0);
                    }else{
                        scrollBy(-offsetX,0);
                    }

                }

                //移动过程中,修改记录每一次移动到的x跟y的位置
                mLastX = currentX;
                mLastY = currentY;
                break;
            //如果手指离开屏幕
            case MotionEvent.ACTION_UP:

                if(getScrollX()<-mMenuWidth/2){
                    //打开menu
                    //使用startScroll方法,第一个参数是X的起始坐标,第二个参数是Y的起始坐标,第三个参数是X方向偏移量(即要移动多少),第四个参数是Y方向偏移量,第五个参数是动画持续时间
                    mScroller.startScroll(getScrollX(),0,-mMenuWidth-getScrollX(),0,200);
                    isOpen=true;
                    invalidate();
                }else{
                    //如果当前滚动到的X位置是大于侧边栏的宽度的一半的,说明是没达到打开menu的要求
                    mScroller.startScroll(getScrollX(),0,-getScrollX(),0,200);
                    isOpen=false;
                    invalidate();
                }
                break;
        }
        return true;
    }
</span></span>

MotionEvent.ACTION_UP中我们对menu进行了是否打开了二分之一的判断,如果打开了二分之一,我们就自动打开,否则就自动关闭,这个时候,我们需要用到Scroller类。

十二、我们在构造方法中初始化一个Scroller

public SlidingMenu(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();
        }
    }

此时如果你的listview已经加入adapter且有数据了,你会发现,还是不能侧滑,原因是因为我们的SlidingMenu默认是不拦截事件的,那么事件会传递给他的子View去执行,也就是说传递给了Content的ListView去执行了,所以listview是可以滑动的,所以我们需要重写onInterceptTouchEvent方法,当向上或者向下滑动的时候,我们将滑动的事件传递给listview,向左向右则SlidingMenu自己处理

十三、重写onInterceptTouchEvent方法

@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=mLastXIntercept=x;
        mLastY=mLastYIntercept=y;

        return intercept;
    }

到此,我们的功能基本实现了,侧滑跟listview的上下滑动也不冲突了,以上是简单的侧滑菜单的实现