在自定义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这个方法。我们跟进去看看这个方法

   case MotionEvent.ACTION_MOVE: {
if (mInitialMotionX == null || mInitialMotionY == null) break; // First to cross a touch slop over a draggable view wins. Also report edge drags.
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
final float dy = y - mInitialMotionY[pointerId]; final View toCapture = findTopChildUnder((int) x, (int) y);
final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
if (pastSlop) {
// check the callback's
// getView[Horizontal|Vertical]DragRange methods to know
// if you can move at all along an axis, then see if it
// would clamp to the same value. If you can't move at
// all in every dimension with a nonzero range, bail.
final int oldLeft = toCapture.getLeft();
final int targetLeft = oldLeft + (int) dx;
final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
targetLeft, (int) dx);
final int oldTop = toCapture.getTop();
final int targetTop = oldTop + (int) dy;
final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
(int) dy);
final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
toCapture);
final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
if ((horizontalDragRange == 0 || horizontalDragRange > 0
&& newLeft == oldLeft) && (verticalDragRange == 0
|| verticalDragRange > 0 && newTop == oldTop)) {
break;
}
}
reportNewEdgeDrags(dx, dy, pointerId);
if (mDragState == STATE_DRAGGING) {
// Callback might have started an edge drag
break;
} if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
break;
}
}
saveLastMotion(ev);
break;
}

注意看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图片我就不上了大家可以自己在手机里跑一下就可以。

上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以

看下源码。

Android 一步一步教你使用ViewDragHelper的更多相关文章

  1. Ace教你一步一步做Android新闻客户端(一)

    复制粘贴了那么多博文很不好意思没点自己原创的也说不出去,现在写一篇一步一步教你做安卓新闻客户端,借此机会也是让自己把相关的技术再复习一遍,大神莫笑,专门做给新手看. 手里存了两篇,一个包括软件视图 和 ...

  2. 一步一步教你在 Android 里创建自己的账号系统(一)

    大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 大家在 ...

  3. 一步一步了解Cocos2dx 3.0 正式版本开发环境搭建(Win32/Android)

    cocos2d-x 3.0发布有一段时间了,作为一个初学者,我一直觉得cocos2d-x很坑.每个比较大的版本变动,都会有不一样的项目创建方式,每次的跨度都挺大…… 但是凭心而论,3.0RC版本开始 ...

  4. 一步一步教你如何在linux下配置apache+tomcat(转)

    一步一步教你如何在linux下配置apache+tomcat   一.安装前准备. 1.   所有组件都安装到/usr/local/e789目录下 2.   解压缩命令:tar —vxzf 文件名(. ...

  5. 一步一步教你将普通的wifi路由器变为智能广告路由器

    一步一步教你将普通的wifi路由器变为智能广告路由器 相信大家对WiFi智能广告路由器已经不再陌生了,现在很多公共WiFi上网,都需要登录并且验证,这也就是WiFi广告路由器的最重要的功能.大致就是下 ...

  6. 一步一步学android控件(之十五) —— DegitalClock & AnalogClock

    原本计划DigitalClock和AnalogClock单独各一篇来写,但是想想,两个控件的作用都一样,就和在一起写一篇了. DegitalClock和AnalogClock控件主要用于显示当前时间信 ...

  7. 一步一步学android控件(之十六)—— CheckBox

    根据使用场景不同,有时候使用系统默认的CheckBox样式就可以了,但是有时候就需要自定义CheckBox的样式.今天主要学习如何自定义CheckBox样式.在CheckBox状态改变时有时需要做一些 ...

  8. 一步一步教你使用Git

    一步一步教你使用Git 互联网给我们带来方便的同时,也时常让我们感到困惑.随便搜搜就出一大堆结果,然而总是有大量的重复和错误.小妖发出的内容,都是自己实测过的,有问题请留言. 现在,你已经安装了Git ...

  9. Android 从硬件到应用程序:一步一步爬上去 6 -- 我写的APP测试框架层硬件服务(终点)

    创Android Applicationproject:采用Eclipse的Android插入ADT创Androidproject,project名字Gpio,创建完成后,project文件夹pack ...

  10. 使用WPF教你一步一步实现连连看

    使用WPF教你一步一步实现连连看(一) 第一步: 问题,怎样动态的建立一个10*10的grid(布局) for (int i = 0; i < 10; i++){ RowDefinition r ...

随机推荐

  1. lintcode:排颜色 II

    排颜色 II 给定一个有n个对象(包括k种不同的颜色,并按照1到k进行编号)的数组,将对象进行分类使相同颜色的对象相邻,并按照1,2,...k的顺序进行排序. 样例 给出colors=[3, 2, 2 ...

  2. 本地替换文件读取MYSQL密码

    Mysql 的密码默认是存储在/data/mysql/下面的三个文件中:user.MYD,user.frm,user.MYI 先把这三个文件下载到本地,然后替换本地的这三个文件 使用net stop ...

  3. Java 类加载器(转)

    java虚拟机中可以安装多个类加载,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap(内嵌在java虚拟机中由C++编写),ExtClassLoader,AppClassLoa ...

  4. vmware 虚拟机 桥接 设置静态 IP

    最简单的方式: 图形界面下,进入设置IP的地方,设置一个静态IP. 然后再转入命令行继续工作.. 不行就 重启  

  5. js中indexOF和lastIndexOf

    indexOFindexOf() 方法返回某个指定的字符串值在字符串中首次出现的位置(从左向右).没有匹配的则返回-1,否则返回首次出现位置的字符串的下标值. var src="images ...

  6. 数组工具类 - ArrayUtil.java

    数组工具类,提供数组.对象之间转换的方法. 源码如下:(点击下载 - ArrayUtil.java .commons-lang-2.6.jar) import java.lang.reflect.Ar ...

  7. YTU 2616: A代码完善--简易二元运算

    2616: A代码完善--简易二元运算 时间限制: 1 Sec  内存限制: 128 MB 提交: 280  解决: 187 题目描述 注:本题只需要提交填写部分的代码,请按照C++方式提交. 编写二 ...

  8. 【HDOJ】5446 Unknown Treasure

    1. 题目描述题目很简单,就是求$C(n,m) % M$. 2. 基本思路这是一道应用了众多初等数论定理的题目,因为数据范围较大因此使用Lucas求$C(n,m) % P$.而M较大,因此通过$a[i ...

  9. 《OD学Sqoop》数据转换工具Sqoop

    一. 第二阶段课程回顾 hadoop 2.x HDFS YARN MapReduce Zookeeper Hive 二.大数据协作框架 对日志类型的海量数据进行分析 hdfs mapreduce/hi ...

  10. Hbase源码分析:Hbase UI中Requests Per Second的具体含义

    Hbase源码分析:Hbase UI中Requests Per Second的具体含义 让运维加监控,被问到Requests Per Second(见下图)的具体含义是什么?我一时竟回答不上来,虽然大 ...