Android自帶的側滑菜單 使用代碼 以下java
<?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <!--視圖的主頁面, 佈局必須用FrameLayout--> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/img2" /> </FrameLayout> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent" android:layout_gravity="start" > <!--android:layout_gravity="start"這行代碼決定了菜單是從左側劃出仍是從右側滑出--> <Button android:layout_width="match_parent" android:layout_height="match_parent" android:text="btn_test12222222" /> </LinearLayout> </android.support.v4.widget.DrawerLayout>
側滑和主頁面內容用按鈕和圖片代替了, 這樣基本的側滑功能就實現了android
下面自定義來實現相似於QQ的側邊欄效果app
新建 獲取屏幕相關的輔助類 ScreenUtils , 直接貼代碼了, 裏面註釋很詳細ide
/** * 得到屏幕相關的輔助類 * * */ public class ScreenUtils { private ScreenUtils() { /* cannot be instantiated */ throw new UnsupportedOperationException("cannot be instantiated"); } /** * 得到屏幕高度 * * @param context * @return */ public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; } /** * 得到屏幕寬度 * * @param context * @return */ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.heightPixels; } /** * 得到狀態欄的高度 * * @param context * @return */ public static int getStatusHeight(Context context) { int statusHeight = -1; try { Class<?> clazz = Class.forName("com.android.internal.R$dimen"); Object object = clazz.newInstance(); int height = Integer.parseInt(clazz.getField("status_bar_height") .get(object).toString()); statusHeight = context.getResources().getDimensionPixelSize(height); } catch (Exception e) { e.printStackTrace(); } return statusHeight; } /** * 獲取當前屏幕截圖,包含狀態欄 * * @param activity * @return */ public static Bitmap snapShotWithStatusBar(Activity activity) { View view = activity.getWindow().getDecorView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); int width = getScreenWidth(activity); int height = getScreenHeight(activity); Bitmap bp = null; bp = Bitmap.createBitmap(bmp, 0, 0, width, height); view.destroyDrawingCache(); return bp; } /** * 獲取當前屏幕截圖,不包含狀態欄 * * @param activity * @return */ public static Bitmap snapShotWithoutStatusBar(Activity activity) { View view = activity.getWindow().getDecorView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); Rect frame = new Rect(); activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); int statusBarHeight = frame.top; int width = getScreenWidth(activity); int height = getScreenHeight(activity); Bitmap bp = null; bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height - statusBarHeight); view.destroyDrawingCache(); return bp; } /** * Dip into pixels */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * Pixels converted into a dip */ public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } }
在values下建立 attrs.xml 來指定 菜單與屏幕右邊的距離佈局
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="QQSlider"> <!--菜單與屏幕右邊的距離--> <attr name="qqMenuRightMargin" format="dimension"/> </declare-styleable> </resources>
建立java類QQSlider ,直接上代碼了, 裏面註釋很詳細 ui
/** * desc:QQ側滑效果 */ public class QQSlider extends HorizontalScrollView { //左邊菜單佈局 private View mLeftMenu; //內容佈局 private View mContentView; //菜單是否打開 private boolean mMenuIsOpen; //是否攔截事件 private boolean mIntercept; //手勢處理類 private GestureDetector mGestureDetector; //菜單的寬度 private int mMenuWidth; private View mShadeView; private Context mContext; public QQSlider(Context context) { this(context,null); } public QQSlider(Context context, AttributeSet attrs) { this(context, attrs,0); } public QQSlider(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQSlider); //菜單離右邊屏幕邊緣的距離 float rightMargin = array.getDimension(R.styleable.QQSlider_qqMenuRightMargin, ScreenUtils.dip2px(mContext, 50)); mMenuWidth = (int) (ScreenUtils.getScreenWidth(mContext) - rightMargin); array.recycle(); mGestureDetector = new GestureDetector(mContext, mGestureDetectorListener); } private GestureDetector.SimpleOnGestureListener mGestureDetectorListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 快速滑動 // 向右快速滑動會是正的 + 向左快速滑動 是 - // 若是菜單是打開的 向右向左快速滑動都會回調這個方法 if (mMenuIsOpen) { if (velocityX < 0) { closeMenu(); return true; } } else { if (velocityX > 0) { openMenu(); return true; } } return super.onFling(e1, e2, velocityX, velocityY); } }; @Override protected void onFinishInflate() { super.onFinishInflate(); //拿到根佈局 ViewGroup rootView = (ViewGroup) getChildAt(0); int childCount = rootView.getChildCount(); if (childCount != 2) throw new RuntimeException("You can only place two sub view in the root"); //拿到菜單佈局 mLeftMenu= rootView.getChildAt(0); //指定菜單的寬度 ViewGroup.LayoutParams mLeftMenuLayoutParams = mLeftMenu.getLayoutParams(); mLeftMenuLayoutParams.width = mMenuWidth; mLeftMenu.setLayoutParams(mLeftMenuLayoutParams); //拿到內容佈局 mContentView= rootView.getChildAt(1); //指定內容的寬度 ViewGroup.LayoutParams mContentLayoutParams = mContentView.getLayoutParams(); //把內容佈局單讀提出來 rootView.removeView(mContentView); //在外面套一層陰影 RelativeLayout contentContainer = new RelativeLayout(mContext); contentContainer.addView(mContentView); mShadeView = new View(mContext); mShadeView.setBackgroundColor(Color.parseColor("#55000000")); contentContainer.addView(mShadeView); //把容器放回去 mContentLayoutParams.width = ScreenUtils.getScreenWidth(mContext); contentContainer.setLayoutParams(mContentLayoutParams); rootView.addView(contentContainer); mShadeView.setAlpha(0.0f); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); scrollTo(mMenuWidth,0); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { mIntercept = false; if (mMenuIsOpen) { float currentX = ev.getX(); if (currentX > mMenuWidth) { //關閉菜單 closeMenu(); //子view不響應任何事件 攔截子view的觸摸事件 //若是返回true 表明會攔截子view的觸摸事件,可是會相應本身的onTouch事件 mIntercept = true; return true; } } return super.onInterceptTouchEvent(ev); } //3 事件的攔截處理 @Override public boolean onTouchEvent(MotionEvent ev) { //獲取手指滑動速率,獲取手指滑動的速率,當期大於必定值就認爲是快速滑動 , GestureDetector(系統提供好的類) //當菜單打開的時候,手指觸摸右邊內容部分須要關閉菜單,還須要攔截事件(打開狀況下點擊內容頁不會響應點擊事件) //這裏保證了手勢處理類的調用 //快速滑動了 下面的攔截事件就不要處理了、 if (mGestureDetector.onTouchEvent(ev)) { return true; } //若是有攔截,則不執行本身的onTouch方法 if (mIntercept){ return true; } // 攔截處理事件 int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: int currentScrollX = getScrollX(); //在這裏注意currentScrollX的變化,當咱們默認關閉菜單的時候去拉動ScrollView,數值在不斷的變小 if (currentScrollX < mMenuWidth / 2) { //打開菜單 openMenu(); } else { //關閉菜單 closeMenu(); } //確保super.onTouchEvent不會執行 這裏看super.onTouchEvent源碼中的fling方法 //和smoothScrollTo的源碼 return true; } return super.onTouchEvent(ev); } /** * 關閉菜單 */ private void closeMenu() { smoothScrollTo(mMenuWidth, 0); mMenuIsOpen = false; } /** * 打開菜單 */ private void openMenu() { smoothScrollTo(0, 0); mMenuIsOpen = true; } //4 處理主頁內容的,這就須要不斷的獲取當前的滑動位置 @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); //l是從mMuneWidth一直變化到0 //計算梯度值 float scale = 1f * l / mMenuWidth; //梯度從1逐漸變爲0 //控制陰影 從0變化到1 float alphaScale = 1 -scale; mShadeView.setAlpha(alphaScale); ViewCompat.setTranslationX(mLeftMenu, 0.6f * l); } }
在values下的 styles.xml新建一個主題 , 爲了去掉標題欄, 防止標題欄影響側滑效果this
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <!--新建一個主題樣式, 目的爲了去掉標題欄--> <style name="AppThemeNoTitle" parent="AppTheme" > <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> </resources>
而後就可使用了3d
新建一個側滑菜單的佈局 layout_slider_menu.xml , 這裏面就是滑出的菜單的佈局和裏面的控件 , 根據需求指定code
<RelativeLayout 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" android:background="#000" android:orientation="vertical"> <LinearLayout android:id="@+id/layout_menu_top" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="match_parent" android:layout_height="match_parent" android:text="這是側滑菜單" /> </LinearLayout> </RelativeLayout>
建立一個Activity, SliderDemoorm
xml代碼爲:
<?xml version="1.0" encoding="utf-8"?> <com.lanyu96.qqsliderdemo.QQSlider android:layout_width="match_parent" android:background="#ffffff" xmlns:app="http://schemas.android.com/apk/res-auto" app:qqMenuRightMargin="100dp" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <include layout="@layout/layout_slider_menu" /> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/rl_content" android:background="#63d6cf" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="主頁內容" android:textColor="@color/colorAccent" android:textSize="30sp" /> </LinearLayout> </LinearLayout> </>
還有一種效果就是 側滑時, 主頁佈局 有縮放效果
QQSlider.java代碼爲
/** * desc:QQ側滑效果 , 帶縮放效果 */ public class QQSlider extends HorizontalScrollView { //左邊的菜單 private View mMenuView; //左邊菜單佈局 private View mLeftMenu; //內容佈局 private View mContentView; //菜單是否打開 private boolean mMenuIsOpen; //是否攔截事件 private boolean mIntercept; //手勢處理類 private GestureDetector mGestureDetector; //菜單的寬度 private int mMenuWidth; private View mShadeView; private Context mContext; public QQSlider(Context context) { this(context,null); } public QQSlider(Context context, AttributeSet attrs) { this(context, attrs,0); } public QQSlider(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; mGestureDetector = new GestureDetector(mContext, new GestureDetectorListener()); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.QQSlider); float rightMargin = array.getDimension(R.styleable.QQSlider_qqMenuRightMargin, ScreenUtils.dip2px(mContext, 50)); //菜單的寬度 = 屏幕的寬度-菜單離右邊的距離 mMenuWidth = (int) (ScreenUtils.getScreenWidth(mContext) - rightMargin); array.recycle(); } /** * 手勢處理的監聽類 */ private class GestureDetectorListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 快速滑動 // 向右快速滑動會是正的 + 向左快速滑動 是 - // 若是菜單是打開的 向右向左快速滑動都會回調這個方法 if (mMenuIsOpen) { if (velocityX < 0) { closeMenu(); return true; } } else { if (velocityX > 0) { openMenu(); return true; } } return false; } } /** * 1.這個方法在整個佈局xml解析完畢走這個方法 */ @Override protected void onFinishInflate() { super.onFinishInflate(); //獲取菜單和主頁內容 //咱們在這裏getChildAt(0)拿的是咱們佈局中的LinearLayout ViewGroup container = (ViewGroup) getChildAt(0); int childCount = container.getChildCount(); if (childCount != 2) { //拋運行時異常,只能放置兩個子view throw new RuntimeException("You can only place two sub view"); } //拿到咱們的菜單佈局 mMenuView = container.getChildAt(0); //拿到咱們的主頁內容的佈局 mContentView = container.getChildAt(1); ViewGroup.LayoutParams layoutMenuParams = mMenuView.getLayoutParams(); //指定菜單的寬度 layoutMenuParams.width = mMenuWidth; //7.0 mMenuView.setLayoutParams(layoutMenuParams); ViewGroup.LayoutParams layoutContentParams = mContentView.getLayoutParams(); //指定內容的寬度 指定寬高後會從新擺放 在onLayout中 layoutContentParams.width = ScreenUtils.getScreenWidth(mContext); mContentView.setLayoutParams(layoutContentParams); } //2 佈局擺放 默認進來進來是關閉的 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); // 用來排放子佈局的 等子View所有擺放完才能去滾動 咱們一進來的時候默認是關閉菜單的 //類比縱向的ScrollVew的來理解 scrollTo(mMenuWidth, 0); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { mIntercept = false; if (mMenuIsOpen) { float currentX = ev.getX(); if (currentX > mMenuWidth) { //關閉菜單 closeMenu(); //子view不響應任何事件 攔截子view的觸摸事件 //若是返回true 表明會攔截子view的觸摸事件,可是會相應本身的onTouch事件 mIntercept = true; return true; } } return super.onInterceptTouchEvent(ev); } //3 事件的攔截處理 @Override public boolean onTouchEvent(MotionEvent ev) { //獲取手指滑動速率,獲取手指滑動的速率,當期大於必定值就認爲是快速滑動 , GestureDetector(系統提供好的類) //當菜單打開的時候,手指觸摸右邊內容部分須要關閉菜單,還須要攔截事件(打開狀況下點擊內容頁不會響應點擊事件) //這裏保證了手勢處理類的調用 //快速滑動了 下面的攔截事件就不要處理了、 if (mGestureDetector.onTouchEvent(ev)) { return true; } //若是有攔截,則不執行本身的onTouch方法 if (mIntercept){ return true; } // 攔截處理事件 int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: int currentScrollX = getScrollX(); //在這裏注意currentScrollX的變化,當咱們默認關閉菜單的時候去拉動ScrollView,數值在不斷的變小 if (currentScrollX < mMenuWidth / 2) { //打開菜單 openMenu(); } else { //關閉菜單 closeMenu(); } //確保super.onTouchEvent不會執行 這裏看super.onTouchEvent源碼中的fling方法 //和smoothScrollTo的源碼 return true; } return super.onTouchEvent(ev); } //4 處理主頁內容的縮放,左邊的縮放和透明度的調節 這就須要不斷的獲取當前的滑動位置 @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); //l是從mMuneWidth一直變化到0 //計算梯度值 float scale = 1f * l / mMenuWidth; //梯度從1逐漸變爲0 //右邊的縮放 最小0.7f 最大是1 float rightScale = 0.7f + 0.3f * scale; //設置主頁內容的縮放,默認是中心點縮放 //設置縮放的中心點 ViewCompat.setPivotX(mContentView, 0); ViewCompat.setPivotY(mContentView, mContentView.getMeasuredHeight() / 2); ViewCompat.setScaleX(mContentView, rightScale); ViewCompat.setScaleY(mContentView, rightScale); //設置菜單的縮放和透明度 從半透明到徹底透明 0.5f到1.0f float menuAlpha = 0.5f + (1 - scale) * 0.5f; ViewCompat.setAlpha(mMenuView, menuAlpha); //縮放處理 float menuScale = 0.7f + (1 - scale) * 0.3f; ViewCompat.setScaleX(mMenuView, menuScale); ViewCompat.setScaleY(mMenuView, menuScale); //設置平移 l*0.7f ViewCompat.setTranslationX(mMenuView, 0.25f * l); } /** * 關閉菜單 */ private void closeMenu() { smoothScrollTo(mMenuWidth, 0); mMenuIsOpen = false; } /** * 打開菜單 */ private void openMenu() { smoothScrollTo(0, 0); mMenuIsOpen = true; } }