人人客戶端有一個特效還是挺吸引人的,在主界面手指向右滑動,就可以將菜單展示出來,而主界面會被隱藏大部分,但是仍有左側(cè)的一小部分同菜單一起展示。
據(jù)說人人客戶端的這個特效是從facebook客戶端模仿來的,至于facebook是不是又從其它地方模仿來的就不得而知了。好,今天我們就一起來實現(xiàn)這個效果,總之我第一次看到這個特效是在人人客戶端看到的,我也就主觀性地認為我是在模仿人人客戶端的特效了。
雖然現(xiàn)在網(wǎng)上類似這種效果的實現(xiàn)也非常多,可是我發(fā)現(xiàn)實現(xiàn)方案大都非常復(fù)雜,并不容易理解。但其實這種效果并不難實現(xiàn),因此我今天給大家?guī)淼囊彩鞘飞献詈唵蔚幕瑒硬藛螌崿F(xiàn)方案。
首先還是講一下實現(xiàn)原理。在一個Activity的布局中需要有兩部分,一個是菜單(menu)的布局,一個是內(nèi)容(content)的布局。兩個布局橫向排列,菜單布局在左,內(nèi)容布局在右。初始化的時候?qū)⒉藛尾季窒蜃笃?,以至于能夠完全隱藏,這樣內(nèi)容布局就會完全顯示在Activity中。然后通過監(jiān)聽手指滑動事件,來改變菜單布局的左偏移距離,從而控制菜單布局的顯示和隱藏。
原理圖如下:
將菜單布局的左偏移值改成0時。
效果圖如下:
好,我們開始用代碼來實現(xiàn)。首先在Eclipse中新建一個Android項目,項目名就叫做RenRenSlideMenuDemo。然后寫一下布局文件,創(chuàng)建或打開layout目錄下的activity_main.xml文件,加入如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
< 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" > < LinearLayout android:id = "@+id/menu" android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:background = "@drawable/menu" > </ LinearLayout > < LinearLayout android:id = "@+id/content" android:layout_width = "fill_parent" android:layout_height = "fill_parent" android:background = "@drawable/content" > </ LinearLayout > </ LinearLayout > |
這個布局文件的最外層布局是一個LinearLayout,排列方向是水平方向排列。這個LinearLayout下面嵌套了兩個子LinearLayout,分別就是菜單的布局和內(nèi)容的布局。這里為了要讓布局盡量簡單,菜單布局和內(nèi)容布局里面沒有加入任何控件,只是給這兩個布局各添加了一張背景圖片,這兩張背景圖片是我從人人客戶端上截下來的圖。這樣我們可以把注意力都集中在如何實現(xiàn)滑動菜單的效果上面,不用關(guān)心里面各種復(fù)雜的布局了。
創(chuàng)建或打開MainActivity,這個類仍然是程序的主Activity,也是這次demo唯一的Activity,在里面加入如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
|
public class MainActivity extends Activity implements OnTouchListener { /** * 滾動顯示和隱藏menu時,手指滑動需要達到的速度。 */ public static final int SNAP_VELOCITY = 200 ; /** * 屏幕寬度值。 */ private int screenWidth; /** * menu最多可以滑動到的左邊緣。值由menu布局的寬度來定,marginLeft到達此值之后,不能再減少。 */ private int leftEdge; /** * menu最多可以滑動到的右邊緣。值恒為0,即marginLeft到達0之后,不能增加。 */ private int rightEdge = 0 ; /** * menu完全顯示時,留給content的寬度值。 */ private int menuPadding = 80 ; /** * 主內(nèi)容的布局。 */ private View content; /** * menu的布局。 */ private View menu; /** * menu布局的參數(shù),通過此參數(shù)來更改leftMargin的值。 */ private LinearLayout.LayoutParams menuParams; /** * 記錄手指按下時的橫坐標(biāo)。 */ private float xDown; /** * 記錄手指移動時的橫坐標(biāo)。 */ private float xMove; /** * 記錄手機抬起時的橫坐標(biāo)。 */ private float xUp; /** * menu當(dāng)前是顯示還是隱藏。只有完全顯示或隱藏menu時才會更改此值,滑動過程中此值無效。 */ private boolean isMenuVisible; /** * 用于計算手指滑動的速度。 */ private VelocityTracker mVelocityTracker; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); initValues(); content.setOnTouchListener( this ); } /** * 初始化一些關(guān)鍵性數(shù)據(jù)。包括獲取屏幕的寬度,給content布局重新設(shè)置寬度,給menu布局重新設(shè)置寬度和偏移距離等。 */ private void initValues() { WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE); screenWidth = window.getDefaultDisplay().getWidth(); content = findViewById(R.id.content); menu = findViewById(R.id.menu); menuParams = (LinearLayout.LayoutParams) menu.getLayoutParams(); // 將menu的寬度設(shè)置為屏幕寬度減去menuPadding menuParams.width = screenWidth - menuPadding; // 左邊緣的值賦值為menu寬度的負數(shù) leftEdge = -menuParams.width; // menu的leftMargin設(shè)置為左邊緣的值,這樣初始化時menu就變?yōu)椴豢梢? menuParams.leftMargin = leftEdge; // 將content的寬度設(shè)置為屏幕寬度 content.getLayoutParams().width = screenWidth; } @Override public boolean onTouch(View v, MotionEvent event) { createVelocityTracker(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 手指按下時,記錄按下時的橫坐標(biāo) xDown = event.getRawX(); break ; case MotionEvent.ACTION_MOVE: // 手指移動時,對比按下時的橫坐標(biāo),計算出移動的距離,來調(diào)整menu的leftMargin值,從而顯示和隱藏menu xMove = event.getRawX(); int distanceX = ( int ) (xMove - xDown); if (isMenuVisible) { menuParams.leftMargin = distanceX; } else { menuParams.leftMargin = leftEdge + distanceX; } if (menuParams.leftMargin < leftEdge) { menuParams.leftMargin = leftEdge; } else if (menuParams.leftMargin > rightEdge) { menuParams.leftMargin = rightEdge; } menu.setLayoutParams(menuParams); break ; case MotionEvent.ACTION_UP: // 手指抬起時,進行判斷當(dāng)前手勢的意圖,從而決定是滾動到menu界面,還是滾動到content界面 xUp = event.getRawX(); if (wantToShowMenu()) { if (shouldScrollToMenu()) { scrollToMenu(); } else { scrollToContent(); } } else if (wantToShowContent()) { if (shouldScrollToContent()) { scrollToContent(); } else { scrollToMenu(); } } recycleVelocityTracker(); break ; } return true ; } /** * 判斷當(dāng)前手勢的意圖是不是想顯示content。如果手指移動的距離是負數(shù),且當(dāng)前menu是可見的,則認為當(dāng)前手勢是想要顯示content。 * * @return 當(dāng)前手勢想顯示content返回true,否則返回false。 */ private boolean wantToShowContent() { return xUp - xDown < 0 && isMenuVisible; } /** * 判斷當(dāng)前手勢的意圖是不是想顯示menu。如果手指移動的距離是正數(shù),且當(dāng)前menu是不可見的,則認為當(dāng)前手勢是想要顯示menu。 * * @return 當(dāng)前手勢想顯示menu返回true,否則返回false。 */ private boolean wantToShowMenu() { return xUp - xDown > 0 && !isMenuVisible; } /** * 判斷是否應(yīng)該滾動將menu展示出來。如果手指移動距離大于屏幕的1/2,或者手指移動速度大于SNAP_VELOCITY, * 就認為應(yīng)該滾動將menu展示出來。 * * @return 如果應(yīng)該滾動將menu展示出來返回true,否則返回false。 */ private boolean shouldScrollToMenu() { return xUp - xDown > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY; } /** * 判斷是否應(yīng)該滾動將content展示出來。如果手指移動距離加上menuPadding大于屏幕的1/2, * 或者手指移動速度大于SNAP_VELOCITY, 就認為應(yīng)該滾動將content展示出來。 * * @return 如果應(yīng)該滾動將content展示出來返回true,否則返回false。 */ private boolean shouldScrollToContent() { return xDown - xUp + menuPadding > screenWidth / 2 || getScrollVelocity() > SNAP_VELOCITY; } /** * 將屏幕滾動到menu界面,滾動速度設(shè)定為30. */ private void scrollToMenu() { new ScrollTask().execute( 30 ); } /** * 將屏幕滾動到content界面,滾動速度設(shè)定為-30. */ private void scrollToContent() { new ScrollTask().execute(- 30 ); } /** * 創(chuàng)建VelocityTracker對象,并將觸摸content界面的滑動事件加入到VelocityTracker當(dāng)中。 * * @param event * content界面的滑動事件 */ private void createVelocityTracker(MotionEvent event) { if (mVelocityTracker == null ) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); } /** * 獲取手指在content界面滑動的速度。 * * @return 滑動速度,以每秒鐘移動了多少像素值為單位。 */ private int getScrollVelocity() { mVelocityTracker.computeCurrentVelocity( 1000 ); int velocity = ( int ) mVelocityTracker.getXVelocity(); return Math.abs(velocity); } /** * 回收VelocityTracker對象。 */ private void recycleVelocityTracker() { mVelocityTracker.recycle(); mVelocityTracker = null ; } class ScrollTask extends AsyncTask<Integer, Integer, Integer> { @Override protected Integer doInBackground(Integer... speed) { int leftMargin = menuParams.leftMargin; // 根據(jù)傳入的速度來滾動界面,當(dāng)滾動到達左邊界或右邊界時,跳出循環(huán)。 while ( true ) { leftMargin = leftMargin + speed[ 0 ]; if (leftMargin > rightEdge) { leftMargin = rightEdge; break ; } if (leftMargin < leftEdge) { leftMargin = leftEdge; break ; } publishProgress(leftMargin); // 為了要有滾動效果產(chǎn)生,每次循環(huán)使線程睡眠20毫秒,這樣肉眼才能夠看到滾動動畫。 sleep( 20 ); } if (speed[ 0 ] > 0 ) { isMenuVisible = true ; } else { isMenuVisible = false ; } return leftMargin; } @Override protected void onProgressUpdate(Integer... leftMargin) { menuParams.leftMargin = leftMargin[ 0 ]; menu.setLayoutParams(menuParams); } @Override protected void onPostExecute(Integer leftMargin) { menuParams.leftMargin = leftMargin; menu.setLayoutParams(menuParams); } } /** * 使當(dāng)前線程睡眠指定的毫秒數(shù)。 * * @param millis * 指定當(dāng)前線程睡眠多久,以毫秒為單位 */ private void sleep( long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } } |
全部的代碼都在這里了,我們可以看到,加上注釋總共才兩百多行的代碼就能實現(xiàn)滑動菜單的特效。下面我來對以上代碼解釋一下,首先初始化的時候調(diào)用initValues方法,在這里面將內(nèi)容布局的寬度設(shè)定為屏幕的寬度,菜單布局的寬度設(shè)定為屏幕的寬度減去menuPadding值,這樣可以保證在菜單布局展示的時候,仍有一部分內(nèi)容布局可以看到。如果不在初始化的時候重定義兩個布局寬度,就會按照layout文件里面聲明的一樣,兩個布局都是fill_parent,這樣就無法實現(xiàn)滑動菜單的效果了。然后將菜單布局的左偏移量設(shè)置為負的菜單布局的寬度,這樣菜單布局就會被完全隱藏,只有內(nèi)容布局會顯示在界面上。
之后給內(nèi)容布局注冊監(jiān)聽事件,這樣當(dāng)手指在內(nèi)容布局上滑動的時候就會觸發(fā)onTouch事件。在onTouch事件里面,根據(jù)手指滑動的距離會改變菜單布局的左偏移量,從而控制菜單布局的顯示和隱藏。當(dāng)手指離開屏幕的時候,會判斷應(yīng)該滑動到菜單布局還是內(nèi)容布局,判斷依據(jù)是根據(jù)手指滑動的距離或者滑動的速度,細節(jié)可以看代碼中的注釋。
最后還是給出AndroidManifest.xml的代碼,都是自動生成的,非常簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<? xml version = "1.0" encoding = "utf-8" ?> < manifest xmlns:android = "http://schemas.android.com/apk/res/android" package = "com.example.renrenslidemenudemo" 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.renrenslidemenudemo.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 > |
好了,現(xiàn)在我們運行一下,看一下效果吧,首先是程序剛打開的時候,顯示的是內(nèi)容布局。用手指在界面向右滑動,可以看到菜單布局出現(xiàn)。
而當(dāng)菜單布局完全展示的時候,效果如下圖:
今天大家看到了史上最簡單的滑動菜單實現(xiàn)方案,確實是非常簡單。那么有朋友也許會問了,在一個Activity當(dāng)中這樣實現(xiàn)滑動菜單是很簡單,可是如果我的應(yīng)用程序有好多個Activity都需要滑動菜單,每個Activity里都這么實現(xiàn)一遍,也變得復(fù)雜了。沒錯,當(dāng)前的這個解決方案只適用于單個Activity中,如果是想在多個Activity中都實現(xiàn)滑動菜單的效果,請參考我的另一篇文章 Android實現(xiàn)滑動菜單特效的完全解析。
有對雙向滑動菜單感興趣的朋友請轉(zhuǎn)閱 Android實現(xiàn)雙向滑動特效的實例代碼。
好了,今天的講解到此結(jié)束,有疑問的朋友可以在下面留言。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://blog.csdn.net/guolin_blog/article/details/8714621