學習出處:http://blog.csdn.net/guolin_blog/article/details/8714621java
這裏不轉載內容了,按照本身理解寫一篇android
側滑菜單效果 就是手機版QQ的左側向右滑動出現菜單欄的那一種效果app
實現原理。在一個Activity的佈局中須要有兩部分,一個是菜單(menu)的佈局,一個是內容(content)的佈局。兩個佈局橫向排列,菜單佈局在左,內容佈局在右。初始化的時候將菜單佈局向左偏移,以致於可以徹底隱藏,這樣內容佈局就會徹底顯示在Activity中。而後經過監聽手指滑動事件,來改變菜單佈局的左偏移距離,從而控制菜單佈局的顯示和隱藏。ide
原理圖(學習做者畫的,非本菜鳥畫的。)以下:佈局
content是主界面 至關於手機QQ聊天的那個界面 學習
menu是側滑菜單,至關於顯示我的信息的那個界面 (不截圖了,由於QQ滑動縮小,本菜鳥作的滑動兩個界面大小都不變化)動畫
將菜單佈局的左偏移值改爲0時,效果圖以下:this
全部代碼由三部分組成spa
src文件下的 Main.java .net
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>
效果圖:
未滑動時的圖
滑動結束的圖
滑動過程當中的圖