首页 > 代码库 > 自定义android侧滑菜单

自定义android侧滑菜单

这里实现两种侧滑菜单效果,第一种拖拽内容部分,菜单像是被拖出来的感觉的这种效果,第二种是拖拽内容部分,菜单在内容后面不动,感觉有一种层次感的效果,如下



第一种效果的代码实现如下:

package com.tenghu.customsideslip.menu.view;

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;

/**
 * Created by Arvin_Li on 2014/11/19.
 */
public class ScrollSideslipMenu extends LinearLayout{

    private static final int SNAP_VELOCITY=200;//速度阈值
    private View mMenu;//菜单布局
    private View mContent;//内容布局

    private int screenWidth;//屏幕宽度
    private int menuWidth;//菜单宽度

    private int leftEdge;//左边界
    private int rightEdge=0;//右边界,值恒为0

    private float xUp;//手指抬起时记录横坐标
    private float xDown;//手指按下时记录横坐标
    private float xMove;//手指移动时记录横坐标

    private int toRightPaddingWidth=50;//菜单完全显示时,留给内容的宽度
    private LayoutParams menuParams;//菜单布局参数

    private boolean once=false;//初始化数据只加载一次
    private boolean isShowMenu;//是否显示菜单

    private VelocityTracker velocityTracker;//速度跟踪器

    public ScrollSideslipMenu(Context context) {
        super(context);
        initWindowWidth(context);
    }

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

    /**
     * 初始化获取屏幕宽度
     */
    private void initWindowWidth(Context context){
        WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics=new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        //获取屏幕宽度
        screenWidth=displayMetrics.widthPixels;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(!once){
            mMenu=this.getChildAt(0);//获取菜单布局
            mContent=this.getChildAt(1);//获取内容布局
            menuParams= (LayoutParams) mMenu.getLayoutParams();//获取菜单布局参数
            menuWidth=menuParams.width=screenWidth-toRightPaddingWidth;//设置菜单宽度
            mContent.getLayoutParams().width=screenWidth;//设置内容宽度
            leftEdge=-menuWidth;//左边界
            menuParams.leftMargin=leftEdge;//默认菜单不显示
            once=true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        createVelocityTracker(event);
        switch (event.getAction()){
            //按下
            case MotionEvent.ACTION_DOWN:
                xDown=event.getRawX();//记录按下时的横坐标
                break;
            //移动
            case MotionEvent.ACTION_MOVE:
                //记录移动时的横坐标
                xMove=event.getRawX();
                //计算移动时与按下时的距离
                int moveDistanceX= (int) (xMove-xDown);
                if(isShowMenu){
                    menuParams.leftMargin=moveDistanceX;
                }else{
                    menuParams.leftMargin=leftEdge+moveDistanceX;
                }

                if(menuParams.leftMargin<leftEdge){
                    menuParams.leftMargin=leftEdge;
                }

                if(menuParams.leftMargin>rightEdge){
                    menuParams.leftMargin=rightEdge;
                }

                mMenu.setLayoutParams(menuParams);//设置参数
                break;
            //抬起
            case MotionEvent.ACTION_UP:
                //记录抬起时的横坐标
                xUp=event.getRawX();
                //计算抬起时与按下时的距离
                int upDistanceX= (int) (xUp-xDown);
                if(upDistanceX>0&&!isShowMenu){
                    if(upDistanceX>menuWidth/2||getScrollVelocity()>SNAP_VELOCITY){
                        scrollToMenu();

                    }else{
                        scrollToContent();
                    }
                }else if(upDistanceX<0&&isShowMenu){
                    if(Math.abs(upDistanceX)>menuWidth/2||getScrollVelocity()>SNAP_VELOCITY){
                        scrollToContent();
                    }else{
                        scrollToMenu();
                    }
                }
                mMenu.setLayoutParams(menuParams);
                break;
        }
        return true;
    }

    /**
     * 滚动内容部分
     */
    private void scrollToContent(){
        new ScrollTask().execute(-30);
    }

    /**
     * 滚动菜单部分
     */
    private void scrollToMenu(){
        new ScrollTask().execute(30);
    }

    /**
     * 创建速度阈值
     */
    private void createVelocityTracker(MotionEvent event){
        if(null==velocityTracker){
            velocityTracker=VelocityTracker.obtain();
        }
        velocityTracker.addMovement(event);
    }

    /**
     * 获取滚动时的速度
     * @return
     */
    private int getScrollVelocity(){
        velocityTracker.computeCurrentVelocity(1000);
        int velocity= (int) velocityTracker.getXVelocity();//获取横向速度
        return Math.abs(velocity);
    }

    /**
     * 创建一个异步滚动任务
     */
    class ScrollTask extends AsyncTask<Integer,Integer,Integer>{

        @Override
        protected Integer doInBackground(Integer... params) {
            int leftMargin=menuParams.leftMargin;
            while(true){
                leftMargin=leftMargin+params[0];
                if(leftMargin<leftEdge){
                    leftMargin=leftEdge;
                    break;
                }
                if(leftMargin>rightEdge){
                    leftMargin=rightEdge;
                    break;
                }
                publishProgress(leftMargin);
                sleep(20);
            }

            if(params[0]>0){
                isShowMenu=true;
            }else{
                isShowMenu=false;
            }
            return leftMargin;

        }

        /**
         * 执行结束
         * @param integer
         */
        @Override
        protected void onPostExecute(Integer integer) {
            menuParams.leftMargin=integer;
            mMenu.setLayoutParams(menuParams);
        }

        /**
         * 执行doInBackground中的publishProgress调用该方法
         * @param values
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            menuParams.leftMargin=values[0];
            mMenu.setLayoutParams(menuParams);
        }
    }

    /**
     * 当前线程睡眠多少毫秒
     * @param millis
     */
    private void sleep(long millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.tenghu.customsideslip.menu.view.ScrollSideslipMenu xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="@drawable/bg_01">
    <!--引用菜单布局文件-->
    <include layout="@layout/left_menu"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/bg_02"></LinearLayout>
</com.tenghu.customsideslip.menu.view.ScrollSideslipMenu>

第二种效果实现:

package com.tenghu.customsideslip.menu.view;

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.WindowManager;
import android.widget.RelativeLayout;

/**
 * Created by Arvin_Li on 2014/11/19.
 */
public class CustomSideslipMenu extends RelativeLayout {
    private static final int SNAP_VELOCITY=200;//手势滑动的速度
    //屏幕宽度
    private int mScreenWidth;
    //菜单布局
    private View mMenu;
    //主体内容部分
    private View mContent;
    //声明菜单宽度
    private int menuWidth;
    //菜单完全显示时,留给内容部分的宽度
    private int toRightPaddingWidth=50;
    //主体内容布局参数
    private LayoutParams contentParams;
    //主体内容滑动到左边缘,由菜单宽度来决定
    private int leftEdge;
    //菜单显示时,主体内容到右边界,值恒为0
    private int rightEdge=0;
    private VelocityTracker velocityTracker;//声明速度跟踪器
    private float xDown;//记录手指按下的横坐标
    private float xUp;//记录手指抬起时的横坐标
    private float xMove;//记录手指移动时的横坐标
    private boolean once=false;//只执行一次
    private boolean isShowMenu=true;//是否显示菜单
    public CustomSideslipMenu(Context context) {
        super(context);
        init(context);
    }

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

    /**
     * 初始化
     */
    private void init(Context context){
        //获取窗口管理类
        WindowManager windowManager= (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        //创建DisplayMetrics
        DisplayMetrics displayMetrics=new DisplayMetrics();
        windowManager.getDefaultDisplay().getMetrics(displayMetrics);
        //获取屏幕宽度
        mScreenWidth=displayMetrics.widthPixels;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(!once){
            //获取菜单布局
            mMenu=this.getChildAt(0);
            //获取主体内容布局
            mContent=this.getChildAt(1);
            contentParams= (LayoutParams) mContent.getLayoutParams();//获取主体菜单参数
            //菜单宽度
            menuWidth=mMenu.getLayoutParams().width=mScreenWidth-toRightPaddingWidth;
            //设置主体内容的宽度
            mContent.getLayoutParams().width=mScreenWidth;
            leftEdge=menuWidth;//设置左边界
            once=true;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //调用创建速度跟踪器
        createVelocityTracker(event);
        switch (event.getAction()){
            //手指按下
            case MotionEvent.ACTION_DOWN:
                xDown=event.getRawX();
                break;
            //手指移动
            case MotionEvent.ACTION_MOVE:
                xMove=event.getRawX();
                //计算移动距离
                int distanceX= (int) (xMove-xDown);
                if(isShowMenu){
                    contentParams.leftMargin=distanceX;
                    contentParams.rightMargin=-distanceX;
                }else{
                    contentParams.leftMargin=leftEdge+distanceX;
                }

                if(contentParams.leftMargin>leftEdge){
                    contentParams.leftMargin=leftEdge;
                    contentParams.rightMargin=-leftEdge;
                }

                if(contentParams.leftMargin<rightEdge){
                    contentParams.leftMargin=rightEdge;
                    contentParams.rightMargin=rightEdge;
                }
                mContent.setLayoutParams(contentParams);//测试参数
                break;
            //手指抬起
            case MotionEvent.ACTION_UP:
                xUp=event.getRawX();//手指抬起时横坐标
                //计算抬起时与按下时的距离
                int upDistanceX=(int) (xDown-xUp);
                if(upDistanceX>0&&!isShowMenu){
                        if(upDistanceX>menuWidth/2||getScrollVelocity()>SNAP_VELOCITY){
                            scrollToMenu();
                        }else{
                            scrollToContent();
                        }
                }else if(upDistanceX<0&&isShowMenu){
                    if(Math.abs(upDistanceX)>menuWidth/2||getScrollVelocity()>SNAP_VELOCITY){
                        scrollToContent();
                    }else{
                        scrollToMenu();
                    }
                    //手指抬起时销毁
                    recycleVelocityTracker();
                }
                break;
        }
        return true;
    }

    /**
     * 滚动菜单
     */
    private void scrollToMenu(){
        new ScrollTask().execute(-30);
    }

    /**
     * 滚动内容
     */
    private void scrollToContent(){
        new ScrollTask().execute(30);
    }

    /**
     * 获取速度
     * @return
     */
    private int getScrollVelocity(){
        velocityTracker.computeCurrentVelocity(1000);
        int velocity= (int) velocityTracker.getXVelocity();
        return Math.abs(velocity);
    }

    /**
     * 销毁速度跟踪器
     */
    private void recycleVelocityTracker(){
        if (null != velocityTracker) {
            velocityTracker.recycle();
            velocityTracker = null;
        }
    }


    /**
     * 创建速度跟踪器
     */
    private void createVelocityTracker(MotionEvent event){
        if(null==velocityTracker){
            velocityTracker=velocityTracker.obtain();
        }
        velocityTracker.addMovement(event);
    }

    /**
     * 创建异步滚动任务类
     */
    class ScrollTask extends AsyncTask<Integer,Integer,Integer>{

        @Override
        protected Integer doInBackground(Integer... params) {
            int leftMargin=contentParams.leftMargin;
            while(true){
                leftMargin=leftMargin+params[0];
                if(leftMargin>leftEdge){
                    leftMargin=leftEdge;
                    break;
                }

                if(leftMargin<rightEdge){
                    leftMargin=rightEdge;
                    break;
                }
                if(params[0]<0){
                    isShowMenu=true;
                   // leftMargin=rightEdge;
                }else{
                    isShowMenu=false;
                }
                publishProgress(leftMargin);
                sleep(20);
            }
            return leftMargin;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            contentParams.leftMargin=values[0];
           contentParams.rightMargin=-values[0];
            mContent.setLayoutParams(contentParams);
        }

        @Override
        protected void onPostExecute(Integer integer) {
            contentParams.leftMargin=integer;
           contentParams.rightMargin=-integer;
            mContent.setLayoutParams(contentParams);
        }
    }

    /**
     * 当前线程睡眠多少毫秒
     * @param millis
     */
    private void sleep(long millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

布局文件:

<com.tenghu.customsideslip.menu.view.CustomSideslipMenu 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"
    android:background="@drawable/bg_01"
    tools:context=".MainActivity">

    <include layout="@layout/left_menu" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/bg_02"
            android:orientation="vertical">
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="testScrollMenu"
                android:text="测试第二种侧滑菜单"/>
        </LinearLayout>
    </LinearLayout>
</com.tenghu.customsideslip.menu.view.CustomSideslipMenu>

菜单布局文件:

<?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:gravity="center_vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:layout_marginBottom="10dp">

            <ImageView
                android:id="@+id/iv_img_01"
                android:layout_width="50dp"
                android:layout_height="match_parent"
                android:src=http://www.mamicode.com/"@drawable/app_01" />>
这里的菜单,可以是用ListView来布局,做测试就没有那样做了,所有代码全部在这里了,可以看出,两种效果只是继承了不通的布局,感觉第二种效果在设置背景时有点问题,就是在这里
<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/bg_02"
            android:orientation="vertical">
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="testScrollMenu"
                android:text="测试第二种侧滑菜单"/>
        </LinearLayout>
    </LinearLayout>
如果将背景设置到第一层的LinearLayout上,那么自定义侧滑菜单那里设置背景就显示不出来,设置到第二层就可以,不知道是肿么回事,如果各位大师找到了,麻烦告诉小弟。

自定义android侧滑菜单