学习出处:http://blog.csdn.net/guolin_blog/article/details/8714621
这里不转载内容了,按照自己理解写一篇
侧滑菜单效果 就是手机版QQ的左侧向右滑动出现菜单栏的那一种效果
实现原理。在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局。两个布局横向排列,菜单布局在左,内容布局在右。初始化的时候将菜单布局向左偏移,以至于能够完全隐藏,这样内容布局就会完全显示在Activity中。然后通过监听手指滑动事件,来改变菜单布局的左偏移距离,从而控制菜单布局的显示和隐藏。
原理图(学习作者画的,非本菜鸟画的。)如下:
content是主界面 相当于手机QQ聊天的那个界面 
menu是侧滑菜单,相当于显示个人信息的那个界面 (不截图了,因为QQ滑动缩小,本菜鸟做的滑动两个界面大小都不变化)

将菜单布局的左偏移值改成0时,效果图如下:

所有代码由三部分组成
src文件下的 Main.java
res/layout下的activity_main.xml
AndroidManifest.xml
先来看布局文件:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="fill_parent"
4 android:layout_height="fill_parent"
5 android:orientation="horizontal"
6 tools:context=".Main" >
7
8 <LinearLayout
9 android:orientation="horizontal"
10 //第一行在我学习的文章中是没有的,但是自己不加就出错,这是设置水平布局的意思
11 android:id="@+id/menu"
12 android:layout_width="fill_parent"
13 android:layout_height="fill_parent"
14 android:background="@drawable/right" > //添加背景图片,为了显示方便。这是侧滑界面。就是滑动出来的界面
15 </LinearLayout>
16
17 <LinearLayout
18 android:orientation="horizontal"
19 android:id="@+id/content"
20 android:layout_width="fill_parent"
21 android:layout_height="fill_parent"
22 android:background="@drawable/main_picture" //这是主界面,就是不滑动时显示的界面
23 >
24 </LinearLayout>
25
26 </LinearLayout>
这个布局文件的最外层布局是一个LinearLayout,排列方向是水平方向排列。这个LinearLayout下面嵌套了两个子LinearLayout,分别就是菜单的布局和内容的布局。这里为了要让布局尽量简单,菜单布局和内容布局里面没有加入任何控件,只是给这两个布局各添加了一张背景图片,这样我们可以把注意力都集中在如何实现滑动菜单的效果上面,不用关心里面各种复杂的布局了。
然后是主类
Main.java
1 package xqx;
2
3 import com.example.xqx_lianxi.R;
4
5 import android.app.Activity;
6 import android.content.Context;
7 import android.os.AsyncTask;
8 import android.os.Bundle;
9 import android.view.MotionEvent;
10 import android.view.VelocityTracker;
11 import android.view.View;
12 import android.view.View.OnTouchListener;
13 import android.view.WindowManager;
14 import android.widget.LinearLayout;
15
16
17 public class Main extends Activity implements OnTouchListener{
18 /**
19 * 滚动显示和隐藏menu时,手指滑动需要达到的速度。
20 */
21 public static final int SNAP_VELOCITY = 200;
22
23 /**
24 * 屏幕宽度值。
25 */
26 private int screenWidth;
27
28 /**
29 * menu最多可以滑动到的左边缘。值由menu布局的宽度来定,marginLeft到达此值之后,不能再减少。
30 */
31 private int leftEdge;
32
33 /**
34 * menu最多可以滑动到的右边缘。值恒为0,即marginLeft到达0之后,不能增加。
35 */
36 private int rightEdge = 0;
37
38 /**
39 * menu完全显示时,留给content的宽度值。
40 */
41 private int menuPadding = 80;
42
43 /**
44 * 主内容的布局。
45 */
46 private View content;
47
48 /**
49 * menu的布局。
50 */
51 private View menu;
52
53 /**
54 * menu布局的参数,通过此参数来更改leftMargin的值。
55 */
56 private LinearLayout.LayoutParams menuParams;
57
58 /**
59 * 记录手指按下时的横坐标。
60 */
61 private float xDown;
62
63 /**
64 * 记录手指移动时的横坐标。
65 */
66 private float xMove;
67
68 /**
69 * 记录手机抬起时的横坐标。
70 */
71 private float xUp;
72
73 /**
74 * menu当前是显示还是隐藏。只有完全显示或隐藏menu时才会更改此值,滑动过程中此值无效。
75 */
76 private boolean isMenuVisible;
77
78 /**
79 * 用于计算手指滑动的速度。
80 */
81 private VelocityTracker mVelocityTracker;
82 @Override
83 protected void onCreate(Bundle savedInstanceState) {
84 // TODO Auto-generated method stub
85 super.onCreate(savedInstanceState);
86 setContentView(R.layout.activity_main);
87
88 initValues();
89 content.setOnTouchListener(this);
90 }
91
92 /**
93 * 初始化一些关键性数据。包括获取屏幕的宽度,给content布局重新设置宽度,给menu布局重新设置宽度和偏移距离等。
94 */
95 private void initValues() {
96 WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
97 screenWidth = window.getDefaultDisplay().getWidth();
98 content = findViewById(R.id.content);
99 menu = findViewById(R.id.menu);
100 menuParams = (LinearLayout.LayoutParams) menu.getLayoutParams();
101 // 将menu的宽度设置为屏幕宽度减去menuPadding
102 menuParams.width = screenWidth - menuPadding;
103 // 左边缘的值赋值为menu宽度的负数
104 leftEdge = -menuParams.width;
105 // menu的leftMargin设置为左边缘的值,这样初始化时menu就变为不可见
106 menuParams.leftMargin = leftEdge;
107 // 将content的宽度设置为屏幕宽度
108 content.getLayoutParams().width = screenWidth;
109 }
110
111 @Override
112 public boolean onTouch(View v, MotionEvent event) {
113 createVelocityTracker(event);
114 switch (event.getAction()) {
115 case MotionEvent.ACTION_DOWN:
116 // 手指按下时,记录按下时的横坐标
117 xDown = event.getRawX();
118 break;
119 case MotionEvent.ACTION_MOVE:
120 // 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整menu的leftMargin值,从而显示和隐藏menu
121 xMove = event.getRawX();
122 int distanceX = (int) (xMove - xDown);
123 if (isMenuVisible) {
124 menuParams.leftMargin = distanceX;
125 } else {
126 menuParams.leftMargin = leftEdge + distanceX;
127 }
128 if (menuParams.leftMargin < leftEdge) {
129 menuParams.leftMargin = leftEdge;
130 } else if (menuParams.leftMargin > rightEdge) {
131 menuParams.leftMargin = rightEdge;
132 }
133 menu.setLayoutParams(menuParams);
134 break;
135 case MotionEvent.ACTION_UP:
136 // 手指抬起时,进行判断当前手势的意图,从而决定是滚动到menu界面,还是滚动到content界面
137 xUp = event.getRawX();
138 if (wantToShowMenu()) {
139 if (shouldScrollToMenu()) {
140 scrollToMenu();
141 } else {
142 scrollToContent();
143 }
144 } else if (wantToShowContent()) {
145 if (shouldScrollToContent()) {
146 scrollToContent();
147 } else {
148 scrollToMenu();
149 }
150 }
151 recycleVelocityTracker();
152 break;
153 }
154 return true;
155 }
156
157 /**
158 * 判断当前手势的意图是不是想显示content。如果手指移动的距离是负数,且当前menu是可见的,则认为当前手势是想要显示content。
159 *
160 * @return 当前手势想显示content返回true,否则返回false。
161 */
162 private boolean wantToShowContent() {
163 return xUp - xDown < 0 && isMenuVisible;
164 }
165
166 /**
167 * 判断当前手势的意图是不是想显示menu。如果手指移动的距离是正数,且当前menu是不可见的,则认为当前手势是想要显示menu。
168 *
169 * @return 当前手势想显示menu返回true,否则返回false。
170 */
171 private boolean wantToShowMenu() {
172 return xUp - xDown > 0 && !isMenuVisible;
173 }
174
175 /**
176 * 判断是否应该滚动将menu展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,
177 * 就认为应该滚动将menu展示出来。
178 *
179 * @return 如果应该滚动将menu展示出来返回true,否则返回false。
180 */
181 private boolean shouldScrollToMenu() {
182 return xUp - xDown > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
183 }
184
185 /**
186 * 判断是否应该滚动将content展示出来。如果手指移动距离加上menuPadding大于屏幕的1/2,
187 * 或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将content展示出来。
188 *
189 * @return 如果应该滚动将content展示出来返回true,否则返回false。
190 */
191 private boolean shouldScrollToContent() {
192 return xDown - xUp + menuPadding > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY;
193 }
194
195 /**
196 * 将屏幕滚动到menu界面,滚动速度设定为30.
197 */
198 private void scrollToMenu() {
199 new ScrollTask().execute(30);
200 }
201
202 /**
203 * 将屏幕滚动到content界面,滚动速度设定为-30.
204 */
205 private void scrollToContent() {
206 new ScrollTask().execute(-30);
207 }
208
209 /**
210 * 创建VelocityTracker对象,并将触摸content界面的滑动事件加入到VelocityTracker当中。
211 *
212 * @param event
213 * content界面的滑动事件
214 */
215 private void createVelocityTracker(MotionEvent event) {
216 if (mVelocityTracker == null) {
217 mVelocityTracker = VelocityTracker.obtain();
218 }
219 mVelocityTracker.addMovement(event);
220 }
221
222 /**
223 * 获取手指在content界面滑动的速度。
224 *
225 * @return 滑动速度,以每秒钟移动了多少像素值为单位。
226 */
227 private int getScrollVelocity() {
228 mVelocityTracker.computeCurrentVelocity(1000);
229 int velocity = (int) mVelocityTracker.getXVelocity();
230 return Math.abs(velocity);
231 }
232
233 /**
234 * 回收VelocityTracker对象。
235 */
236 private void recycleVelocityTracker() {
237 mVelocityTracker.recycle();
238 mVelocityTracker = null;
239 }
240
241 class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
242
243 @Override
244 protected Integer doInBackground(Integer... speed) {
245 int leftMargin = menuParams.leftMargin;
246 // 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
247 while (true) {
248 leftMargin = leftMargin + speed[0];
249 if (leftMargin > rightEdge) {
250 leftMargin = rightEdge;
251 break;
252 }
253 if (leftMargin < leftEdge) {
254 leftMargin = leftEdge;
255 break;
256 }
257 publishProgress(leftMargin);
258 // 为了要有滚动效果产生,每次循环使线程睡眠20毫秒,这样肉眼才能够看到滚动动画。
259 sleep(20);
260 }
261 if (speed[0] > 0) {
262 isMenuVisible = true;
263 } else {
264 isMenuVisible = false;
265 }
266 return leftMargin;
267 }
268
269 @Override
270 protected void onProgressUpdate(Integer... leftMargin) {
271 menuParams.leftMargin = leftMargin[0];
272 menu.setLayoutParams(menuParams);
273 }
274
275 @Override
276 protected void onPostExecute(Integer leftMargin) {
277 menuParams.leftMargin = leftMargin;
278 menu.setLayoutParams(menuParams);
279 }
280 }
281
282 /**
283 * 使当前线程睡眠指定的毫秒数。
284 *
285 * @param millis
286 * 指定当前线程睡眠多久,以毫秒为单位
287 */
288 private void sleep(long millis) {
289 try {
290 Thread.sleep(millis);
291 } catch (InterruptedException e) {
292 e.printStackTrace();
293 }
294 }
295 }
296
对以上代码解释一下,首先初始化的时候调用initValues方法,在这里面将内容布局的宽度设定为屏幕的宽度,菜单布局的宽度设定为屏幕的宽度减去menuPadding值,这样可以保证在菜单布局展示的时候,仍有一部分内容布局可以看到。如果不在初始化的时候重定义两个布局宽度,就会按照layout文件里面声明的一样,两个布局都是fill_parent,这样就无法实现滑动菜单的效果了。然后将菜单布局的左偏移量设置为负的菜单布局的宽度,这样菜单布局就会被完全隐藏,只有内容布局会显示在界面上。
之后给内容布局注册监听事件,这样当手指在内容布局上滑动的时候就会触发onTouch事件。在onTouch事件里面,根据手指滑动的距离会改变菜单布局的左偏移量,从而控制菜单布局的显示和隐藏。当手指离开屏幕的时候,会判断应该滑动到菜单布局还是内容布局,判断依据是根据手指滑动的距离或者滑动的速度,细节可以看代码中的注释。
最后是AndroidManifest.xml的代码
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 package="com.example.xqx_lianxi"
3 android:versionCode="1"
4 android:versionName="1.0" >
5
6 <uses-sdk
7 android:minSdkVersion="8"
8 android:targetSdkVersion="18" />
9
10 <application
11 android:allowBackup="true"
12 android:icon="@drawable/ic_launcher"
13 android:label="@string/app_name"
14 android:theme="@style/AppTheme" >
15 <activity android:name="xqx.Main">
16 <intent-filter >
17 <action android:name="android.intent.action.MAIN"/>
18 <category android:name="android.intent.category.LAUNCHER"/>
19 </intent-filter>
20 </activity>
21 </application>
22
23 </manifest>
效果图:
未滑动时的图
滑动结束的图
滑动过程中的图