(二十九)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
- (二十九)DrawerLayout 打造炫酷侧滑菜单
- [UI]抽屉菜单DrawerLayout分析(二)
- [UI]抽屉菜单DrawerLayout分析(二)
- [UI]抽屉菜单DrawerLayout分析(二)
- 抽屉菜单(DrawerLayout)
- android DrawerLayout 实现侧滑菜单 知识整理(二)
- Android之 利用DrawerLayout打造一个侧滑菜单
- 侧滑菜单之二 DrawerLayout 详解
- [UI]抽屉菜单DrawerLayout分析(二)(测拉菜单)
- VB打造超酷个性化菜单(二)
- 日拱一卒(二十九)
- CSS(二十九)
- [UI]抽屉菜单DrawerLayout分析(一)
- [UI]抽屉菜单DrawerLayout分析(三)
- [UI]抽屉菜单DrawerLayout分析(一)
- [UI]抽屉菜单DrawerLayout分析(一)
- [UI]抽屉菜单DrawerLayout分析(三)
- [UI]抽屉菜单DrawerLayout分析(一)
- 文章标题
- Linux基础——linux中进程的类型
- SSH框架下单元测试的实现
- lintcode----最近公共祖先
- 根据后序和中序建立二叉树
- (二十九)DrawerLayout 打造炫酷侧滑菜单
- Leetcode:AddTwoNumbers
- 作业2:循环计数器Verilog实现
- Spring框架学习之高级依赖关系配置(二)
- Physx中的DistanceJoint
- 详解java内存模型与对象
- 打造属于自己的进度条(笔记)
- WebView
- Python爬虫倒立文字验证码登录知乎