Android ViewDragHelper使用介绍

ViewDragHelper是support.v4下提供的用于处理拖拽滑动的辅助类,查看Android的DrawerLayout源码,可以发现,它内部就是使用了该辅助类来处理滑动事件的.

public DrawerLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  final float density = getResources().getDisplayMetrics().density;
  mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f);
  final float minVel = MIN_FLING_VELOCITY * density;
  mLeftCallback = new ViewDragCallback(Gravity.LEFT);
  mRightCallback = new ViewDragCallback(Gravity.RIGHT);
  mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback);
  mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
  mLeftDragger.setMinVelocity(minVel);
  mLeftCallback.setDragger(mLeftDragger); 

  mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback);
  mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
  mRightDragger.setMinVelocity(minVel);
  mRightCallback.setDragger(mRightDragger); 

  //省略
 } 

有了ViewDragHelper,我们在写与拖拽滑动相关的自定义控件的时候就变得非常简单了,例如我们可以用来实现自定义侧滑菜单,再也不需要在onTouchEvent方法里计算滑动距离来改变布局边框的位置了.

使用ViewDragHelper类的大体步骤分为3步:

步骤1.在自定义的ViewGroup子类下通过ViewDragHelper的静态方法获取到ViewDragHelper的实例引用,注意它是一个单例的.

查看源码:

/**
 * Factory method to create a new ViewDragHelper.
 *
 * @param forParent Parent view to monitor
 * @param cb Callback to provide information and receive events
 * @return a new ViewDragHelper instance
 */
 public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
  return new ViewDragHelper(forParent.getContext(), forParent, cb);
 } 

可以发现它需要接收2个参数,参数1就是当前要使用ViewDragHelper的自定义控件的引用,Callback是一个回调抽象类,该回调接口是用于建立当前自定义控件与ViewDragHelper沟通的桥梁,Callback内定义了多个回调函数,这些回调函数涵盖了与当前自定义控件相关的是否允许拖拽,当前拖拽的View是哪一个view,拖拽的view的位置如何变化,释放的时候那个view被释放了,释放时的速度是怎么样的等等.稍后会详细介绍.

步骤2.有了ViewDragHelper的引用后,我们就需要传递相关的触摸事件给ViewDragHelper来帮我们处理,那么怎么传递呢?

可以通过重写onInterceptTouchEvent和onTouchEvent这2个函数来传递,前者是用于决定是否要拦截中断事件的,后者是用于消费触摸事件的,如果前者return true则表示事件需要被拦截,那么事件就会直接回调给onTouchEvent去处理,如果onTouchEvent返回true,则事件被消费,返回false则向上返回它的父类调用处,如果事件在向上层层返回的过程中没有被处理的话,那么事件最终将会消失;当然,如果onInterceptTouchEvent返回false的话,那么事件就会继续向下传递个它的直接子View去分发处理,关于事件分发的更多理论知识,大家可以看这篇文章事件分发机制的原理总结.

实例代码:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
 //由ViewDragHelper类来决定是否要拦截事件
 return dragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
 try {
  //由ViewDragHelper类来决定是否要处理触摸事件,这里可能有异常
  dragHelper.processTouchEvent(event);
 } catch (Exception e) {
  e.printStackTrace();
 }
 //返回true,可以持续接收到后续事件
 return true;
} 

步骤3:重写ViewDragHelper.Callback()的相关回调方法,处理事件,大体有如下方法:

下面将通过demo来分别介绍几个常用的方法,先来看看demo的整体代码,这是一个自定义的ViewGroup的子类,里面有2个子控件,分别是侧边栏和主体布局

/**
 * Created by mChenys on 2015/12/16.
 */
public class DragLayout extends FrameLayout {
 private String TAG = "DragLayout";
 private ViewDragHelper dragHelper;
 private LinearLayout mLeftContent; //左侧面板
 private LinearLayout mMainContent;//主体面板 

 public DragLayout(Context context) {
  this(context, null);
 } 

 public DragLayout(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 } 

 public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }
 //重写此方法,可以获取该容器下的所有的直接子View
 @Override
 protected void onFinishInflate() {
  super.onFinishInflate();
  mLeftContent = (LinearLayout) getChildAt(0);
  mMainContent = (LinearLayout) getChildAt(1);
 } 

 private void init() {
  //step1 通过ViewDragHelper的单例方法获取ViewDragHelper的实例
  dragHelper = ViewDragHelper.create(this, mCallback);
 }
 //step2 传递触摸事件,需要重写onInterceptTouchEvent和onTouchEvent
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  //由ViewDragHelper类来决定是否要拦截事件
  return dragHelper.shouldInterceptTouchEvent(ev);
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  try {
   //由ViewDragHelper类来决定是否要处理触摸事件
   dragHelper.processTouchEvent(event);
  } catch (Exception e) {
   e.printStackTrace();
  }
  //返回true,可以持续接收到后续事件
  return true;
 }
 //step3 重写ViewDragHelper.Callback()的相关回调方法,处理事件
 private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
  /**
   * 1.改方法是abstract的方法,必须要实现,其返回结果决定当前child是否可以拖拽
   * @param child 当前被拖拽的view
   * @param pointerId pointerId 区分多点触摸的id
   * @return true表示允许拖拽, false则不允许拖拽 ,默认返回false
   */
  @Override
  public boolean tryCaptureView(View child, int pointerId) {
   Log.d(TAG, "tryCaptureView:当前被拖拽的view:" + child);
   return false;
  }
 };
} 

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<mchenys.net.csdn.blog.mytencentqq.view.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/dragLayout"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 <!--左侧-->
 <LinearLayout
  android:id="@+id/layout_left"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/holo_red_light"
  android:orientation="vertical">
  <TextView
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="center"
   android:text="左侧"
   android:textSize="30sp" />
 </LinearLayout>
 <!--主体布局-->
 <LinearLayout
  android:id="@+id/layout_main"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/holo_blue_light"
  android:orientation="vertical">
  <TextView
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:gravity="center"
   android:text="主体"
   android:textSize="30sp" />
 </LinearLayout>
</mchenys.net.csdn.blog.mytencentqq.view.DragLayout> 

运行效果:

打印的log

D/DragLayout: tryCaptureView:当前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
D/DragLayout: tryCaptureView:当前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
D/DragLayout: tryCaptureView:当前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
D/DragLayout: tryCaptureView:当前被拖拽的view:android.widget.LinearLayout{32f4d44b V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main} 

由上面的log可以知道tryCaptureView方法被执行了,但是mMainContent却没有被拖动出来,只是为什么呢,因为tryCaptureView返回了false.默认是返回false的,下面修改为mMainContent可以拖动,mLeftContent不可以拖动:

@Override
public boolean tryCaptureView(View child, int pointerId) {
 Log.d(TAG, "tryCaptureView:当前被拖拽的view:" + child);
 if (child == mMainContent) {
  return true; //只有主题布局可以被拖动
 }
 return false;
} 

运行效果还是移动不了,这是为什么呢?

这是以因为我们还没有重写clampViewPositionHorizontal方法,下面将介绍该方法的使用

/**
 * 根据建议值修正将要移动到的横向位置,此时没有发生真正的移动
 * @param child 当前被拖拽的View
 * @param left 新的建议值
 * @param dx 水平位置的变化量
 * @return
 */
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
 Log.d(TAG, "clampViewPositionHorizontal:" + "旧的left坐标oldLeft:" + child.getLeft()
   + " 水平位置的变化量dx:" + dx + " 新的建议值left:" + left); 

 return super.clampViewPositionHorizontal(child, left, dx); //父类默认返回0
} 

该方法返回的是水平方向的移动建议值,该建议值等于当前的X坐标+水平方向的变化量,向右移动,偏移量为正值,向左移动则为负数.默认返回的是调用父类的重写的方法,查看源码可以发现默认返回的是0,如果建议值等于0的话,就表示水平方向不会移动.如果想要移动,我们需要返回它提供的建议值left,我们来看看运行的log:

tryCaptureView:当前被拖拽的view:android.widget.LinearLayout{23a3c537 V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:1 新的建议值left:1
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:12 新的建议值left:12
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:63 新的建议值left:63
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:53 新的建议值left:53
tryCaptureView:当前被拖拽的view:android.widget.LinearLayout{23a3c537 V.E..... ........ 0,0-720,1134 #7f0c0052 app:id/layout_main}
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:-5 新的建议值left:-5
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:-2 新的建议值left:-2
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:-6 新的建议值left:-6
clampViewPositionHorizontal:旧的left坐标oldLeft:0 水平位置的变化量dx:-11 新的建议值left:-11 

由上面的log可以看出,分别是向右拖拽和向左拖拽的结果,如果我们返回了它的建议值,就可以实现水平方向的拖动了.

将clampViewPositionHorizontal的返回值修改成return left;看看运行效果:

跟我们预想的结果一样,只有主体布局可以滑动,左侧的布局不能滑动,如果想要左侧布局也可以滑动,那么只需要在tryCaptureView直接返回true即可.效果如下:

同样的,如果要实现垂直方向的拖拽滚动,就需要重新下面这个方法了.

/**
 * 根据建议值修正将要移动到的纵向位置,此时没有发生真正的移动
 * @param child 当前被拖拽的View
 * @param top 新的建议值
 * @param dy 垂直位置的变化量
 * @return
 */
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
 Log.d(TAG, "clampViewPositionHorizontal:" + "旧的top坐标oldTop:" + child.getTop()
   + " 垂直位置的变化量dy:" + dy + " 新的建议值top:" + top);
 return top;
} 

效果如下,可以随意的滑动,实现起来是不是很简单啊

继续介绍剩下的回调方法

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

该回调方法和tryCaptureView一样都可以获取到当前被拖拽的View,不同点在于tryCaptureView是可以决定哪个View是可以被拖拽滑动的,而onViewCaptured只是用来获取当前到底哪个View被正在拖拽而已.

/**
 * 返回拖拽的范围,不对拖拽进行真正的限制,仅仅决定了动画执行的速度
 * @param child
 * @return 返回一个固定值
 */
@Override
public int getViewHorizontalDragRange(View child) {
 int range = super.getViewHorizontalDragRange(child);
 Log.d(TAG, "被拖拽的范围getViewHorizontalDragRange:" + range);
 return range;
} 

该回调方法是用于决定水平方向的动画执行速度,相对的垂直方向肯定也会有相应的方法,没错,就是下面这个:

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

那么话又说回来,我们有什么办法可以限制子View的滑动范围呢,如果范围不能很好的控制的话,那滑动也没有什么意义了.还记得上面介绍的clampViewPositionHorizontal和clampViewPositionVertical吗,分别用于设置水平方向和垂直方向的移动建议值,假设我们要限制该自定义控件的子View在水平方向移动的范围为0-屏幕宽度的0.6,那么如何控制呢.要实现这个限制,我们现在获取屏幕的宽度,由于当前的自定义控件是全屏显示的,所以直接就可以那控件的宽度来作为屏幕的宽度,那么如何获取呢?有2种方式,分别是在onMeasure和onSizeChange方法里面调用getMeasuredWidth()方法获取.前者是测量完之后获取,后者是当控件的宽高发生变化后获取,例如:

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w, h, oldw, oldh);
 // 当尺寸有变化的时候调用
 mHeight = getMeasuredHeight();
 mWidth = getMeasuredWidth();
 // 移动的范围
 mRange = (int) (mWidth * 0.6f);
} 

接下来,在clampViewPositionHorizontal方法内部做判断,如果当前的建议值left超过了mRange,那么返回mRange,如果小于了0,则返回0,这样一来子View的滑动范围就限定在0-mRange之间了,修改代码如下:

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
 Log.d(TAG, "clampViewPositionHorizontal 建议值left:" + left + " mRange:" + mRange);
 if (left > mRange) {
  left = mRange;
 } else if (left < 0) {
  left = 0;
 }
 return left;
} 

如果垂直方向也想限定的话,那就修改clampViewPositionVertical返回的建议值

@Override
public int clampViewPositionVertical(View child, int top, int dy) {
 Log.d(TAG, "clampViewPositionVertical 建议值top:" + top + " mRange:" + mRange);
 if (top > mRange) {
  top = mRange;
 } else if (top < 0) {
  top = 0;
 }
 return top;
} 

来看看运行的效果:

如此一来,我们就可以随意的控制子View的拖拽滑动的范围了.那么新的问题又来的,如果现在的需求是只能mMainContent被拖拽,mLeftContent不能被拖拽,也许你会说,这还不简单吗,直接在tryCaptureView判断当前拖拽的子View是mLeftContent的话就返回false不就得了,没错,如果需求只是这样的话确实可以满足了,但是如果在加上一个条件,那就是拖拽mLeftContent的时候的效果相当于把mMainContent拖拽了,什么意思呢,也就说现在mMainContent已经是打开的状态了,我想通过滑动mLeftContent就能把mMainContent滑动了.而mLeftContent还是原来的位置不动.这个要怎么实现呢?

首先可以肯定的是,tryCaptureView方法必须返回true,表示mMainContent和mLeftContent都可以被滑动,接下来要处理的就是如何在mLeftContent滑动的时候是滑动mMainContent的.那么现在就要介绍另一个回调方法了,如下所示:

/**
 * 当View的位置发生变化的时候,处理要做的事情(更新状态,伴随动画,重绘界面)
 * 此时,View已经发生了位置的改变
 *
 * @param changedView 改变位置的View
 * @param left  新的左边值
 * @param top   新的上边值
 * @param dx   水平方向的变化量
 * @param dy   垂直方向的变化量
 */
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
 Log.d(TAG, "位置发生变化onViewPositionChanged:" + "新的左边值left: " + left + " 水平方向的变化量dx:" + dx
   + " 新的上边值top:" + top + " 垂直方向的变化量dy:" + dy);
 super.onViewPositionChanged(changedView, left, top, dx, dy);
 // 为了兼容低版本, 每次修改值之后, 进行重绘
 invalidate();
} 

该方法是随着View的位置发生变化而不断回调的.四个参数如上面的注释所述,通过该方法可以拿到当前正在拖拽滑动的View是哪个View,有了这依据之后,我们就将在mLeftContent上的滑动的水平方向和垂直方向的变化量传递给mMainContent,这样一来,滑动mLeftContent的效果不就等于滑动mMainContent了吗.需要注意的是,该回调方法在低版本上为了兼容还需要加上invalidate();这句代码,invalidate是用来刷新界面的,他会导致界面的重绘.

滑动mMainContent来看看log

D/DragLayout: 位置发生变化onViewPositionChanged:新的左边值left: 15 水平方向的变化量dx:15 新的上边值top:8 垂直方向的变化量dy:8 
D/DragLayout: 位置发生变化onViewPositionChanged:新的左边值left: 32 水平方向的变化量dx:17 新的上边值top:16 垂直方向的变化量dy:8 
D/DragLayout: 位置发生变化onViewPositionChanged:新的左边值left: 121 水平方向的变化量dx:89 新的上边值top:46 垂直方向的变化量dy:30 
D/DragLayout: 位置发生变化onViewPositionChanged:新的左边值left: 145 水平方向的变化量dx:24 新的上边值t 
由log可以看出,最新的left和top值是等于上一次的位置+水平/垂直方向的变化量.这个特点有点类似建议值了.不同的是建议值发生了改变不代表View就一定已经处于滑动,除非返回的值也是建议值,但是onViewPositionChanged方法就不同了,这个方法只要一执行,就说明目标View是处于滑动状态的.

以水平方向滑动为例,垂直方向不移动,接下来就可以在onViewPositionChanged方法内做判断了,如下所示:

@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
 super.onViewPositionChanged(changedView, left, top, dx, dy);
 //获取最新的left坐标
 int newLeft = left;
 if (changedView == mLeftContent) {
  //如果滑动的是mLeftContent,则将其水平变化量dx传递给mMainContent,记录在newLeft中
  newLeft = mMainContent.getLeft() + dx;
 }
 //矫正范围
 if (newLeft > mRange) {
  newLeft = mRange;
 } else if (newLeft < 0) {
  newLeft = 0;
 }
 //再次判断,限制mLeftContent的滑动
 if (changedView == mLeftContent) {
  //强制将mLeftContent的位置摆会原来的位置,这里通过layout方法传入左,上,右,下坐标来实现
  //当然方法不限于这一种,例如还可以通过scrollTo(x,y)方法
  mLeftContent.layout(0, 0, mWidth, mHeight);
  //改变mMainContent的位置
  mMainContent.layout(newLeft, 0, newLeft + mWidth, mHeight);
 }
 // 为了兼容低版本, 每次修改值之后, 进行重绘
 invalidate();
} 

效果图:

由上面的效果图可以发现已经可以实现当手指向右滑动mLeftContent时,滑动的效果等于向右滑动mMainContent,当同时也会发现一个问题,那就是手指在mLeftContent向左滑动的时候并没有效果,这是因为我们限制了子View的滑动范围就是0-mRange,所以,如果滑动时小于0是没有效果的.那如果我们想要实现在mLeftContent当手指有向左滑动的趋势,或者手指在mMainContent有向左滑动的趋势时,就关闭mLeftContent,让mMainContent自动向左滑动到x=0的位置,反之就是打开mLeftContent,让mMainContent滑动到x=mRange的位置,这个要怎么实现呢?首先我们要能够想到的时,这个向左滑动的趋势肯定是与手指松手后相关的,那有没有一个回调方法是与手指触摸松开相关的呢?下面将介绍另一个回调方法,如下所示:

/**
 * 当被拖拽的View释放的时候回调,通常用于执行收尾的操作(例如执行动画)
 * @param releasedChild 被释放的View
 * @param xvel 水平方向的速度,向右释放为正值,向左为负值
 * @param yvel 垂直方向的速度,向下释放为正值,向上为负值
 */
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
 Log.d(TAG, "View被释放onViewReleased:" + "释放时水平速度xvel:" + xvel + " 释放时垂直速度yvel:" + yvel);
 super.onViewReleased(releasedChild, xvel, yvel);
} 

有了这个方法,我们就可以实现我们刚刚说到的效果了,首先我们来考虑下那些情况是和打开mLeftContent相关的,有2种情况:

1.当前水平方向的速度xvel=0,同时mMainContent的x位置是大于mRange/2.0f的.

2.当前水平方向的速度xvel>0

考虑了所有打开的情况,那么剩下的情况就是关闭mLeftContent.

具体逻辑如下:

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
 Log.d(TAG, "View被释放onViewReleased:" + "释放时水平速度xvel:" + xvel + " 释放时垂直速度yvel:" + yvel);
 super.onViewReleased(releasedChild, xvel, yvel);
 if (xvel > 0 || (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f)) {
  //打开mLeftContent,即mMainContent的x=mRange
  mMainContent.layout(mRange, 0, mRange + mWidth, mHeight);
 } else {
  //关闭mLeftContent,即mMainContent的x=0
  mMainContent.layout(0, 0, mWidth, mHeight);
 }
} 

效果图:

细心的话,可以发现上面的打开和关闭动画都是瞬间完成的,看起来效果不怎么好,如何实现平滑的打开和关闭呢?

通过ViewDragHelper的smoothSlideViewTo(View child, int finalLeft, int finalTop)方法可以实现平滑的滚动目标View到指定的left或者top坐标位置,接收3个参数,参数child表示要滑动的目标View,finalLeft和finalTop表示目标view最终平滑滑动到的位置.翻看源码,发现其实现原理是通过Scroller对象来实现的,也就说我们还需要重写computeScroll方法,不断的刷新当前界面,具体设置如下:

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
 Log.d(TAG, "View被释放onViewReleased:" + "释放时水平速度xvel:" + xvel + " 释放时垂直速度yvel:" + yvel);
 super.onViewReleased(releasedChild, xvel, yvel);
 if (xvel > 0 || (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f)) {
  //打开mLeftContent,即mMainContent的x=mRange
  if (dragHelper.smoothSlideViewTo(mMainContent, mRange, 0)) {
   // 返回true代表还没有移动到指定位置, 需要刷新界面.
   // 参数传this(child所在的ViewGroup)
   ViewCompat.postInvalidateOnAnimation(DragLayout.this);
  }
 } else {
  //关闭mLeftContent,即mMainContent的x=0
  if (dragHelper.smoothSlideViewTo(mMainContent, 0, 0)) {
   ViewCompat.postInvalidateOnAnimation(DragLayout.this);
  }
 }
@Override
public void computeScroll() {
 super.computeScroll();
 // 2. 持续平滑动画 (高频率调用)
 if(dragHelper.continueSettling(true)){
  // 如果返回true, 动画还需要继续执行
  ViewCompat.postInvalidateOnAnimation(this);
 }
} 

效果如下:

总结

以上所述是小编给大家介绍的Android ViewDragHelper使用介绍,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

时间: 2017-08-02

Android编程之控件可拖动的实现方法

本文实例讲述了Android编程之控件可拖动的实现方法.分享给大家供大家参考,具体如下: 点击和触摸的区别是什么? 点击: 一组动作的集合 手指按下着按钮 手指要在按钮停留一段时间 手指离开按钮 private static final String TAG = "DragViewActivity"; private ImageView iv_dv_view; private TextView tv_drag_view; private int startx; private int

Android ViewDragHelper完全解析 自定义ViewGroup神器

一.概述 在自定义ViewGroup中,很多效果都包含用户手指去拖动其内部的某个View(eg:侧滑菜单等),针对具体的需要去写好onInterceptTouchEvent和onTouchEvent这两个方法是一件很不容易的事,需要自己去处理:多手指的处理.加速度检测等等. 好在官方在v4的支持包中提供了ViewDragHelper这样一个类来帮助我们方便的编写自定义ViewGroup.简单看一下它的注释: ViewDragHelper is a utility class for writin

Android ViewDragHelper仿淘宝拖动加载效果

拖动加载是我在淘宝的商品详情界面发现的,感觉很实用.于是就分析它的实现方式,感觉用ViewDragHelper可以很方便的实现这种效果.下面大致把我的思路分步骤写一下.先上图吧. 首先建工程什么的我就不多说了.咱从ViewDragHelper的实现开始说吧,ViewDragHelper一般用在一个自定义ViewGroup的内部,可以对其子View进行移动操作. 创建自定义ViewGroup: package com.maxi.viewdraghelpertest.widget; import a

Android 仿淘宝、京东商品详情页向上拖动查看图文详情控件DEMO详解

一.淘宝商品详情页效果 我们的效果 二.实现思路 使用两个scrollView,两个scrollView 竖直排列,通过自定义viewGroup来控制两个scrollView的竖直排列,以及滑动事件的处理.如下图 三.具体实现 1.继承viewGroup自定义布局View 重写onMeasure()和onLayout方法,在onLayout方法中完成对两个子ScrollView的竖直排列布局,代码如下: 布局文件: <RelativeLayout xmlns:android="http:/

Android 可拖动的seekbar自定义进度值

最近接了个项目其中有需要要实现此功能:seekbar需要显示最左和最右值,进度要跟随进度块移动.下面通过此图给大家展示下效果,可能比文字描述要更清晰. 其实实现起来很简单,主要是思路.自定义控件的话也不难,之前我的博客也有专门介绍,这里就不再多说. 实现方案 这里是通过继承seekbar来自定义控件,这样的方式最快.主要难点在于进度的显示,其实我很的是最笨的方法,就是用了一个popwindow显示在进度条的上方,然后在移动滑块的时候实时的改变它显示的横坐标.看进度显示的核心代码: private

Android基于ViewDragHelper仿QQ5.0侧滑界面效果

QQ5.0侧滑效果实现方案有很多方式,今天我们使用ViewDragHelper来实现一下. 先上效果图: ①自定义控件SlidingMenu继承FrameLayout,放在FrameLayout上面的布局一层叠着者一层,通过getChildAt()可以很方便的获取到任意一层,进而控制此布局的变化. public class SlidingMenu extends FrameLayout { private ViewDragHelper mViewDragHelper; private int m

Android 中通过ViewDragHelper实现ListView的Item的侧拉划出效果

先来看看,今天要实现的自定义控件效果图: 关于ViewDragHelper的使用,大家可以先看这篇文章ViewDragHelper的使用介绍 实现该自定义控件的大体步骤如下: 1.ViewDragHelper使用的3部曲,初始化ViewDragHelper,传递触摸事件,实现ViewDragHelper.Callback抽象类. 2.需要创建2个直接的子View,分别是前景View和背景View,代表ListView每一项Item的布局的组成,如下所示: 未划出时显示的FrontView: 划出

android 添加随意拖动的桌面悬浮窗口

用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断当前显示的是为桌面.这个内容我在前面的帖子里面已经有过介绍,如果还没看过的赶快稳步看一下哦. 2.使用windowManager往最顶层添加一个View .这个知识点就是为本文主要讲解的内容哦.在本文的讲解中,我们还会讲到下面的知识点: a.如果获取到状态栏的高度 b.悬浮窗口的拖动 c.悬浮窗口的点击事件 有开始之前,我们先来看一下效果图:  接下来我们来

Android使用ViewDragHelper实现QQ6.X最新版本侧滑界面效果实例代码

(一).前言: 这两天QQ进行了重大更新(6.X)尤其在UI风格上面由之前的蓝色换成了白色居多了,侧滑效果也发生了一些变化,那我们今天来模仿实现一个QQ6.X版本的侧滑界面效果.今天我们还是采用神器ViewDragHelper来实现. 本次实例具体代码已经上传到下面的项目中,欢迎各位去star和fork一下. https://github.com/jiangqqlmj/DragHelper4QQ FastDev4Android框架项目地址:https://github.com/jiangqqlm

Android使用ViewDragHelper实现仿QQ6.0侧滑界面(一)

QQ是大家离不开的聊天工具,方便既实用,自从qq更新至6.0之后,侧滑由原来的划出后主面板缩小变成了左右平滑,在外观上有了很大的提升,于是我就是尝试理解下里面的各种逻辑,结合相关资料,研究研究. 知道这里面的一个主要类是ViewDragHelper,那么首先我们要先来了解一下这个ViewDragHelper类,正所谓打蛇打七寸,我们就先来看看官方文档怎么介绍的,有什么奇特的功能. 首先继承: java.lang.Object android.support.v4.widget.ViewDragH

Android滑动优化高仿QQ6.0侧滑菜单(滑动优化)

 推荐阅读:Android使用ViewDragHelper实现仿QQ6.0侧滑界面(一) 但是之前的实现,只是简单的可以显示和隐藏左侧的菜单,但是特别生硬,而且没有任何平滑的趋势,那么今天就来优化一下吧,加上平滑效果,而且可以根据手势滑动的方向来判断是否是显示和隐藏. 首先先来实现手势判断是否隐藏和显示 这里就要用到了一个方法了,如下: 这个是ViewDradHelper里面的方法: /** * 当view被释放的时候处理的事情(松手) * * @param releasedChild 被释放的

Android高仿QQ6.0侧滑删除实例代码

推荐阅读: 先给大家分享一下,侧滑删除,布局也就是前面一个item,然后有两个隐藏的按钮(TextView也可以),然后我们可以向左侧滑动,然后显示出来,然后对delete(删除键)实现监听,就可以了哈.好了那就来看看代码怎么实现的吧. 首先和之前一样 自定义View,初始化ViewDragHelper: package com.example.removesidepull; import android.content.Context; import android.support.v4.wi

Android使用DrawerLayout仿QQ6.0双侧滑菜单

本文实例为大家分享了Android使用DrawerLayout仿QQ6.0双侧滑菜单的具体代码,供大家参考,具体内容如下 上面是效果图. 相关实现的源码和文章网上已经很多了,比较流行的做法分别是使用 FrameLayout, HorizontalScrollView或者是DrawerLayout 其实要实现QQ 6.X版本侧滑效果最好的方案是使用HorizontalScrollView,因为左侧菜单的透视效果在DrawerLayout下无法实现,或者说实现起来很麻烦,所以在这里我们还是先介绍使用

Android使用DrawerLayout实现仿QQ双向侧滑菜单

1.概述 之前写了一个Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭 ,恰逢QQ5.2又加了一个右侧菜单,刚好看了下DrawerLayout,一方面官方的东西,我都比较感兴趣:另一方面,这玩意用起来的确方便,于是简单写了个demo,高仿QQ5.2双向侧滑,分享给大家. 首先看看效果图: DrawerLayout用起来真的很方便,下面一起看看用法~ 2.DrawerLayout的使用 直接将DrawerLayout作为根布局,然后其内部第一个View为内容区域,第二个View为左侧

Android仿QQ6.0主页面侧滑效果

1.概述 最近一直都在带实习生做项目,发现自己好久没有写博客了,这几天更新会比较频繁,今天玩QQ的时候发现QQ主页菜单滑动效果早就变了,实在忍不住晚上就来实现一下了! 2.实现 2.1. 实现的方式多种多样 2.1.1 自定义ViewGroup ,处理其onTouch事件 2.1.2 FrameLayout + 手势处理类GestureDetector 2.2.3 使用Google自带的DrawerLayout 对其进行修改 2.2.4 继承自水平滚动HorizontalScrollView 大

基于Android实现仿QQ5.0侧滑

本课程将带领大家通过自定义控件实现QQ5.0侧滑菜单,课程将循序渐进,首先实现最普通的侧滑菜单,然后引入属性动画与拖动菜单效果相结合,最终实现QQ5.0侧滑菜单效果.通过本课程大家会对侧滑菜单有更深层次的了解,通过自定义控件和属性动画打造千变万化的侧滑菜单效果 效果图如下所示: package com.example; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android

Android仿新浪微博oauth2.0授权界面实现代码(2)

oauth2.0授权界面,大致流程图: 前提准备: 在新浪开放平台申请appkey和appsecret:http://open.weibo.com/. 熟悉oauth2.0协议,相关知识:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html OAuth2的access_token接口:http://open.weibo.com/wiki/OAuth2/access_token 代码详解 大致思路如下:建立一个webview加载授权界面,授权回