您的位置:首页 > 移动开发 > Android开发

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

2015-11-03 15:55 447 查看
上一篇博客写了水滴状的下拉刷新头,不过那个只能垂直下拉,也就是一个方向,而且经常用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 attributes
mPaint = 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();

}

@Override
protected 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;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected 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);

}

@Override
public 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 {
@Override
public 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;
}

}


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

完~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android 自定义组件