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