椭圆轨迹的等待加载view--ParticleView

本文介绍了如何在Android中创建一个自定义的ParticleView,该视图显示小球沿椭圆轨迹进行加载动画。作者在实现过程中遇到了绘制斜椭圆轨迹、属性动画应用到椭圆路径以及数学转换等挑战。文章详细阐述了问题解决过程,从初始化视图大小、设置小球起始位置到绘制轨迹和小球的方法。最后,提供了源码供读者参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        前两天在看《生活大爆炸》时,在片头看到这样一个动画(印象中看到过很多次),想起用在安卓等待加载的情景下好像还不错,于是尝试动手写这样一个等待加载的ParticleView。不多说,直接上效果:

        开撸之前,先简单的分析一下这个效果。我的第一反应是:这东西其实也还是比较简单的,三个椭圆,然后让3个实心圆沿着这三个椭圆轨迹动画起来,就是这么简单,于是我动手了...

        当开始着手写这个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基本就完成了,主要难点上面都有注释,有处理不当的地方还望大家指教...

        最后附上源码:点这里!!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值