上一篇博客写了水滴状的下拉刷新头,不过那个只能垂直下拉,也就是一个方向,而且经常用QQ的人也知道,QQ消息来了后,列表右边会有一个红色消息小球,这个小球是可以拉动的,拉起来就像一根皮筋一样,效果很棒,于是我根据那个上篇博客,进行修改,得到一个可以平面拉动的小球,基本近似QQ。

嗯,效果还不错,基本原理和上篇一样,不过因为可以平面拖动,和原来相比,就相当于一维到二维进步,计算难度和运算量基本上了几个数量级,还总是要考虑圆心连线的方向性,头都晕了,数学不好可以绕过了

连接两个圆的圆心,过一个圆的圆心做垂线,交圆上AB两点,同理在大圆上面也作出CD两点

分别连线,现在要根据两个圆的圆心坐标和两个圆的半径求出ABCD这4点坐标

一开始想用解析几何的方式求4点坐标,做法:

求出圆心连线的斜率,根据垂直直线斜率相乘等于-1,得到AB直线的斜率

AB直线过原点,则AB直线方程可以用点斜式表达出,与圆的方程联解,即可得到AB两点

问题有两个:1,需要考虑到斜率不存在的情况2,需要求解二元二次方程组,很麻烦

于是,想到了三角函数的办法,因为计算机中求取反三角很简单,所以可以完全不顾解析几何那一套,做法:

求出圆心连线的斜率,根据垂直直线斜率相乘等于-1,得到AB直线的斜率

根据斜率k=tanθ,θ是直线AB和X轴的夹角,θ=arctank

AB点都是圆上的点,圆的方程已经说的很清楚了,x=cosθ*R+Rx,y=sinθ*R+Ry,其中Rx,Ry都是圆心坐标

CD点同理可得,下面需要根据这四个点做二次曲线,因此需要借助到中间点E,F。

直线EF是圆心连线的垂直平分线,交A,B两点延生的两条平行于圆心连线于E,F

因此这两个点也可以通过三角函数来求,以A为原点,已知AF的距离(圆心距一半),AF和X轴的夹角(θ+90),很容易求得

于是乎,剩下两个半圆弧的绘制方法,依旧是用那个方法、arcTo,依旧是要注意圆弧的方向,和整条路径的方向,不然很可能无法闭合曲线。

当然,实际情况比这还要复杂的多

你需要考虑这种情况

当圆出现在下面时,AB直线的斜率依旧不变,可是绘制圆弧的方向却完全不一样了,因此需要在代码中加入一个判断

闭合曲线也是麻烦的要死,嗯,说到这里,绘制这部分算是完了

至于触摸事件,摸着改动大圆圆心坐标就是了,然后刷新view

说说回弹,这次用的回弹和上次有所不同,上次用的是匀速返回,这种效果并不是很好

想作出那种皮筋,或者弹簧的效果来,果然,想到弹簧上的小球正好就是物理上面所说的简谐运动

这正是我想要的,于是乎,看原理:

我需要的是小球运动时圆心距的改变,从圆心距推算出大圆的圆心坐标

而我们可以让圆心距改变的大小是一个简谐运动,圆心距x=A*sin(w*t+Ψ)

振幅A不停减少,一直到0停止,t的经历的时间(不是间隔时间)
根据圆心距和角度angle,可及时的计算出运动中的圆心坐标
small球在上面时,即islow=1,x为正,在下面时islow=-1,x为负,需要根据这个计算初相φ,φ=islow*π/2
A=手放开的时候的圆心距
周期T=2π/ω,则ω=2π/T

当A递减到0,回弹线程结束

好了,下面贴代码:

package com.example.kaifa.myapplication;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;/*** TODO: document your custom view class.*/
public class DanqiuView extends View {private Paint mPaint;/*** 拉伸进度*/private float progress = 0;/*** view的宽高*/private int viewheight, viewwdith;/*** 大圆半径*/private float GreatCircleRadius = 20;/*** 小圆半径*/private float SmallCircleRadius = 20;/*** 大圆的圆心*/private Point GreatCirclePoint;/*** 小圆的圆心*/private Point SmallCirclePoint;/*** 分别在大圆和小圆上面的4个点*/private Point A, B, C, D;/*** AB直线与X轴的夹角(AB直线和圆心连线垂直),单位 弧度*/private double angle;/*** 手指按下接触到的第一个点*/private Point firstPoint;/*** 两条二次曲线的分别两个中间点*/private Point ACmiddlePoint, BDmiddlePoint;/*** 绘制路径*/private Path mPath;/*** 1表示小圆在大圆下面,-1反之*/private int islow = -1;/*** 两园的圆心距*/double dance=0;/*** 周期,单位/毫秒*/float T=2000;public DanqiuView(Context context) {super(context);init(null, 0);}public DanqiuView(Context context, AttributeSet attrs) {super(context, attrs);init(attrs, 0);}public DanqiuView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(attrs, defStyle);}private void init(AttributeSet attrs, int defStyle) {// Load attributesmPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setColor(0xff0000ff);mPaint.setStyle(Paint.Style.FILL);mPath = new Path();GreatCirclePoint = new Point();SmallCirclePoint = new Point();A = new Point();B = new Point();C = new Point();D = new Point();firstPoint = new Point();ACmiddlePoint = new Point();BDmiddlePoint = new Point();}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);viewwdith = w;viewheight = h;GreatCirclePoint.x = w / 2;GreatCirclePoint.y = h / 2;SmallCirclePoint.x = GreatCirclePoint.x;SmallCirclePoint.y = GreatCirclePoint.y;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//路径重置mPath.reset();//若重合,则绘制初始圆if (GreatCirclePoint.x == SmallCirclePoint.x && GreatCirclePoint.y == SmallCirclePoint.y) {//初始绘制mPath.addCircle(GreatCirclePoint.x, GreatCirclePoint.y, GreatCircleRadius, Path.Direction.CW);canvas.drawPath(mPath, mPaint);return;}//计算四个点的坐标和角度值//AB直线和圆心连线垂直,斜率相乘等于-1.因此可以得到AB直线的斜率float kAB = (GreatCirclePoint.x - SmallCirclePoint.x) / (SmallCirclePoint.y - GreatCirclePoint.y);angle = Math.atan(kAB);Log.v("xingyun", "斜率K=" + kAB + "  角度a=" + angle);A.x = (float) (GreatCirclePoint.x + GreatCircleRadius * Math.cos(angle));A.y = (float) (GreatCirclePoint.y + GreatCircleRadius * Math.sin(angle));B.x = (float) (GreatCirclePoint.x + GreatCircleRadius * Math.cos(angle + Math.PI));B.y = (float) (GreatCirclePoint.y + GreatCircleRadius * Math.sin(angle + Math.PI));C.x = (float) (SmallCirclePoint.x + SmallCircleRadius * Math.cos(angle));C.y = (float) (SmallCirclePoint.y + SmallCircleRadius * Math.sin(angle));D.x = (float) (SmallCirclePoint.x + SmallCircleRadius * Math.cos(angle + Math.PI));D.y = (float) (SmallCirclePoint.y + SmallCircleRadius * Math.sin(angle + Math.PI));if (GreatCirclePoint.y < SmallCirclePoint.y) {islow = 1;} else {islow = -1;}dance = Math.sqrt((GreatCirclePoint.x - SmallCirclePoint.x) * (GreatCirclePoint.x - SmallCirclePoint.x) + (GreatCirclePoint.y - SmallCirclePoint.y) * (GreatCirclePoint.y - SmallCirclePoint.y)) / 2;progress = (float) dance * 2 / viewheight;SmallCircleRadius = (float) (20 * (1 - progress * 0.1));GreatCircleRadius = (float) (20 * (1 - progress));if (progress < 0.5) {BDmiddlePoint.x = (float) (B.x + dance * Math.cos(angle + islow * Math.PI / 2));BDmiddlePoint.y = (float) (B.y + dance * Math.sin(angle + islow * Math.PI / 2));ACmiddlePoint.x = (float) (A.x + dance * Math.cos(angle + islow * Math.PI / 2));ACmiddlePoint.y = (float) (A.y + dance * Math.sin(angle + islow * Math.PI / 2));} else {BDmiddlePoint.x = GreatCirclePoint.x / 2 + SmallCirclePoint.x / 2;BDmiddlePoint.y = GreatCirclePoint.y / 2 + SmallCirclePoint.y / 2;ACmiddlePoint.x = BDmiddlePoint.x;ACmiddlePoint.y=BDmiddlePoint.y;}mPath.arcTo(new RectF(GreatCirclePoint.x - GreatCircleRadius, GreatCirclePoint.y - GreatCircleRadius, GreatCirclePoint.x + GreatCircleRadius, GreatCirclePoint.y + GreatCircleRadius),(float) (angle * 180 / Math.PI), islow * (-180));//从B点到D点,选取其中心点作为渐进点mPath.quadTo(BDmiddlePoint.x, BDmiddlePoint.y, D.x, D.y);mPath.arcTo(new RectF(SmallCirclePoint.x - SmallCircleRadius, SmallCirclePoint.y - SmallCircleRadius, SmallCirclePoint.x + SmallCircleRadius, SmallCirclePoint.y + SmallCircleRadius),(float) (angle * 180 / Math.PI+180), islow * (-180));//从A点到C点mPath.quadTo(ACmiddlePoint.x, ACmiddlePoint.y, A.x, A.y);canvas.drawPath(mPath, mPaint);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:firstPoint.x = event.getX();firstPoint.y = event.getY();break;case MotionEvent.ACTION_MOVE:float dx = event.getX() - firstPoint.x;float dy = event.getY() - firstPoint.y;if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {SmallCirclePoint.x = SmallCirclePoint.x + dx;SmallCirclePoint.y = SmallCirclePoint.y + dy;invalidate();}firstPoint.x = event.getX();firstPoint.y = event.getY();break;case MotionEvent.ACTION_UP://开启线程匀速返回,回弹new MyTread().start();break;}return true;}/*** 回弹的线程,作为简谐运动,则圆心距x=Asin(ωt+φ),振幅A不停减少,一直到0停止,t的经历的时间(不是间隔时间)* 根据圆心距和角度angle,可及时的计算出运动中的圆心坐标* small球在上面时,即islow=1,x为正,在下面时islow=-1,x为负,需要根据这个计算初相φ,φ=islow*π/2* A=手放开的时候的圆心距* 周期T=2π/ω,则ω=2π/T*/class MyTread extends Thread {@Overridepublic void run() {float A=(float)dance*2;float fai=(float)( islow*Math.PI/2);float w=(float)(2* Math.PI/T);long t=0;double angle1=angle;while (A>0){float x=(float)(A*Math.sin(w*t+fai));A=A-(float)0.5;t=t+10;Log.v("xingyun","振幅:"+A+"  X:"+x);SmallCirclePoint.x=GreatCirclePoint.x+x*(float)Math.cos(angle1+Math.PI/2);SmallCirclePoint.y=GreatCirclePoint.y+x*(float) Math.sin(angle1+Math.PI/2);try {sleep(10);postInvalidate();} catch (InterruptedException e) {e.printStackTrace();}}}}private class Point {float x, y;}}

能力有限,只能做到这里啦~(计算量这么大幸好也没出现卡顿的现象,运气好好~)

完~

转载于:https://www.cnblogs.com/xingyun1992/p/5021086.html

刷新头的升级版,仿QQ的红色消息小球相关推荐

  1. android qq消息数 拖拽动画,史上最详细仿QQ未读消息拖拽粘性效果的实现

    好久没写文章了,前段时间由于项目代码重构忙了一段时间,现在终于有点时间了就为大家带来一篇关于动画学习的自定义View:类似QQ消息拖拽的效果. 其实QQ当时更新的时候我还没注意到这个小红点是可以拖拽的 ...

  2. android+仿qq未读消息数量,仿qq自定义未读消息数显示角标

    66FF020E13B921CB19C7542F4801AF43.png 如图所示,我们需要实现的效果 在消息tab上,是一个组合的自定义view,具体实现如下 android:layout_widt ...

  3. Android 仿QQ未读消息拖拽删除粘性控件效果

    效果图: 分析  一 : 1.应用的地方:如未读数据的清除等 2.这个控件要实现哪些功能呢? 1)拖拽超出范围时,断开了,此时我们松手,图标消失 2)拖拽超出范围时,断开了,此时我们把图标移动回去,图 ...

  4. 安卓高仿QQ头像截取升级版

    观看此篇文章前,请先阅读上篇文章:高仿QQ头像截取: 本篇之所以为升级版,是在截取头像界面添加了与qq类似的阴影层(裁剪区域以外的部分),且看效果图:   为了适应大家不同需求,这次打了两个包,及上图 ...

  5. Android:高仿QQ头像截取升级版

    观看此篇文章前,请先阅读上篇文章:高仿QQ头像截取: 本篇之所以为升级版,是在截取头像界面添加了与qq类似的阴影层(裁剪区域以外的部分),且看效果图:   为了适应大家不同需求,这次打了两个包,及上图 ...

  6. iOS之仿QQ好友列表展开收缩效果的实现

    使用UICollectionView实现 思路 很明显整体它是一个列表,它的分组是一个列表,它里面的好友列表也是一个列表,所以就可以使用组头来设置分组列表,使用cell设置好友列表: 当点击组头的时候 ...

  7. 自定义View | 仿QQ运动步数进度效果

    项目GitHub地址 思路 固定不动的蓝色大圆弧 动画变动的红色小圆弧 中间的步数文字显示 相关的自定义属性 比如固定不动的大圆弧, 我们不能写死他的蓝色颜色属性, 要提供一个颜色的自定义属性给用户自 ...

  8. ios音乐播放器-仿QQ音乐

    这篇文章主要写一个iOS系统下的音乐播放器 , 包括简单的仿QQ音乐播放器界面.音乐播放.歌词解析.后台控制等  ,如果你正好需要 , 希望你看完后能够对你的提升有所帮助 , 当然,阅读中如果发现什么 ...

  9. iOS仿QQ分组效果

    本篇主要讲解仿QQ分组效果的实现,通过本遍的学习,估计都可以自己去实现了(老司机可以),在这里只说仿QQ分组的效果,代码简单,逻辑清晰.其他的功能的可以自行添加,好了,进入主题吧. 效果图 下面的是其 ...

最新文章

  1. android 弹出fragment,Android中ViewPager获取当前显示的Fragment
  2. 【解析】工业机器人中的各类传感器技术应用
  3. 移植MT76x8 私有WIFI驱动V4.1.0.0到Openwrt 18.06所遇到的坑
  4. Linux内核访问外设I O资源的方式
  5. BF533和触摸屏接口芯片TSC2200调试日志
  6. 结合前段修改mysql表数据_jquery实现点击文字可编辑并修改保存至数据库
  7. 【安富莱二代示波器教程】第8章 示波器设计—测量功能
  8. Opencv--addWeighted()
  9. html修改鼠标手势,css设置鼠标手势的方法
  10. 华为vlan简单配置
  11. 如何把Win7系统桌面上的内容保存在非系统盘
  12. Python + Opencv 实现遥感影像tif格式转jpg
  13. idea中maven打包报错:Compilation failure: Compilation failure
  14. 虚拟机模拟搭建Redis集群环境
  15. mysql 索引方法_mysql索引方式
  16. python编程好学吗-零基础可以学会python吗?python好学吗?
  17. 服务器配置与软件安装合集
  18. 利用java swing编写一个简易的计算器,实现了括号,优先级,三角函数,阶乘等功能
  19. 基于Java的员工管理系统
  20. java timezone prc_TimeZone

热门文章

  1. 用于金融科技应用开发的高性能图表控件
  2. 物流运输无法透明,需求供给难匹配,这家公司靠可视化实现了透明
  3. 全球与中国纤维素乙醇市场深度研究分析报告
  4. TFT espi相关
  5. 系统设计目标(一):如何提升系统性能?
  6. 图片去水印app-一键去除图片水印
  7. 2021年终总结暨2022新年计划
  8. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java基于用户激励的图书管理系统fx8il
  9. windows10恶作剧----教你把别人的屏幕倒过来
  10. 婴儿摇篮语音播放芯片,高品质MP3音乐播放芯片,WT2003H