Android之侧滑菜单,即右边菜单

侧滑菜单有很多,比较有名有:SlidingMenu.,其实我们有时侯仅仅需要一个功能点:侧滑。
LeftSliderLayout的出现就是解决了这个问题。它很简单只有一个java源文件,使用起来也很方便。有很多应用都在使用它。

它实现的原理很简单:在一个FrameLayout下有二个子布局,一个是菜单,另一个是LeftSliderLayout。当向右拖动LeftSliderLayout时,就显示露出菜单布局。而向左拖动LeftSliderLayout时,就覆盖菜单布局。
它的使用也简单:创建一个FrameLayout,在FrameLayout添加二个布局:菜单布局和LeftSliderLayout。其中,菜单布局的宽度要与LeftSliderLayout的SLIDING_WIDTH保持一致。而LeftSliderLayout下面可以放二个子布局:第一个是阴影布局(左边阴影),第二个是要拖动的内容。
LeftSliderLayout有一个Listener。它有二个函数,一个是LeftSliderLayout的打开与关闭的状态改变;另一个是InterceptTouchEvent的回调,主要解决的是在拖动内容中有要处理左右滑动的控件与LeftSliderLayout的左右滑动的事件有冲突,当它返回true时,LeftSliderLayout会处理左右滑动,当它返回false时,就不处理左右滑动的事件。

Android之侧滑菜单,即右边菜单_第1张图片

实现代码如下:

(1)main_layout_above.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/white" >
    
    <Button 
        android:id="@+id/main_btn_enable"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:text="enableSlider"
        android:onClick="onClick"/>

    <Button 
        android:id="@+id/main_btn_disable"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:text="disableSlider"
        android:onClick="onClick"/>
    
    <Button 
        android:id="@+id/main_btn_open"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:text="openSlider"
        android:onClick="onClick"/>

    <Button 
        android:id="@+id/main_btn_close"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:text="closeSlider"
        android:onClick="onClick"/>
    
    <HorizontalScrollView 
        android:id="@+id/main_horizontal_scroll_view"
        android:layout_width="fill_parent"
        android:layout_height="240dp"
        android:scrollbars="none"
        android:paddingTop="3.0dip"
        android:paddingBottom="3.0dip"
        android:fadingEdge="none"
        android:background="#ff808080">
        
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:orientation="horizontal" >
            
		    <ImageView 
		        android:layout_width="wrap_content"
		        android:layout_height="wrap_content"
		        android:padding="4dp"
		        android:scaleType="fitCenter"
		        android:adjustViewBounds="true"
		        android:contentDescription="@null"
		        android:src="@drawable/main_image_1" />
		    
            <ImageView 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="4dp"
                android:scaleType="fitCenter"
                android:adjustViewBounds="true"
                android:contentDescription="@null"
                android:src="@drawable/main_image_2" />
            
            <ImageView 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="4dp"
                android:scaleType="fitCenter"
                android:adjustViewBounds="true"
                android:contentDescription="@null"
                android:src="@drawable/main_image_3" />
            
            <ImageView 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="4dp"
                android:scaleType="fitCenter"
                android:adjustViewBounds="true"
                android:contentDescription="@null"
                android:src="@drawable/main_image_4" />
            
        </LinearLayout>
        
    </HorizontalScrollView>
    
</LinearLayout>

(2)main_layout_below.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="260dp"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="@android:color/darker_gray" >
    
    <Button 
        android:id="@+id/main_btn_below"
        android:layout_width="90dp"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:text="btnBelow"
        android:onClick="onClick" />

</RelativeLayout>

(3)main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <include android:id="@+id/main_layout_below" layout="@layout/main_layout_below" />

    <com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout
        android:id="@+id/main_slider_layout"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
        
        <!-- Shadow Child -->
        <ImageView
            android:layout_width="15px"
            android:layout_height="fill_parent"
            android:contentDescription="@null"
            android:scaleType="fitXY"
            android:src="@drawable/main_side_shadow" />
         
        <!-- Main Child -->
        <include android:id="@+id/main_slider_main" layout="@layout/main_layout_above" />
        
    </com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout>
</FrameLayout>

(4)LeftSliderLayout.java

package com.zhaoxufeng.leftsliderlayout.lib;

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

public class LeftSliderLayout extends ViewGroup {

    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    /**
     * Constant value for touch state
     * TOUCH_STATE_REST : no touch
     * TOUCH_STATE_SCROLLING : scrolling
     */
    private static final int TOUCH_STATE_REST = 0;
    private static final int TOUCH_STATE_SCROLLING = 1;
    private int mTouchState = TOUCH_STATE_REST;
    
    /**
     * Distance in pixels a touch can wander before we think the user is scrolling
     */
    private int mTouchSlop;
    
    /**
     * Values for saving axis of the last touch event.
     */
    private float mLastMotionX;
    private float mLastMotionY;
    
    /**
     * Values for VelocityTracker to compute current velocity.
     * VELOCITY_UNITS in dp
     * mVelocityUnits in px
     */
    private static final int VELOCITY_UNITS = 1000;
    private int mVelocityUnits;	
    
    /**
     * The minimum velocity for determining the direction.
     * MINOR_VELOCITY in dp
     * mMinorVelocity in px
     */
    private static final float MINOR_VELOCITY = 150.0f;
    private int mMinorVelocity;								
    
    /**
     * The width of Sliding distance from left. 
     * And it should be the same with the width of the View below SliderLayout in a FrameLayout.
     * DOCK_WIDTH in dp
     * mDockWidth in px
     */
    private static final float SLIDING_WIDTH = 260.0f;			
    private int mSlidingWidth;									
    
    /**
     * The default values of shadow.
     * VELOCITY_UNITS in dp
     * mVelocityUnits in px
     */
    private static final float DEF_SHADOW_WIDTH = 30.0f;		
    private int mDefShadowWidth;								

    /**
     * Value for checking a touch event is completed.
     */
    private boolean mIsTouchEventDone = false;				
    
    /**
     * Value for checking slider is open.
     */
    private boolean mIsOpen = false;						
    
    /**
     * Value for saving the last offset of scroller 鈥�x-axis.
     */
    private int mSaveScrollX = 0;							
    
    /**
     * Value for checking slider is allowed to slide.
     */
    private boolean mEnableSlide = true;					
    
    private View mMainChild = null;
    private OnLeftSliderLayoutStateListener mListener = null;

	/**
	 * Instantiates a new LeftSliderLayout.
	 *
	 * @param context the associated Context
	 * @param attrs AttributeSet
	 */
    public LeftSliderLayout(Context context, AttributeSet attrs) {
    	this(context, attrs, 0);
    }

	/**
	 * Instantiates a new LeftSliderLayout.
	 *
	 * @param context the associated Context
	 * @param attrs AttributeSet
	 * @param defStyle Style
	 */
    public LeftSliderLayout(Context context, AttributeSet attrs, int defStyle) {
    	super(context, attrs, defStyle);
        mScroller = new Scroller(context);
        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        
        /**
         * Convert values in dp to values in px;
         */
        final float fDensity = getResources().getDisplayMetrics().density;
        mVelocityUnits = (int) (VELOCITY_UNITS * fDensity + 0.5f);
        mMinorVelocity = (int) (MINOR_VELOCITY * fDensity + 0.5f);
        mSlidingWidth = (int) (SLIDING_WIDTH * fDensity + 0.5f);
        mDefShadowWidth = (int) (DEF_SHADOW_WIDTH * fDensity + 0.5f);
    }
    
    @Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    	
    	// check Measure Mode is Exactly.
    	final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            throw new IllegalStateException("LeftSliderLayout only canmCurScreen run at EXACTLY mode!");
        }
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY) {
        	throw new IllegalStateException("LeftSliderLayout only can run at EXACTLY mode!");
        }

        // measure child views
        int nCount = getChildCount();
        for (int i = 2; i < nCount; i++) {
        	removeViewAt(i);
        }
        nCount = getChildCount();
        if (nCount > 0) {
        	if (nCount > 1) {
        		mMainChild = getChildAt(1);
        		getChildAt(0).measure(widthMeasureSpec, heightMeasureSpec);
			} else {
				mMainChild = getChildAt(0);
			}
        	mMainChild.measure(widthMeasureSpec, heightMeasureSpec);
		}
        
        // Set the scrolled position 
        scrollTo(mSaveScrollX, 0);
    }
    
	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		final int nCount = getChildCount();
		if (nCount <= 0) {
			return;
		}
		
		// Set the size and position of Main Child
		if (mMainChild != null) {
			mMainChild.layout(
				l,
				t,
				l + mMainChild.getMeasuredWidth(),
				t + mMainChild.getMeasuredHeight());
		}
		
		// Set the size and position of Shadow Child
		if (nCount > 1) {
			int nLeftChildWidth = 0;
			View leftChild = getChildAt(0);
			ViewGroup.LayoutParams layoutParams = leftChild.getLayoutParams();
			if (layoutParams.width == ViewGroup.LayoutParams.FILL_PARENT
					|| layoutParams.width == ViewGroup.LayoutParams.MATCH_PARENT) {
				nLeftChildWidth = mDefShadowWidth;
			} else {
				nLeftChildWidth = layoutParams.width;
			}
			leftChild.layout(
					l - nLeftChildWidth,
					t,
					l,
					t + leftChild.getMeasuredHeight());
		}
	}
	
    @Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();
		}
    }

    @Override
	public boolean onTouchEvent(MotionEvent event) { 
    	
    	int nCurScrollX = getScrollX();
    	
    	// check touch point is in the rectangle of Main Child
    	if (mMainChild != null
    			&& mTouchState != TOUCH_STATE_SCROLLING
    			&& mIsTouchEventDone) {
			Rect rect = new Rect();
			mMainChild.getHitRect(rect);
			if (!rect.contains((int)event.getX() + nCurScrollX, (int)event.getY())) {
				return false;
			}
		}
    	
    	if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }

        mVelocityTracker.addMovement(event);
        
        final int action = event.getAction();
        final float x = event.getX();
        
        switch (action) {
        case MotionEvent.ACTION_DOWN: {
        	if (!mScroller.isFinished()) {
        		mScroller.abortAnimation();
        	}
        	
        	mIsTouchEventDone = false;
            mLastMotionX = x;
            break;
        }

        case MotionEvent.ACTION_MOVE: {
        	// check slider is allowed to slide.
        	if (!mEnableSlide) {
				break;
			}
        	
        	// compute the x-axis offset from last point to current point
            int deltaX = (int) (mLastMotionX - x);
            if (nCurScrollX + deltaX < getMinScrollX()) {
        		deltaX = getMinScrollX() - nCurScrollX;
        		mLastMotionX = mLastMotionX - deltaX;
			} else if (nCurScrollX + deltaX > getMaxScrollX()) {
        		deltaX = getMaxScrollX() - nCurScrollX;
        		mLastMotionX = mLastMotionX - deltaX;
			} else {
				mLastMotionX = x;
			}
        	
            // Move view to the current point
        	if (deltaX != 0) {
        		scrollBy(deltaX, 0);
			}
        	
        	// Save the scrolled position 
        	mSaveScrollX = getScrollX();
        	break;
        }

        case MotionEvent.ACTION_CANCEL: 
        case MotionEvent.ACTION_UP: {
        	
        	// check slider is allowed to slide.
        	if (!mEnableSlide) {
				break;
			}
        	
        	final VelocityTracker velocityTracker = mVelocityTracker;
        	velocityTracker.computeCurrentVelocity(mVelocityUnits);
        	
        	// Set open or close state, when get ACTION_UP or ACTION_CANCEL event.
        	if (nCurScrollX < 0) {
        		int velocityX = (int) velocityTracker.getXVelocity();
        		if (velocityX > mMinorVelocity) {
	            	scrollByWithAnim(getMinScrollX() - nCurScrollX);
	            	setState(true);
	            }
	            else if (velocityX < -mMinorVelocity) {
	            	scrollByWithAnim(-nCurScrollX);
	            	setState(false);
	            } else {
	            	if (nCurScrollX >= getMinScrollX() / 2) {
	            		scrollByWithAnim(- nCurScrollX);
						setState(false);
					} else {
						scrollByWithAnim(getMinScrollX() - nCurScrollX);
	            		setState(true);
					}
	            }
			} else {
				if (nCurScrollX > 0) {
					scrollByWithAnim(-nCurScrollX);
				}
				setState(false);
			}
        	
            if (mVelocityTracker != null) {
            	mVelocityTracker.recycle();
            	mVelocityTracker = null;
            }

            mTouchState = TOUCH_STATE_REST;
            mIsTouchEventDone = true;
            break;
        }
        
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    	
        final int action = ev.getAction();
    	
		if (mListener != null && !mListener.OnLeftSliderLayoutInterceptTouch(ev)) {
			return false;
		}
        
        if ((action == MotionEvent.ACTION_MOVE)
      		  && (mTouchState != TOUCH_STATE_REST)) {
                 return true;
        }

        final float x = ev.getX();
        final float y = ev.getY();
        switch (action) {
        case MotionEvent.ACTION_DOWN:
                 mLastMotionX = x;
                 mLastMotionY = y;
                 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; 
                 break;

        case MotionEvent.ACTION_MOVE:
                 final int xDiff = (int) Math.abs(mLastMotionX - x);
                 if (xDiff > mTouchSlop) { 
                          if (Math.abs(mLastMotionY - y) / Math.abs(mLastMotionX - x) < 1)
                                   mTouchState = TOUCH_STATE_SCROLLING;
                }
                break;

        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
                 mTouchState = TOUCH_STATE_REST;
                 break;
        }
        return mTouchState != TOUCH_STATE_REST;
    }
    
    /**
     * With the horizontal scroll of the animation
     * 
     * @param nDx x-axis offset
     */
    void scrollByWithAnim(int nDx) {
    	if (nDx == 0) {
			return;
		}
    	
		mScroller.startScroll(getScrollX(), 0, nDx, 0,
				Math.abs(nDx));

		invalidate();
    }

    /**
     * Get distance of the maximum horizontal scroll
     * 
     * @return distance in px
     */
    private int getMaxScrollX() {
    	return 0;
    }
    
    /**
     * Get distance of the minimum horizontal scroll
     * @return distance in px
     */
    private int getMinScrollX() {
    	return -mSlidingWidth;
    }
    
    
    /**
     * Open LeftSlideLayout
     */
    public void open() {
    	if (mEnableSlide) {
        	scrollByWithAnim(getMinScrollX() - getScrollX());
        	setState(true);
		}
    }
    
    /**
     * Close LeftSlideLayout
     */
    public void close() {
    	if (mEnableSlide) {
	    	scrollByWithAnim((-1) * getScrollX());
	    	setState(false);
    	}
    }
    
    /**
     * Determine whether LeftSlideLayout is open
     * 
     * @return true-open锛宖alse-close
     */
    public boolean isOpen() {
    	return mIsOpen;
    }
    
    /**
     * Set state of LeftSliderLayout
     * 
     * @param bIsOpen the new state
     */
    private void setState(boolean bIsOpen) {
    	boolean bStateChanged = false;
    	if (mIsOpen && !bIsOpen) {
			bStateChanged = true;
		} else if (!mIsOpen && bIsOpen) {
			bStateChanged = true;
		}
    	
    	mIsOpen = bIsOpen;
    	
    	if (bIsOpen) {
    		mSaveScrollX = getMaxScrollX();
		} else {
			mSaveScrollX = 0;
		}
    	
    	if (bStateChanged && mListener != null) {
			mListener.OnLeftSliderLayoutStateChanged(bIsOpen);
		}
    }
    
    /**
     * enable slide action of LeftSliderLayout 
     * 
     * @param bEnable
     */
    public void enableSlide(boolean bEnable) {
		mEnableSlide = bEnable;
	}
    
    /**
     * Set listener to LeftSliderLayout
     */
    public void setOnLeftSliderLayoutListener(OnLeftSliderLayoutStateListener listener) {
    	mListener = listener;
    }

    /**
     * LeftSliderLayout Listener
     *
     */
    public interface OnLeftSliderLayoutStateListener { 
        
    	/**
    	 * Called when LeftSliderLayout鈥檚 state has been changed.
    	 * 
    	 * @param bIsOpen the new state
    	 */
    	public void OnLeftSliderLayoutStateChanged(boolean bIsOpen);
    	
    	/**
    	 * Called when LeftSliderLayout has got onInterceptTouchEvent.
    	 * 
    	 * @param ev Touch Event
    	 * @return true - LeftSliderLayout need to manage the InterceptTouchEvent.
    	 *         false - LeftSliderLayout don't need to manage the InterceptTouchEvent.
    	 */
    	public boolean OnLeftSliderLayoutInterceptTouch(MotionEvent ev);
   }
}


(5)MainActivity.java


package com.zhaoxufeng.leftsliderlayout.example;

import com.zhaoxufeng.leftsliderlayout.R;
import com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout;
import com.zhaoxufeng.leftsliderlayout.lib.LeftSliderLayout.OnLeftSliderLayoutStateListener;

import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
import android.app.Activity;

public class MainActivity extends Activity implements OnLeftSliderLayoutStateListener, OnClickListener {

	//https://github.com/xMobile/LeftSliderLayout
	
	LeftSliderLayout leftSliderLayout;
	View horizontalScrollView;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        horizontalScrollView = (View) findViewById(R.id.main_horizontal_scroll_view);
        leftSliderLayout = (LeftSliderLayout) findViewById(R.id.main_slider_layout);
        leftSliderLayout.setOnLeftSliderLayoutListener(this);
    }

	@Override
	public void OnLeftSliderLayoutStateChanged(boolean bIsOpen) {
		if (bIsOpen) {
			Toast.makeText(this, "LeftSliderLayout is open!", Toast.LENGTH_SHORT).show();
		} else {
			Toast.makeText(this, "LeftSliderLayout is close!", Toast.LENGTH_SHORT).show();
		}
		
	}

	@Override
	public boolean OnLeftSliderLayoutInterceptTouch(MotionEvent ev) {
		if (isViewTouched(horizontalScrollView, ev.getRawX(), ev.getRawY())) {
			return false;
		}
		return true;
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.main_btn_enable: {
			leftSliderLayout.enableSlide(true);
			break;
		}
		
		case R.id.main_btn_disable: {
			leftSliderLayout.enableSlide(false);
			break;
		}

		case R.id.main_btn_open: {
			leftSliderLayout.open();
			break;
		}

		case R.id.main_btn_close: {
			leftSliderLayout.close();
			break;
		}
		
		case R.id.main_btn_below: {
			Toast.makeText(this, "btnBelow is clicked!", Toast.LENGTH_SHORT).show();
			break;
		}

		default:
			break;
		}
	}
	
	private boolean isViewTouched(View view, float fX, float fY) {
		int location[] = new int[2];
		view.getLocationOnScreen(location);

		int nStartY = location[1];
		int nEndY = location[1] + view.getHeight();

		int nStartX = location[0];
		int nEndX = location[0] + view.getWidth();

		if ((fY >= nStartY && fY < nEndY) && (fX > nStartX && fX < nEndX)) {
			return true;
		}

		return false;
	}
    
}
 
 

通过研读以上代码,对于侧边滑动的宽度可以进行调节,
可以调节 SLIDING_WIDTH 值调节滑动菜单的宽度
同时可以调节xml文件的宽度对下面的界面进行调节。
LeftSliderLayout源代码在GitHub地址:https://github.com/xMobile/LeftSliderLayout

你可能感兴趣的:(android,动态规划)