使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 )

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/51935896

使用ViewDragHelper打造属于自己的DragLayout(抽屉开关 )


DrawLayout这个自定义的空间很常见,qq,网易新闻,知乎等等,都有这种效果,那这种效果是怎样实现的呢?本篇博客将带你来怎样实现它。

转载请注明原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/51935896

废话不多说,先来看一下 效果

首先我们先来看一下我们要怎样使用它

其实只需要两个 步骤,使用起来 非常方便

1.在XML文件

DragLayout至少要有两个孩子,且都是 ViewGroup或者ViewGroup的实现类

<com.xujun.drawerLayout.drag.DragLayout
    android:id="@+id/dl"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg"
    app:range="480"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="50dp"
        android:paddingLeft="10dp"
        android:paddingRight="50dp"
        android:paddingTop="50dp">

        <ImageView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@drawable/head"/>

        <ListView
            android:id="@+id/lv_left"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>
    </LinearLayout>

    <com.xujun.drawerLayout.drag.MyLinearLayout
        android:id="@+id/mll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#ffffff"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="#18B6EF"
            android:gravity="center_vertical">

            <ImageView
                android:id="@+id/iv_header"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_marginLeft="15dp"
                android:src="@drawable/head"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:text="Header"/>
        </RelativeLayout>

        <ListView
            android:id="@+id/lv_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>
    </com.xujun.drawerLayout.drag.MyLinearLayout>

</com.xujun.drawerLayout.drag.DragLayout>

在代码中若想为其设置监听器,

分别可以监听打开的 时候,关闭的时候,拖动的时候,可以在里面做相应的处理,同时我还加入了 自定义属性可以通过 app:range=”480”或者setRange()方法,即可设置打开抽屉的范围。

 mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {

            @Override
            public void onOpen() {
                Utils.showToast(MainActivity.this, "onOpen");
                // 左面板ListView随机设置一个条目
                Random random = new Random();
                Log.i(TAG, "onOpen:=" +mDragLayout.getRange());
                int nextInt = random.nextInt(50);
                mLeftList.smoothScrollToPosition(nextInt);

            }

            @Override
            public void onDraging(float percent) {
                Log.d(TAG, "onDraging: " + percent);// 0 -> 1
                // 更新图标的透明度
                // 1.0 -> 0.0
                ViewHelper.setAlpha(mHeaderImage, 1 - percent);
            }

            @Override
            public void onClose() {
                Utils.showToast(MainActivity.this, "onClose");
                // 让图标晃动
                ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f);
                mAnim.setInterpolator(new CycleInterpolator(4));
                mAnim.setDuration(500);
                mAnim.start();
            }
        });

实现方式

关于ViewDragHelper的一些 讨论

DrawLayout在网上的 实现方式很多,千奇百怪,有一些是直接监听 onTouchEvent事件,处理Activon_Move,Action_Down,Action_up等动作,这样实现的话稍微有点复杂。本篇博客是使用ViewDragHelper来 处理触摸事件和拖拽事件的的,ViewDragHelper是2013Google IO大会推出的,目的是为了给开发者提供一个处理触摸事件,节省开发者的时间。

关于Google官方 关于ViewDragHelper的解释,简单来说就是处理ViewGroup的 触摸事件和拖拽事件

ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.

实现思路

  • 1) 我是通过继承FrameLayout来实现的,相比较于继承ViewGroup来实现,这样有一个好处就是省去了自己重写 onMeasure (),onLayout ()方法
  • 2)在构造方法里面初始化mDragHelper,mSensitivity代表打开抽屉的 难易程度,是Float类型,至于mCallback是什么,下面会详细讲,这里先不着急。
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initAttars(context, attrs);
    // a.初始化 (通过静态方法)
    mDragHelper = ViewDragHelper.create(this, mSensitivity, mCallback);
}
  • 3)重写 onInterceptTouchEvent和onTouchevent 方法 ,将事件交给
// b.传递触摸事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    // 传递给mDragHelper
    return mDragHelper.shouldInterceptTouchEvent(ev);
}

/***
 * 将事件交给mDragHelper处理
 *
 * @param event
 * @return
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
    try {
        mDragHelper.processTouchEvent(event);
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 返回true, 持续接受事件
    return true;
}
  • 4)重写onFinishInflate方法,在里面拿到 我们的侧滑菜单mLeftContent和主菜单mMainContent
@Override
protected void onFinishInflate() {
    super.onFinishInflate();

    // 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)
    if (getChildCount() < 2) {
        throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at " +
                "least.");
    }
    if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {
        throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an " +
                "instance of ViewGroup");
    }

    mLeftContent = (ViewGroup) getChildAt(0);
    mMainContent = (ViewGroup) getChildAt(1);
}

下面我们一起来看一下这个mCallBack是什么东西

看之前我们需要了解Status和OnDragStatusChangeListener这两个东西。

  • Status代表DrawLayout 当前的状态,是否是打开,关闭还是拖拽。
  • OnDragStatusChangeListener是个监听器,在DrawLayout状态改变的时候会回调相关的方法,方便与外界进行通讯。
  • 我们可以通过 setDragStatusListener(OnDragStatusChangeListener mListener);这个方法设置监听
/**
 * 状态枚举
 */
public static enum Status {
    Close, Open, Draging;
}

/**
 * 抽屉开关的监听器
 */
public interface OnDragStatusChangeListener {
    void onClose();

    void onOpen();

    void onDraging(float percent);
}

接下来我们来看ViewDragHelper.Callback几个主要的方法


tryCaptureView(View child, int pointerId) Called when the user’s input indicates that they want to capture the given child view with the pointer indicated by pointerId. onViewCaptured(View capturedChild, int activePointerId) Called when a child view is captured for dragging or settling. getViewHorizontalDragRange(View child) Return the magnitude of a draggable child view’s horizontal range of motion in pixels. clampViewPositionHorizontal(View child, int left, int dx) Restrict the motion of the dragged child view along the horizontal axis. onViewPositionChanged(View changedView, int left, int top, int dx, int dy) Called when the captured view’s position changes as the result of a drag or settle. onViewReleased(View releasedChild, float xvel, float yvel) Called when the child view is no longer being actively dragged. 谷歌官方的连接;https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.Callback.html


下面的代码有关于这几个方法的中文解释,这里就不详细讲解了

ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
    // d. 重写事件

    // 1. 根据返回结果决定当前child是否可以拖拽
    // child 当前被拖拽的View
    // pointerId 区分多点触摸的id
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        Log.d(TAG, "tryCaptureView: " + child);
        return mToogle;
    }

    @Override
    public void onViewCaptured(View capturedChild, int activePointerId) {
        Log.d(TAG, "onViewCaptured: " + capturedChild);
        // 当capturedChild被捕获时,调用.
        super.onViewCaptured(capturedChild, activePointerId);
    }

    @Override
    public int getViewHorizontalDragRange(View child) {
        // 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
        Log.i(TAG, "getViewHorizontalDragRange:mRange=" +mRange);
        return mRange;
    }

    // 2. 根据建议值 修正将要移动到的(横向)位置   (重要)
    // 此时没有发生真正的移动
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        // child: 当前拖拽的View
        // left 新的位置的建议值, dx 位置变化量
        // left = oldLeft + dx;
        Log.d(TAG, "clampViewPositionHorizontal: "
                + "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " + left);

        if (child == mMainContent) {
            left = fixLeft(left);
        }
        return left;
    }

    // 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
    // 此时,View已经发生了位置的改变
    @Override
    public void onViewPositionChanged(View changedView, int left, int top,
                                      int dx, int dy) {
        // changedView 改变位置的View
        // left 新的左边值
        // dx 水平方向变化量
        super.onViewPositionChanged(changedView, left, top, dx, dy);
        Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);

        int newLeft = left;
        if (changedView == mLeftContent) {
            // 把当前变化量传递给mMainContent
            newLeft = mMainContent.getLeft() + dx;
        }

        // 进行修正
        newLeft = fixLeft(newLeft);

        if (changedView == mLeftContent) {
            // 当左面板移动之后, 再强制放回去.
            mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
            mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
        }
        // 更新状态,执行动画
        dispatchDragEvent(newLeft);

        // 为了兼容低版本, 每次修改值之后, 进行重绘
        invalidate();
    }

    // 4. 当View被释放的时候, 处理的事情(执行动画)
    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
        // View releasedChild 被释放的子View
        // float xvel 水平方向的速度, 向右为+
        // float yvel 竖直方向的速度, 向下为+
        Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
        super.onViewReleased(releasedChild, xvel, yvel);

        // 判断执行 关闭/开启
        // 先考虑所有开启的情况,剩下的就都是关闭的情况
        if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {
            open();
        } else if (xvel > 0) {
            open();
        } else {
            close();
        }

    }

    @Override
    public void onViewDragStateChanged(int state) {
        // TODO Auto-generated method stub
        super.onViewDragStateChanged(state);
    }

};

其实主要思路就是

  • 1)在方法public boolean tryCaptureView(View child, int pointerId)处理那些child可以被捕捉,这里我们返回true表示所有的都可以被捕捉
  • 2)在public int clampViewPositionHorizontal(View child, int left, int dx)方法中根据child返回将要移动的水平位置的偏移量
  • 3)在 void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)方法中处理要做的事情 包括更新状态, 伴随动画, 重绘界面等
public void onViewPositionChanged(View changedView, int left, int top,  int dx, int dy) {

      // 进行修正
   newLeft = fixLeft(newLeft);

  if (changedView == mLeftContent) {
    // 当左面板移动之后, 再强制放回去.
    mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
    mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 +    mHeight);

    if (changedView == mLeftContent) {
    // 把当前变化量传递给mMainContent
    newLeft = mMainContent.getLeft() + dx;
   }

}


    // 更新状态,执行动画
    dispatchDragEvent(newLeft);

    // 为了兼容低版本, 每次修改值之后, 进行重绘
    invalidate();
}

protected void dispatchDragEvent(int newLeft) {
    float percent = newLeft * 1.0f / mRange;
    //0.0f -> 1.0f
    Log.d(TAG, "percent: " + percent);

    if (mListener != null) {
        mListener.onDraging(percent);
    }

    // 更新状态, 执行回调
    Status preStatus = mStatus;
    mStatus = updateStatus(percent);
    if (mStatus != preStatus) {
        // 状态发生变化
        if (mStatus == Status.Close) {
            // 当前变为关闭状态
            if (mListener != null) {
                mListener.onClose();
            }
        } else if (mStatus == Status.Open) {
            if (mListener != null) {
                mListener.onOpen();
            }
        }
    }

    //    * 伴随动画:
    animViews(percent);

}
  • 4)在void onViewReleased(View releasedChild, float xvel, float yvel)的时候处理要做的事情,包括更新状态, 伴随动画, 重绘界面等,这里就不列出代码了,有兴趣的话下载源码看看

转载请注明原博客地址: http://blog.csdn.net/gdutxiaoxu/article/details/51935896

源码下载地址: https://github.com/gdutxiaoxu/drawLayout.git

关于更多自定义View的例子,可查看以下我的一些博客,同时如果大家觉得还可以的,欢迎在github上面 star或者fork,谢谢。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Flutter入门到实战

WebView深度学习(二)之全面总结WebView遇到的坑及优化

这篇文章讲一下WebView遇到的那些坑,带领各位爬坑。这里如果有你没遇到的问题,欢迎留言告诉我,我尽我所能帮你解决。感谢大家支持。

59030
来自专栏青蛙要fly的专栏

Android技能树 — Drawable小结

我们知道平常使用最多的Drawable可能是图片了,我们知道一个图片的原本的尺寸,比如下面这个图:

6610
来自专栏学海无涯

Android开发之高德地图实现定位

在应用开发中,地图开发是经常需要使用的“组件”,Google Map虽然有官方教程,无奈用不起来,原因你懂的~~那么国内比较出名的是就是百度地图和高德地图,由于...

43740
来自专栏宋凯伦的技术小栈

Android - 页面返回上一页面的三种方式

  今年刚刚跳槽到了新公司,也开始转型做Android,由此开始Android的学习历程。   最近在解很多UI的bug,在解bug过程中,总结了在UI的实现过...

44180
来自专栏Android 技术栈

Android 关于WebView全方面的使用(项目应用篇)

WebView的使用已经是老生常谈了,看到很多文章说了用法,但我很少看到全的或者是项目中可以直接使用的,都是看了很多后,自己把功能都集合在一起。这里是一份比较全...

23340
来自专栏Android干货

Android多媒体录制--MediaRecorder视频录制

47170
来自专栏向治洪

高仿今日头条

高仿今日头条 --- 第一篇:(android高仿系列)今日头条 --新闻阅读器 (一) 上次,已经完成了头部新闻分类栏目的拖动效果。 这篇文章是继续去完善...

52990
来自专栏双十二技术哥

深入Weex系列(六)Weex渲染流程分析

在前两篇文章中我们结合源码学习了Module、Component的注册、调用、回调等流程,相信大家一定收获颇多,对Weex的理解也一定愈加深入。

14350
来自专栏Android开发经验

自适应软键盘的Dialog以及监听软键盘弹起

30730
来自专栏葡萄城控件技术团队

Wijmo 更优美的jQuery UI部件集:在对Wijmo GridView进行排序或者过滤时保留选择

许多客户面临这样的场景,他们希望在应用了排序或者过滤之后仍然将最终用户的行选状态保留。通常情况下,当我们在选择了任何行之后应用排序或者过滤会导致回传之后选择状态...

19390

扫码关注云+社区

领取腾讯云代金券