如何将一个视图沿着圆形轨迹进行动画?

7
我想为一个View设置一个无限移动的动画,绕着一个圆圈,就像下面的图片一样。我该如何实现?
这个View可以是TextView或任何简单或复杂的视图。
我知道我可以把我的视图放在一个ViewGroup中,并设置旋转动画,但我不想旋转视图,因为视图的方向很重要。

enter image description here

3个回答

7
我不知道有任何本地方法可以实现这个功能,因此我认为您最好的方法是确定动画的路径(xy坐标),并创建一个Handler以重复调用postDelayed。每次处理程序触发时,都会将视图平移到路径中的下一个点。请参考此处了解更多信息。 编辑:我已经创建了一个解决方案,并测试了向左、向右移动和圆形移动。 ViewPathAnimator.java

import android.graphics.Path;
import android.graphics.PathMeasure;
import android.os.Handler;
import android.util.Pair;
import android.view.View;

import java.lang.ref.WeakReference;
import java.util.HashMap;

public class ViewPathAnimator
{
    public static final int DEFAULT_DELAY     = 1000 / 10;
    public static final int DEFAULT_FRAMESKIP = 3;

    private static Handler                        handler;
    private static HashMap<Integer, PathRunnable> animatedViews;

    public static void animate(View view, Path path)
    {
        animate(view, path, DEFAULT_DELAY, DEFAULT_FRAMESKIP);
    }

    public static void animate(View view, Path path, int delay)
    {
        animate(view, path, delay, DEFAULT_FRAMESKIP);
    }

    public static void animate(View view, Path path, int delay, int frameSkip)
    {
        if (animatedViews == null)
        {
            animatedViews = new HashMap<>();
        }

        if (handler == null)
        {
            handler = new Handler();
        }

        if (animatedViews.containsKey(view.hashCode()))
        {
            cancel(view);
        }

        PathRunnable runnable = new PathRunnable(view, path, delay, frameSkip);
        animatedViews.put(view.hashCode(), runnable);
        handler.postDelayed(runnable, delay);
    }

    public static void cancel(View view)
    {
        if (animatedViews != null && handler != null)
        {
            PathRunnable task = animatedViews.get(view.hashCode());
            if (task != null)
            {
                handler.removeCallbacks(task);
                animatedViews.remove(view.hashCode());
            }
        }
    }

    private static class PathRunnable implements Runnable
    {
        private WeakReference<View> view;
        Pair<Float, Float>[] points;
        private int delay;
        private int frameSkip;
        private int frame;

        PathRunnable(View view, Path path, int delay, int frameSkip)
        {
            this.view = new WeakReference<>(view);
            this.points = getPoints(path);
            this.delay = delay;
            this.frameSkip = Math.max(frameSkip, 0);
            this.frame = 0;
        }

        @Override
        public void run()
        {
            frame = (frame + frameSkip + 1) % points.length;
            Pair<Float, Float> pair = points[frame];

            View v = view.get();
            if (v != null)
            {
                v.setTranslationX(pair.first);
                v.setTranslationY(pair.second);

                handler.postDelayed(this, delay);
            }
        }

        // https://dev59.com/BWsz5IYBdhLWcg3wVGPJ
        private Pair<Float, Float>[] getPoints(Path path)
        {
            PathMeasure pathMeasure = new PathMeasure(path, true);
            int         frames      = (int) pathMeasure.getLength();

            Pair<Float, Float>[] pointArray   = new Pair[frames];
            float                length       = pathMeasure.getLength();
            float                distance     = 0f;
            float                speed        = length / pointArray.length;
            int                  counter      = 0;
            float[]              aCoordinates = new float[2];

            while ((distance < length) && (counter < pointArray.length))
            {
                // get point from the path
                pathMeasure.getPosTan(distance, aCoordinates, null);
                pointArray[counter] = new Pair<>(aCoordinates[0], aCoordinates[1]);
                counter++;
                distance = distance + speed;
            }

            return pointArray;
        }
    }
}

这可以通过给 Graphics 路径添加动画来实现,例如:
View view = findViewById(R.id.text);
Path path = new Path();
path.addCircle(0, 0, 100, Path.Direction.CW);

ViewPathAnimator.animate(view, path, 1000/ 30, 2);

真的,但对于更复杂的路径,您可能仍然需要指定路径上的点。对于圆形,您至少可以通过程序计算路径。 - Kevin
这听起来是一个不错的解决方案,但我无法使其工作。它在原地随机动画,就像随机抖动一样。我增加了圆形半径,但没有运气。 - Morteza Rastgoo
奇怪,我刚刚在Marshmallow模拟器和Jellybean设备上再次尝试了一下。两者都能工作。你有进行任何翻译、调整大小或重新布局吗? - Kevin
问题在于动画的速度。我该如何控制它的速度? - Morteza Rastgoo
我把速度变量乘以了一个数,结果应用程序崩溃了。 - Morteza Rastgoo
显示剩余5条评论

3
这个问题可以通过我的解决方案得到解决,它会将视图容器顺时针旋转,而TextView逆时针旋转:)。
布局:
<RelativeLayout
    android:id="@+id/cont"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="150dp"
    android:animateLayoutChanges="true"
    android:clipChildren="false">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="M"
        android:textSize="60sp"/>
</RelativeLayout>

动画:
    View v = findViewById(R.id.cont);
    View text = findViewById(R.id.text);

    int durationMillis = 10000;

    RotateAnimation r = new RotateAnimation(0, 360,
            Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    r.setDuration(durationMillis);
    r.setInterpolator(new LinearInterpolator());
    r.setRepeatMode(Animation.RESTART);
    r.setRepeatCount(Animation.INFINITE);
    text.startAnimation(r);

    RotateAnimation rC = new RotateAnimation(360, 0,
            Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    rC.setDuration(durationMillis);
    rC.setInterpolator(new LinearInterpolator());
    rC.setRepeatMode(Animation.RESTART);
    rC.setRepeatCount(Animation.INFINITE);
    v.startAnimation(rC);

但如果您想将对象移动到其他路径并寻找一般解决方案,请查看@Kevin的解决方案(感谢他)。


1

也许ValueAnimator可以帮忙?根据它的动画进度,你可以计算出路径的坐标,并将其设置到你的视图中。


你能在这里粘贴一个示例吗? 我不熟悉动画。 - Morteza Rastgoo

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