效果圖:android
須要繼承ViewGroup,由於包含了子控件,菜單子控件 與 主頁面子控件ide
Activity Xml佈局相關:佈局
<!-- 自定義側滑菜單 SlideMenu --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <view.custom.heimacustomview.my_slide_menu.MySlideMenu android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/slide_menu" /> <include layout="@layout/slide_main" /> </view.custom.heimacustomview.my_slide_menu.MySlideMenu> </LinearLayout>
菜單界面子控件佈局相關:post
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="300dp" android:layout_height="match_parent" android:background="#ffff80" android:orientation="vertical"> <!-- 這裏的View不能寫 wrap_content 否則在測量後,一直是TextView的寬度 --> <TextView android:layout_width="300dp" android:layout_height="wrap_content" android:textSize="30sp" android:text="側滑菜單" android:gravity="center_horizontal" /> </LinearLayout>
主頁界面子控件佈局相關:spa
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#80ffff"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我是首頁" android:textSize="30sp" android:layout_centerInParent="true" android:layout_centerHorizontal="true"/> </RelativeLayout>
自定義側滑菜單類相關:code
package view.custom.heimacustomview.my_slide_menu; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; public class MySlideMenu extends ViewGroup { private static final String TAG = MySlideMenu.class.getSimpleName(); private Scroller mScroller; /** * 以往我是在onTouchEvent方法中處理,此次我採用手勢識別器 * 注意:手勢識別器只處理事件相關,不能攔截時間,至關於只過濾處理水,並不能阻止水 */ private GestureDetector mGestureDetector; float mDistanceX; float countX = 0f; /** * 構造方法,由佈局xml指引來初始化,並傳入屬性集合 * @param context * @param attrs */ public MySlideMenu(final Context context, AttributeSet attrs) { super(context, attrs); mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){ /** * 滑動過程當中的方法 * @param e1 能夠理解爲 手指按下記錄用到的MotionEvent * @param e2 能夠理解爲 手機滑到某個點記錄用到的MotionEvent * @param distanceX 計算好的X軸距離值 * @param distanceY 計算好的Y軸距離值 * @return */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // return super.onScroll(e1, e2, distanceX, distanceY); /* if (getScrollX() < 0) { distanceX = 0; }*/ Log.d(TAG, "onScroll() distanceX:" + distanceX + " getScrollX:" + getScrollX()); mDistanceX = distanceX; countX += distanceX; if (countX > 0) { countX = 0; } else if (countX < -slideMenuView.getMeasuredWidth()) { countX = -slideMenuView.getMeasuredWidth(); } scrollTo((int) countX, getScrollY()); return true; } /*@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // return super.onFling(e1, e2, velocityX, velocityY); Log.d(TAG, "onFling() velocityX:" + velocityX + " velocityY:" + velocityY); if (countX > -slideMenuView.getMeasuredWidth() / 2) { countX = 0; } else if (countX < -slideMenuView.getMeasuredWidth() / 2) { countX = -slideMenuView.getMeasuredWidth(); } int dx = (int) countX - getScrollX(); // scrollTo((int) countX, getScrollY()); mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(1000)); invalidate(); return true; }*/ }); // 實現彈性滑動,不要滑動那麼生硬 mScroller = new Scroller(context); } /** * 定義兩個子控件 */ private View slideMenuView; private View slideMainView; /** * 當佈局xml加載完成後,就會調用此方法,而後此方法再去獲取子控件View */ @Override protected void onFinishInflate() { super.onFinishInflate(); // 獲取子控件View slideMenuView = getChildAt(0); slideMainView = getChildAt(1); } /** * 測量方法 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 測量菜單子控件的高和寬,寬是佈局文件設置的寬度,高度獲取當前MySlideMenu的高度(與屏幕保存一致高度) int slideMenuViewWidth = slideMenuView.getLayoutParams().width; Log.d(TAG, "獲取佈局中的寬度 slideMenuViewWidth:" + slideMenuViewWidth); if (null != slideMenuView) { slideMenuView.measure(slideMenuViewWidth, heightMeasureSpec); } // 測量主頁子控件的高和寬,寬度高度獲取當前MySlideMenu的寬度高度(與屏幕保存一致高度) if (null != slideMainView) { slideMainView.measure(widthMeasureSpec, heightMeasureSpec); } } /** * 給子控件位置排版,固定好位置 * @param changed 當發生改變的時候 * @param l 父控件距離左手邊左邊線距離 * @param t 父控件距離頂邊頂邊線距離 * @param r 父控件距離左手邊右邊線距離 * @param b 父控件距離頂部邊底部線距離 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { slideMenuView = getChildAt(0); Log.d(TAG, "slideMenuView.getMeasuredWidth():" + slideMenuView.getMeasuredWidth()); Log.d(TAG, "slideMainView.getMeasuredWidth():" + slideMainView.getMeasuredWidth()); // 給菜單子控件固定好位置 slideMenuView.layout(-slideMenuView.getMeasuredWidth(), 0, 0, slideMenuView.getMeasuredHeight()); // 給主頁子控件固定好位置 r和父控件保持一直,b和父控件保持一致 slideMainView.layout(0, 0, r, b); } private float downX; @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); mGestureDetector.onTouchEvent(event); if (event.getAction() == MotionEvent.ACTION_UP) { if (countX > -slideMenuView.getMeasuredWidth() / 2) { countX = 0; } else if (countX < -slideMenuView.getMeasuredWidth() / 2) { countX = -slideMenuView.getMeasuredWidth(); } int dx = (int) countX - getScrollX(); // scrollTo((int) countX, getScrollY()); // 這種方式體驗過於生硬 mScroller.startScroll(getScrollX(), 0, dx, 0, Math.abs(1000)); invalidate(); } else if (event.getAction() == MotionEvent.ACTION_DOWN) { downX = event.getX(); } return true; } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { int currX = mScroller.getCurrX(); scrollTo(currX, mScroller.getCurrY()); postInvalidate(); } } }