转载http://blog.csdn.net/guolin_blog/article/details/8744400
之前我向大家介绍了史上最简单的滑动菜单的实现方式,相信大家都还记得。如果忘记了其中的实现原理或者还没看过的朋友,请先去看一遍之前的文章 Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现 ,因为我们今天要实现的滑动菜单框架也是基于同样的原理的。
之前的文章中在最后也提到了,如果是你的应用程序中有很多个Activity都需要加入滑动菜单的功能,那么每个Activity都要写上百行的代码才能实现效果,再简单的滑动菜单实现方案也没用。因此我们今天要实现一个滑动菜单的框架,然后在任何Activity中都可以一分钟引入滑动菜单功能。
首先还是讲一下实现原理。说是滑动菜单的框架,其实说白了也很简单,就是我们自定义一个布局,在这个自定义布局中实现好滑动菜单的功能,然后只要在Activity的布局文件里面引入我们自定义的布局,这个Activity就拥有了滑动菜单的功能了。原理讲完了,是不是很简单?下面我们来动手实现吧。
在Eclipse中新建一个Android项目,项目名就叫做RenRenSlidingLayout。
新建一个类,名叫SlidingLayout,这个类是继承自LinearLayout的,并且实现了OnTouchListener接口,具体代码如下:
- public class SlidingLayout extends LinearLayout implements OnTouchListener {
-
-
-
-
- public static final int SNAP_VELOCITY = 200;
-
-
-
-
- private int screenWidth;
-
-
-
-
- private int leftEdge;
-
-
-
-
- private int rightEdge = 0;
-
-
-
-
- private int leftLayoutPadding = 80;
-
-
-
-
- private float xDown;
-
-
-
-
- private float xMove;
-
-
-
-
- private float xUp;
-
-
-
-
- private boolean isLeftLayoutVisible;
-
-
-
-
- private View leftLayout;
-
-
-
-
- private View rightLayout;
-
-
-
-
- private View mBindView;
-
-
-
-
- private MarginLayoutParams leftLayoutParams;
-
-
-
-
- private MarginLayoutParams rightLayoutParams;
-
-
-
-
- private VelocityTracker mVelocityTracker;
-
-
-
-
-
-
-
- public SlidingLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- screenWidth = wm.getDefaultDisplay().getWidth();
- }
-
-
-
-
-
-
-
- public void setScrollEvent(View bindView) {
- mBindView = bindView;
- mBindView.setOnTouchListener(this);
- }
-
-
-
-
- public void scrollToLeftLayout() {
- new ScrollTask().execute(30);
- }
-
-
-
-
- public void scrollToRightLayout() {
- new ScrollTask().execute(-30);
- }
-
-
-
-
-
-
- public boolean isLeftLayoutVisible() {
- return isLeftLayoutVisible;
- }
-
-
-
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- if (changed) {
-
- leftLayout = getChildAt(0);
- leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
-
- leftLayoutParams.width = screenWidth - leftLayoutPadding;
-
- leftEdge = -leftLayoutParams.width;
- leftLayoutParams.leftMargin = leftEdge;
- leftLayout.setLayoutParams(leftLayoutParams);
-
- rightLayout = getChildAt(1);
- rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
- rightLayoutParams.width = screenWidth;
- rightLayout.setLayoutParams(rightLayoutParams);
- }
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- createVelocityTracker(event);
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
-
- xDown = event.getRawX();
- break;
- case MotionEvent.ACTION_MOVE:
-
- xMove = event.getRawX();
- int distanceX = (int) (xMove - xDown);
- if (isLeftLayoutVisible) {
- leftLayoutParams.leftMargin = distanceX;
- } else {
- leftLayoutParams.leftMargin = leftEdge + distanceX;
- }
- if (leftLayoutParams.leftMargin < leftEdge) {
- leftLayoutParams.leftMargin = leftEdge;
- } else if (leftLayoutParams.leftMargin > rightEdge) {
- leftLayoutParams.leftMargin = rightEdge;
- }
- leftLayout.setLayoutParams(leftLayoutParams);
- break;
- case MotionEvent.ACTION_UP:
-
- xUp = event.getRawX();
- if (wantToShowLeftLayout()) {
- if (shouldScrollToLeftLayout()) {
- scrollToLeftLayout();
- } else {
- scrollToRightLayout();
- }
- } else if (wantToShowRightLayout()) {
- if (shouldScrollToContent()) {
- scrollToRightLayout();
- } else {
- scrollToLeftLayout();
- }
- }
- recycleVelocityTracker();
- break;
- }
- return isBindBasicLayout();
- }
-
-
-
-
-
-
- private boolean wantToShowRightLayout() {
- return xUp - xDown < 0 && isLeftLayoutVisible;
- }
-
-
-
-
-
-
- private boolean wantToShowLeftLayout() {
- return xUp - xDown > 0 && !isLeftLayoutVisible;
- }
-
-
-
-
-
-
-
- private boolean shouldScrollToLeftLayout() {
- return xUp - xDown > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
- }
-
-
-
-
-
-
-
- private boolean shouldScrollToContent() {
- return xDown - xUp + leftLayoutPadding > screenWidth / 2
- || getScrollVelocity() > SNAP_VELOCITY;
- }
-
-
-
-
-
-
-
-
- private boolean isBindBasicLayout() {
- if (mBindView == null) {
- return false;
- }
- String viewName = mBindView.getClass().getName();
- return viewName.equals(LinearLayout.class.getName())
- || viewName.equals(RelativeLayout.class.getName())
- || viewName.equals(FrameLayout.class.getName())
- || viewName.equals(TableLayout.class.getName());
- }
-
-
-
-
-
-
-
- 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;
- }
-
- class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
-
- @Override
- protected Integer doInBackground(Integer... speed) {
- int leftMargin = leftLayoutParams.leftMargin;
-
- while (true) {
- leftMargin = leftMargin + speed[0];
- if (leftMargin > rightEdge) {
- leftMargin = rightEdge;
- break;
- }
- if (leftMargin < leftEdge) {
- leftMargin = leftEdge;
- break;
- }
- publishProgress(leftMargin);
-
- sleep(20);
- }
- if (speed[0] > 0) {
- isLeftLayoutVisible = true;
- } else {
- isLeftLayoutVisible = false;
- }
- return leftMargin;
- }
-
- @Override
- protected void onProgressUpdate(Integer... leftMargin) {
- leftLayoutParams.leftMargin = leftMargin[0];
- leftLayout.setLayoutParams(leftLayoutParams);
- }
-
- @Override
- protected void onPostExecute(Integer leftMargin) {
- leftLayoutParams.leftMargin = leftMargin;
- leftLayout.setLayoutParams(leftLayoutParams);
- }
- }
-
-
-
-
-
-
-
- private void sleep(long millis) {
- try {
- Thread.sleep(millis);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
看到这里,我相信大家一定会觉得这些代码非常熟悉。没错,基本上这些代码和之前那篇文章的代码大同小异,只不过以前这些代码是写在Activity里的,而现在我们移动到了自定义的View当中。
接着我来说明一下和以前不同的部分。我们可以看到,这里将onLayout方法进行了重写,使用getChildAt(0)获取到的布局作为左边布局,使用getChildAt(1)获取到的布局作为右边布局。并将左边布局的宽度重定义为屏幕宽度减去leftLayoutPadding,将右侧布局的宽度重定义为屏幕宽度。然后让左边布局偏移出屏幕,这样能看到的就只有右边布局了。因此在这里我们也可以看出,使用SlidingLayout这个布局的前提条件,必须为这个布局提供两个子元素,第一个元素会作为左边布局偏移出屏幕,第二个元素会作为右边布局显示在屏幕上。
然后我们看一下setScrollEvent方法,这个方法接收一个View作为参数,然后为这个View绑定了一个touch事件。这是什么意思呢?让我们来想象一个场景,如果右侧布局是一个LinearLayout,我可以通过监听LinearLayout上的touch事件来控制左侧布局的显示和隐藏。但是如果右侧布局的LinearLayout里面加入了一个ListView,而这个ListView又充满了整个LinearLayout,这个时候LinearLayout将不可能再被touch到了,这个时候我们就需要将touch事件注册到ListView上。setScrollEvent方法也就是提供了一个注册接口,touch事件将会注册到传入的View上。
最后还有一个陌生的方法,isBindBasicLayout。这个方法就是判断了一下注册touch事件的View是不是四个基本布局之一,如果是就返回true,否则返回false。这个方法在整个SlidingLayout中起着非常重要的作用,主要用于控制onTouch事件是返回true还是false,这将影响到布局当中的View的功能是否可用。由于里面牵扯到了Android的事件转发机制,内容比较多,就不在这里详细解释了,我会考虑以后专门写一篇文章来介绍Android的事件机制。这里就先简单记住如果是基本布局就返回true,否则就返回false。
好了,我们的SlidingLayout写完了,接下来就是见证奇迹的时刻,让我们一起看看如何一分钟在Activity中引入滑动菜单功能。
创建或打开layout目录下的activity_main.xml文件,加入如下代码:
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal"
- tools:context=".MainActivity" >
-
-
-
- <com.example.slide.SlidingLayout
- android:id="@+id/slidingLayout"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="horizontal" >
-
- <!--
- 侧滑布局的根节点下,有且只能有两个子元素,这两个子元素必须是四种基本布局之一,
- 即LinearLayout, RelativeLayout, FrameLayout或TableLayout。
- 第一个子元素将做为左侧布局,初始化后被隐藏。第二个子元素将做为右侧布局,
- 也就是当前Activity的主布局,将主要的数据放在里面。
- -->
-
- <RelativeLayout
- android:id="@+id/menu"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:background="#00ccff" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:text="This is menu"
- android:textColor="#000000"
- android:textSize="28sp" />
- </RelativeLayout>
-
- <LinearLayout
- android:id="@+id/content"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
-
- <Button
- android:id="@+id/menuButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Menu" />
-
- <ListView
- android:id="@+id/contentList"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
- </ListView>
- </LinearLayout>
- </com.example.slide.SlidingLayout>
-
- </LinearLayout>
我们可以看到,在根布局的下面,我们引入了自定义布局com.example.slide.SlidingLayout,然后在它里面加入了两个子元素,一个RelativeLayout和一个LinearLayout。RelativeLayout中比较简单,就加入了一个TextView。LinearLayout里面我们加入了一个按钮和一个ListView。
然后创建或打开MainActivity作为程序的主Activity,加入代码:
- public class MainActivity extends Activity {
-
-
-
-
- private SlidingLayout slidingLayout;
-
-
-
-
- private Button menuButton;
-
-
-
-
- private ListView contentListView;
-
-
-
-
- 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);
- slidingLayout = (SlidingLayout) findViewById(R.id.slidingLayout);
- menuButton = (Button) findViewById(R.id.menuButton);
- contentListView = (ListView) findViewById(R.id.contentList);
- contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
- contentItems);
- contentListView.setAdapter(contentListAdapter);
-
- slidingLayout.setScrollEvent(contentListView);
- menuButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
-
- if (slidingLayout.isLeftLayoutVisible()) {
- slidingLayout.scrollToRightLayout();
- } else {
- slidingLayout.scrollToLeftLayout();
- }
- }
- });
- }
-
- }
上述代码重点是调用SlidingLayout的setScrollEvent方法,为ListView注册touch事件。同时给按钮添加了一个点击事件,实现了点击一下显示左边布局,再点击一下隐藏左边布局的功能。
最后还是老规矩,给出AndroidManifest.xml的代码:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.example.slide"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="8" />
-
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@android:style/Theme.NoTitleBar" >
- <activity
- android:name="com.example.slide.MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
- </manifest>
好了,现在让我们运行一下吧。首先是程序打开的时候,显示的是右边布局。用手指在界面上向右滑动,可以看到左边布局出现。
而当左边布局完全显示的时候,效果图如下:
除此之外,点击Menu按钮也可以控制左边布局的显示和隐藏,大家可以自己试一下。
使用自定义布局的话,就可以用简单的方式在任意Activity中加入滑动菜单功能,即使你有再多的Activity也不用怕了,一分钟引入滑动菜单妥妥的。
再总结一下吧,向Activity中加入滑动菜单功能只需要两步:
1. 在Acitivty的layout中引入我们自定义的布局,并且给这个布局要加入两个直接子元素。
2. 在Activity中通过setScrollEvent方法,给一个View注册touch事件
补充:
由于这段文章写的比较早了,那时写的滑动菜单还存在着不少问题,我之后又将上面的代码做了不少改动,编写了一个修正版的滑动菜单,这个版本主要修正了以下内容:
1.将滑动方式改成了覆盖型。
2.ListView上下滚动时不会轻易滑出菜单。
3.正在滑动时屏蔽掉内容布局上的事件。
4.当菜单布局展示时,点击一下右侧的内容布局,可以将菜单隐藏。
5.修复刚打开程序时,菜单可能会短暂显示一下,然后瞬间消失的bug。
源码下载:
源码下载