Android之十SlidingMenu侧滑菜单的实现分析
SlidingMenu侧滑菜单是一种比较新的设置界面或配置界面的效果,在主界面左滑或者右滑出现设置界面效果,能方便的进行各种操作。很多优秀的应用都采用了这种界面方案,像facebook、人人网、everynote、Google+、网易新闻、知乎日报、有道云笔记等等
侧滑菜单实现原理:
在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局。两个布局横向排列,菜单布局在左,内容布局在右。初始化的时候将菜单布局向左偏移,以至于能够完全隐藏,这样内容布局就会完全显示在Activity中。然后通过监听手指滑动事件,来改变菜单布局的左偏移距离,从而控制菜单布局的显示和隐藏。
二:实现过程:点开按钮,展开侧滑菜单,再次点击关闭侧滑菜单。
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>
最终布局
点击左边的按钮或者往右滑动;