(二十九)DrawerLayout 打造炫酷侧滑菜单

来源:互联网 发布:大数据100指数 编辑:程序博客网 时间:2024/06/04 18:35

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、DrawerLayout 的 demo

先来看一下 DrawerLayout 的简单 demo。

效果:
这里写图片描述

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><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"    tools:context="com.xiaoyue.drawerlayout.MainActivity">    <android.support.v4.widget.DrawerLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:layout_editor_absolutyX="5dp"        tools:layout_editor_absolutyY="5dp">        <ImageView            android:layout_width="match_parent"            android:layout_height="match_parent"            android:src="@drawable/xiaoyue"            />        <LinearLayout            android:layout_width="match_parent"            android:layout_height="match_parent"            android:background="@color/colorAccent"            android:layout_gravity="start">            <TextView                android:layout_width="match_parent"                android:layout_height="50dp"                android:text="侧滑菜单" />        </LinearLayout>    </android.support.v4.widget.DrawerLayout></LinearLayout>

主要在布局文件里面引用 DrawerLayout 即可,DrawerLayout 下的子控件,当添加属性android:layout_gravity 的值 为 “start”,即可设置为侧边菜单。

上面虽然在侧边菜单设置宽度为 match_parent,但实际显示效果并没有占满屏幕。这是抽屉里的宽度不能超过320 dp, 所以用户总是可以看到内容视图的一部分。

**注:这边不要在最外层使用 ConstraintLayout。**DrawerLayout 要求宽高必须是 MeasureSpec.EXACTLY。正常情况下 match_parent 和具体值都是 MeasureSpec.EXACTLY,但 ConstraintLayout 的 match_parent,有点不同,不是。

二、DrawerLayout 方法介绍

1. setDrawerLockMode

锁住 DrawerLayout 的指定模式(侧滑界面出现或关闭),不再允许滑动改变。

setDrawerLockMode(@LockMode int lockMode):
传入指定的锁住模式,左右两边都不在允许侧滑。

setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity):
传入指定的锁住模式和哪一边的侧滑进行锁住,对选择的侧滑进行锁住。

setDrawerLockMode(@LockMode int lockMode, View drawerView):
传入指定的锁住模式和要进行锁住的侧滑 view。

2.addDrawerListener

添加滑动的监听:

     drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {            /**             * 监听滑动距离             * @param drawerView 侧滑菜单             * @param slideOffset 滑动的百分比(完全打开的时候为 1)             */            @Override            public void onDrawerSlide(View drawerView, float slideOffset) {            }            /**             * 当侧滑打开的时候             * @param drawerView             */            @Override            public void onDrawerOpened(View drawerView) {            }            /**             * 当侧滑关闭的时候             * @param drawerView             */            @Override            public void onDrawerClosed(View drawerView) {            }            /**             * 当状态改变的时候(有三种状态)             * DrawerLayout.STATE_IDLE 静止             * DrawerLayout.STATE_DRAGGING 准备滑动(手指一点上去就会触发)             * DrawerLayout.STATE_SETTLING 滑动完成之后             * @param newState             */            @Override            public void onDrawerStateChanged(int newState) {            }        });

比较重要的是 onDrawerSlide,onDrawerStateChanged 的三种状态最好自己日志添加去试一下,不好讲清楚。

三、自定义侧滑菜单

1.效果

这里写图片描述

这边如果说侧滑菜单只使用一个自定义 ViewGroup 进行实现的话,会比较复杂。

1.要重写 onLayout 方法,当然也可以直接继承 LinearLayout 等。2.ViewGroup 的 onDraw 方法不一定都会被调用。如果重写 dispatchOnDraw 方法,又会导致每个子控件也进行重新绘制。3.监听事件的处理。

这边采用职责分明的策略,用多个自定义控件组合实现这个效果。

1.实现 MyDrawerLayout 继承 DrawerLayout,用自带的监听处理滑动事件2.实现 MyDrawerLayoutSlideBar 继承LinearLayout,处理子控件的摆放问题3.实现 MyDrawerLayoutBgView 继承 View,进行侧滑菜单的背景绘制(蓝色背景部分,绘制区域随手指变化)4.实现 MyDrawerBgRelativeLayout 继承 RelativeLayout,把 MyDrawerLayoutSlideBar 和 MyDrawerLayoutBgView 组合在一起,形成真正的侧滑菜单

2.布局文件

MainActivity:

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.xiaoyue.drawerlayout.MainActivity">    <com.xiaoyue.widget.mydrawerlayout.MyDrawerLayout        android:layout_width="match_parent"        android:layout_height="match_parent">        <!--内容  区域-->        <ImageView            android:layout_width="match_parent"            android:layout_height="match_parent"            android:scaleType="fitXY"            android:src="@drawable/fake"            />        <!--侧滑区域  LinearLayout    ColorDrawable -->        <com.xiaoyue.widget.mydrawerlayout.MyDrawerLayoutSlideBar            android:layout_width="200dp"            android:layout_height="match_parent"            android:layout_gravity="start"            app:maxTranslationX="66dp"            android:background="@color/colorPrimaryDark"            >            <TextView                style="@style/MenuText"                android:drawableLeft="@drawable/circle"                android:text="朋友圈" />            <TextView                style="@style/MenuText"                android:drawableLeft="@drawable/wallet"                android:text="钱包" />            <TextView                style="@style/MenuText"                android:drawableLeft="@drawable/coupon"                android:text="优惠券" />        </com.xiaoyue.widget.mydrawerlayout.MyDrawerLayoutSlideBar>    </com.xiaoyue.widget.mydrawerlayout.MyDrawerLayout></RelativeLayout>

修改完成的自定义控件的使用尽量保证与原先的使用风格一样,这边在布局文件中就像是使用 DrawerLayout 一样。

3.自定义的 DrawerLayout

MyDrawerLayout:

public class MyDrawerLayout extends DrawerLayout implements DrawerLayout.DrawerListener{    //侧滑菜单    private MyDrawerLayoutSlideBar mSlideBar;    //用来装载 MyDrawerLayoutSlideBar    private MyDrawerBgRelativeLayout mBgRelativeLayout;    //内容    private View mContenView;    //侧滑菜单的滑动百分比    private float mSlideOffset;    //当前手指触摸的 Y 坐标    private float mTouchY;    public MyDrawerLayout(Context context) {        super(context);    }    public MyDrawerLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        init();    }    private void init() {        //循环子控件,获取内容的 View 和侧滑菜单        for(int i = 0; i < getChildCount(); i ++) {            View childView = getChildAt(i);            if (childView instanceof MyDrawerLayoutSlideBar) {                mSlideBar = (MyDrawerLayoutSlideBar) childView;            } else {                mContenView = childView;            }        }        //偷梁换柱        //为 MyDrawerLayoutSlideBar 添加一层 MyDrawerBgRelativeLayout包装        //先移除 MyDrawerLayoutSlideBar        removeView( mSlideBar);        //把 MyDrawerLayoutSlideBar 添加到 MyDrawerBgRelativeLayout 下        mBgRelativeLayout = new MyDrawerBgRelativeLayout(mSlideBar);        addView(mBgRelativeLayout);        addDrawerListener(this);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        //不能在 OnTouch 中获取 Y 的坐标,避免事件被子控件消费的时候,获取不到        mTouchY = ev.getY();        if (ev.getAction() == MotionEvent.ACTION_UP) {            //手指松开的时候,侧滑菜单关闭            closeDrawers();            mSlideBar.onMotionUp(mTouchY);            return super.dispatchTouchEvent(ev);        }        if (mSlideOffset == 1 ) {            mBgRelativeLayout.setTouchY(mTouchY, mSlideOffset);        }        return super.dispatchTouchEvent(ev);    }    @Override    public void onDrawerSlide(View drawerView, float slideOffset) {        mSlideOffset = slideOffset;        //传递触摸 Y 坐标,触发背景动画        mBgRelativeLayout.setTouchY(mTouchY, slideOffset);        //设置内容区域向右偏移为侧滑菜单的一半,形成视觉差        float contentViewOffset = drawerView.getWidth() * slideOffset / 2;        mContenView.setTranslationX(contentViewOffset);    }    @Override    public void onDrawerOpened(View drawerView) {    }    @Override    public void onDrawerClosed(View drawerView) {    }    @Override    public void onDrawerStateChanged(int newState) {    }}

MyDrawerLayout 主要做三件事:一是对布局文件中的侧滑菜单 MyDrawerLayoutSlideBar 进行了一次包装,偷偷把 MyDrawerLayoutSlideBar 换成 MyDrawerBgRelativeLayout,并添加 MyDrawerLayoutBgView,这也是核心的一个实现思路。二是对侧滑菜单的滑动进行监听,实现对内容区域的同步滑动。三是监听手指的动作,每一次滑动进行背景的变化,以及每次手指离开屏幕的时候,关闭侧滑菜单。

4.侧滑菜单

MyDrawerLayoutSlideBar:

public class MyDrawerLayoutSlideBar extends LinearLayout {    //子控件的最大偏移量    private float maxTranslationX;    public MyDrawerLayoutSlideBar(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init(context, attrs);    }    private void init(Context context, AttributeSet attrs) {        //设置摆放方向为竖直方向        setOrientation(VERTICAL);        if (attrs != null) {            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);            maxTranslationX = typedArray.getDimension(R.styleable.SideBar_maxTranslationX, 0);            typedArray.recycle();        }    }    /**     * 对子控件进行相应的偏移     * @param y 当前触摸点 Y 坐标     * @param slideOffset 侧滑栏菜单滑出的百分比     */    public void setTouchY(float y, float slideOffset) {        //遍历全部子控件  给每一个子控件进行偏移        for (int i=0;i<getChildCount();i++) {            View chlid= getChildAt(i);            //偏移方法            apply(getParent(), chlid, y, slideOffset);        }    }    /**     * 对子控件进行偏移     * @param parent 父控件     * @param childView 要偏移的子控件     * @param y 偏移最大的 Y 坐标     * @param slideOffset 偏移比例     */    private void apply(ViewParent parent, View childView, float y, float slideOffset) {        //计算子控件的中点 Y 坐标        int centerY = (childView.getTop() + childView.getBottom()) / 2;        //计算子控件中点与手指触摸的 Y 方向距离        float distanceY = Math.abs(y - centerY);        //计算子控件偏移距离        float scale = distanceY / getHeight() * 3;  //3   放大系数        float translationX = maxTranslationX * (1f - scale);        childView.setTranslationX(translationX);    }    /**     * 手指松开的时候处理     * @param y 触摸点 Y 坐标     */    public void onMotionUp(float y) {        View childView;        for (int i=0; i<getChildCount(); i++) {            childView = getChildAt(i);            childView.setPressed(false);            //要判断  y坐落在哪一个子控件    松手的那一刻  进行回调  跳转其他页面            boolean isHover = y > childView.getTop() && y < childView.getBottom();            if (isHover) {                childView.performClick();                //回调操作,可以采用监听,这边使用吐司只是为了验证回调到了                Toast toast = Toast.makeText(getContext(), ((TextView) childView).getText(), Toast.LENGTH_SHORT);                toast.show();                break;            }        }    }}

MyDrawerLayoutSlideBar 是被引用的布局文件里,从表面看这个是侧滑菜单,实际是上只是管理侧滑菜单的每一个子控件。当手指触摸屏幕进行滑动时候,背景变化,每个子控件做出相应的位移(位移大小没有具体参考值,需要对实际情况进行调整)。另外是在手指松开的时候,会去判断调用哪一个子控件的触摸方法(只根据 Y 进行判断,没有进行 X 的处理)。

5.背景绘制

MyDrawerLayoutBgView:

public class MyDrawerLayoutBgView extends View {    //画笔    private Paint mPaint;    //路径    private Path mPath;    private Drawable mDrawable;    public MyDrawerLayoutBgView(Context context) {        this(context, null);    }    public MyDrawerLayoutBgView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init();    }    /**     * 初始化参数     */    private void init() {        mPaint = new Paint();        mPaint.setAntiAlias(true);        mPath = new Path();    }    /**     * 设置当前触摸点的 Y 坐标,从而改变背景波浪     * @param y 当前触摸点 Y 坐标     * @param slideOffset 侧滑栏菜单滑出的百分比     */    public void setTouchY(float y, float slideOffset) {        //重置路径        mPath.reset();        //获取侧滑菜单滑出来的宽度        float width = getWidth() * slideOffset;        float height = getHeight();        //计算贝塞尔曲线 Y 方向超出去的距离(8 效果可能会好一些)        float offsetY = height / 8;        //计算被赛尔曲线 X 方向偏移的距离(也是为了效果好一些)        float offsetX = width / 2;        mPath.lineTo(offsetX, - offsetY);        mPath.quadTo( width * 3 / 2, y, offsetX , height + offsetY);        mPath.lineTo(0,height);        mPath.close();        invalidate();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mDrawable == null) {            //绘制背景为颜色的            canvas.drawPath(mPath, mPaint);        } else {            //绘制背景为图片的,根据 Path 对 图片进行截取            mDrawable.setBounds(0, 0, getWidth(), getHeight());            canvas.clipPath(mPath);            mDrawable.draw(canvas);        }    }    /**     * 设置背景     * (供 MyDrawerBgRelativeLayout 传递 MyDrawerLayoutSlideBar 的 background )     * @param drawable     */    public void setDrawable(Drawable drawable) {        if (drawable instanceof ColorDrawable) {            //支持背景为颜色            mPaint.setColor(((ColorDrawable) drawable).getColor());        }else {            //支持背景为图片            mDrawable = drawable;        }    }}

MyDrawerLayoutBgView 对贝塞尔曲线(蓝色背景)进行确认,背景是在 MyDrawerBgRelativeLayout包装 MyDrawerLayoutSlideBar 的时候,把 MyDrawerLayoutSlideBar 的背景传递进来(目前支取支持颜色和背景图片,其他未尝试)。

四、附

代码链接:http://download.csdn.net/download/qq_18983205/10112208

原创粉丝点击