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; } }