纯属好奇心驱动写的一个学习性Demo,效果如下:

这个小功能最重要的点在于起始点和触摸点之间的连接线绘制,它并不是一条单纯的直线,而是中间细两头粗的一条不规则的Path,而这个中间向内弯曲的效果正是一条贝塞尔曲线,中间这个Path是由两条贝塞尔曲线和两条直线组成。看下图:

两个带圆弧的线就是由三点确认的一个贝塞尔曲线:

在Android已经有提供画贝塞尔曲线的接口,三个点传进去,效果就出来了。
贝塞尔曲线是用三个或多个点来确定的一条曲线,它在图形图像学中有相当重要的地位,Path中也提供了一些方法来给我们模拟低阶贝赛尔曲线。
本文的主要目的是学习贝塞尔曲线,看一下贝塞尔曲线的一下使用案例。
案例一:

案例二:

案例三:

案例四:

其实还有很多,还有QQ就是上面拖拽清除消息的气泡。看了之后你会好奇是怎么做的吧。

这里不讲背景,只讲贝塞尔曲线的一些效果,先从维基百科上盗图,看看不同数量的点上绘制贝塞尔曲线的效果。
两个点,绘制出来就是直线,也就是所谓的一阶贝塞尔曲线

三个点,二阶贝塞尔曲线:

四个点,三阶贝塞尔曲线:

五个点,四阶贝塞尔曲线:

六个点,五阶贝塞尔曲线:

大家看到上面的图,其实可以这么理解,这也是参考网友的理解。不管是一阶、二阶、还是六阶,他们都至少有两个点,也就是起点和终点。先用橡皮筋把这两个点连接好。而除了起点和终点之外的点都相当于对橡皮筋有吸引力的点,他们因为距离原因的不同,对橡皮筋产生不同程度的吸力,最终达到一个平衡状态,所以橡皮筋往非起始点有所偏移,如下图:

在Android中提供了绘制一阶、二阶、三阶的接口:
一阶接口:

public void lineTo(float x, float y)

二阶接口:

public void quadTo(float x1, float y1, float x2, float y2)

三阶接口:

public void cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

关于最上面的QQ拖拽清除效果,就是用的二阶绘制的。

下面来简要讲解一下它的实现原理吧:

第一:那个99+消息是一个ImageView,它是根据OnTouch事件移动的,这个好理解。这是是通过在onTouch方法里面不断的setX()、setY()实现的。
第二:记录起始位置p1(x1,y1),记录手指拖拽位置p2(x2,y2),有了这两点我们就可以绘制直线了,但是我们这里绘制的是带有贝塞尔曲线的Path,所以重要工作是构造Path
第三:构造Path,如下图:

知道了上图两个黑点的坐标,并且知道绿色线的宽度Len,很好求出P1、P2、P3当中的点了,画个坐标系,用三角函数就求出来了。当然这个宽度Len是根据两个黑点之间的距离动态的去计算了,它与两个黑点之间的距离成反比。构造Path的步骤是:
p1-p3-p2之间:贝塞尔曲线
p2-p5之间:直线
p5-p3-p4之间:贝塞尔曲线
p4-p1之间:直线
构造了这样一个Path,然后用实心的画笔就画出效果来了。
当然,要完全达到要求还要加上逻辑控制了,代码如下:

package rander.com.bezier.bezier;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.AnimationDrawable;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;import rander.com.bezier.R;/*** Created by Rander.C on 16-4-3.*/
public class QQDragtoClearView extends FrameLayout {/*** 最大拖拽长度*/private final int DRAG_MAX_LEN = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());/*** 默认的红点半径大小*/private final int DEFAULT_RADIUS = 40;/*** 消息数量背景,QQ里面起始用的就是一个图片,而不是在红色背景上画一个数量*/private ImageView mIvMsgCountBg;/*** 拖拽清除的时候的动画视图*/private ImageView mIvClearAnim;/*** 手触摸的x,y坐标*/private float mTouchX;private float mTouchY;/*** 初始的x,y坐标,默认安放的位置*/private float mStartX = 300;private float mStartY = 300;/*** 手触摸的坐标和初始点坐标的中间位置* mCenterX = (mTouchX + mStartX)/2* mCenterY = (mTouchY + mStartY )/2* 这个是用来画贝塞尔曲线的一个中转点*/private float mCenterX;private float mCenterY;private static final int TOUCH_SLOP = 10;/*** 画贝塞尔曲线的画笔*/private Paint mPaint;/*** 画贝塞尔曲线的Path*/private Path mPath;/*** 是否被点中了*/private boolean isTouch;/*** 默认的半径大小*/private int mRaduis = DEFAULT_RADIUS;/*** 判断贝塞尔曲线是否断掉了*/private boolean isBroken = false;public QQDragtoClearView(Context context) {this(context, null);}public QQDragtoClearView(Context context, AttributeSet attrs) {this(context, attrs, -1);}public QQDragtoClearView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}/*** 初始化*/private void init() {mPath = new Path();mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setStyle(Paint.Style.FILL_AND_STROKE);mPaint.setStrokeWidth(2);mPaint.setColor(Color.RED);FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(60, 60);mIvClearAnim = new ImageView(getContext());mIvClearAnim.setLayoutParams(params);mIvClearAnim.setImageResource(R.drawable.tip_anim);mIvClearAnim.setVisibility(INVISIBLE);mIvMsgCountBg = new ImageView(getContext());mIvMsgCountBg.setLayoutParams(params);mIvMsgCountBg.setImageResource(R.drawable.skin_tips_newmessage_ninetynine);mIvMsgCountBg.setVisibility(VISIBLE);addView(mIvClearAnim);addView(mIvMsgCountBg);}@Overridepublic boolean onTouchEvent(MotionEvent event) {mTouchX = (int) event.getX();mTouchY = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN://ACTION_DOWN的作用就是判断触摸的点Rect rect = new Rect();int[] location = new int[2];mIvMsgCountBg.getDrawingRect(rect);mIvMsgCountBg.getLocationOnScreen(location);rect.left = location[0];rect.top = location[1];rect.right = rect.right + location[0];rect.bottom = rect.bottom + location[1];if (rect.contains((int) event.getRawX(), (int) event.getRawY())) {//如果isTouch为ture则表示开始绘制开始isTouch = true;}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL://当抬起的时候,先判断是否断开了,才会出发动画,并且消息数量提示消失//如果没有断开,则回到原点if (isBroken) {boolean isPositionNoChanged = false;//如果弹起位置跟起始位置相差不大,则回到起始位置,不消失if (Math.abs(mTouchX - mStartX) < TOUCH_SLOP && Math.abs(mTouchY - mStartY) < TOUCH_SLOP) {isPositionNoChanged = true;}if (isPositionNoChanged) {break;} else {mIvMsgCountBg.setVisibility(INVISIBLE);mIvClearAnim.setVisibility(View.VISIBLE);mIvClearAnim.setX(mTouchX - mIvClearAnim.getWidth() / 2);mIvClearAnim.setY(mTouchY - mIvClearAnim.getHeight() / 2);mIvClearAnim.setImageResource(R.drawable.tip_anim);((AnimationDrawable) mIvClearAnim.getDrawable()).stop();((AnimationDrawable) mIvClearAnim.getDrawable()).start();mPath.reset();new Handler().postDelayed(new Runnable() {@Overridepublic void run() {mIvMsgCountBg.setVisibility(VISIBLE);}}, 1200);}isBroken = false;}isTouch = false;mIvMsgCountBg.setX(mStartX - mIvMsgCountBg.getWidth() / 2);mIvMsgCountBg.setY(mStartY - mIvMsgCountBg.getHeight() / 2);break;}mCenterX = (mTouchX + mStartX) / 2;mCenterY = (mTouchY + mStartY) / 2;if (isTouch) {mIvMsgCountBg.setX(mTouchX - mIvMsgCountBg.getWidth() / 2);mIvMsgCountBg.setY(mTouchY - mIvMsgCountBg.getHeight() / 2);}invalidateView();return true;}public void invalidateView() {if (Looper.myLooper() == Looper.getMainLooper()) {invalidate();} else {postInvalidate();}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {mIvClearAnim.setX(mStartX);mIvClearAnim.setY(mStartY);mIvMsgCountBg.setX(mStartX - mIvMsgCountBg.getWidth() / 2);mIvMsgCountBg.setY(mStartY - mIvMsgCountBg.getHeight() / 2);super.onLayout(changed, l, t, r, b);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//如果在touch中,且线没有断掉,则继续绘制,会泽清楚画布.if (isTouch && !isBroken) {checkDragLen();canvas.drawPath(mPath, mPaint);canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);canvas.drawCircle(mStartX, mStartY, mRaduis, mPaint);canvas.drawCircle(mTouchX, mTouchY, mRaduis, mPaint);} else {//相当于清楚绘制信息canvas.drawCircle(mStartX, mStartY, 0, mPaint);canvas.drawCircle(mTouchX, mTouchY, 0, mPaint);canvas.drawLine(0, 0, 0, 0, mPaint);canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY);}}/*** 检查拖拽长度是不是够了,超过一定长度就断开,不绘制了*/private void checkDragLen() {int len = (int) Math.sqrt(Math.pow(mTouchX - mStartX, 2) + Math.pow(mTouchY - mStartY, 2));mRaduis = -len / 15 + DEFAULT_RADIUS;//如果长度超过最大长度,isBroken设置为true,在后续就不绘制path了if (len > DRAG_MAX_LEN) {isBroken = true;return;}//得到绘制贝塞尔曲线需要的四个点float offsetX = (float) (mRaduis * Math.sin(Math.atan((mTouchY - mStartY) / (mTouchX - mStartX))));float offsetY = (float) (mRaduis * Math.cos(Math.atan((mTouchY - mStartY) / (mTouchX - mStartX))));float x1 = mStartX - offsetX;float y1 = mStartY + offsetY;float x2 = mTouchX - offsetX;float y2 = mTouchY + offsetY;float x3 = mTouchX + offsetX;float y3 = mTouchY - offsetY;float x4 = mStartX + offsetX;float y4 = mStartY - offsetY;mPath.reset();mPath.moveTo(x1, y1);mPath.quadTo(mCenterX, mCenterY, x2, y2);mPath.lineTo(x3, y3);mPath.quadTo(mCenterX, mCenterY, x4, y4);mPath.lineTo(x1, y1);}
}

使用activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<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"><rander.com.bezier.bezier.QQDragtoClearView
        android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/transparent" />
</RelativeLayout>

Activity

package rander.com.bezier;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}
}

效果就是上面的实线效果

Android 贝塞尔曲线实现QQ拖拽清除效果相关推荐

  1. android开发之仿QQ拖拽界面效果(侧滑面板)

    仿QQ拖拽界面效果(侧滑面板),我们一般继承Layout,不会直接去继承ViewGroup,而是继承FrameLayout,为什么五大布局我们偏偏只继承FrameLayout呢? 第一,FrameLa ...

  2. Android 贝塞尔曲线——类似QQ红点拖拽效果

    在Android绘制中,提供了更为丰富绘制api--Path类,包括直线,二阶贝塞尔曲线,三阶贝塞尔曲线,弧形,圆,椭圆,圆角矩形等等,path的绘制最终是调用了C中的绘制方法. 下面来看一下常用的几 ...

  3. 红橙Darren视频笔记 贝塞尔曲线实现消息拖拽粘性效果 画笔练习

    效果图 要实现这样的效果 我们要先知道那段弧线是如何绘制的,实际上那段弧线就是贝塞尔曲线 一. 什么是贝塞尔曲线 https://www.jianshu.com/p/8f82db9556d2 上面有个 ...

  4. Android qq消息气泡实现效果,Android 实现仿QQ拖拽气泡效果的示例

    效果图: 一.实现思路 在列表中默认使用自定义的TextView控件来展示消息气泡,在自定义的TextView控件中重写onTouchEvent方法,然后在DOWN.MOVE.UP事件中分别处理拖拽效 ...

  5. android 贝塞尔曲线点击区域,白话经典贝塞尔曲线及其在 Android 中的应用

    一.前言 谈到贝塞尔曲线可能不少人会浮现它高大上的数学公式.然而,在实际应用中,并不需要我们去完全理解或者推导出公式才能应用得上.实际情况是,即使真的只是一个学渣,我们应该也能很轻松的掌握贝塞尔曲线的 ...

  6. android贝塞尔曲线,一文解析 Android 贝塞尔曲线

    原标题:一文解析 Android 贝塞尔曲线 相信很多同学都知道"贝塞尔曲线"这个词,我们在很多地方都能经常看到.利用"贝塞尔曲线"可以做出很多好看的UI效果, ...

  7. android动态波浪效果,android贝塞尔曲线实现波浪效果

    本文实例为大家分享了android贝塞尔曲线实现波浪效果的具体代码,供大家参考,具体内容如下 因为手机录制gif不知道下什么软件好,所以暂时就先忽略效果图了 我在屏幕外多画了1.5个波浪,延伸至屏幕内 ...

  8. android波浪动画简书,Android贝塞尔曲线————波浪效果(大波浪)

    Hello大家好,很高兴又一次与大家见面,今天是农历丁酉鸡年(大年初四),现在跟大家拜年有点晚,算是拜晚年,祝大家晚年幸福. 这么快大伙都到了晚年了,Android贝塞尔曲线我也准备以一个大波浪来结束 ...

  9. android圆形贝塞尔 菜单,Android 贝塞尔曲线——圆渐变心

    大家好!我是一名执着的Android开发攻城狮,第一次写简书,没有写好的希望大家多多包涵,万事开头难,从去年开始我就想写点自己的东西,但是一直没有写下去的勇气和毅力,希望这是我一个好的习惯开始.在这我 ...

最新文章

  1. IDEA源码阅读利器 — UML类图插件Diagram
  2. 朋友问我学习高并发需不需要阅读源码,我是这样分析的!!
  3. MaxCompute - ODPS重装上阵 第二弹 - 新的基本数据类型与内建函数
  4. 10月第2周.ORG总量TOP10:中国增1701个 涨幅最大
  5. AOC的显示器真的很烂
  6. VS网站开发的发布部署的不同情况说明
  7. 成功通过pmp_这就是你为啥要学PMP!!!
  8. telnet到设备里 php_金融行业思科设备典型网络故障案例:76系列典型案例(一)...
  9. 书写README的各种markdown语法
  10. sed的高级命令和软件包管理器rpm
  11. HDFS 读取、写入、遍历文件夹获取文件全路径、append
  12. python: 动态网页playwright 爬虫实践
  13. Pr 添加字幕与自动字幕时间轴
  14. 仿照支付宝等——自动获取短信中的验证码
  15. 使用 snapseed p 图,图片局部黑白,简单实用!!
  16. python体验课讲什么_火遍朋友圈的Python小课体验起来是什么样的?
  17. 计算机教师继续教育心得,教师继续教育学习培训心得体会(精选5篇)
  18. java选择,智力,数量,推理
  19. easyphp 登陆mysql_EasyPHP 16.1.1无法启动MySQL
  20. 电脑怎么连接隐藏的无线WiFi信号呢

热门文章

  1. GFFcompare详解
  2. Win11安卓子系统导致安卓模拟器无法启动
  3. ETU-LINK 400G DAC 高速线缆 你get了吗?
  4. SciTE: 中文字符支持问题
  5. 用Opencv实时画运动轨迹的思路
  6. 卸载oracle9i精简版,Oracle精简版客户端
  7. 你知道ppt内容翻译成英文怎么操作吗
  8. Excel条件格式,高效管理数据,高亮显示一目了然
  9. JFrame和Frame的区别
  10. Latex文字加颜色的三种办法