欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-01-09 14:08:31
这个模拟功能的实现主要依靠了path和二阶贝塞尔曲线。首先上一张图来简单看一下: 这个模拟功能有以下几个特点: 在开始的时候点击圆以外的区域不会触发拖动事件...

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

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

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

  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的文章网上一大堆。

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

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

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

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

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

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,类似于小球弹跳的动画,在我前边的文章中有介绍。

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

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

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

demo下载地址:pathapplication_jb51.rar

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