Android自带的侧滑菜单 使用代码 如下
<?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>
侧滑和主页面内容用按钮和图片代替了, 这样基本的侧滑功能就实现了
下面自定义来实现类似于QQ的侧边栏效果
新建 获取屏幕相关的辅助类 ScreenUtils , 直接贴代码了, 里面注释很详细
/**
* 获得屏幕相关的辅助类
*
*
*/
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 ,直接上代码了, 里面注释很详细
/**
* 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新建一个主题 , 为了去掉标题栏, 防止标题栏影响侧滑效果
<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>
然后就可以使用了
新建一个侧滑菜单的布局 layout_slider_menu.xml , 这里面就是滑出的菜单的布局和里面的控件 , 根据需求指定
<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, SliderDemo
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;
}
}