Android側滑菜單實現

之前都一直是看郭大神的博客,也就看到了那個仿人人網的側滑菜單,但是感覺太冗雜,權當理解原理最好不過。後來實際開發過程中也要用到,我就想可不可以自己寫一個,自制側滑側單,既可以左側滑出,也可以從右側滑出,或者是雙向滑動的,那該多好啊,於是,我就上路了。。。

在此我得知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:[email protected]
 */
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側滑?完全可以,源碼都給你了,自己去慢慢研究吧,^_^

發佈了30 篇原創文章 · 獲贊 21 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章