之前都一直是看郭大神的博客,也就看到了那个仿人人网的侧滑菜单,但是感觉太冗杂,权当理解原理最好不过。后来实际开发过程中也要用到,我就想可不可以自己写一个,自制侧滑侧单,既可以左侧滑出,也可以从右侧滑出,或者是双向滑动的,那该多好啊,于是,我就上路了。。。

在此我得知android本身就有一个类Scroller,用于处理布局内容的滑动,然后就一探究竟(这里面尤其值得注意滑动的偏移量与屏幕坐标系是相反的,详情可自行科普),用这个类的确省了好多事,那我也废话不多说,先上源码:

package com.cjt_pc.myslidingmenu;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Scroller;

/**
 * Created by cjt-pc on 2015/7/27.
 * Email:879309896@qq.com
 */
public class SlidingLayout extends FrameLayout {

    // 滚动显示和隐藏menu时,手指滑动需要达到的速度
    public static final int SNAP_VELOCITY = 400;
    // 手指横向滑动临界距离,判断滑动类型
    public static final int SCROLL_DIS = 20;
    // 上下文
    private Context mContext;
    // 左中右三个layout
    private BaseSlideLayout leftSlideLayout, middleSlideLayout, rightSlideLayout;
    // 中间内容的“面罩”
    private BaseSlideLayout maskLayout;
    // 是否开启滑动渐变效果,默认为true
    private boolean isOnAlpha = true;
    // 渐变程度,1代表满足条件时完全不透明
    private float alphaRate = 0.5f;
    // 手指按下的坐标
    private int downX, downY;
    // 当前手指触摸屏幕的点
    private Point point;
    // 用于计算手指滑动的速度
    private VelocityTracker mVelocityTracker;
    // 滚动控制器
    private Scroller mScroller;
    // 手指移动类型是否为横向滑动
    private boolean isLeftRight = false;
    // 是否计算了滑动类型
    private boolean isCalTyped = false;
    // 是否屏蔽所有事件
    private boolean isIntercept = false;
    // 手指是否抬起
    private boolean fingerUp = true;
    // 视图移动的距离范围,注意这是偏移量,正负与坐标相反
    private int minX = 0, maxX = 0;
    // 侧边菜单的宽度比例,默认为主界面的0.8
    private double widthRate = 0.8;
    // 侧边菜单的宽度
    private int menuWidth = 0;

    public SlidingLayout(Context context) {
        super(context);
        mContext = context;
        mScroller = new Scroller(context, new DecelerateInterpolator());
        point = new Point();
        // 设置clickable可以使dispatchTouchEvent恒为true
        this.setClickable(true);
    }

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int finalX = 0;
        // 分别给存在的layout设置大小
        if (middleSlideLayout != null) {
            middleSlideLayout.measure(widthMeasureSpec, heightMeasureSpec);
            maskLayout.measure(widthMeasureSpec, heightMeasureSpec);
            menuWidth = (int) (middleSlideLayout.getMeasuredWidth() * widthRate);
            finalX = (MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY));
        }

        if (leftSlideLayout != null) {
            leftSlideLayout.measure(finalX, heightMeasureSpec);
        }
        if (rightSlideLayout != null) {
            rightSlideLayout.measure(finalX, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (middleSlideLayout != null) {
            middleSlideLayout.layout(l, t, r, b);
            maskLayout.layout(l, t, r, b);
        }
        if (leftSlideLayout != null) {
            leftSlideLayout.layout(l - leftSlideLayout.getMeasuredWidth(), t, l, b);
            minX = -leftSlideLayout.getMeasuredWidth();
        }
        if (rightSlideLayout != null) {
            rightSlideLayout.layout(r, t, r + rightSlideLayout.getMeasuredWidth(), b);
            maxX = rightSlideLayout.getMeasuredWidth();
        }
    }

    @Override
    public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
        createVelocityTracker(ev);
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                fingerUp = false;
                downX = (int) ev.getX();
                downY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                int dX = (int) (ev.getX() - point.x);
                // 当滑动距离超过了计算滑动类型最小值,判断是否为左右滑动,只计算一次
                if (!isCalTyped && ((Math.abs((int) ev.getX() - downX) >= SCROLL_DIS) ||
                        Math.abs((int) ev.getY() - downY) >= SCROLL_DIS)) {
                    if (Math.abs(ev.getX() - downX) > Math.abs(ev.getY() - downY)) {
                        isLeftRight = true;
                    }
                    isCalTyped = true;
                }
                if (isLeftRight) {
                    isIntercept = true;
                    int expectX = getScrollX() - dX;
                    // 左右滑动的最小和最大值
                    if (expectX >= minX && expectX <= maxX) {
                        // 只有视图在滑动的时候让当前视图屏蔽掉所有控件事件
                        // 滚动视图到指定点
                        scrollBy(-dX, 0);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                fingerUp = true;
                isLeftRight = false;
                isCalTyped = false;
                beginStart();
                invalidate();
                recycleVelocityTracker();
                break;
            default:
                break;
        }
        point.x = (int) ev.getX();
        point.y = (int) ev.getY();
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(x, y);
        if (isOnAlpha) {
            int curScroX = Math.abs(getScrollX());
            float scale = curScroX / (float) menuWidth;
            if (middleSlideLayout != null) {
                maskLayout.setAlpha(scale * alphaRate);
            }
        }
    }

    // 手指抬起时判断情况滚动视图到指定点
    private void beginStart() {
        int curScroX = getScrollX();
        int cpX = menuWidth >> 1;
        int moveSp = getScrollVelocity();
        // 当手指移动速度满足要求时换一种判断方式
        if (Math.abs(moveSp) < SNAP_VELOCITY) {
            if (curScroX >= -cpX && curScroX < 0) {//左侧菜单缩进
                mScroller.startScroll(curScroX, 0, -curScroX, 0);
            } else if (curScroX < -cpX) {//左侧菜单展出
                mScroller.startScroll(curScroX, 0, -menuWidth - curScroX, 0);
            } else if (curScroX > 0 && curScroX <= cpX) {//右侧菜单缩进
                mScroller.startScroll(curScroX, 0, -curScroX, 0);
            } else if (curScroX > cpX) {//右侧菜单展出
                mScroller.startScroll(curScroX, 0, menuWidth - curScroX, 0);
            }
        } else {
            if (moveSp < 0 && getScrollX() < 0 && getScrollX() > -menuWidth) {
                mScroller.startScroll(curScroX, 0, -curScroX, 0);
            } else if (moveSp > 0 && getScrollX() < 0 && getScrollX() > -menuWidth) {
                mScroller.startScroll(curScroX, 0, -menuWidth - curScroX, 0);
            } else if (moveSp > 0 && getScrollX() > 0 && getScrollX() < menuWidth) {
                mScroller.startScroll(curScroX, 0, -curScroX, 0);
            } else if (moveSp < 0 && getScrollX() > 0 && getScrollX() < menuWidth) {
                mScroller.startScroll(curScroX, 0, menuWidth - curScroX, 0);
            }
        }
    }

    /**
     * 初始化VelocityTracker对象,并将触摸滑动事件加入到VelocityTracker当中
     *
     * @param event 触摸滑动事件
     */
    private void createVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    /**
     * 获取手指在content界面滑动的速度
     *
     * @return 滑动速度,以每秒钟移动了多少像素值为单位
     */
    private int getScrollVelocity() {
        mVelocityTracker.computeCurrentVelocity(1000);
        return (int) mVelocityTracker.getXVelocity();
    }

    /**
     * 回收VelocityTracker对象。
     */
    private void recycleVelocityTracker() {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }

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

    // scrollTo就会触发该事件,scrollBy为scrollTo的重写方法
    @Override
    public void computeScroll() {// 动画绘制方法
        super.computeScroll();

        if (fingerUp) {
            if (!mScroller.computeScrollOffset()) {// 滑动完成
                isIntercept = false;
                // 滑动结束如若在两端就屏蔽掉中间layout事件
                middleSlideLayout.isIntercept =
                        (Math.abs(mScroller.getFinalX()) == menuWidth);
                return;
            }
            int tempX = mScroller.getCurrX();
            scrollTo(tempX, 0);
            postInvalidate();
        }
    }

    public void setLeftSlideLayout(View view) {
        if (leftSlideLayout == null) {
            leftSlideLayout = new BaseSlideLayout(mContext);
            leftSlideLayout.addView(view);
            this.addView(leftSlideLayout);
        }
    }

    public void setMiddleSlideLayout(View view) {
        if (middleSlideLayout == null) {
            middleSlideLayout = new BaseSlideLayout(mContext);
            middleSlideLayout.addView(view);
            middleSlideLayout.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    int curScroX = getScrollX();
                    mScroller.startScroll(curScroX, 0, -curScroX, 0);
                }
            });
            this.addView(middleSlideLayout);
            maskLayout = new BaseSlideLayout(mContext);
            maskLayout.setBackgroundColor(Color.GRAY);
            // float类型,0-1,完全透明-完全不透明
            maskLayout.setAlpha(0.0f);
            this.addView(maskLayout);
        }
    }

    public void setRightSlideLayout(View view) {
        if (rightSlideLayout == null) {
            rightSlideLayout = new BaseSlideLayout(mContext);
            rightSlideLayout.addView(view);
            this.addView(rightSlideLayout);
        }
    }

    public void setOnAlpha(boolean onAlpha) {
        this.isOnAlpha = onAlpha;
    }

    public void setAlphaRate(float rate) {
        this.alphaRate = rate;
    }

    public void setWidthRate(double rate) {
        this.widthRate = rate;
    }

    private class BaseSlideLayout extends RelativeLayout {

        private boolean isIntercept = false;

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

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

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

很清楚这是一个FrameLayout类,这里就是一个可自定义功能的布局。里面注释已经写的很清楚了,我还是先介绍一下这个里面提供的公共方法:

  1. setLeftSlideLayout(View view):设置左侧菜单
  2. setMiddleSlideLayout(View view):设置中间内容
  3. setRightSlideLayout(View view):设置右侧菜单
  4. setOnAlpha(boolean onAlpha):是否开启滑动时中间模糊处理
  5. setAlphaRate(float rate):设置模糊处理程度
  6. setWidthRate(double rate):设置菜单布局宽度系数
    具体使用方法呢,也非常的简单,首先将这个类添加到你的工程中,然后在要展示的Activity中的onCreate方法中:
SlidingLayout slidingLayout = new SlidingLayout(this);
    slidingLayout.setLeftSlideLayout(new LinearLayout(this));
    slidingLayout.setMiddleSlideLayout(new LinearLayout(this));
    slidingLayout.setRightSlideLayout(new LinearLayout(this));
    slidingLayout.setOnAlpha(true);
    slidingLayout.setAlphaRate(0.6f);
    slidingLayout.setWidthRate(0.6);
    setContentView(slidingLayout);

当然,你也可以有自己的想法,仿qq侧滑?完全可以,源码都给你了,自己去慢慢研究吧,^_^