SlideMenu: 仿QQ側滑菜單
SlideMenu
春節的時候在家里完成一個二手交易項目的時候,需求要求做一個側滑菜單,菜單的形式就跟QQ差不多,效果上跟QQ略有一點差別。QQ在菜單滑動的時候,菜單部分的View兩邊都有一個隱藏的效果;我們這個App僅僅需要實現最簡單的側滑菜單效果即可,實現復雜程度上,較QQ小了許多。
以前都是項目中有側滑菜單,我都是拿現成的開源項目SlidingMenu來實現的。放假在家,時間比較充裕,就干脆自己實現了一波。
先上演示圖(是用360手機助手實時演示的,所以效果上有些卡頓。在真機上運行很流暢。)
再看看QQ的側滑菜單演示圖
QQ菜單在滑動的時候菜單View兩端同時隱藏或滑出
實現思路
對于菜單的橫向滑動,我們可以借助Android提供的HorizontalScrollView幫助我們實現。我們先通過繼承HorizontalScrollView幫助我們創建一個可以橫向滾動的視圖。
對于自定義控件,特別是自定義靈活的組合控件,一般要實現一個構造器
public class SlideMenu extends HorizontalScrollView { public SlideMenu(Context context, AttributeSet attrs){ super(context, attrs); } } 第二個參數是一個屬性集合,通過它我們能獲得一些自定義的屬性
在values文件夾下建立一個attrs.xml文件,編寫我們需要的自定義屬性
在實際項目中,我們一般需要設置菜單View的寬度,菜單以及內容View是使用哪一個布局,所以一般建立以下這三個屬性就可以了。有其他需求的也可以自己擴展。
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="SlideMenu"> <attr name="menu_layout" format="reference"></attr> <attr name="content_layout" format="reference"></attr> <attr name="menu_width" format="reference|dimension"></attr> </declare-styleable> </resources>
在SlideMenu類中,我們可以創建一個init()方法,來幫我們完成解析自定義屬性的值,并做一些其他初始化參數的操作。
private void init(AttributeSet attrs) { //解析自定義參數的值 TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SlideMenu); int menuLayoutId = typedArray.getResourceId(0, -1); int contentLayoutId = typedArray.getResourceId(1, -1); mMenuWidth = typedArray.getDimensionPixelOffset(2, 0); //加載View mMenu = View.inflate(getContext(), menuLayoutId, null); mContent = View.inflate(getContext(), contentLayoutId, null); //獲取屏幕寬高 DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); mScreenWidth = displayMetrics.widthPixels; mScreenHeight = displayMetrics.heightPixels; //設置為不能滑出邊界(滑出邊界的效果比較難看) setOverScrollMode(OVER_SCROLL_NEVER); //設置無滾動條 setHorizontalScrollBarEnabled(false); }
自定義ViewGroup通常要實現兩個方法onMeasure 和 onLayout方法 onMeasure:計算所有ChildView的寬度和高度 然后根據ChildView的計算結果,設置自己的寬和高 onLayout :對子View進行布局,會依次調用子ViewGroup的layout方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //設置測量的寬高為屏幕的寬高 setMeasuredDimension( mScreenWidth, mScreenHeight); if(!mHasInit){ //由于會多次測量,所以使用mHasInit保證布局只加載一次 mHasInit = true; //創建子View,并加到ScrollView中 mWrapper = new LinearLayout(getContext()); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(mMenuWidth+mScreenWidth,getMeasuredHeight()); mWrapper.setOrientation(LinearLayout.HORIZONTAL); LinearLayout.LayoutParams menuParams = new LinearLayout.LayoutParams(mMenuWidth, mScreenHeight); mWrapper.addView(mMenu, menuParams); LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(mScreenWidth, mScreenHeight); mWrapper.addView(mContent, contentParams); mWrapper.setLayoutParams(layoutParams); addView(mWrapper); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); //change表示view有新的布局和尺寸。在子view被初始化并加載到父view的時候就會調用onLayout方法,并且change為true if(changed){ //將ScrollView滾動到mMenuWidth的位置,也就是滾動到跟內容View的左側對其,讓菜單欄剛好完全隱藏在屏幕左側 //注意滾動的時機,當view有新的布局和尺寸時調用,也就是在初始化的時候去滾動。這個時候滾動才不會在打開頁面時有滾動的效果 scrollTo(mMenuWidth, 0); } }
這樣就基本實現了能夠滑動的布局。接下來我們要處理觸摸事件,以滿足以下要求:
- 當Scroller滾動到0位置時,可以響應任何事件
- 當Scroller滾動到mMenuWidth位置時,只有觸摸事件的X坐標小于50dp(大小任意設置)時,才能響應橫向滑動事件。
- 當Scroller滾動到0的位置時,點擊事件的X坐標大于mMenuWidth時,使菜單View隱藏掉
- 當手指抬起時Scroller滾動到小于mMenuWidth/2位置時,使Scroller平緩滾動到0,當手指抬起時Scroller滾動到大于mMenuWidth/2位置時,使Scroller平緩滾動到mMenuWidth
</ul>
如果了解了View的事件分發機制,對觸摸事件的處理寫起來就沒有難度,只需要不斷的調試就能實現效果,在這里就不貼代碼了。
</li> </ol>基本使用方法
<com.opensource.slidemenu.SlideMenu android:id="@+id/menu" android:layout_width="match_parent" android:layout_height="match_parent" app:menu_layout="@layout/menu" app:content_layout="@layout/content" app:menu_width="300dp" > </com.opensource.slidemenu.SlideMenu>
在@layout/menu和@layout/content中寫布局或者直接引入Fragment都可以,靈活度比較高。
QQ側滑的思考
雖然沒有動手去實現QQ的側滑,但我也對QQ的側滑菜單實現方法做了一些思考。
由于QQ菜單在滑動的時候會出現菜單View被壓再內容View下面的情況,所以容器肯定不能使用LinearLayout,View疊加的頁面我們就需要使用FrameLayout。
側滑的效果可以這樣實現:
我們可以處理觸摸事件,在橫向滑動的時候,計算滑動距離,根據滑動距離計算此時 菜單View和內容View的距離屏幕最左邊的值,然后動態改變菜單View和內容View的margin值。