当开始着手写这个View时发现自己有点懵B了,有几个点好像还有点搞不定(毕竟自己还是个小菜鸟),但是感觉搞不定就不搞了吗,那肯定是不行的,搞不定就不搞了,那我还做毛android啊!
1、水平椭圆轨迹好绘制,canvas本来就提供了drawOval()方法,但是斜着的椭圆......没做过;
2、可以用属性动画让小球沿着某一条轨迹动起来是没错,但是椭圆的轨迹还真没试过;
3、就算我勉强通过椭圆标准公式:
换算出了水平椭圆的轨迹,那斜着的那两个椭圆怎么换算的出来呢(我还不会说我是学数学专业的);
带着这些问题开始写代码。
首先定义一个ParticleView继承View,复写构造方法,完成准备工作。然后是测量view的大小,这里注意,我们的ParticleView是一个接近正方形的view(严格上不是一个正方形,高会比宽小点,这里简单的当作正方形来处理)。同时初始化小球的初始位置坐标(这里我们先放下3个轨迹的问题,先做好简单的水平椭圆),椭圆对应的Rect的宽高。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
int desired = (int) (getPaddingLeft() + getWidth() + getPaddingRight());
width = desired;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
int desired = (int) (getPaddingTop() + getWidth() + getPaddingBottom());
height = desired;
}
mSize = width < height ? width : height;// 保证view是正方形
roundPoint = new PointF(roundRadius, mSize / 2);//小球对应的初始坐标
trackWidth = mSize - 2 * roundRadius;//轨迹宽,view的宽度-小球直径
trackHeight = (mSize - 2 * roundRadius) / 3;//轨迹高,(view的宽度-小球直径)/3
setMeasuredDimension((int) mSize, (int) mSize);
}
准备好初始数据后,复写onDraw(Canvas canvas)方法,画轨迹和小球:
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
drawTrack(canvas, roundPoint);
}
/**
* 画轨迹
* @param canvas
* @param p 小球的位置
*/
private void drawTrack(Canvas canvas, PointF p) {
mPaint.setStyle(Style.STROKE);
mPaint.setStrokeWidth(2);
if(needTrack){
// 画轨迹
RectF oval = new RectF(roundRadius, roundRadius
+ (mSize - 2 * roundRadius) / 3, mSize - roundRadius,
roundRadius + (mSize - 2 * roundRadius) * 2 / 3);
canvas.drawOval(oval, mPaint);
}
// 画小球
mPaint.setStyle(Style.FILL_AND_STROKE);
canvas.drawCircle(p.x, p.y, roundRadius, mPaint);
}
这部分就不用多说了,全是canvas自带的方法。到这后,不得不考虑3条轨迹的问题了,翻遍canvasAPI,硬是没看到画斜着的椭圆的方法!但是还是有意外收获的,canvas.rotate(degrees, px, py),以(px,py)为基准点旋转canvas,旋转角度为degrees,我去,惊喜啊!这三条轨迹不都是长得一个样吗,我画完一条后,转动一下canvas再画一条同样的轨迹,不就可以得到第二条斜着的椭圆轨迹吗,同理也可得到第三条轨迹了啊。这三条轨迹的问题不就解决了吗!其实惊喜还远远不止这里,让三个小球同步的动画起来,岂不是也不用单独做了,我们给第一个小球添加动画后,旋转canvas后给第二个小球,第三个小球加同样的动画,它们自然也就同步了。那问题不就只有第一个小球的动画了!接着看代码:
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if (anim == null) {//如果没开启动画,开启动画
startTrackAnim();
}
drawTrack(canvas, roundPoint);//画水平椭圆及小球
canvas.rotate(120, mSize / 2, mSize / 2);//旋转一圈是360度,这里有三条退机,所以他们间隔为120度,旋转120度,画椭圆及小球
drawTrack(canvas, roundPoint);
canvas.rotate(120, mSize / 2, mSize / 2);//旋转120度,画椭圆及小球
drawTrack(canvas, roundPoint);
mPaint.reset();
}
private void startTrackAnim() {
TypeEvaluator<PointF> evaluator = new OvalTypeEvaluator(trackWidth / 2,
trackHeight / 2, roundRadius, roundRadius
+ (mSize - 2 * roundRadius) / 3);//初始化自定义的估值器;
anim = new ValueAnimator().ofObject(evaluator, new PointF());//初始化动画
anim.setDuration(ANIM_DURATION);//设置动画周期
anim.addUpdateListener(new AnimatorUpdateListener() {//监听动画过程
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//动画变化时,获取到当前小球的位置,重画界面
roundPoint = (PointF) animation.getAnimatedValue();
Log.i(ParticleView.class.getSimpleName(),"roundPoint="+roundPoint);
invalidate();
}
});
anim.setRepeatCount(-1);//设置动画重复次数(-1,为无限重复 )
anim.start();//开启动画
}
上面代码主要就是一个属性动画的调用,最后,关键就剩下TypeEvaluator估值器的实现了,我们要通过它来实现一个椭圆的轨迹,下面是OvalTypeEvaluator:
public class OvalTypeEvaluator implements TypeEvaluator<PointF> {
private float a;//椭圆短半轴
private float b;//椭圆长半轴
private float pandingLeft;//椭圆轨迹离view的左边距
private float pandingTop;//椭圆轨迹离view的上边距
/**
*
* @param a椭圆长半轴
* @param b椭圆短半轴
* @param pandingLeft椭圆轨迹离view的左边距
* @param pandingTop椭圆轨迹离view的上边距
*/
public OvalTypeEvaluator(float a,float b,float pandingLeft,float pandingTop) {
this.a = a;
this.b = b;
this.pandingLeft = pandingLeft;
this.pandingTop = pandingTop;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
//椭圆标准公式x^2/a^2+y^2/b^2=1,其中a为椭圆长轴,b为椭圆短轴;
PointF p = new PointF();
if(fraction<=0.5){
//动画前半段,x坐标是递增的
p.x = pandingLeft+a*2.0f*fraction*2.0f;
p.y = b+pandingTop-(float)(Math.sqrt(1-(p.x-pandingLeft-a)*(p.x-pandingLeft-a)/a/a))*b;
}else{
//动画后半段,x是递减的
p.x = pandingLeft+a*2-(2*a*(fraction-0.5f)*2);
p.y = b+pandingTop+(float)(Math.sqrt(1-(p.x-pandingLeft-a)*(p.x-pandingLeft-a)/a/a))*b;
}
return p;
}
}
这个代码就不细说了,完全是根据数学公式x^2/a^2+y^2/b^2=1,然后x,y加入偏移量后得到的,文字实在不好描述,有看不懂的可私聊交流...
到这里,这个等待加载的view基本就完成了,主要难点上面都有注释,有处理不当的地方还望大家指教...
最后附上源码:点这里!!!