自定义DrawerLayout抽屉布局

最近项目需求需要做一个抽屉滑动的效果,也就是从屏幕的右边滑进来页面,就像这样:

很简单,系统自带的DrawerLayout就能完成这样基本的需求。但是我们的需求要稍微复杂一点,在关闭侧边栏之前啊,我们想去做一下数据保存。这时候怎么办呢?相信大家都会想,这东西肯定有滑动监听啊,去监听一下滑动事件不就好了吗,ok,那我们现在来看一下Drawerlayout的滑动监听,代码如下:

mDrawerLayout.addDrawerListener(new MyDrawerLayout.DrawerListener() {
            @Override
            public void onDrawerSlide(@NonNull View var1, float var2) {

            }

            @Override
            public void onDrawerOpened(@NonNull View var1) {

            }

            @Override
            public void onDrawerClosed(@NonNull View var1) {

            }

            @Override
            public void onDrawerStateChanged(int var1) {

            }
        });

我们看到系统给我们提供了四个滑动监听,最符合我们需求的看上去是这个方法,


@Overridepublic void onDrawerClosed(@NonNull View var1) {

}

那么它能实现在关闭动作开始之前回调到我们的页面吗?答案是,不能!这个方法的调用时机是在侧边栏滑动结束之后,也会是,等到侧边栏彻底的滑动到了屏幕的外面,滑动动作完全结束之后才会执行这个方法! emmmm,shitaaa

那么其他的方法好使吗?答案是,不好使,至于为什么不好使,大家私下里自己去试吧,这里就不描述了。

那么,咋办呢,网上搜索了半天也没啥好法子,没办法,自己造一下轮子吧。

首先整理一下思路,我们想要这样一个方法,命名为

beforeClose()

该方法的执行时机是在侧边栏关闭动作发生之前,也就是说,当我们点击了屏幕中白色的部分,此时,不执行系统默认的关闭操作,执行该方法(beforeClose()),也就是将系统的默认关闭操作拦截住,然后,在此方法中去执行我们的业务代码,当执行完我们的业务代码之后,继续执行系统默认的关闭操作,ok,思路顺出来了,我们去解决问题。

第一个问题,先找到系统默认的关闭操作在哪写的。在哪写的呢,我们先来认真看一下系统默认的关闭流程是怎样的,首先,我们点击屏幕上的空白部分,对应的触摸事件应该是    ACTION_DOWN,那么此时手不要着急从屏幕上松开,我们观察此时侧边栏滑动了吗?答案是没有!那么这证明了,在ACTION_DOWN这步操作中,未执行侧边栏关闭事件。接着我们松开手指,此时应该执行 ACTION_UP,那么此时我们看到,哎,侧边栏滑动回去了,这一步证明了,系统默认的关闭方法在  ACTION_UP这一个事件底下,好,我们现在去源码里看一下这部分代码,很容易找到,如下:

public boolean onTouchEvent(MotionEvent ev) {
      ......
        switch (action & 255) {
            case ACTION_DOWN:
               ........
                break;
            case ACTION_UP:
                x = ev.getX();
                y = ev.getY();
                boolean peekingOnly = true;
                View touchedView = this.mLeftDragger.findTopChildUnder((int) x, (int) y);
                if (touchedView != null && this.isContentView(touchedView)) {
                    float dx = x - this.mInitialMotionX;
                    float dy = y - this.mInitialMotionY;
                    int slop = this.mLeftDragger.getTouchSlop();
                    if (dx * dx + dy * dy < (float) (slop * slop)) {
                        View openDrawer = this.findOpenDrawer();
                        if (openDrawer != null) {
                            peekingOnly = this.getDrawerLockMode(openDrawer) == 2;
                        }
                    }
                }

                this.closeDrawers(peekingOnly);

                this.mDisallowInterceptRequested = false;
           ..............
        }

        return wantTouchEvents;
    }

代码较多,无关内容略过,我们只看重要的,那么在   onTouchEvent  的  ACTION_UP动作下面我们发现了这样一个方法,

 this.closeDrawers(peekingOnly);也就是:
void closeDrawers(boolean peekingOnly) {    boolean needsInvalidate = false;    final int childCount = getChildCount();    for (int i = 0; i < childCount; i++) {        final View child = getChildAt(i);        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) {            continue;        }

        final int childWidth = child.getWidth();

        if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) {            needsInvalidate |= mLeftDragger.smoothSlideViewTo(child,                    -childWidth, child.getTop());        } else {            needsInvalidate |= mRightDragger.smoothSlideViewTo(child,                    getWidth(), child.getTop());        }

        lp.isPeeking = false;    }

    mLeftCallback.removeCallbacks();    mRightCallback.removeCallbacks();

    if (needsInvalidate) {        invalidate();    }}

那么这个是我们要找的吗 ?这里有这么一句代码:

mLeftDragger.smoothSlideViewTo(..,..,..)

看到这个方法名激不激动,翻译一下这个方法名,顺滑的侧边视图to........,是不是很激动?怎么去验证到底是不是它呢?开debug,自己断点验证一下,没错,就是它!!!

ok,到这里我们的工作就完成的大半了,剩下来就是去在closeDrawers里去拦截这个方法的调用,然后去执行我们的业务代码,然后再调用这个代码了。

有小伙伴可能问了,drawerlayout 是系统的控件,不允许我们更改,我们又不能改代码,怎么拦截啊?呵呵,系统不让我们改,我们还不能自己新建一个类吗,代码直接copy系统的就完事了呗。

所以,就有了下面这个东西:

package com.zhd.life.helloworld2;
........
public class MyDrawerLayout extends ViewGroup {
   public MyDrawerLayout(@NonNull Context context) {
        this(context, (AttributeSet) null);
    }

    public MyDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {

    }

   .......
}

我们新建一个类。命名为MyDrawerLayout,然后里面的细节代码直接全部copy系统的,一点不变,这次让改了吧???!!!

好了,现在开始拦截,由于视图的滑动在MyDrawerLayout里面,而我们的业务代码在我们的activity中,那么我们就需要  closeDrawers()方法调用之前,通知我们的activity,通知方式有很多,handler,接口回调,eventbus,都行,在此我们选择接口回调的方式,为什么选择这个呢?我个人习惯,大家习惯用那种方式,大家自己选,废话少说,看代码,

首先定义一个接口如下,

public interface OnClickWiteLisener {
        void beforeClose();
    }

在我们的MydrawerLayout 的closeDrawers中这样写,

void closeDrawers(boolean peekingOnly) {
        needsInvalidate = false;
        int childCount = this.getChildCount();
        for (int i = 0; i < childCount; ++i) {
         ...........
            if (this.isDrawerView(child) && (!peekingOnly || lp.isPeeking)) {

                if (mOnClickWiteLisener!=null){
                    mOnClickWiteLisener.beforeClose();
                }else{
                    close();
                }
            }
        }

        this.mLeftCallback.removeCallbacks();
        this.mRightCallback.removeCallbacks();
        if (needsInvalidate) {
            this.invalidate();
        }

    }    

close方法如下:

   private View child;
    private boolean needsInvalidate = false;
    private MyDrawerLayout.LayoutParams lp;

    public void close() {
        int childWidth = child.getWidth();
        if (this.checkDrawerViewAbsoluteGravity(child, 3)) {
            needsInvalidate |= this.mLeftDragger.smoothSlideViewTo(child, -childWidth, child.getTop());
        } else {
            needsInvalidate |= this.mRightDragger.smoothSlideViewTo(child, this.getWidth(), child.getTop());
        }

        lp.isPeeking = false;
    }

这样就完成了我们的拦截功能,当我们在activity中实现了 OnClickWiteLisener 的时候,执行我们自己定义的回调;没有实现的时候,继续系统原来的流程不变,。

在activity中这样使用,

public class MainActivity extends AppCompatActivity implements MyDrawerLayout.OnClickWiteLisener {

    //自定义的MyDrawerLayout
    private MyDrawerLayout mDrawerLayout;

    //点击此按钮打开侧边栏
    private TextView show;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDrawerLayout = findViewById(R.id.aaa);
        show = findViewById(R.id.show);
        mDrawerLayout.setOnClickWiteLisener(this);
        show.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDrawerLayout.openDrawer(Gravity.END);
            }
        });
    }

    @Override
    public void beforeClose() {
        Toast.makeText(this, "我点击了白屏,想要关闭,我在此执行了保存数据操作,", Toast.LENGTH_SHORT).show();
        mDrawerLayout.close();
    }
}

ok,大功告成。就这样就可以了。

困了,睡

原文地址:https://www.cnblogs.com/zhdsky/p/12670870.html

时间: 2024-10-22 01:41:27

自定义DrawerLayout抽屉布局的相关文章

安卓笔记:DrawerLayout抽屉布局的使用

DrawerLayout(抽屉布局),在各种app中经常出现,比如csdn.. 要点: 1.使用DrawerLayout时,在xml布局中,把主界面的布局放在前面,后面才放上抽屉里的布局内容 2.记得为抽屉内的布局加上android:layout_gravity="left"或"start",指明抽屉出现在哪一侧. 代码如下所示: activity_drawer.xml <RelativeLayout xmlns:android="http://sc

Android侧滑菜单DrawerLayout(抽屉布局)实现

应用场景: 由于侧滑菜单有更好的用户体验效果,所以更多的App使用侧滑抽屉式菜单列表,如网易客户端.百度影音.爱奇艺等等.至此,侧滑菜单有了更多的使用需求. 知识点介绍: 实现侧滑菜单功能的方法有很多,如果开源的项目SlidingMenu,下载地址为https://github.com/jfeinstein10/SlidingMenu.该开源项目依赖于另一个开源项目ActionBarSherlock,下载地址为https://github.com/JakeWharton/ActionBarShe

Android DrawerLayout 抽屉

Android DrawerLayout 抽屉 DrawerLayout 在supportV4 Lib中,类似开源slidemenu一样,DrawerLayout父类为ViewGroup,自定义组件基本都是扩展这个类. android.support.v4.widget.DrawerLayout 下面是个简单的用法演示.点左上角的按钮 打开抽屉菜单. 点击对应的ITEM 切换对应的内容,内容显示使用Fragment,这里没用到ActionBar来做切换 <?xml version="1.0

Android DrawerLayout抽屉效果

官网guide:http://developer.android.com/training/implementing-navigation/nav-drawer.html 官网示例:NavigationDrawer.zip android.support.v4.widget.DrawerLayout 抽屉layout.该widget只能实现从左向右.从右向左 openDrawer(), closeDrawer(), isDrawerOpen() 下面贴一下示例的主要的布局文件 和 activit

android.support.v4.widget.DrawerLayout 抽屉效果导航菜单

抽屉效果导航菜单图示 如图所示,抽屉效果的导航菜单不用切换到另一个页面,也不用去按菜单的硬件按钮,直接在界面左上角的一个按钮点击,菜单就滑出来,而且感觉能放很多东西 概况:实现上图所示的抽屉效果的导航菜单有以下两种方式 方式1.用SlidingDrawer: http://developer.android.com/reference/android/widget/SlidingDrawer.html 但是不知道为什么这个类官方不建议再继续用了: Deprecated since API lev

Android自定义Dialog及其布局

 实际项目开发中默认的Dialog无法满足需求,需要自定义Dialog及其布局,并响应布局中控件的事件. 上效果图: 自定义Dialog,LogoutDialog: 要将自定义布局传入构造函数中,才能在Activity中通过 dialog.findviewbyid 获取到控件,否则返回null. public class LogoutDialog extends Dialog{ Context context; public LogoutDialog(Context context) { sup

自定义layout中布局文件的属性

以前一直都是用ndroid自带的属性,突然发现自定义xml属性也是非常重要,于是总结了一下. 在values文件夹下新建的attr.xml文件,该文件为自定义属性. //attr.xml <?xml version="1.0" encoding="utf-8"?> <resources> <!-- MyView为自定义视图类 --> <!-- 注意:自定义属性必须一个不少的添加到布局文件中,否则编译失败 --> <

Customize the View Items Layout 自定义视图项目布局

In this lesson, you will learn how to customize the default editor layout in a Detail View. For this purpose, the Contact Detail View will be used. 在本课中,您将学习如何在详细视图中自定义默认编辑器布局.为此,将使用"联系人详细信息"视图. Note 注意 Before proceeding, take a moment to review

Android 抽屉布局

目前部分APP使用一种类似抽屉式的布局,像QQ那种,感觉很炫,自己也一直想做一个像那样的布局,(ps网上很多这样的例子,我下面做的就是参考网上的改变的) 废话不就不说了,直接上代码 1.首先建立一个布局文件activity_drawer.xml 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android