ViewDragHelper实现QQ侧滑效果

前言

侧滑的实现方式有很多方式来实现,这次总结的ViewDragHelper就是其中一种方式,ViewDragHelper是2013年谷歌I/O大会发布的新的控件,为了解决界面控件拖拽问题。下面就是自己学习写的一个实现类似于QQ侧滑效果的实现。
activity_main.xml:

<com.yctc.drag.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:id="@+id/dl"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@drawable/bg"
 tools:context=".MainActivity" >

 <LinearLayout
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:paddingBottom="50dp"
  android:paddingLeft="10dp"
  android:paddingRight="50dp"
  android:paddingTop="50dp" >

  <ImageView
   android:layout_width="50dp"
   android:layout_height="50dp"
   android:src="@drawable/head" />

  <ListView
   android:id="@+id/lv_left"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
  </ListView>
 </LinearLayout>

 <com.yctc.drag.MyLinearLayout
  android:id="@+id/mll"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#ffffff"
  android:orientation="vertical" >

  <RelativeLayout
   android:layout_width="match_parent"
   android:layout_height="50dp"
   android:background="#18B6EF"
   android:gravity="center_vertical" >

   <ImageView
    android:id="@+id/iv_header"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:layout_marginLeft="15dp"
    android:src="@drawable/head" />

   <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:text="Header" />
  </RelativeLayout>

  <ListView
   android:id="@+id/lv_main"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
  </ListView>
 </com.yctc.drag.MyLinearLayout>
</com.yctc.drag.DragLayout>

DragLayout.Java:

public class DragLayout extends FrameLayout {

 private static final String TAG = "TAG";
 private ViewDragHelper mDragHelper;
 private ViewGroup mLeftContent;
 private ViewGroup mMainContent;
 private OnDragStatusChangeListener mListener;
 private Status mStatus = Status.Close;

 /**
  * 状态枚举
  */
 public static enum Status {
  Close, Open, Draging;
 }
 public interface OnDragStatusChangeListener{
  void onClose();
  void onOpen();
  void onDraging(float percent);
 }

 public Status getStatus() {
  return mStatus;
 }

 public void setStatus(Status mStatus) {
  this.mStatus = mStatus;
 }

 public void setDragStatusListener(OnDragStatusChangeListener mListener){
  this.mListener = mListener;
 }

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

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

 public DragLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);

  // a.初始化 (通过静态方法)
  mDragHelper = ViewDragHelper.create(this , mCallback);

 }

 ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
  // c. 重写事件

  // 1. 根据返回结果决定当前child是否可以拖拽
  // child 当前被拖拽的View
  // pointerId 区分多点触摸的id
  @Override
  public boolean tryCaptureView(View child, int pointerId) {
   Log.d(TAG, "tryCaptureView: " + child);
   return true;
  };

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

  @Override
  public int getViewHorizontalDragRange(View child) {
   // 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
   return mRange;
  }

  // 2. 根据建议值 修正将要移动到的(横向)位置 (重要)
  // 此时没有发生真正的移动
  public int clampViewPositionHorizontal(View child, int left, int dx) {
   // child: 当前拖拽的View
   // left 新的位置的建议值, dx 位置变化量
   // left = oldLeft + dx;
   Log.d(TAG, "clampViewPositionHorizontal: "
     + "oldLeft: " + child.getLeft() + " dx: " + dx + " left: " +left);

   if(child == mMainContent){
    left = fixLeft(left);
   }
   return left;
  }

  // 3. 当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
  // 此时,View已经发生了位置的改变
  @Override
  public void onViewPositionChanged(View changedView, int left, int top,
    int dx, int dy) {
   // changedView 改变位置的View
   // left 新的左边值
   // dx 水平方向变化量
   super.onViewPositionChanged(changedView, left, top, dx, dy);
   Log.d(TAG, "onViewPositionChanged: " + "left: " + left + " dx: " + dx);

   int newLeft = left;
   if(changedView == mLeftContent){
    // 把当前变化量传递给mMainContent
    newLeft = mMainContent.getLeft() + dx;
   }

   // 进行修正
   newLeft = fixLeft(newLeft);

   if(changedView == mLeftContent) {
    // 当左面板移动之后, 再强制放回去.
    mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
    mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
   }
   // 更新状态,执行动画
   dispatchDragEvent(newLeft);

   // 为了兼容低版本, 每次修改值之后, 进行重绘
   invalidate();
  }

  // 4. 当View被释放的时候, 处理的事情(执行动画)
  @Override
  public void onViewReleased(View releasedChild, float xvel, float yvel) {
   // View releasedChild 被释放的子View
   // float xvel 水平方向的速度, 向右为+
   // float yvel 竖直方向的速度, 向下为+
   Log.d(TAG, "onViewReleased: " + "xvel: " + xvel + " yvel: " + yvel);
   super.onViewReleased(releasedChild, xvel, yvel);

   // 判断执行 关闭/开启
   // 先考虑所有开启的情况,剩下的就都是关闭的情况
   if(xvel == 0 && mMainContent.getLeft() > mRange / 2.0f){
    open();
   }else if (xvel > 0) {
    open();
   }else {
    close();
   }

  }

  @Override
  public void onViewDragStateChanged(int state) {
   // TODO Auto-generated method stub
   super.onViewDragStateChanged(state);
  }

 };

 /**
  * 根据范围修正左边值
  * @param left
  * @return
  */
 private int fixLeft(int left) {
  if(left < 0){
   return 0;
  }else if (left > mRange) {
   return mRange;
  }
  return left;
 }

 protected void dispatchDragEvent(int newLeft) {
  float percent = newLeft * 1.0f/ mRange;
  //0.0f -> 1.0f
  Log.d(TAG, "percent: " + percent);

  if(mListener != null){
   mListener.onDraging(percent);
  }

  // 更新状态, 执行回调
  Status preStatus = mStatus;
  mStatus = updateStatus(percent);
  if(mStatus != preStatus){
   // 状态发生变化
   if(mStatus == Status.Close){
    // 当前变为关闭状态
    if(mListener != null){
     mListener.onClose();
    }
   }else if (mStatus == Status.Open) {
    if(mListener != null){
     mListener.onOpen();
    }
   }
  }

//  * 伴随动画:
  animViews(percent);

 }

 private Status updateStatus(float percent) {
  if(percent == 0f){
   return Status.Close;
  }else if (percent == 1.0f) {
   return Status.Open;
  }
  return Status.Draging;
 }

 private void animViews(float percent) {
  //  > 1. 左面板: 缩放动画, 平移动画, 透明度动画
     // 缩放动画 0.0 -> 1.0 >>> 0.5f -> 1.0f >>> 0.5f * percent + 0.5f
   //  mLeftContent.setScaleX(0.5f + 0.5f * percent);
   //  mLeftContent.setScaleY(0.5f + 0.5f * percent);
     ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
     ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
     // 平移动画: -mWidth / 2.0f -> 0.0f
     ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
     // 透明度: 0.5 -> 1.0f
     ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));

  //  > 2. 主面板: 缩放动画
     // 1.0f -> 0.8f
     ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
     ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));

  //  > 3. 背景动画: 亮度变化 (颜色变化)
     getBackground().setColorFilter((Integer)evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), Mode.SRC_OVER);
 }

 /**
  * 估值器
  * @param fraction
  * @param startValue
  * @param endValue
  * @return
  */
 public Float evaluate(float fraction, Number startValue, Number endValue) {
  float startFloat = startValue.floatValue();
  return startFloat + fraction * (endValue.floatValue() - startFloat);
 }
 /**
  * 颜色变化过度
  * @param fraction
  * @param startValue
  * @param endValue
  * @return
  */
 public Object evaluateColor(float fraction, Object startValue, Object endValue) {
  int startInt = (Integer) startValue;
  int startA = (startInt >> 24) & 0xff;
  int startR = (startInt >> 16) & 0xff;
  int startG = (startInt >> 8) & 0xff;
  int startB = startInt & 0xff;

  int endInt = (Integer) endValue;
  int endA = (endInt >> 24) & 0xff;
  int endR = (endInt >> 16) & 0xff;
  int endG = (endInt >> 8) & 0xff;
  int endB = endInt & 0xff;

  return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
    (int)((startR + (int)(fraction * (endR - startR))) << 16) |
    (int)((startG + (int)(fraction * (endG - startG))) << 8) |
    (int)((startB + (int)(fraction * (endB - startB))));
 }

 @Override
 public void computeScroll() {
  super.computeScroll();

  // 2. 持续平滑动画 (高频率调用)
  if(mDragHelper.continueSettling(true)){
   // 如果返回true, 动画还需要继续执行
   ViewCompat.postInvalidateOnAnimation(this);
  }
 }

 public void close(){
  close(true);
 }
 /**
  * 关闭
  */
 public void close(boolean isSmooth) {
  int finalLeft = 0;
  if(isSmooth){
   // 1. 触发一个平滑动画
   if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
    // 返回true代表还没有移动到指定位置, 需要刷新界面.
    // 参数传this(child所在的ViewGroup)
    ViewCompat.postInvalidateOnAnimation(this);
   }
  }else {
   mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
  }
 }

 public void open(){
  open(true);
 }
 /**
  * 开启
  */
 public void open(boolean isSmooth) {
  int finalLeft = mRange;
  if(isSmooth){
   // 1. 触发一个平滑动画
   if(mDragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){
    // 返回true代表还没有移动到指定位置, 需要刷新界面.
    // 参数传this(child所在的ViewGroup)
    ViewCompat.postInvalidateOnAnimation(this);
   }
  }else {
   mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);
  }
 }

 private int mHeight;
 private int mWidth;
 private int mRange;

 // b.传递触摸事件
 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  // 传递给mDragHelper
  return mDragHelper.shouldInterceptTouchEvent(ev);
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  try {
   mDragHelper.processTouchEvent(event);
  } catch (Exception e) {
   e.printStackTrace();
  }
  // 返回true, 持续接受事件
  return true;
 }

 @Override
 protected void onFinishInflate() {
  super.onFinishInflate();
  // Github
  // 写注释
  // 容错性检查 (至少有俩子View, 子View必须是ViewGroup的子类)

  if(getChildCount() < 2){
   throw new IllegalStateException("布局至少有俩孩子. Your ViewGroup must have 2 children at least.");
  }
  if(!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)){
   throw new IllegalArgumentException("子View必须是ViewGroup的子类. Your children must be an instance of ViewGroup");
  }

  mLeftContent = (ViewGroup) getChildAt(0);
  mMainContent = (ViewGroup) getChildAt(1);
 }

 @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);

 }

}

MyLineatLayout.java:

public class MyLinearLayout extends LinearLayout {

 private DragLayout mDragLayout;

 public MyLinearLayout(Context context) {
  super(context);
 }

 public MyLinearLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public void setDraglayout(DragLayout mDragLayout){
  this.mDragLayout = mDragLayout;
 }

 @Override
 public boolean onInterceptTouchEvent(MotionEvent ev) {
  // 如果当前是关闭状态, 按之前方法判断
  if(mDragLayout.getStatus() == Status.Close){
   return super.onInterceptTouchEvent(ev);
  }else {
   return true;
  }
 }

 @Override
 public boolean onTouchEvent(MotionEvent event) {
  // 如果当前是关闭状态, 按之前方法处理
  if(mDragLayout.getStatus() == Status.Close){
   return super.onTouchEvent(event);
  }else {
   // 手指抬起, 执行关闭操作
   if(event.getAction() == MotionEvent.ACTION_UP){
    mDragLayout.close();
   }

   return true;
  }
 }

}

MainActivity.java:

public class MainActivity extends Activity {

 private static final String TAG = "TAG";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  requestWindowFeature(Window.FEATURE_NO_TITLE);
  setContentView(R.layout.activity_main);

  final ListView mLeftList = (ListView) findViewById(R.id.lv_left);
  final ListView mMainList = (ListView) findViewById(R.id.lv_main);
  final ImageView mHeaderImage = (ImageView) findViewById(R.id.iv_header);
  MyLinearLayout mLinearLayout = (MyLinearLayout) findViewById(R.id.mll);

  // 查找Draglayout, 设置监听
  DragLayout mDragLayout = (DragLayout) findViewById(R.id.dl);

  // 设置引用
  mLinearLayout.setDraglayout(mDragLayout);

  mDragLayout.setDragStatusListener(new OnDragStatusChangeListener() {

   @Override
   public void onOpen() {
    Utils.showToast(MainActivity.this, "onOpen");
    // 左面板ListView随机设置一个条目
    Random random = new Random();

    int nextInt = random.nextInt(50);
    mLeftList.smoothScrollToPosition(nextInt);

   }

   @Override
   public void onDraging(float percent) {
    Log.d(TAG, "onDraging: " + percent);// 0 -> 1
    // 更新图标的透明度
    // 1.0 -> 0.0
    ViewHelper.setAlpha(mHeaderImage, 1 - percent);
   }

   @Override
   public void onClose() {
    Utils.showToast(MainActivity.this, "onClose");
    // 让图标晃动
//    mHeaderImage.setTranslationX(translationX)
    ObjectAnimator mAnim = ObjectAnimator.ofFloat(mHeaderImage, "translationX", 15.0f);
    mAnim.setInterpolator(new CycleInterpolator(4));
    mAnim.setDuration(500);
    mAnim.start();
   }
  });

  mLeftList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings){
   @Override
   public View getView(int position, View convertView, ViewGroup parent) {
    View view = super.getView(position, convertView, parent);
    TextView mText = ((TextView)view);
    mText.setTextColor(Color.WHITE);
    return view;
   }
  });

  mMainList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, Cheeses.NAMES))
 }
}

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

(0)

相关推荐

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

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

  • 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

  • ViewDragHelper实现QQ侧滑效果

    前言 侧滑的实现方式有很多方式来实现,这次总结的ViewDragHelper就是其中一种方式,ViewDragHelper是2013年谷歌I/O大会发布的新的控件,为了解决界面控件拖拽问题.下面就是自己学习写的一个实现类似于QQ侧滑效果的实现. activity_main.xml: <com.yctc.drag.DragLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools=&qu

  • Android_UI 仿QQ侧滑菜单效果的实现

    相信大家对QQ侧滑菜单的效果已经不陌生了吧,侧滑进入个人头像一侧,进行对头像的更改,我的收藏,QQ钱包,我的文件等一系列的操作,今天呢,主要是实现进入侧滑菜单的这一效果原理进行分析. 主要思路分析 1.首先写一个SlideMenu 继承一个帧布局FrameLayout ,因为如果继承自ViewGroup的话,需要我们自己来实现onMeasure方法,而该方法的实现一般比较麻烦且没有必要,所以选择继承系统的已有的控件FrameLayout,不用其他控件是因为FrameLayout最轻量级 2.在布

  • Android自定义view系列之99.99%实现QQ侧滑删除效果实例代码详解

    首先声明本文是基于GitHub上"baoyongzhang"的SwipeMenuListView修改而来,该项目地址: https://github.com/baoyongzhang/SwipeMenuListView 可以说这个侧滑删除效果是我见过效果最好且比较灵活的项目,没有之一!!! 但是在使用它之前需要给大家提两点注意事项: 1,该项目支持Gradle dependence,但是目前作者提供的依赖地址对应的项目不是最新的项目,依赖过后的代码与demo中使用的不一致,会提示没有B

  • Android程序开发之使用Design包实现QQ动画侧滑效果和滑动菜单导航

    Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的Android Design Support Library,在这个support库里面,Google给我们提供了更加规范的MD设计风格的控件.最重要的是,Android Design Support Library的兼容性更广,直接可以向下兼容到Android 2.2.这不得不说是一个良心之作. 使用方法很简单,只需要添加一句依赖 compile 'com.android

  • Android使用ViewDragHelper实现QQ聊天气泡拖动效果

    QQ聊天气泡拖动效果Android实现代码,供大家参考,具体内容如下 概述 本文的目的是实现类似于QQ消息提醒的气泡的拖拽效果.网上已有大神的实现效果是通过监听控件的OnTouchEvent事件的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件来处理相应的拖拽效果,这里采用ViewDragHelper的方式去实现拖拽,顺便学习了一下ViewDragHelper的使用方式,拖拽时的粘连效果采用贝塞尔曲线来实现. 用ViewDragHelper实现拖拽效果 ViewDragHe

  • Android实现QQ侧滑菜单效果

    QQ侧滑菜单的Android实现代码,供大家参考,具体内容如下 实现逻辑 1.先写出菜单页面和主页面的布局 2.创建一个类,继承RelativeLayout,实现里面的onLayout 3.在主布局文件中添加子空间 4.在onLayout里面获取子控件的宽和高,并对子控件的位置进行绘制 5.给子布局设置滑动事件,分别在手指落下\移动\抬起的时候,获取手指的位置 6.在手指移动的过程中,对菜单页面的移动距离进行限制,防止菜单页面跑出指定的页面 7.在手指抬起的时候,判定一下手指移动的距离,如果移动

  • Android中自定义view实现侧滑效果

    效果图: 看网上的都是两个view拼接,默认右侧的不显示,水平移动的时候把右侧的view显示出来.但是看最新版QQ上的效果不是这样的,但给人的感觉却很好,所以献丑来一发比较高仿的. 知识点: 1.ViewDragHelper 的用法: 2.滑动冲突的解决: 3.自定义viewgroup. ViewDragHelper 出来已经比较久了 相信大家都比较熟悉,不熟悉的话google一大把这里主要简单用一下它的几个方法 1.tryCaptureView(View child, int pointerI

  • iOS中仿QQ侧滑菜单功能

    UITabBarController做QQ侧滑菜单效果: 首先要了解UITabBarController的层级结构: UITabBarController加载的其它UIViewController的View都是被添加在UITransitionView上(这是一个私有API),UITransitionView在self.view的0层,UITabBar在的第一层. 所以我的思路是这样的: UITransitionView与UITabBar转移到一个新的View1上去,作为滑动的部分: 在View1

  • Android自定义View 仿QQ侧滑菜单的实现代码

    先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

  • Android使用Item Swipemenulistview实现仿QQ侧滑删除功能

    大家都用过QQ,肯定有人好奇QQ滑动删除Item的效果是怎样实现的,其实我们使用Swipemenulistview就可以简单的实现.先看看我们项目中的效果: 使用的时候可以把Swipemenulistview作为一个library,也可以把Swipemenulistview的源码拷贝到我们的项目中来,使用步骤大致可以分为三步:1.在布局中配置:2.在Java代码中初始化配置:3.按钮点击事件的处理  1.在布局中配置 xml布局文件中只需要简单使用这个自定义的ListView就行了,需要注意的是

随机推荐