在画布上绘制圆形/弧线的动画

4
更新:20/11/13:这仍未解决。
我正在尝试在自定义视图中动画创建一个圆。我想要动画化周长的创建 - 在动画开始时有一段弧线,到动画结束时,圆形完成。
我通过遵循这个答案成功实现了这一点 - https://dev59.com/f2gu5IYBdhLWcg3wt5Lt#11168363 - 并只需添加一个重复的Handler来增加sweepAngle并调用invalidate(); 然而,这不能按照我想要的方式工作,因为我无法设置完成圆的持续时间。
这是我的当前代码:
  Path mOuterCirclePath = new Path();
        final RectF mOval = new RectF();
        int mSweepAngle = 0;
        int mStartAngle = -90;

    @Override
        protected void onDraw(Canvas canvas) {

                mOval.set(0, 0, mBorderRect.right, mBorderRect.bottom); //mBorderRect is the outside border of my view
                mOuterCirclePath.arcTo(mOval, 0, 0, true);
                canvas.drawArc(mOval, -mStartAngle, mSweepAngle, false,
                        mOuterCirclePaint);
    }

        public void drawOuterCircle() {

                startUpdateOuterCircle();

        }

        Runnable mCircleInvalidator = new Runnable() {
            @Override
            public void run() {
                              if (mSweepAngle <= 360) {
                                mSweepAngle++
    }
                invalidate();
                mHandler.postDelayed(mCircleInvalidator, 20); 
            }
        };

        void startUpdateOuterCircle() {
            mCircleInvalidator.run();
        }

        void stopUpdateOuterCircle() {
            mHandler.removeCallbacks(mCircleInvalidator);
        }

主要问题是:如何设置动画的持续时间?我希望这可以像动画器类中那样轻松更改。
附注:据我所知,我无法使用任何动画器,例如ViewPropertyAnimator或ObjectAnimator。如果我错了,请纠正我!

检查这个 https://dev59.com/xm455IYBdhLWcg3wD_sB - Arun
@RiThBo,你需要为你的Arc类创建getter和setter方法。我有一个名为“sweepAngle”的变量,并创建了getSweepAngle()和setSweepAngle()方法。你的代码与我的非常相似,所以我认为你需要创建getMSweepAngle()和setMSweepAngle()方法。我已经在下面添加了一个答案来澄清。 - Zhang
请查看 https://medium.com/@dbottillo/animate-android-custom-view-a94473412054。 - CoolMind
2个回答

2
要使用自定义对象动画属性,您需要按照Stackoverflow答案中所述创建getter和setter方法Android Property Animation 在我的情况下,我有一个CircleView类和一个sweepAngle变量,类似于这样:
public class CircleView extends View
{
    public float sweepAngle = 0.0f; // start at 0 degrees

    ...

    // ---------------------------------------------------
    // getter and setter method to turn "sweepAngle"
    // into a property for ObjectAnimator to use
    // ---------------------------------------------------
    public float getSweepAngle()
    {
        return sweepAngle;
    }

    public void setSweepAngle(float angle)
    {
        sweepAngle = angle;
    }

    // ---------------------------------------------------
    // animation method to be called outside this class
    // ---------------------------------------------------
    public void animateArc(float fromAngle, float toAngle, long duration)
    {
        sweepAngle = fromAngle;

        invalidate();

        ObjectAnimator anim = ObjectAnimator.ofFloat(this, "sweepAngle", fromAngle, toAngle);
        anim.setDuration(duration);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
        {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator)
            {
                // calling invalidate(); will trigger onDraw() to execute
                invalidate();
            }
        });
        anim.start();
    }
}

注意:

上述具体示例是Hithredin建议使用ObjectAnimator的演示。

自定义简易实现

我建议您不要使用下面的代码块,但我在这里包含它,以防你想知道我如何做出自己的自定义实现,我发现它比上面的代码慢8毫秒,只允许线性插值(没有缓入缓出),它与上面的代码类似但略有不同:

public void animateArc(float fromAngle, float toAngle, long duration)
{
    sweepAngle = fromAngle;

    invalidate();

    // ---------------------------------------------------------------
    // Note: might want to change use absolute value for totalAngle
    // in case you want the animation to play backwards
    // ---------------------------------------------------------------
    float totalAngle = toAngle - fromAngle;

    updateArc(totalAngle, duration);
}

public void updateArc(final float totalAngle, final long durationInMilliSeconds)
{
    final long stepMilliseconds = 1;

    handler.postDelayed(new Runnable()
    {
        @Override
        public void run()
        {
            // ------------------------------------------------
            // 17790.0 is a number I fine tuned and came out
            // with on my Android phone to get the animation
            // as close as possible to the animation
            // duration specified
            // ------------------------------------------------
            double stepAngle = totalAngle / 1000.0 * (17790.0 / durationInMilliSeconds);

            sweepAngle += stepAngle;
            animationTime += stepMilliseconds;
            invalidate();

            if(animationTime < durationInMilliSeconds && sweepAngle < totalAngle)
            {
                updateArc(totalAngle, durationInMilliSeconds);
            }
            else
            {
                // --------------------------------------------------------
                // time duration reached, stop incrementing/decrementing
                // angle and reset animation time back to 0
                // --------------------------------------------------------
                animationTime = 0;
            }
        }
    }, 0);
}

我肯定是在“穷人”的领域里努力了。很棒的答案,我自己想不出来。干得好! - Aaron Smentkowski

1

使用Handler时,您的消息不能保证按时到达,因此动画可能看起来很奇怪。

  • 如果您想保留drawArc代码,可以使用PropertyAnimation。在onAnimationUpdate()方法中,您将使视图无效。 请注意,invalidate不是同步保证的。如果动画非常快,则可能会跳过某些帧。

  • 您还有另一种解决方案,即Drawable Animation。您需要创建与动画所需的图片数量相同的图片,但实现起来很容易。


1
哇,我和RiThBo一样遇到了同样的问题,并实现了自己的动画方法,但是它偏移了约8毫秒。但是,通过遵循这个答案,我能够使用ObjectAnimator来执行动画。与我的自定义实现一样,它可能不准确,但我可以通过使用Object Animator获得缓动或无缓动的好处,而不仅仅是使用自己的自定义实现进行线性动画。写起来也更短 :P - Zhang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接