之前都一直是看郭大神的博客,也就看到了那個仿人人網的側滑菜單,但是感覺太冗雜,權當理解原理最好不過。後來實際開發過程中也要用到,我就想可不可以自己寫一個,自制側滑側單,既可以左側滑出,也可以從右側滑出,或者是雙向滑動的,那該多好啊,於是,我就上路了。。。
在此我得知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類,這裏就是一個可自定義功能的佈局。裏面註釋已經寫的很清楚了,我還是先介紹一下這個裏面提供的公共方法:
- setLeftSlideLayout(View view):設置左側菜單
- setMiddleSlideLayout(View view):設置中間內容
- setRightSlideLayout(View view):設置右側菜單
- setOnAlpha(boolean onAlpha):是否開啓滑動時中間模糊處理
- setAlphaRate(float rate):設置模糊處理程度
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側滑?完全可以,源碼都給你了,自己去慢慢研究吧,^_^