Android之十SlidingMenu侧滑菜单的实现分析


SlidingMenu侧滑菜单是一种比较新的设置界面或配置界面的效果,在主界面左滑或者右滑出现设置界面效果,能方便的进行各种操作。很多优秀的应用都采用了这种界面方案,像facebook、人人网、everynote、Google+、网易新闻、知乎日报、有道云笔记等等




侧滑菜单实现原理:
在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局。两个布局横向排列,菜单布局在左,内容布局在右。初始化的时候将菜单布局向左偏移,以至于能够完全隐藏,这样内容布局就会完全显示在Activity中。然后通过监听手指滑动事件,来改变菜单布局的左偏移距离,从而控制菜单布局的显示和隐藏。






二:实现过程:点开按钮,展开侧滑菜单,再次点击关闭侧滑菜单。


Android之十SlidingMenu侧滑菜单的实现分析_侧滑菜单的实现


1、引入自定义的SlideMenu 组件


SlideMenu.java


package   com.example.walkerlogin1.view;

import android.content.Context;
import android.graphics.Canvas;
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 SlideMenu extends ViewGroup {
	public static final int SCREEN_MENU = 0;
	public static final int SCREEN_MAIN = 1;
	private static final int SCREEN_INVALID = -1;
	
	private int mCurrentScreen;
	private int mNextScreen = SCREEN_INVALID;

	private Scroller mScroller;
	private VelocityTracker mVelocityTracker;
	private int mTouchSlop;
	
	private float mLastMotionX;
	private float mLastMotionY;

	private final static int TOUCH_STATE_REST = 0;
	private final static int TOUCH_STATE_SCROLLING = 1;
	private static final int SNAP_VELOCITY = 1000;

	public int mTouchState = TOUCH_STATE_REST;
	private boolean mLocked;
	private boolean mAllowLongPress;

	public SlideMenu(Context context) {
		this(context, null, 0);
	}

	public SlideMenu(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public SlideMenu(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		mScroller = new Scroller(getContext());
		mCurrentScreen = SCREEN_MAIN;
		mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		measureViews(widthMeasureSpec, heightMeasureSpec);
	}

	public void measureViews(int widthMeasureSpec, int heightMeasureSpec) {
		View menuView = getChildAt(0);
		menuView.measure(menuView.getLayoutParams().width + menuView.getLeft()
				+ menuView.getRight(), heightMeasureSpec);

		View contentView = getChildAt(1);
		contentView.measure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int childCount = getChildCount();
		if (childCount != 2) {
			throw new IllegalStateException(
					"The childCount of SlidingMenu must be 2");
		}

		View menuView = getChildAt(0);
		final int width = menuView.getMeasuredWidth();
		menuView.layout(-width, 0, 0, menuView.getMeasuredHeight());

		View contentView = getChildAt(1);
		contentView.layout(0, 0, contentView.getMeasuredWidth(),
				contentView.getMeasuredHeight());
	}
	
	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		View child;
		for (int i = 0; i < getChildCount(); i++) {
			child = getChildAt(i);
			child.setFocusable(true);
			child.setClickable(true);
		}
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if (mLocked) {
			return true;
		}

		final int action = ev.getAction();
		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_MOVE:

			final int xDiff = (int) Math.abs(x - mLastMotionX);
			final int yDiff = (int) Math.abs(y - mLastMotionY);

			final int touchSlop = mTouchSlop;
			boolean xMoved = xDiff > touchSlop;
			boolean yMoved = yDiff > touchSlop;

			if (xMoved || yMoved) {

				if (xMoved) {
					// Scroll if the user moved far enough along the X axis
					mTouchState = TOUCH_STATE_SCROLLING;
					enableChildrenCache();
				}
				// Either way, cancel any pending longpress
				if (mAllowLongPress) {
					mAllowLongPress = false;
					// Try canceling the long press. It could also have been
					// scheduled
					// by a distant descendant, so use the mAllowLongPress flag
					// to block
					// everything
					final View currentScreen = getChildAt(mCurrentScreen);
					currentScreen.cancelLongPress();
				}
			}
			break;

		case MotionEvent.ACTION_DOWN:
			// Remember location of down touch
			mLastMotionX = x;
			mLastMotionY = y;
			mAllowLongPress = true;

			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
					: TOUCH_STATE_SCROLLING;

			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			// Release the drag
			clearChildrenCache();
			mTouchState = TOUCH_STATE_REST;
			mAllowLongPress = false;
			break;
		}

		/*
		 * The only time we want to intercept motion events is if we are in the
		 * drag mode.
		 */
		return mTouchState != TOUCH_STATE_REST;
	}
	
	void enableChildrenCache() {
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View layout = (View) getChildAt(i);
			layout.setDrawingCacheEnabled(true);
		}
	}

	void clearChildrenCache() {
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			final View layout = (View) getChildAt(i);
			layout.setDrawingCacheEnabled(false);
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (mLocked) {
			return true;
		}

		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(ev);

		final int action = ev.getAction();
		final float x = ev.getX();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			/*
			 * If being flinged and user touches, stop the fling. isFinished
			 * will be false if being flinged.
			 */
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}

			// Remember where the motion event started
			mLastMotionX = x;
			break;
		case MotionEvent.ACTION_MOVE:
			if (mTouchState == TOUCH_STATE_SCROLLING) {
				// Scroll to follow the motion event
				final int deltaX = (int) (mLastMotionX - x);
				mLastMotionX = x;

				if (deltaX < 0) {
					if (deltaX + getScrollX() >= -getChildAt(0).getWidth()) {
						scrollBy(deltaX, 0);
					}

				} else if (deltaX > 0) {
					final int availableToScroll = getChildAt(
							getChildCount() - 1).getRight()
							- getScrollX() - getWidth();

					if (availableToScroll > 0) {
						scrollBy(Math.min(availableToScroll, deltaX), 0);
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (mTouchState == TOUCH_STATE_SCROLLING) {
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(1000);
				int velocityX = (int) velocityTracker.getXVelocity();

				if (velocityX > SNAP_VELOCITY && mCurrentScreen == SCREEN_MAIN) {
					// Fling hard enough to move left
					snapToScreen(SCREEN_MENU);
				} else if (velocityX < -SNAP_VELOCITY
						&& mCurrentScreen == SCREEN_MENU) {
					// Fling hard enough to move right
					snapToScreen(SCREEN_MAIN);
				} else {
					snapToDestination();
				}

				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
			}
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST;
		}

		return true;
	}
	
	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
		} else if (mNextScreen != SCREEN_INVALID) {
			mCurrentScreen = Math.max(0,
					Math.min(mNextScreen, getChildCount() - 1));
			mNextScreen = SCREEN_INVALID;
			clearChildrenCache();
		}
	}

	@Override
	public void scrollTo(int x, int y) {
		super.scrollTo(x, y);
		postInvalidate();
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		final int scrollX = getScrollX();
		super.dispatchDraw(canvas);
		canvas.translate(scrollX, 0);
	}

	@Override
	public boolean dispatchUnhandledMove(View focused, int direction) {
		if (direction == View.FOCUS_LEFT) {
			if (getCurrentScreen() > 0) {
				snapToScreen(getCurrentScreen() - 1);
				return true;
			}
		} else if (direction == View.FOCUS_RIGHT) {
			if (getCurrentScreen() < getChildCount() - 1) {
				snapToScreen(getCurrentScreen() + 1);
				return true;
			}
		}
		return super.dispatchUnhandledMove(focused, direction);
	}
	
	protected void snapToScreen(int whichScreen) {

		enableChildrenCache();

		whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));
		boolean changingScreens = whichScreen != mCurrentScreen;

		mNextScreen = whichScreen;

		View focusedChild = getFocusedChild();
		if (focusedChild != null && changingScreens
				&& focusedChild == getChildAt(mCurrentScreen)) {
			focusedChild.clearFocus();
		}

		final int newX = (whichScreen - 1) * getChildAt(0).getWidth();
		final int delta = newX - getScrollX();
		mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
		invalidate();
	}

	protected void snapToDestination() {
		if (getScrollX() == 0) {
			return;
		}
		final int screenWidth = getChildAt(0).getWidth();
		final int whichScreen = (screenWidth + getScrollX() + (screenWidth / 2))
				/ screenWidth;
		snapToScreen(whichScreen);
	}
	
	public int getCurrentScreen() {
		return mCurrentScreen;
	}
	
	public boolean isMainScreenShowing() {
		return mCurrentScreen == SCREEN_MAIN;
	}
	
	public void openMenu() {
		mCurrentScreen = SCREEN_MENU;
		snapToScreen(mCurrentScreen);
	}
	
	public void closeMenu() {
		mCurrentScreen = SCREEN_MAIN;
		snapToScreen(mCurrentScreen);
	}
	
	public void unlock() {
		mLocked = false;
	}

	public void lock() {
		mLocked = true;
	}
	
}


在vlues下面的strings.xml,增加以下代码

<?xml version="1.0" encoding="utf-8"?>
<resources>

    
    <item>用户登录</item>
    <item>运动测试</item>
    <item>个人信息</item>
    <item>行程记录</item>
    <item>天气查询</item>
    <item>健康栏目</item>
    <item>软件设置</item>
   
</string-array>
</resources>

Item.java


package com.example.walkerlogin1.view;

public class Item {
	private String name;
	private int imageId;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getImageId() {
		return imageId;
	}
	public void setImageId(int imageId) {
		this.imageId = imageId;
	}
	public Item(String name, int imageId) {
		super();
		this.name = name;
		this.imageId = imageId;
	}
	
}


建立数据源

ItemAdapter.java


package com.example.walkerlogin1.view;

import java.util.List;

import com.example.walkerlogin1.R;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class ItemAdapter extends ArrayAdapter<Item> {

public ItemAdapter(Context context, int textViewResourceId,
			List<Item> objects) {
		super(context,  textViewResourceId, objects);
		// TODO Auto-generated constructor stub
		resourceId=textViewResourceId;
	}


	private int resourceId;

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Item item = getItem(position);
		View view;
		ViewHolder viewHolder;
		if (convertView == null) {
			view = LayoutInflater.from(getContext()).inflate(resourceId, null);

			viewHolder = new ViewHolder();
			viewHolder.ivIcon = (ImageView) view.findViewById(R.id.ivIcon);
			viewHolder.tvIntroduction = (TextView) view.findViewById(R.id.tvIntroduction);
			view.setTag(viewHolder);
		} else {
			view = convertView;
			viewHolder = (ViewHolder) view.getTag();
		}
		viewHolder.ivIcon.setImageResource(item.getImageId());
		viewHolder.tvIntroduction.setText(item.getName());
		return view;
	}
	
	class ViewHolder {
		
		ImageView ivIcon;
		
		TextView tvIntroduction;
		
	}

}


主界面MainActivity2.java

package com.example.walkerlogin1;

import java.util.ArrayList;
import java.util.List;

import com.example.walkerlogin1.view.Item;
import com.example.walkerlogin1.view.ItemAdapter;
import com.example.walkerlogin1.view.SlideMenu;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Toast;



public class MainActivity2 extends Activity {
	private SlideMenu slideMenu;
	private ImageView ivSwitchSlideMenu;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.activity_main2);
		initMenuList();
		slideMenu=(SlideMenu) findViewById(R.id.slideMenu);
		ivSwitchSlideMenu=(ImageView) findViewById(R.id.switch_slidemenu);
		ivSwitchSlideMenu.setOnClickListener(new OnClickListener() {
		public void onClick(View view) {
		if(slideMenu.isMainScreenShowing()){
		slideMenu.openMenu();
		}else{
		slideMenu.closeMenu();
		}
		}
		});
	}
	private void initMenuList() {
		int[] icons = { R.drawable.icons_menu_login,
		R.drawable.icons_menu_sport, R.drawable.icons_menu_inform,
		R.drawable.icons_menu_history, R.drawable.icons_menu_weather,
		R.drawable.icons_menu_health, R.drawable.icons_menu_setting };
		final String[] introductons = getResources().getStringArray(R.array.menulist);
		List<Item> items=new ArrayList<Item>();
		for(int i=0;i<icons.length;i++){
		   items.add(new Item(introductons[i],icons[i]));
		}
		ListView lvMenuList=(ListView) findViewById(R.id.lvMenuList);
		ItemAdapter itemAdapter=new ItemAdapter(this, R.layout.menulist_item, items);
		lvMenuList.setAdapter(itemAdapter);
		lvMenuList.setOnItemClickListener(new OnItemClickListener() {
		public void onItemClick(AdapterView<?> adapterView, View view, int position,
		long id) {
		//Toast.makeText(MainActivity2.this," 你点击了"+introductons[position],
		//Toast.LENGTH_LONG).show();
			Intent  intent=new Intent();
		switch (position) {
		  case 0:  //这里可以进行listview的点击跳转
			Toast.makeText(MainActivity2.this," 你点击了"+introductons[position],
					Toast.LENGTH_LONG).show();
			intent.setClass(getApplicationContext(), MainActivity.class);
			startActivity(intent);
			break;

		    default:
			break;
		}
		}
		});
}
}


UI布局 activity_main.xml


<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" >

    <com.example.walkerlogin1.view.SlideMenu
        android:id="@+id/slideMenu"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- 侧滑菜单 -->

        <include layout="@layout/leftmenu" />
        <!-- 主界面 -->

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <include layout="@layout/main_layout_titlebar" />

            <include layout="@layout/main_layout_content" />
        </LinearLayout>
    </com.example.walkerlogin1.view.SlideMenu>

</RelativeLayout>


leftmenu.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.example.walkerlogin1"
    android:layout_width="200dp"
    android:layout_height="match_parent"
    android:background="@drawable/leftmenu_bg" >

    <com.makeramen.roundedimageview.RoundedImageView
        android:id="@+id/rivUserPhoto"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:src="@drawable/test_photo"
        app:riv_oval="true" />

    <TextView
        android:id="@+id/tvMotto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/rivUserPhoto"
        android:layout_marginBottom="24dp"
        android:layout_marginLeft="5dp"
        android:layout_toRightOf="@+id/rivUserPhoto"
        android:text="奔跑无止境"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textColor="@color/white" />

    <ListView
        android:id="@+id/lvMenuList"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/tvMotto"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:divider="@color/whitesmoke"
        android:dividerHeight="1dp"
        android:listSelector="#00000000" >
    </ListView>

</RelativeLayout>


main_layout_titlebar.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="45.0dip"
    android:background="@drawable/titlebar_bg"
    android:gravity="center_vertical" >

    <ImageView
        android:id="@+id/switch_slidemenu"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="3.0dip"
        android:gravity="center"       
        android:src="@drawable/switch_silidemenu" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:text="Walker"
        android:textColor="@color/white"
        android:textSize="22sp" />

    <ImageView
        android:id="@+id/switch_map"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="15.0dip"
        android:src="@drawable/switch_map" />

</RelativeLayout>


main_layout_content.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/welcome_bg" >

    <TextView
        android:id="@+id/tvCity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:text="滨州市"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/tvTemperature"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/tvCity"
        android:layout_below="@+id/tvCity"
        android:layout_marginRight="26dp"
        android:layout_marginTop="40dp"
        android:text="temperature"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/tvDay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tvTemperature"
        android:layout_below="@+id/tvTemperature"
        android:layout_marginTop="20dp"
        android:text="星期日"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/tvWeather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tvDay"
        android:layout_below="@+id/tvDay"
        android:layout_marginTop="24dp"
        android:text="晴"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/tvTemperature"
        android:layout_marginLeft="24dp"
        android:layout_toRightOf="@+id/tvTemperature"
        android:background="#00000000"
        android:src="@drawable/monkey" />

</RelativeLayout>


最终布局


Android之十SlidingMenu侧滑菜单的实现分析_ide_02


点击左边的按钮或者往右滑动;

Android之十SlidingMenu侧滑菜单的实现分析_ci_03