开始动手之前先来讲一下实现原理,在一个Activity的布局中需要有三部分,一个是左侧菜单的布局,一个是右侧菜单的布局,一个是内容布局。左侧菜单居屏幕左边缘对齐,右侧菜单居屏幕右边缘对齐,然后内容布局占满整个屏幕,并压在了左侧菜单和右侧菜单的上面。当用户手指向右滑动时,将右侧菜单隐藏,左侧菜单显示,然后通过偏移内容布局的位置,就可以让左侧菜单展现出来。同样的道理,当用户手指向左滑动时,将左侧菜单隐藏,右侧菜单显示,也是通过偏移内容布局的位置,就可以让右侧菜单展现出来。
1.新建Android项目,然后新建一个DoubleSlideMenu继承自RelativeLayout,核心类代码:
public class DoubleMenu extends RelativeLayout implements OnTouchListener { //滚动和隐藏菜单布局时手指所需要的速度 private static final int SNAP_VELOCITY=200; //未进行任何滑动 private static final int DO_NOTHING=0; //左侧滑动菜单的显示 private static final int SHOW_LEFT_MENU=1; //右侧菜单显示 private static final int SHOW_RIGHT_MENU=2; //左侧菜单隐藏 private static final int HIDE_LEFT_MENU=3; //右侧菜单隐藏 private static final int HIDE_RIGHT_MENU=4; //记录滑动的状态 private int slideState; //滑动菜单最小的距离 private int touchSlop; //屏幕宽 private int screenWidth; //按下的横坐标 private float xDown; //按下的纵坐标 private float yDown; //移动的横坐标 private float xMove; //移动的纵坐标 private float yMove; //弹起的横坐标 private float xUp; //标志左侧菜单是否显示 private boolean isLeftMenuVisible; //标志右侧菜单是否显示 private boolean isRightMenuVisible; //左侧菜单布局 private View leftMenuLayout; //右侧菜单布局 private View rightMenuLayout; //左侧菜单布局参数 private MarginLayoutParams leftMenuLayoutParams; //右侧菜单布局参数 private MarginLayoutParams rightMenuLayoutParams; //内容页面布局 private View contentLayout; //内容页面布局参数 private RelativeLayout.LayoutParams contentLayoutParams; //标志菜单是否正在滑动 private boolean isSliding; //用于监听滑动事件的View private View mBindView; //手指的速度 private VelocityTracker mVelocityTracker; public DoubleMenu(Context context, AttributeSet attrs) { super(context, attrs); WindowManager wm=(WindowManager) context.getSystemService(Context.WINDOW_SERVICE); screenWidth=wm.getDefaultDisplay().getWidth(); touchSlop=ViewConfiguration.get(context).getScaledTouchSlop(); //触发滚动菜单事件的最小距离 } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(changed){ //获取左侧菜单 leftMenuLayout=getChildAt(0); leftMenuLayoutParams=(MarginLayoutParams) leftMenuLayout.getLayoutParams(); //获取右侧菜单 rightMenuLayout=getChildAt(1); rightMenuLayoutParams=(MarginLayoutParams) rightMenuLayout.getLayoutParams(); //获取内容页面 contentLayout=getChildAt(2); contentLayoutParams=(RelativeLayout.LayoutParams) contentLayout.getLayoutParams(); contentLayoutParams.width=screenWidth; contentLayout.setLayoutParams(contentLayoutParams); } } //绑定监听滑动的View public void setScrollEvent(View bindView){ mBindView=bindView; mBindView.setOnTouchListener(this); } //判断左侧布局是否显示 public boolean isLeftMenuVisible(){ return isLeftMenuVisible; } //判断右侧布局是否显示 public boolean isRightMenuVisible(){ return isRightMenuVisible; } //显示左侧菜单时初始化 public void initShowLeftState(){ contentLayoutParams.rightMargin=0; contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0); contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); contentLayout.setLayoutParams(contentLayoutParams); leftMenuLayout.setVisibility(View.VISIBLE); rightMenuLayout.setVisibility(View.GONE); } //显示右侧菜单时初始化 public void initShowRightState(){ contentLayoutParams.leftMargin=0; contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, 0); contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); contentLayout.setLayoutParams(contentLayoutParams); rightMenuLayout.setVisibility(View.VISIBLE); leftMenuLayout.setVisibility(View.GONE); } //根据手指移动的方向,判断用户滑动意图 private void checkSlideState(int moveDistanceX,int moveDistanceY){ if(isLeftMenuVisible){ //如果左侧菜单已经正在显示,判断是否需要关闭 if(!isSliding&&Math.abs(moveDistanceX)>touchSlop&&moveDistanceX<0){ isSliding = true; slideState=HIDE_LEFT_MENU; } } else if(isRightMenuVisible){ if(!isSliding&&Math.abs(moveDistanceX)>touchSlop&&moveDistanceX>0){ isSliding = true; slideState=HIDE_RIGHT_MENU; } } else{ if(!isSliding&&Math.abs(moveDistanceX)>touchSlop&&moveDistanceX>0&&Math.abs(moveDistanceY)<touchSlop){ isSliding = true; slideState=SHOW_LEFT_MENU; initShowLeftState(); } else if(!isSliding&&Math.abs(moveDistanceX)>touchSlop&&moveDistanceX<0&&Math.abs(moveDistanceY)<touchSlop){ isSliding=true; slideState=SHOW_RIGHT_MENU; initShowRightState(); } } } //在滑动中,检查左侧菜单的边界值 private void checkLeftMenuBorder(){ if(contentLayoutParams.rightMargin>0){ contentLayoutParams.rightMargin=0; //隐藏左侧菜单的时刻 } else if(contentLayoutParams.rightMargin<-leftMenuLayoutParams.width){ contentLayoutParams.rightMargin=-leftMenuLayoutParams.width; //显示左侧菜单的时刻 } } //在滑动过程中检查右侧菜单的边界值 private void checkRightMenuBorder(){ if(contentLayoutParams.leftMargin>0){ contentLayoutParams.leftMargin=0; } else if(contentLayoutParams.leftMargin<-rightMenuLayoutParams.width){ contentLayoutParams.leftMargin=-rightMenuLayoutParams.width; } } /** * 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。 * * @param event * 右侧布局监听控件的滑动事件 */ private void createVelocityTracker(MotionEvent event) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** * 获取手指在绑定布局上的滑动速度。 * * @return 滑动速度,以每秒钟移动了多少像素值为单位。 */ private int getScrollVelocity() { mVelocityTracker.computeCurrentVelocity(1000); int velocity = (int) mVelocityTracker.getXVelocity(); return Math.abs(velocity); } /** * 回收VelocityTracker对象。 */ private void recycleVelocityTracker() { mVelocityTracker.recycle(); mVelocityTracker = null; } //判断是否应该将左侧菜单显示出来 private boolean shouldScrollToLeftMenu(){ return xUp-xDown>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY; } //判断是否应将右侧菜单显示 private boolean shouldScrollToRightMenu(){ return xDown-xUp>rightMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY; } //判断是否应从左侧转到内容页面 private boolean shouldScrollToContentFromLeft(){ return xDown-xUp>leftMenuLayoutParams.width/2||getScrollVelocity()>SNAP_VELOCITY; } //判断是否应从右侧转到内容页面 private boolean shouldScrollToContentFromRight(){ return xUp-xDown>rightMenuLayoutParams.width/2|getScrollVelocity()>SNAP_VELOCITY; } //让获得焦点的控件在滑动过程中失去焦点 private void unFocusBindView(){ if(mBindView!=null){ mBindView.setPressed(false); mBindView.setFocusable(false); mBindView.setFocusableInTouchMode(false); } } class LeftMenuScrollTask extends AsyncTask<Integer, Integer, Integer>{ @Override protected Integer doInBackground(Integer... speed) { int rightMargin=contentLayoutParams.rightMargin; while(true){ rightMargin=rightMargin+speed[0]; if(rightMargin>0){ //隐藏 rightMargin=0; break; } if(rightMargin<-leftMenuLayoutParams.width){ rightMargin=-leftMenuLayoutParams.width; break; } publishProgress(rightMargin); sleep(15); } if(speed[0]>0){ isLeftMenuVisible=false; } else{ isLeftMenuVisible=true; } isSliding=false; return rightMargin; } @Override protected void onProgressUpdate(Integer... values) { contentLayoutParams.rightMargin=values[0]; contentLayout.setLayoutParams(contentLayoutParams); unFocusBindView(); } @Override protected void onPostExecute(Integer result) { contentLayoutParams.rightMargin=result; contentLayout.setLayoutParams(contentLayoutParams); } } class RightMenuScrollTask extends AsyncTask<Integer, Integer, Integer>{ @Override protected Integer doInBackground(Integer... speed) { int leftMargin=contentLayoutParams.leftMargin; while(true){ leftMargin=leftMargin+speed[0]; if(leftMargin>0){ leftMargin=0; break; } if(leftMargin<-rightMenuLayoutParams.width){ leftMargin=-rightMenuLayoutParams.width; break; } publishProgress(leftMargin); sleep(20); } if(speed[0]>0){ isRightMenuVisible=false; } else{ isRightMenuVisible=true; } isSliding=false; return leftMargin; } @Override protected void onProgressUpdate(Integer... values) { contentLayoutParams.leftMargin=values[0]; contentLayout.setLayoutParams(contentLayoutParams); unFocusBindView(); } @Override protected void onPostExecute(Integer result) { contentLayoutParams.leftMargin=result; contentLayout.setLayoutParams(contentLayoutParams); } } private void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } //将界面滚到左侧菜单 public void scrollToLeftMenu(){ new LeftMenuScrollTask().execute(-30); } //将界面滚动到右侧菜单 public void scrollToRightMenu(){ new RightMenuScrollTask().execute(-30); } //将界面从左侧滚到内容 public void scrollToContentFromLeft(){ new LeftMenuScrollTask().execute(30); } //将界面从右侧滚到内容 public void scrollToContentFromRight(){ new RightMenuScrollTask().execute(30); } @Override public boolean onTouch(View v, MotionEvent event) { createVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: xDown=event.getRawX(); yDown=event.getRawY(); slideState=DO_NOTHING; break; case MotionEvent.ACTION_MOVE:{ xMove=event.getRawX(); yMove=event.getRawY(); int moveDistanceX=(int)(xMove-xDown); int moveDistanceY=(int)(yMove-yDown); checkSlideState(moveDistanceX, moveDistanceY); switch (slideState) { case SHOW_LEFT_MENU: contentLayoutParams.rightMargin=-moveDistanceX; checkLeftMenuBorder(); contentLayout.setLayoutParams(contentLayoutParams); break; case HIDE_LEFT_MENU: contentLayoutParams.rightMargin=-leftMenuLayoutParams.width-moveDistanceX; checkLeftMenuBorder(); contentLayout.setLayoutParams(contentLayoutParams); break; case SHOW_RIGHT_MENU: contentLayoutParams.leftMargin=moveDistanceX; checkRightMenuBorder(); contentLayout.setLayoutParams(contentLayoutParams); break; case HIDE_RIGHT_MENU: contentLayoutParams.leftMargin=-rightMenuLayoutParams.width+moveDistanceX; checkRightMenuBorder(); contentLayout.setLayoutParams(contentLayoutParams); break; } break; } case MotionEvent.ACTION_UP: xUp=event.getRawX(); int upDistanceX=(int)(xUp-xDown); if(isSliding){ switch (slideState) { case SHOW_LEFT_MENU: if(shouldScrollToLeftMenu()){ scrollToLeftMenu(); } else{ scrollToContentFromLeft(); } break; case HIDE_LEFT_MENU: if(shouldScrollToContentFromLeft()){ scrollToContentFromLeft(); } else{ scrollToLeftMenu(); } break; case SHOW_RIGHT_MENU: if(shouldScrollToRightMenu()){ scrollToRightMenu(); } else{ scrollToContentFromRight(); } break; case HIDE_RIGHT_MENU: if(shouldScrollToContentFromRight()){ scrollToContentFromRight(); } else{ scrollToRightMenu(); } default: break; } } else if(upDistanceX<touchSlop&&isLeftMenuVisible){ scrollToContentFromLeft(); } else if(upDistanceX<touchSlop&&isRightMenuVisible){ scrollToContentFromRight(); } recycleVelocityTracker(); break; } if(v.isClickable()){ //这里要求控件必须是clickable的. if(isSliding){ unFocusBindView(); //正在滑动时取消控件焦点 return true; } if(isLeftMenuVisible||isRightMenuVisible){ return true;//当左菜单或右菜单显示时,取消触摸事件 } return false; } return true; } }
首先在onLayout()方法中分别获取到左侧菜单、右侧菜单和内容布局的参数,并将内容布局的宽度重定义成屏幕的宽度,这样就可以保证内容布局既能覆盖住下面的菜单布局,还能偏移出屏幕。然后在onTouch()方法中监听触屏事件,以判断用户手势的意图。这里事先定义好了几种滑动状态,DO_NOTHING表示没有进行任何滑动,SHOW_LEFT_MENU表示用户想要滑出左侧菜单,SHOW_RIGHT_MENU表示用户想要滑出右侧菜单,HIDE_LEFT_MENU表示用户想要隐藏左侧菜单,HIDE_RIGHT_MENU表示用户想要隐藏右侧菜单,在checkSlideState()方法中判断出用户到底是想进行哪一种滑动操作,并给slideState变量赋值,然后根据slideState的值决定如何偏移内容布局。接着当用户手指离开屏幕时,会根据当前的滑动距离,决定后续的滚动方向,通过LeftMenuScrollTask和RightMenuScrollTask来完成完整的滑动过程。另外在滑动的过程,内容布局上的事件会被屏蔽掉,主要是通过一系列的return操作实现的.
2.打开activity_main.xml
<com.example.doubleslidemenu.DoubleMenu 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" android:id="@+id/double_slideMenu" tools:context=".MainActivity" > <RelativeLayout android:layout_width="270dp" android:layout_height="fill_parent" android:id="@+id/left_menu" android:layout_alignParentLeft="true" android:background="#00ccff" android:visibility="invisible" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="This is left menu" android:textColor="#000000" android:textSize="28sp" /> </RelativeLayout> <RelativeLayout android:id="@+id/right_menu" android:layout_width="270dip" android:layout_height="fill_parent" android:layout_alignParentRight="true" android:background="#00ffcc" android:visibility="invisible" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="This is right menu" android:textColor="#000000" android:textSize="28sp" /> </RelativeLayout> <LinearLayout android:id="@+id/content" android:layout_width="320dip" android:layout_height="fill_parent" android:orientation="vertical" android:background="#e9e9e9" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/show_left_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:text="show left" /> <Button android:id="@+id/show_right_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="show right" /> </RelativeLayout> <ListView android:id="@+id/contentList" android:layout_width="fill_parent" android:layout_height="0dp" android:layout_weight="1" android:scrollbars="none" android:cacheColorHint="#00000000" > </ListView> </LinearLayout> </com.example.doubleslidemenu.DoubleMenu>
3.打开MainActivity.java
public class MainActivity extends Activity { /** * 双向滑动菜单布局 */ private DoubleMenu bidirSldingLayout; /** * 在内容布局上显示的ListView */ private ListView contentList; /** * ListView的适配器 */ private ArrayAdapter<String> contentListAdapter; /** * 用于填充contentListAdapter的数据源。 */ private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3", "Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7", "Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11", "Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15", "Content Item 16" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bidirSldingLayout = (DoubleMenu) findViewById(R.id.double_slideMenu); Button showLeftButton = (Button) findViewById(R.id.show_left_button); Button showRightButton = (Button) findViewById(R.id.show_right_button); contentList = (ListView) findViewById(R.id.contentList); contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contentItems); contentList.setAdapter(contentListAdapter); bidirSldingLayout.setScrollEvent(contentList); showLeftButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (bidirSldingLayout.isLeftMenuVisible()) { bidirSldingLayout.scrollToContentFromLeft(); } else { bidirSldingLayout.initShowLeftState(); bidirSldingLayout.scrollToLeftMenu(); } } }); showRightButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (bidirSldingLayout.isRightMenuVisible()) { bidirSldingLayout.scrollToContentFromRight(); } else { bidirSldingLayout.initShowRightState(); bidirSldingLayout.scrollToRightMenu(); } } }); } }
至此,可以双向滑动的SlideMenu就完成了.