您好,登錄后才能下訂單哦!
人人客戶端有一個特效還是挺吸引人的,在主界面手指向右滑動,就可以將菜單展示出來,而主界面會被隱藏大部分,但是仍有左側(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文件,加入如下代碼:
<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,在里面加入如下代碼:
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; /** * 記錄手指按下時的橫坐標。 */ private float xDown; /** * 記錄手指移動時的橫坐標。 */ private float xMove; /** * 記錄手機抬起時的橫坐標。 */ private float xUp; /** * menu當前是顯示還是隱藏。只有完全顯示或隱藏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: // 手指按下時,記錄按下時的橫坐標 xDown = event.getRawX(); break; case MotionEvent.ACTION_MOVE: // 手指移動時,對比按下時的橫坐標,計算出移動的距離,來調(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: // 手指抬起時,進行判斷當前手勢的意圖,從而決定是滾動到menu界面,還是滾動到content界面 xUp = event.getRawX(); if (wantToShowMenu()) { if (shouldScrollToMenu()) { scrollToMenu(); } else { scrollToContent(); } } else if (wantToShowContent()) { if (shouldScrollToContent()) { scrollToContent(); } else { scrollToMenu(); } } recycleVelocityTracker(); break; } return true; } /** * 判斷當前手勢的意圖是不是想顯示content。如果手指移動的距離是負數(shù),且當前menu是可見的,則認為當前手勢是想要顯示content。 * * @return 當前手勢想顯示content返回true,否則返回false。 */ private boolean wantToShowContent() { return xUp - xDown < 0 && isMenuVisible; } /** * 判斷當前手勢的意圖是不是想顯示menu。如果手指移動的距離是正數(shù),且當前menu是不可見的,則認為當前手勢是想要顯示menu。 * * @return 當前手勢想顯示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當中。 * * @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ù)傳入的速度來滾動界面,當滾動到達左邊界或右邊界時,跳出循環(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); } } /** * 使當前線程睡眠指定的毫秒數(shù)。 * * @param millis * 指定當前線程睡眠多久,以毫秒為單位 */ 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)聽事件,這樣當手指在內(nèi)容布局上滑動的時候就會觸發(fā)onTouch事件。在onTouch事件里面,根據(jù)手指滑動的距離會改變菜單布局的左偏移量,從而控制菜單布局的顯示和隱藏。當手指離開屏幕的時候,會判斷應(yīng)該滑動到菜單布局還是內(nèi)容布局,判斷依據(jù)是根據(jù)手指滑動的距離或者滑動的速度,細節(jié)可以看代碼中的注釋。
最后還是給出AndroidManifest.xml的代碼,都是自動生成的,非常簡單:
<?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)。
而當菜單布局完全展示的時候,效果如下圖:
今天大家看到了史上最簡單的滑動菜單實現(xiàn)方案,確實是非常簡單。那么有朋友也許會問了,在一個Activity當中這樣實現(xiàn)滑動菜單是很簡單,可是如果我的應(yīng)用程序有好多個Activity都需要滑動菜單,每個Activity里都這么實現(xiàn)一遍,也變得復(fù)雜了。沒錯,當前的這個解決方案只適用于單個Activity中,如果是想在多個Activity中都實現(xiàn)滑動菜單的效果,請參考我的另一篇文章 Android實現(xiàn)滑動菜單特效的完全解析。
有對雙向滑動菜單感興趣的朋友請轉(zhuǎn)閱 Android實現(xiàn)雙向滑動特效的實例代碼。
好了,今天的講解到此結(jié)束,有疑問的朋友可以在下面留言。
源碼下載,請點擊這里
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。