Android 一步一步教你使用ViewDragHelper
2015-08-12 16:47
696 查看
在自定义viewgroup的时候 要重写
推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.
先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑
这个效果其实还蛮简单的(原谅我让臭脚不能动 让BABY动)
布局文件:
然后我们看一下自定义的layout 如何实现2个子view 一个可以滑动 一个不能滑动的
然后再完善一下这个layout,刚才滑动的时候我们的view 出了屏幕的边界很不美观 现在我们修改2个函数 让滑动的范围
在这个屏幕之内(准确的说是在这个layout之内,因为我们的布局文件layout充满了屏幕 所以看上去是在屏幕内)
我们看下效果
然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置
其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。
看下效果:
到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应
滑动事件。
首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数
然后看下效果:
这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。
如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法
注意看29行到末尾 你会发现 只有当
大于0的时候 对应的move事件才会捕获。否则就是丢弃直接丢给子view自己处理了
另外还有一个效果就是 假如我们的 baby被拉倒了边界处,
我们的手指不需要拖动baby这个iv,手指直接在边界的其他地方拖动此时也能把这个iv拖走。
这个效果其实也可以实现,无非就是捕捉你手指在边界处的动作 然后传给你要拖动的view即可。
代码非常简单 两行即可
再重写一个回调函数 然后加个监听
这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。
上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以
看下源码。
onInterceptTouchEvent和
onTouchEvent 这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 把sd卡文本文件的内容显示到界面
- Android异步消息处理机制解析
- Android主线程与子线程通讯
- Android GridView 点击效果(可能是最快捷的实现,另有福利)
- 关于Genymotion使用android5.1报错或者无法启动的解决方法
- 获取当前短信内容或部分匹配内容
- Android常用URI
- 菜鸟学Android第二天
- Android Service绑定与跨进程通信
- Android工程的编译过程
- Android Socket开发 注意事项
- Android工程的编译过程
- 记录帖:Textview在android代码中可以动态的设置字号单位
- Android插件化开发之OpenAtlas初体验
- android自动化之MonkeyRunner测试环境配置(一)
- Android 4.4 新特性
- android与js交互(二)
- android 调用系统出现activity销毁
- 常用的正则表达式集锦
- Android Studio R文件出错