Android ViewDragHelper仿淘宝拖动加载效果

拖动加载是我在淘宝的商品详情界面发现的,感觉很实用。于是就分析它的实现方式,感觉用ViewDragHelper可以很方便的实现这种效果。下面大致把我的思路分步骤写一下。先上图吧。

首先建工程什么的我就不多说了。咱从ViewDragHelper的实现开始说吧,ViewDragHelper一般用在一个自定义ViewGroup的内部,可以对其子View进行移动操作。

创建自定义ViewGroup:

package com.maxi.viewdraghelpertest.widget; 

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

public class DragHelperLayout extends LinearLayout{
  private ViewDragHelper mDragHelper;
  @SuppressWarnings("static-access")
  public DragHelperLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    // TODO Auto-generated constructor stub
    /*
     * 创建带回调接口的ViewDragHelper
     */
    mDragHelper = ViewDragHelper.create(this, 10.0f,new DragHelperCallback());// 参数一:该类生成的对象(当前的ViewGroup)
                      // 参数二:敏感度(越大越敏感)
  }
  class DragHelperCallback extends ViewDragHelper.Callback { 

    @Override
    public boolean tryCaptureView(View arg0, int arg1) {
      // TODO Auto-generated method stub
      return false;
    } 

  }
} 

然后将触摸事件传递给ViewDragHelper:

@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
  return mDragHelper.shouldInterceptTouchEvent(event);//是否应该打断MotionEvent的传递
} 

@Override
public boolean onTouchEvent(MotionEvent event)
{
  mDragHelper.processTouchEvent(event);
  return true;
} 

接着我们开始实现DragHelperCallback,这个ViewDragHelper.Callback回调中可以对ViewGroup中的一些View进行操作,在此我们只对本项目涉及到的相关用法做解析,详细点请自行查阅资料。

class DragHelperCallback extends ViewDragHelper.Callback { 

    @Override
    public boolean tryCaptureView(View arg0, int arg1) {
      // TODO Auto-generated method stub
      return true;  //返回true表示可以捕捉ViewGroup中的View
    }
    /*
     * (non-Javadoc)
     * @see android.support.v4.widget.ViewDragHelper.Callback#clampViewPositionVertical(android.view.View, int, int)
     * 限定View竖直方向上的活动区域,防止滑出ViewGroup
     */
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
       int topBound = getPaddingTop();
       int bottomBound = getHeight() - child.getHeight() - topBound;
       int newHeight = Math.min(Math.max(top, topBound), bottomBound);
       return newHeight;
    } 

  }

在上面的代码段中我已经做了注释,在clampViewPositionVertical中我们对View的竖直方向活动区域做了限制,防止滑出ViewGroup,当然你可以直接return top;不过为了效果我先这么限定一下。还有一个clampViewPositionHorizontal方法,同样是对其水平边界进行控制的,先不多说啦。这个时候咱们自定义的ViewGroup初期已经完成,先去试试水。

在activity_main.xml中加入

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.maxi.viewdraghelpertest.MainActivity" > 

  <com.maxi.viewdraghelpertest.widget.DragHelperLayout
    android:id="@+id/dhl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/darker_gray"
    >
  <TextView
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="@android:color/holo_blue_bright"
  />
  <TextView
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:background="@android:color/holo_orange_dark"
  />
  </com.maxi.viewdraghelpertest.widget.DragHelperLayout> 

</RelativeLayout> 

运行后的效果:

大家是不是都急了,做个拖动加载怎么搞起这东西了,不要急,这才刚刚开始,大家想想拖动加载是不是就是两个View在同一个ViewGroup里通过ViewDragHelper的滑动操作然后实现的?是不是有思路的?没有思路也没关系,咱慢慢来,想要两个View相关联,就是拖动一个View然后另一个View跟着它走该怎么实现呢?首先我们需要ViewDragHelper回调里的另一个方法onViewPositionChanged,该方法是在View位置发生改变时回调的。为的就是在上面的View上拉的时候让下面的View跟着往上走。来看看我们的实现方法:
首先先将两个View初始化:

private View t1, t2;
/*
 * (non-Javadoc)
 * @see android.view.View#onFinishInflate()
 * 初始化两个View
 */
@Override
protected void onFinishInflate() {
  t1 = getChildAt(0);
  t2 = getChildAt(1);
}

得到两个View后我们在回调中判断哪个位置发生了改变,

@Override
public void onViewPositionChanged(View changedView, int left, int top,
    int dx, int dy) {
  // TODO Auto-generated method stub
  int childIndex = 1;
  if (changedView == t2) {
    childIndex = 2;
  }
  viewFollowChanged(childIndex, top);
}

上面的代码段中有个方法viewFollowChanged,主要实现的就是View跟着动。

private void viewFollowChanged(int viewIndex, int posTop) {
  viewH = t1.getMeasuredHeight();
  if (viewIndex == 1) {
    int offsetTopBottom = viewH + t1.getTop() - t2.getTop();
    t2.offsetTopAndBottom(offsetTopBottom);
  } else if (viewIndex == 2) {
    int offsetTopBottom = t2.getTop() - viewH - t1.getTop();
    t1.offsetTopAndBottom(offsetTopBottom);
  }
  invalidate();
} 

在运行是不是发现没有被点击拖动的View会跟着View一起移动,像一个整体双宿双飞。图我就不加了,大家运行看吧。因为我们要获取View的实际大小所以需要以下代码段的支持:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  measureChildren(widthMeasureSpec, heightMeasureSpec); 

  int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
  int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
  setMeasuredDimension(
      resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
      resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
} 

public static int resolveSizeAndState(int size, int measureSpec,
    int childMeasuredState) {
  int result = size;
  int specMode = MeasureSpec.getMode(measureSpec);
  int specSize = MeasureSpec.getSize(measureSpec);
  switch (specMode) {
  case MeasureSpec.UNSPECIFIED:
    result = size;
    break;
  case MeasureSpec.AT_MOST:
    if (specSize < size) {
      result = specSize | MEASURED_STATE_TOO_SMALL;
    } else {
      result = size;
    }
    break;
  case MeasureSpec.EXACTLY:
    result = specSize;
    break;
  }
  return result | (childMeasuredState & MEASURED_STATE_MASK);
} 

然后我们可以尝试将两个View满屏,android:layout_height="match_parent",把clampViewPositionVertical方法里限制的边界去掉吧,暂时先return top;这样试一下是不是有点像拖动加载了,呵呵哒,可是第一个View下拉的时候由于上面没有View怎么办?我们可以在clampViewPositionVertical中将它限定边界啊!

@Override
    public int clampViewPositionVertical(View child, int top, int dy) {
      int slideTop = top;
      if (child == t1) {
        if (top > 0) {
          slideTop = 0;
        }
      } else if (child == t2) {
        if (top < 0) {
          slideTop = 0;
        }
      }
      return child.getTop() + (slideTop - child.getTop());
    }

已经大致成型了,然后就是拖动的时候将View自动置顶或置底,因为自动置顶或置底是在滑动松开之后,所以就需要用到ViewDragHelper回调里的onViewReleased方法,该方法就是在滑动松开之后调用,接下来实现它:

@Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
      putStickOrDown(releasedChild);// 滑动松开后,需要置顶或置底
    }
    private void putStickOrDown(View releasedChild, float yvel) {
    int finalTop = 0; // 默认是粘到最顶端
    if (releasedChild == t1) {
      // 滑动第一个view松开
      if (yvel < 0)//灵敏度自己调吧
        finalTop = -viewH;
    } else {
      // 滑动第二个view松开
      if (yvel > 0)//同上
        finalTop = viewH;
    }
    if (mDragHelper.smoothSlideViewTo(releasedChild, 0, finalTop)) {
      ViewCompat.postInvalidateOnAnimation(this);// 会在下一个Frame开始的时候,发起一些invalidate操作
    }
    } 

    @Override
    public void computeScroll() {
      if (mDragHelper.continueSettling(true)) {
        ViewCompat.postInvalidateOnAnimation(this);
      }
    }

ok,是可以自动置顶或置底了。对了,那种拖动粘滞效果可以设置clampViewPositionVertical里的返回值,return child.getTop() + (finalTop - child.getTop()) / num;num值越大越粘滞。

然后淘宝第一个View是可以滑动的滑动到最底部然后才把手势事件交给ViewDragHelper处理的。这块试想如果用ScrollView的话,手势事件肯定会优先被它消费,这样肯定达不到我们想要的效果,所以在此我们需要对ScrollView进行自定义,大致的实现思路是当用户用户从触发屏幕开始判断是不是ScrollView在最底端,如果在最底端然后判断手势是否是向上滑动的如果也是则满足条件将touch事件交给父View就可以了,即requestDisallowInterceptTouchEvent该方法。然后自定义的ViewGroup中的onInterceptTouchEvent方法也要做相应修改,这里用GestureDetectorCompat处理事件,其回调用来判断是否是上下滑动。先声明private GestureDetectorCompat gestureDC;然后再gestureDC = new GestureDetectorCompat(context,new YSlideDetector());

class YSlideDetector extends SimpleOnGestureListener { 

  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2,
      float distanceX, float distanceY) {
    // TODO Auto-generated method stub
    return Math.abs(distanceY) > Math.abs(distanceX);//Y方向绝对值大于X方向,上下滑动
  }
}<pre name="code" class="java">  @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
  boolean is_y_slide = gestureDC.onTouchEvent(event);
  boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(event);
  int action = event.getActionMasked();
  if (action == MotionEvent.ACTION_DOWN) {
    mDragHelper.processTouchEvent(event);// action_down时就让mDragHelper开始工作,否则有时候导致异常
  }
  return shouldIntercept && is_y_slide;
}

OK。就这样。是不是达到了想要的效果了?

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android ViewDragHelper使用介绍

    ViewDragHelper是support.v4下提供的用于处理拖拽滑动的辅助类,查看Android的DrawerLayout源码,可以发现,它内部就是使用了该辅助类来处理滑动事件的. public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setDescendantFocusability(ViewGroup.FOCUS_AFTER_DE

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

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

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

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

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

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

  • 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实现ListView的Item的侧拉划出效果

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

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

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

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

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

  • 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仿QQ5.0侧滑界面效果

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

随机推荐