android自定义view之模拟qq消息拖拽删除效果

所属分类: 软件编程 / Android 阅读数: 20
收藏 0 赞 0 分享

这个模拟功能的实现主要依靠了PATH和二阶贝塞尔曲线。首先上一张图来简单看一下:

这个模拟功能有以下几个特点:

  1. 在开始的时候点击圆以外的区域不会触发拖动事件
  2. 点击圆的时候可以拖拽,此时会有一个拉伸效果,连接大圆和小圆
  3. 拉伸到一定距离(自己设定)以后两个圆会断开,此时即使再拖拽进距离之内的时候也不会再产生已经断开的连接
  4. 在距离之内松手的时候会回弹会原位置,并伴有一个弹跳动画

介绍了这么多,看过我前边文章的朋友应该会有一个基本思路。

暴露接口

这个模拟功能共分为三部分,一个是那个小圆,固定的位置,一个是那个大圆,可以移动,还有一部分就是中间的连接部分,会跟随大圆一起延伸。

首先看一下都有哪些接口可以调用:

 public void setMinR(float minR) {
    this.minR = minR;
  }

  public void setMaxR(float maxR) {
    this.maxR = maxR;
  }

  public void setBrokeDistance(float distance) {
    this.brokeDistance = distance;
  }

第一个setMinR是设置小圆的半径,第二个setMaxR是设置大圆的半径,第三个setBrokeDistance是设置断开的距离,也就是小圆和大圆的圆心之间的最大连接距离。

初始化

public Buble(Context context) {
    super(context);
    init();
  }

  public Buble(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    init();
  }

简单的看一下初始化方法。

private void init() {
    paint = new Paint();
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(Color.GREEN);
    paint.setAntiAlias(true);
  }

其实只有一个画笔,这里可以为各个区域分别设置画笔。

绘制图形

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawOriginalCircle(canvas);
    if (!canBroke) {
      drawMoveCircle(canvas);
      drawBCurve(canvas);
    }
  }

这三个方法相对简单,drawOriginalCircle是画中心的小圆,然后canBroke是一个开关,控制是否执行画移动的圆和画弧线。

 private void drawOriginalCircle(Canvas canvas) {
    canvas.drawCircle(getWidth() / 2, getHeight() / 2, minR, paint);
  }

  private void drawMoveCircle(Canvas canvas) {
    canvas.drawCircle(moveX, moveY, maxR, paint);
  }

  private void drawBCurve(Canvas canvas) {
    canvas.drawPath(path, paint);
  }

注意,moveX, moveY和path都是变化的,所以在他们的值发生改变以后千万不要忘记invalidate。

path的连接

关于path的文章网上一大堆。

此处的难点主要是大圆和小圆之间的连接。用一张图简单表示一下:

基本就是这个样子,path的路径就是那个黑色的类似于漏斗一样的东西。此处要计算的角度需要用到三角函数关系式,简单表示一下:

图中标出的两个角度是相等的

double angle = Math.atan((offsetX - minCircleX) / (offsetY - minCircleY));

求出这个角度(offsetX是移动圆心的坐标)。

这样就可以算出四个点的坐标了。

private void setPath(float offsetX, float offsetY) {
    float minCircleX = (float) getWidth() / 2;
    float minCircleY = (float) getHeight() / 2;
    double angle = Math.atan((offsetX - minCircleX) / (offsetY - minCircleY));
    float x1 = (float) (minCircleX + Math.cos(angle) * minR);
    float y1 = (float) (minCircleY - Math.sin(angle) * minR);
    float x2 = (float) (offsetX + Math.cos(angle) * maxR);
    float y2 = (float) (offsetY - Math.sin(angle) * maxR);
    float x3 = (float) (offsetX - Math.cos(angle) * maxR);
    float y3 = (float) (offsetY + Math.sin(angle) * maxR);
    float x4 = (float) (minCircleX - Math.cos(angle) * minR);
    float y4 = (float) (minCircleY + Math.sin(angle) * minR);
    float centerX = minCircleX + (offsetX - minCircleX) / 2;
    float centerY = minCircleY + (offsetY - minCircleY) / 2;
    path.reset();
    path.moveTo(minCircleX, minCircleY);
    path.lineTo(x1, y1);
    path.quadTo(centerX, centerY, x2, y2);
    path.lineTo(x3, y3);
    path.quadTo(centerX, centerY, x4, y4);
    path.lineTo(minCircleX, minCircleY);
    path.close();
  }

注意quadTo的四个参数的意义,前两个是你的锚点的坐标,后两个是你要移动到那个点的位置的坐标。

触摸事件

这个直接上代码来实现思路吧,没什么好讲的。

 switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        this.canBroke = false;
        moveX = event.getX();
        moveY = event.getY();
        touchArea = !setCanBroke(moveX, moveY, maxR);
        break;
      case MotionEvent.ACTION_MOVE:
        if (touchArea) {
          moveX = event.getX();
          moveY = event.getY();
          if (setCanBroke(moveX, moveY, brokeDistance)) {
            touchArea = false;
            this.canBroke = true;
          } else {
            setPath(moveX, moveY);
          }
          invalidate();
        }
        break;
      case MotionEvent.ACTION_UP:
        Log.d("aaa", "actionUp" + touchArea);
        if (touchArea) {
          resetCircle(event.getX(), event.getY());
        }
        break;
    }
    return true;

这里主要说明一下这个setCanBroke:

 private boolean setCanBroke(float offsetX, float offsetY, float brokeDistance) {
    float minCircleX = (float) getWidth() / 2;
    float minCircleY = (float) getHeight() / 2;
    return (offsetX - minCircleX) * (offsetX - minCircleX) +
        (offsetY - minCircleY) * (offsetY - minCircleY) > brokeDistance * brokeDistance;
  }

这个表示是否超出了最大移动距离,超出则返回真,未超出则返回假。同时在touchArea的设定中它也用用到了,主要是判断点击区域是否在圆圈上。

最后还要讲一下这个resetCicle,这个是一个属性动画来控制返回原点的弹性动画:

private void resetCircle(float x, float y) {
    valueAnimatorX = ValueAnimator.ofFloat(x, (float) getWidth() / 2);
    valueAnimatorY = ValueAnimator.ofFloat(y, (float) getHeight() / 2);
    valueAnimatorX.removeAllUpdateListeners();
    valueAnimatorY.removeAllUpdateListeners();
    valueAnimatorX.setInterpolator(new BounceInterpolator());
    valueAnimatorY.setInterpolator(new BounceInterpolator());
    valueAnimatorX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        tempX = (float) animation.getAnimatedValue();
        moveX = tempX;
      }
    });
    valueAnimatorY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        tempY = (float) animation.getAnimatedValue();
        moveY = tempY;
        setPath(tempX, tempY);
        postInvalidate();
      }
    });
    set.playTogether(valueAnimatorX, valueAnimatorY);
    set.start();
  }

其中的插值器是BounceInterpolator,类似于小球弹跳的动画,在我前边的文章中有介绍。

最后来看一下不会断开的效果,相当有意思:

关于自定义view的文章会暂时到这里,下一步准备更新自定义viewgroup的文章。相对于自定义view会稍微简单一点。

demo下载地址:PathApplication_jb51.rar

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

更多精彩内容其他人还在看

Android实现悬浮窗体效果

这篇文章主要为大家详细介绍了Android实现悬浮窗体效果,显示悬浮窗口,窗口可以拖动,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Andriod studio 打包aar 的方法

这篇文章主要介绍了Andriod studio 打包aar的方法,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
收藏 0 赞 0 分享

Android加载loading对话框的功能及实例代码(不退出沉浸式效果)

这篇文章主要介绍了Android加载loading对话框的功能及实例代码,不退出沉浸式效果,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中LayoutInflater.inflater()的正确打开方式

这篇文章主要给大家介绍了关于Android中LayoutInflater.inflater()的正确打开方式,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Delphi在Android下使用Java库的方法

这篇文章主要介绍了Delphi在Android下使用Java库的方法,本文以Android的USB串口通讯库为例,给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

Retrofit2日志拦截器的使用

这篇文章主要介绍了Retrofit2日志拦截器的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

Android创建外部lib库及自定义View的图文教程

这篇文章主要给大家介绍了关于Android创建外部lib库及自定义View的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Android分享微信小程序失败的一些事小结

这篇文章主要给大家介绍了关于Android分享微信小程序失败一些事,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Android分享微信小程序技巧之图片优化

这篇文章主要给大家介绍了关于Android分享微信小程序技巧之图片优化的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

Android Viewpager实现无限循环轮播图

这篇文章主要为大家详细介绍了Android Viewpager实现无限循环轮播图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享
查看更多