android自定义view之模拟qq消息拖拽删除效果
作者:sakasa 发布时间:2023-01-29 11:48:34
这个模拟功能的实现主要依靠了PATH和二阶贝塞尔曲线。首先上一张图来简单看一下:
这个模拟功能有以下几个特点:
在开始的时候点击圆以外的区域不会触发拖动事件
点击圆的时候可以拖拽,此时会有一个拉伸效果,连接大圆和小圆
拉伸到一定距离(自己设定)以后两个圆会断开,此时即使再拖拽进距离之内的时候也不会再产生已经断开的连接
在距离之内松手的时候会回弹会原位置,并伴有一个弹跳动画
介绍了这么多,看过我前边文章的朋友应该会有一个基本思路。
暴露接口
这个模拟功能共分为三部分,一个是那个小圆,固定的位置,一个是那个大圆,可以移动,还有一部分就是中间的连接部分,会跟随大圆一起延伸。
首先看一下都有哪些接口可以调用:
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
来源:http://www.jianshu.com/p/e6a6ec3ef27a#
猜你喜欢
- 一、引言在前面的文章中,我们是使用“锁”的方式实现了线程间的通信,这种通信方式比较笨重。除了锁之外,
- 先来看我们以前利用RestTemplate发起远程调用的代码:存在下面的问题:代码可读性差,编程体验不统一参数复杂URL难以维护1. Fei
- 实现功能:模拟简单登录功能,登录成功跳转新页面,登录失败在原登录界面提示登录失败信息开发环境:eclipseTomcat-8.0预备知识:H
- package cn.hp.util;import java.sql.*;public class JDBCUtils { &
- MyBatis一对多的xml配置用的是window上面的画图板,没法以文字的方式展示出来,见谅嵌套查询嵌套结果一对多关联查询xml配置写法
- 1、多态性多态性是面向对象的最后一个特征,它本身主要分为两个方面: 方法的多态性:重载与覆写1、重载:同一个方法名称,根据参数类型以及个数完
- 一、链表的介绍什么是链表链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结
- 在工作上,我最近对一个现有的Java项目代码进行了清理。完成之后,我发现了一些反复出现的不规范代码。所以,我把它们整理成了一个列表出来分享给
- 一、概述运行时变更就是设备在运行时发生变化(例如屏幕旋转、键盘可用性及语言)。发生这些变化,Android会重启Activity,这时就需要
- 本文实例讲述了C#的内存回收方法。分享给大家供大家参考。具体实现方法如下:如下示例代码是调用win32底层操作,可实现内存的回收。 
- 目录第一章 前言概述第01节 概述第02节 区别第二章 核心代码第01节 成员变量第02节 构造方法第三章 扩容操作第01节 扩容代码第一章
- 引言: 最近公司在做一个教育培训学习及在线考试的项目,本人主要从事网络课程模块,主要做课程分类,课程,课件的创建及
- spring cloud oauth2 feign 遇到的坑关于oauth2相关的内容这里不重复描述,在spring cloud中在管理内部
- 前言作为一个写java的使用最多的轻量级框架莫过于spring,不管是老项目用到的springmvc,还是现在流行的springboot,都
- 在 Effecitve Java 一书的第 48 条中提到了双重检查模式,并指出这种模式在 Java 中通常并不适用。该模式的结构如下所示:
- 这里来讲一下后台java如何构造多叉树,这样前台就可接收到数据递归构造树形菜单了。我们来理一下如何实现构造多叉树的逻辑吧,其实整个问题概括起
- MybatisPlus分页排序查询字段带有下划线如果使用MybatisPlus的自动转驼峰命名法,分页排序查询的字段带有下划线时,会出问题。
- 1、回顾一下JDK * 的核心参数如果我们要为target类创建一个【JDK * 对象】,那么我们必须要传入如下三个核心参数加载targ
- 今天被数据大神说了,对接第三方接口返回的json字段我想用驼峰形式,他说我这样不专业。所以就改了,认怂。记住以后再次对接rest接口,返回的
- 实体类package com.whty.entity;public class User {private int id;private S