Android--侧滑菜单应用的实现

侧滑菜单应用现在非常多,而且实现方式也多种多样。通过在网上的多方查找,我找到郭霖少侠的这篇文章:http://blog.csdn.net/guolin_blog/article/details/8744400,研究之后收获颇多。同时记得以前看过一篇讲Scroller实现滑屏的文章:http://www.cnblogs.com/wanqieddy/archive/2012/05/05/2484534.html

那为何不用scroller来实现以下侧滑菜单?闲的蛋疼,那就试试吧,在这里先感谢以上两篇博文给我的启发。

原理:通过scrollBy和scrollTo来移动右侧的content布局,实际上整个过程中,左侧的menu布局未发生滚动,这样出来的效果是右侧content布局覆盖住左侧menu布局。当然scroll的方式也可以实现menu和content同时平移的效果,这个只需要在布局文件上动动手脚就行了,在此先按下不表。

下面是本文的实现效果

Android--侧滑菜单应用的实现

activity_main.xml文件:

首先放入menu布局,因为是RelativeLayout布局因此先放入的会被覆盖,然后是滑动布局,在滑动布局中加入content布局,因为content布局会随滑动布局一起移动。

<RelativeLayout 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"
    tools:context=".MainActivity" >

    <include android:id="@+id/menu" layout="@layout/menu" />
    
    <com.noter.layout.SlideLayout
        android:id="@+id/slide_layout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
        
        <include android:id="@+id/content" layout="@layout/content_diary" />
    
    </com.noter.layout.SlideLayout>

</RelativeLayout>

menu.xml和conent_diary.xml这两个布局文件就不用讲了,大家跟着感觉走,想放什么放什么吧。

 

SlideLayout.java文件:大部分代码都很简单,看注释就能懂。这里只说几个要点,我也是调试过之后才明白的:

1. getScrollX()得到的是当前View的最左边所在的X坐标。程序初始化后此值为0,View向右滑动后,相当于屏幕坐标系向负方向移动了一段,因此此时此值为负数;反之则相反。

2. Scroller实际上只是保存和提供自动滑动时所需的数值,真正完成滑动的还是scrollTo和scrollBy两个函数。

3. 我写的SlideLayout继承自RelativeLayout,其实也可以继承自ViewGroup,但是就需要自己重写onMeasure和onLayout函数,来布局子控件。这个实现中没有特殊的布局要求,所以用RelatiLayout就可以啦。

package com.noter.layout;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.RelativeLayout;
import android.widget.Scroller;

public class SlideLayout extends RelativeLayout {
	private static String TAG = "SlideMenuLayout";
	
	private Context mContext;
	private Scroller mScroller;    //Android 提供的滑动辅助类
	private int mTouchSlop = 0 ;    //在被判定为滚动之前用户手指可以移动的最大值
	private VelocityTracker mVelocityTracker;    //用于计算手指滑动的速度
	public static final int SNAP_VELOCITY = 200;    //滚动显示和隐藏左侧布局时,手指滑动需要达到的速度:每秒200个像素点
	private int mMaxScrollX = 0;    //最大滚动距离,等于menu的宽度
	public void setMaxScrollX(int maxScrollX) {
		this.mMaxScrollX = maxScrollX;
	}

	private float mDownX;    //一次按下抬起的动作中,按下时的X坐标,用于和抬起时的X比较,判断移动距离。少于mTouchSlop则判定为原地点击
	private float mLastX;    //记录滑动过程中的X坐标
	
	private boolean isMenuOpen = false;    //菜单界面是否被打开,只有完全打开才为true
	public boolean isMenuOpen() {
		return isMenuOpen;
	}

	private View mContent;
	
	public SlideLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		mContext = context;
		init();
	}
	
	private void init() {
		Log.v(TAG, "init start");
		mScroller = new Scroller(mContext);
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
	}
	
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		if(changed){
			mContent = getChildAt(0);
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		createVelocityTracker(event);
		int curScrollX = getScrollX();
		// 检查触摸点是否在滑动布局(内容content)中,如果不是则返回false,即本View不处理该事件
		if (mContent != null) {
			Rect rect = new Rect();
			mContent.getHitRect(rect);
			if (!rect.contains((int)event.getX() + curScrollX, (int)event.getY())) {
				return false;
			}
		}

		float x = event.getX();    //取得本次event的X坐标
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mDownX = x;
			mLastX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			int deltaX = (int)(mLastX - x);
			if((curScrollX + deltaX) < -mMaxScrollX) {
				deltaX = -mMaxScrollX - curScrollX;
			}
			if((curScrollX + deltaX) > 0){
				deltaX = -curScrollX;
			}
			
			if (deltaX != 0) {
				scrollBy(deltaX, 0);
			}
			mLastX = x;
			break;
		case MotionEvent.ACTION_UP:
			int velocityX = getScrollVelocity();
			int offsetX = (int) (x - mDownX);
			
			//成立表明移动距离已经达到被判断为滑动的最低标准
			//不成立表明不被判断为滑动,则认为是单一的点击,则关闭menu
			if(Math.abs(offsetX) >= mTouchSlop) {
				
				//成立表明手指移动速度达标,则进行自动滑动;
				//不成立表明速度不达标,但仍然需要判断当前SlideLayout的位置
				//如果已经超过一半,则继续自动完成剩下的滑动,如果没有超过一半,则反向滑动
				if(Math.abs(velocityX) >= SNAP_VELOCITY) {
					if(velocityX > 0){
						openMenu();
					} else if(velocityX < 0) {
						closeMenu();
					}
				} else {
					if (curScrollX >= -mMaxScrollX / 2) {
						closeMenu();
					} else {
						openMenu();
					}
				}
			} else {
				closeMenu();
			}

			recycleVelocityTracker();
			break;
		}
		return true;
	}
	
	private void createVelocityTracker(MotionEvent event) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);
	}
	
	//获取手指在View上的滑动速度,以每秒钟移动了多少像素值为单位
	private int getScrollVelocity() {
		mVelocityTracker.computeCurrentVelocity(1000);
		return (int) mVelocityTracker.getXVelocity();
	}
	
	private void recycleVelocityTracker() {
		mVelocityTracker.recycle();
		mVelocityTracker = null;
	}
	
	//打开Menu布局
	public void openMenu() {
		int curScrollX = getScrollX();
		scrollToDestination(-mMaxScrollX - curScrollX);
		isMenuOpen = true;
	}
	
	//关闭Menu布局
	public void closeMenu() {
		int curScrollX = getScrollX();
		scrollToDestination(-curScrollX);
		isMenuOpen = false;
	}
	
	private void scrollToDestination(int x) {
		if (x == 0)
			return;

		mScroller.startScroll(getScrollX(), 0, x, 0, Math.abs(x));
		invalidate();
	}
}


最后是代码下载,Enjoy it!

侧滑菜单实例

Android--侧滑菜单应用的实现

上一篇:Android中Timer使用方法


下一篇:PostgreSQL 11 相似图像搜索插件 imgsmlr 性能测试与优化 3 - citus 8机128shard (4亿图像)