转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9671609
记得在很早之前,我写了一篇关于Android滑动菜单的文章,其中有一个朋友在评论中留言,希望我可以帮他将这个滑动菜单改成双向滑动的方式。当时也没想花太多时间,简单修改了一下就发给了他,结果没想到后来却有一大批的朋友都来问我要这份双向滑动菜单的代码。由于这份代码写得很不用心,我发了部分朋友之后实在不忍心继续发下去了,于是决定专门写一篇文章来介绍更好的Android双向滑动菜单的实现方法。
在开始动手之前先来讲一下实现原理,在一个Activity的布局中需要有三部分,一个是左侧菜单的布局,一个是右侧菜单的布局,一个是内容布局。左侧菜单居屏幕左边缘对齐,右侧菜单居屏幕右边缘对齐,然后内容布局占满整个屏幕,并压在了左侧菜单和右侧菜单的上面。当用户手指向右滑动时,将右侧菜单隐藏,左侧菜单显示,然后通过偏移内容布局的位置,就可以让左侧菜单展现出来。同样的道理,当用户手指向左滑动时,将左侧菜单隐藏,右侧菜单显示,也是通过偏移内容布局的位置,就可以让右侧菜单展现出来。原理示意图所下所示:
介绍完了原理,我们就开始动手实现吧。新建一个Android项目,项目名就叫做BidirSlidingLayout。然后新建我们最主要的BidirSlidingLayout类,这个类就是实现双向滑动菜单功能的核心类,代码如下所示:
- public class BidirSlidingLayout extends RelativeLayout implements OnTouchListener {
-
-
-
-
- public static final int SNAP_VELOCITY = 200;
-
-
-
-
- public static final int DO_NOTHING = 0;
-
-
-
-
- public static final int SHOW_LEFT_MENU = 1;
-
-
-
-
- public static final int SHOW_RIGHT_MENU = 2;
-
-
-
-
- public static final int HIDE_LEFT_MENU = 3;
-
-
-
-
- public static final int HIDE_RIGHT_MENU = 4;
-
-
-
-
- private int slideState;
-
-
-
-
- private int screenWidth;
-
-
-
-
- private int touchSlop;
-
-
-
-
- private float xDown;
-
-
-
-
- private float yDown;
-
-
-
-
- private float xMove;
-
-
-
-
- private float yMove;
-
-
-
-
- private float xUp;
-
-
-
-
- private boolean isLeftMenuVisible;
-
-
-
-
- private boolean isRightMenuVisible;
-
-
-
-
- private boolean isSliding;
-
-
-
-
- private View leftMenuLayout;
-
-
-
-
- private View rightMenuLayout;
-
-
-
-
- private View contentLayout;
-
-
-
-
- private View mBindView;
-
-
-
-
- private MarginLayoutParams leftMenuLayoutParams;
-
-
-
-
- private MarginLayoutParams rightMenuLayoutParams;
-
-
-
-
- private RelativeLayout.LayoutParams contentLayoutParams;
-
-
-
-
- private VelocityTracker mVelocityTracker;
-
-
-
-
-
-
-
- public BidirSlidingLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- screenWidth = wm.getDefaultDisplay().getWidth();
- touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
- }
-
-
-
-
-
-
-
- public void setScrollEvent(View bindView) {
- mBindView = bindView;
- mBindView.setOnTouchListener(this);
- }
-
-
-
-
- public void scrollToLeftMenu() {
- new LeftMenuScrollTask().execute(-30);
- }
-
-
-
-
- public void scrollToRightMenu() {
- new RightMenuScrollTask().execute(-30);
- }
-
-
-
-
- public void scrollToContentFromLeftMenu() {
- new LeftMenuScrollTask().execute(30);
- }
-
-
-
-
- public void scrollToContentFromRightMenu() {
- new RightMenuScrollTask().execute(30);
- }
-
-
-
-
-
-
- public boolean isLeftLayoutVisible() {
- return isLeftMenuVisible;
- }
-
-
-
-
-
-
- public boolean isRightLayoutVisible() {
- return isRightMenuVisible;
- }
-
-
-
-
- @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);
- }
- }
-
- @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);
- 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);
- default:
- 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 {
- scrollToContentFromLeftMenu();
- }
- break;
- case HIDE_LEFT_MENU:
- if (shouldScrollToContentFromLeftMenu()) {
- scrollToContentFromLeftMenu();
- } else {
- scrollToLeftMenu();
- }
- break;
- case SHOW_RIGHT_MENU:
- if (shouldScrollToRightMenu()) {
- scrollToRightMenu();
- } else {
- scrollToContentFromRightMenu();
- }
- break;
- case HIDE_RIGHT_MENU:
- if (shouldScrollToContentFromRightMenu()) {
- scrollToContentFromRightMenu();
- } else {
- scrollToRightMenu();
- }
- break;
- default:
- break;
- }
- } else if (upDistanceX < touchSlop && isLeftMenuVisible) {
-
- scrollToContentFromLeftMenu();
- } else if (upDistanceX < touchSlop && isRightMenuVisible) {
-
- scrollToContentFromRightMenu();
- }
- recycleVelocityTracker();
- break;
- }
- if (v.isEnabled()) {
- if (isSliding) {
-
- unFocusBindView();
- return true;
- }
- if (isLeftMenuVisible || isRightMenuVisible) {
-
- return true;
- }
- return false;
- }
- return true;
- }
-
-
-
-
-
-
-
-
-
- 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;
- contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, 0);
- contentLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- contentLayout.setLayoutParams(contentLayoutParams);
-
- leftMenuLayout.setVisibility(View.VISIBLE);
- rightMenuLayout.setVisibility(View.GONE);
- } else if (!isSliding && Math.abs(moveDistanceX) >= touchSlop && moveDistanceX < 0
- && Math.abs(moveDistanceY) < touchSlop) {
- isSliding = true;
- slideState = SHOW_RIGHT_MENU;
- 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 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;
- }
- }
-
-
-
-
-
-
-
- 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 shouldScrollToContentFromLeftMenu() {
- return xDown - xUp > leftMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
- }
-
-
-
-
-
-
-
- private boolean shouldScrollToContentFromRightMenu() {
- return xUp - xDown > rightMenuLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
- }
-
-
-
-
-
-
-
- private void createVelocityTracker(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- }
-
-
-
-
-
-
- private int getScrollVelocity() {
- mVelocityTracker.computeCurrentVelocity(1000);
- int velocity = (int) mVelocityTracker.getXVelocity();
- return Math.abs(velocity);
- }
-
-
-
-
- private void recycleVelocityTracker() {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
-
-
-
- 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 < -leftMenuLayoutParams.width) {
- rightMargin = -leftMenuLayoutParams.width;
- break;
- }
- if (rightMargin > 0) {
- rightMargin = 0;
- break;
- }
- publishProgress(rightMargin);
-
- sleep(15);
- }
- if (speed[0] > 0) {
- isLeftMenuVisible = false;
- } else {
- isLeftMenuVisible = true;
- }
- isSliding = false;
- return rightMargin;
- }
-
- @Override
- protected void onProgressUpdate(Integer... rightMargin) {
- contentLayoutParams.rightMargin = rightMargin[0];
- contentLayout.setLayoutParams(contentLayoutParams);
- unFocusBindView();
- }
-
- @Override
- protected void onPostExecute(Integer rightMargin) {
- contentLayoutParams.rightMargin = rightMargin;
- 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 < -rightMenuLayoutParams.width) {
- leftMargin = -rightMenuLayoutParams.width;
- break;
- }
- if (leftMargin > 0) {
- leftMargin = 0;
- break;
- }
- publishProgress(leftMargin);
-
- sleep(15);
- }
- if (speed[0] > 0) {
- isRightMenuVisible = false;
- } else {
- isRightMenuVisible = true;
- }
- isSliding = false;
- return leftMargin;
- }
-
- @Override
- protected void onProgressUpdate(Integer... leftMargin) {
- contentLayoutParams.leftMargin = leftMargin[0];
- contentLayout.setLayoutParams(contentLayoutParams);
- unFocusBindView();
- }
-
- @Override
- protected void onPostExecute(Integer leftMargin) {
- contentLayoutParams.leftMargin = leftMargin;
- contentLayout.setLayoutParams(contentLayoutParams);
- }
- }
-
-
-
-
-
-
-
- private void sleep(long millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
以上代码注释已经写得非常详细,我再来简单解释一下。首先在onLayout()方法中分别获取到左侧菜单、右侧菜单和内容布局的参数,并将内容布局的宽度重定义成屏幕的宽度,这样就可以保证内容布局既能覆盖住下面的菜单布局,还能偏移出屏幕。然后在onTouch()方法中监听触屏事件,以判断用户手势的意图。这里事先定义好了几种滑动状态,DO_NOTHING表示没有进行任何滑动,SHOW_LEFT_MENU表示用户想要滑出左侧菜单,SHOW_RIGHT_MENU表示用户想要滑出右侧菜单,HIDE_LEFT_MENU表示用户想要隐藏左侧菜单,HIDE_RIGHT_MENU表示用户想要隐藏右侧菜单,在checkSlideState()方法中判断出用户到底是想进行哪一种滑动操作,并给slideState变量赋值,然后根据slideState的值决定如何偏移内容布局。接着当用户手指离开屏幕时,会根据当前的滑动距离,决定后续的滚动方向,通过LeftMenuScrollTask和RightMenuScrollTask来完成完整的滑动过程。另外在滑动的过程,内容布局上的事件会被屏蔽掉,主要是通过一系列的return操作实现的,对这一部分不理解的朋友,请参阅 Android事件分发机制完全解析,带你从源码的角度彻底理解 。
然后我们看一下setScrollEvent方法,这个方法接收一个View作为参数,然后为这个View绑定了一个touch事件。这是什么意思呢?让我们来想象一个场景,如果内容布局是一个LinearLayout,我可以通过监听LinearLayout上的touch事件来控制它的偏移。但是如果内容布局的LinearLayout里面加入了一个ListView,而这个ListView又充满了整个LinearLayout,这个时候LinearLayout将不可能再被touch到了,这个时候我们就需要将touch事件注册到ListView上。setScrollEvent方法也就是提供了一个注册接口,touch事件将会注册到传入的View上。
接下来打开或新建activity_main.xml文件,加入如下代码:
- <com.example.bidirslidinglayout.BidirSlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/bidir_sliding_layout"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
-
- <RelativeLayout
- android:id="@+id/left_menu"
- android:layout_width="270dip"
- android:layout_height="fill_parent"
- 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:background="#e9e9e9" >
-
- <ListView
- android:id="@+id/contentList"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:scrollbars="none"
- android:cacheColorHint="#00000000" >
- </ListView>
- </LinearLayout>
-
- </com.example.bidirslidinglayout.BidirSlidingLayout>
可以看到,我们使用了自定义的BidirSlidingLayout作为根布局,然后依次加入了三个子布局分别作为左侧菜单、右侧菜单和内容的布局。左侧菜单和右侧菜单中都只是简单地放入了一个TextView用于显示一段文字,内容布局中放入了一个ListView。注意要让左侧菜单和父布局左边缘对齐,右侧菜单和父布局右边缘对齐。
最后打开或者创建MainActivity作为程序的主Activity,代码如下所示:
- public class MainActivity extends Activity {
-
-
-
-
- private BidirSlidingLayout bidirSldingLayout;
-
-
-
-
- private ListView contentList;
-
-
-
-
- private ArrayAdapter<String> 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 = (BidirSlidingLayout) findViewById(R.id.bidir_sliding_layout);
- 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);
- }
-
- }
这里我们给ListView填充了几条数据,又通过findViewById()方法获取到了BidirSlidingLayout对象,然后调用它的setScrollEvent()方法,将ListView进行绑定,这样就可以通过左右滑动ListView来展示左侧菜单和右侧菜单了。
好了,全部编码工作都已完成,现在让我们运行一下程序吧,效果如下图所示:
看起来还是挺不错的吧!并且更重要的是,以后我们在项目的任何地方都可以轻松加入双向滑动菜单功能,只需要以下两步即可:
1. 在Acitivty的layout中引入我们自定义的BidirSlidingLayout布局,并且给这个布局要加入三个直接子元素。
2. 在Activity中通过setScrollEvent方法,给一个View注册touch事件。
如此一来,一分钟实现双向滑动菜单功能妥妥的。
好了,今天的讲解到此结束,有疑问的朋友请在下面留言。
源码下载,请点击这里