ViewDragHelper

在自定义viewgroup的时候 要重写onInterceptTouchEventonTouchEvent 这2个方法 是非常麻烦的事情,好在谷歌后来

推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.

先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑

这个效果其实还蛮简单的(原谅我让臭脚不能动 让BABY动)

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.administrator.viewdragertestapp.DragLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/a1"></ImageView>

        <ImageView
            android:id="@+id/iv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/a2"></ImageView>

    </com.example.administrator.viewdragertestapp.DragLayout>

</LinearLayout>

然后我们看一下自定义的layout 如何实现2个子view 一个可以滑动 一个不能滑动的

package com.example.administrator.viewdragertestapp;

import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Created by Administrator on 2015/8/12.
 */
public class DragLayout extends LinearLayout {

    private ViewDragHelper mDragger;

    private ViewDragHelper.Callback callback;

    private ImageView iv1;
    private ImageView iv2;

    @Override
    protected void onFinishInflate() {
        iv1 = (ImageView) this.findViewById(R.id.iv1);
        iv2 = (ImageView) this.findViewById(R.id.iv2);
        super.onFinishInflate();

    }

    public DragLayout(Context context) {
        super(context);

    }

    public DragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        callback = new DraggerCallBack();
        //第二个参数就是滑动灵敏度的意思 可以随意设置
        mDragger = ViewDragHelper.create(this, 1.0f, callback);
    }

    class DraggerCallBack extends ViewDragHelper.Callback {

        //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            if (child == iv2) {
                return false;
            }
            return true;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return left;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            return top;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //决定是否拦截当前事件
        return mDragger.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //处理事件
        mDragger.processTouchEvent(event);
        return true;
    }

}

然后再完善一下这个layout,刚才滑动的时候我们的view 出了屏幕的边界很不美观 现在我们修改2个函数 让滑动的范围

在这个屏幕之内(准确的说是在这个layout之内,因为我们的布局文件layout充满了屏幕 所以看上去是在屏幕内)

//这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。
        // 我们要让view滑动的范围在我们的layout之内
        //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
        //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围
        //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //取得左边界的坐标
            final int leftBound = getPaddingLeft();
            //取得右边界的坐标
            final int rightBound = getWidth() - child.getWidth() - leftBound;
            //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left
            //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值
            //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值
            return Math.min(Math.max(left, leftBound), rightBound);
        }

        //纵向的注释就不写了 自己体会
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - child.getHeight() - topBound;
            return Math.min(Math.max(top, topBound), bottomBound);
        }

我们看下效果

然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置

其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。

package com.example.administrator.viewdragertestapp;

import android.content.Context;
import android.graphics.Point;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

/**
 * Created by Administrator on 2015/8/12.
 */
public class DragLayout extends LinearLayout {

    private ViewDragHelper mDragger;

    private ViewDragHelper.Callback callback;

    private ImageView iv1;
    private ImageView iv2;

    private Point initPointPosition = new Point();

    @Override
    protected void onFinishInflate() {
        iv1 = (ImageView) this.findViewById(R.id.iv1);
        iv2 = (ImageView) this.findViewById(R.id.iv2);
        super.onFinishInflate();

    }

    public DragLayout(Context context) {
        super(context);

    }

    public DragLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        callback = new DraggerCallBack();
        //第二个参数就是滑动灵敏度的意思 可以随意设置
        mDragger = ViewDragHelper.create(this, 1.0f, callback);
    }

    class DraggerCallBack extends ViewDragHelper.Callback {

        //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            if (child == iv2) {
                return false;
            }
            return true;
        }

        //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。
        // 我们要让view滑动的范围在我们的layout之内
        //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。
        //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围
        //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            //取得左边界的坐标
            final int leftBound = getPaddingLeft();
            //取得右边界的坐标
            final int rightBound = getWidth() - child.getWidth() - leftBound;
            //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left
            //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值
            //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值
            return Math.min(Math.max(left, leftBound), rightBound);
        }

        //纵向的注释就不写了 自己体会
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - child.getHeight() - topBound;
            return Math.min(Math.max(top, topBound), bottomBound);
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            //松手的时候 判断如果是这个view 就让他回到起始位置
            if (releasedChild == iv1) {
                //这边代码你跟进去去看会发现最终调用的是startScroll这个方法 所以我们就明白还要在computeScroll方法里刷新
                mDragger.settleCapturedViewAt(initPointPosition.x, initPointPosition.y);
                invalidate();
            }
        }
    }

    @Override
    public void computeScroll() {
        if (mDragger.continueSettling(true)) {
            invalidate();
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //布局完成的时候就记录一下位置
        initPointPosition.x = iv1.getLeft();
        initPointPosition.y = iv1.getTop();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //决定是否拦截当前事件
        return mDragger.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //处理事件
        mDragger.processTouchEvent(event);
        return true;
    }

}

看下效果:

到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应

滑动事件。

首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数

         @Override
        public int getViewHorizontalDragRange(View child) {
            return getMeasuredWidth() - child.getMeasuredWidth();
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return getMeasuredHeight()-child.getMeasuredHeight();
        }

然后看下效果:

这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。

如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法

 1 case MotionEvent.ACTION_MOVE: {
 2                 if (mInitialMotionX == null || mInitialMotionY == null) break;
 3
 4                 // First to cross a touch slop over a draggable view wins. Also report edge drags.
 5                 final int pointerCount = MotionEventCompat.getPointerCount(ev);
 6                 for (int i = 0; i < pointerCount; i++) {
 7                     final int pointerId = MotionEventCompat.getPointerId(ev, i);
 8                     final float x = MotionEventCompat.getX(ev, i);
 9                     final float y = MotionEventCompat.getY(ev, i);
10                     final float dx = x - mInitialMotionX[pointerId];
11                     final float dy = y - mInitialMotionY[pointerId];
12
13                     final View toCapture = findTopChildUnder((int) x, (int) y);
14                     final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
15                     if (pastSlop) {
16                         // check the callback‘s
17                         // getView[Horizontal|Vertical]DragRange methods to know
18                         // if you can move at all along an axis, then see if it
19                         // would clamp to the same value. If you can‘t move at
20                         // all in every dimension with a nonzero range, bail.
21                         final int oldLeft = toCapture.getLeft();
22                         final int targetLeft = oldLeft + (int) dx;
23                         final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
24                                 targetLeft, (int) dx);
25                         final int oldTop = toCapture.getTop();
26                         final int targetTop = oldTop + (int) dy;
27                         final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
28                                 (int) dy);
29                         final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
30                                 toCapture);
31                         final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
32                         if ((horizontalDragRange == 0 || horizontalDragRange > 0
33                                 && newLeft == oldLeft) && (verticalDragRange == 0
34                                 || verticalDragRange > 0 && newTop == oldTop)) {
35                             break;
36                         }
37                     }
38                     reportNewEdgeDrags(dx, dy, pointerId);
39                     if (mDragState == STATE_DRAGGING) {
40                         // Callback might have started an edge drag
41                         break;
42                     }
43
44                     if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
45                         break;
46                     }
47                 }
48                 saveLastMotion(ev);
49                 break;
50             }

注意看29行到末尾 你会发现 只有当

horizontalDragRange 和verticalDragRange  

大于0的时候 对应的move事件才会捕获。否则就是丢弃直接丢给子view自己处理了

另外还有一个效果就是 假如我们的 baby被拉倒了边界处,

我们的手指不需要拖动baby这个iv,手指直接在边界的其他地方拖动此时也能把这个iv拖走。

这个效果其实也可以实现,无非就是捕捉你手指在边界处的动作 然后传给你要拖动的view即可。

代码非常简单 两行即可

再重写一个回调函数 然后加个监听

         @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            mDragger.captureChildView(iv1, pointerId);
        }
mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。

时间: 2024-11-25 06:32:18

ViewDragHelper的相关文章

Android开发:使用ViewDragHelper实现抽屉拉伸效果

事实上,有非常多方法能够实现一个Layout的抽屉拉伸效果,最常常的方法就是自己定义一个ViewGroup,然后控制点击事件.控制移动之类的,这样的方法的代码量多,并且实现起来复杂,后期维护添加其它效果也非常麻烦,直到今天看到了 ViewDragHelper这个类,就是专门为实现View的移动而生的.我就试着开发了一个抽屉拉伸的效果,效果图例如以下: 全部移动的控制在ViewDragHelper.Callback里面来实现.移动就用dragHelper.smoothSlideViewTo来实现,

ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

QQ5.0的侧滑效果有多种实现方式, 如http://blog.csdn.net/lmj623565791/article/details/39257409   就是利用HorizontalScrollView实现的,简单实用; 如http://blog.csdn.net/manoel/article/details/39013095/   通过改造SlidingMenu实现,没有改变原有SlidingMenu功能,屏幕边缘侧滑也可以.... 相对来说ViewDragHelper实现方式最为复杂

ViewDragHelper详解

2013年谷歌i/o大会上介绍了两个新的layout: SlidingPaneLayout和DrawerLayout,现在这俩个类被广泛的运用,其实研究他们的源码你会发现这两个类都运用了ViewDragHelper来处理拖动.ViewDragHelper是framework中不为人知却非常有用的一个工具. ViewDragHelper解决了android中手势处理过于复杂的问题,在DrawerLayout出现之前,侧滑菜单都是由第三方开源代码实现的,其中著名的当属MenuDrawer ,Menu

Android应用ViewDragHelper详解及部分源码浅析

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 很久没有更新博客了,忙里偷闲产出一篇.写这片文章主要是去年项目中的一个需求,当时三下五除二的将其实现了,但是源码的阅读却一直扔在那迟迟没有时间理会,现在拣起来看看吧,否则心里一直不踏实. 关于啥是ViewDragHelper,这里不再解释,官方下面这个解释已经很牛逼了,如下: /** * ViewDragHelper is a utility class for

ViewDragHelper实践之仿Android官方侧滑菜单NavigationDrawer效果

相信经常使用移动应用的用户都很熟悉侧滑菜单栏, 下拉, 下弹, 上弹等应用场景, 几乎主流的移动应用无论IOS 还是Android都能看到. 2.3以前的时候, 很多第三方比如SlidingMenu, MenuDrawer, ActionbarSherlock等等都很大程度的丰富和深化了这种交互理念.能让小小的屏幕, 容纳更多的交互接口. 也是这种趋势, Android官方在v4终于推出了DrawerLayout. 表示对侧滑的重视与肯定. 唠叨到这了. 去看了DrawerLayout的源码和官

Android 中 View移动总结:ViewDragHelper学习及用法详解

如上图简单呈现出两个方块后,提出一个需求: 1.拖动方块时,方块(即子View)可以跟随手指移动. 2.一个方块移动时,另一个方块可以跟随移动. 3.将方块移动到左边区域(右边区域)后放开(即手指离开屏幕),它会自动移动到左边界(右边界). 4.移动的时候给方块加点动画(duang~duang~duang~) . View移动的相关方法总结: 1. layout 在自定义控件中,View绘制的一个重写方法layout(),用来设置显示的位置.所以,可以通过修改View的坐标值来改变view在父V

Android -- ViewDragHelper

SlidingPaneLayout和DrawerLayout,现在这俩个类被广泛的运用,其实研究他们的源码你会发现这两个类都运用了ViewDragHelper来处理拖动. ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难. 好的特点 ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器

ViewDragHelper(拖拽控件之3种方法)

+++++++++++++++++++++++++++++++方法1++++++++++++++++++++++++++++++++++++ activity_main.xml布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_wi

Android下拉刷新库,利用viewdraghelper实现,集成了下拉刷新,底部加载更多,数据初始加载显示loading等功能

项目Github地址:https://github.com/sddyljsx/pulltorefresh Android下拉刷新库,利用viewdraghelper实现. 集成了下拉刷新,底部加载更多,以及刚进入加载数据的loadview.包括了listview与gridview的改写. 效果1: 效果2: 效果3: 效果4: 效果5: 使用说明: imageList=(ListView)findViewById(R.id.image_list); imageAdapter=new ImageA

ViewDragHelper的使用

一.ViewDragHelper的原理 是一个能够自用移动ViewGroup内部View的控件. 通过获取ViewGroup的点击事件,之后通过Scroller滑动来进行对ViewGroup内部控件的移动. 二.ViewDragHelper的作用 ①.自由移动ViewGroup的内部控件 ②.仿QQ的侧滑栏 ③.拼图游戏啊之类的. 核心就是能够让View自由移动 三.制作简单的仿QQ的侧滑菜单 ①.自定义一个Layout,因为ViewDragHelper是对ViewGroup的处理. publi