您的位置:首页 > 移动开发 > Android开发

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

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

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



实现代码如下:

(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 动态规划