在Android自定义视图中模拟动画

4
我有一个自定义视图,看起来像一个从0到9的数字旋转轮盘。基本上在我从服务器下载东西的时候它会从0滚动到9,当值返回时,动画停在该位置。动画是通过更新我绘制的文本的Y值来实现的。
以下是一些相关的代码:
public class TimeSpinner extends View
{

    .....
    private void initialize()
    {
    .....

        handler = new Handler();
        repetitiveRunnable = new Runnable() {
            @Override
            public void run() {
                updateData();
            }
        };

    }

    public void updateData() {

        float delta = 3;

        mDigitY += delta;
        mDigitAboveY += delta;
        mDigitBelowY += delta;

        //Test if animation needs to stop
        if (mCurrentDigit == animateToDigit) {
            handler.removeCallbacks(repetitiveRunnable);
        } else {
            handler.postDelayed(repetitiveRunnable, 5);
        }

        invalidate();
    }


    public void startLoadingDigit() {
        handler.postDelayed(repetitiveRunnable, 5);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);

        canvas.drawText(mDigitString, mDigitX, mDigitY, mDigitPaint);
        canvas.drawText(mDigitAboveString, mDigitX, mDigitAboveY, mDigitPaint);
        canvas.drawText(mDigitBelowString, mDigitX, mDigitBelowY, mDigitPaint);

    }

}

问题在于UI线程还需要在其他视图上进行绘制,所以根据手机速度的不同,动画可能不够流畅。就像帧率不佳或有时会停顿半秒钟。在功能强大的设备上效果还可以。

现在问题来了,我该怎么做才能使动画平滑,而不受应用程序正在执行的影响?这个View很简单,不是SurfaceView。我是否应该使用某种线程?提供一些代码示例将会很棒。

后来我尝试使用AsycTask更新Y坐标。

public class TimeSpinnerTask extends AsyncTask<Void, Void, Void> {

        @Override
        protected Void doInBackground(Void... arg0) {
            Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

            while (animationRunning) {

                updateData();
                publishProgress();

                try {
                    Thread.sleep(35);
                } catch (InterruptedException e) {

                }
            }
            return null;
        }


        @Override
        protected void onProgressUpdate(Void... values) {
            invalidate();
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Void result) {
            invalidate();
            super.onPostExecute(result);
        }
    }

public void updateData() {

        mDigitY += delta;
        mDigitAboveY += delta;
        mDigitBelowY += delta;

        if (mDigitAboveY > findCenterY(mCurrentDigit)) {
            setCurrentDigit(mDigitAbove);
        }

        if (mCurrentDigit == animateToDigit) {

            animationRunning = false;

            setCurrentDigit(animateToDigit);
        }
    }

尽管看起来更加流畅,但在旧设备上仍然会出现卡顿,有时甚至会在暂停1秒后才继续动画。
asynctask是通过executeOnExecutor启动的,并且该视图显示在Android Maps V2的地图控件上。当地图正在加载瓦片时,就会出现卡顿。
也许我没有选择最佳方法。这是我想要获得的最终结果: enter image description here 有什么想法吗?

你使用了5毫秒的postDelayed?这意味着200帧每秒,为什么? - pskink
每个postDelayed都会将Y坐标增加3...在5毫秒时,我得到了一个合理的速度。这可能与没有使用单独的线程有关... - Alin
1
@pskink,我认为这并不容易。因为我正在进行动画制作,所以需要在屏幕上显示2个数字,当滚动发生时,我不确定能否使用动画来实现这一点。 - Alin
是的,但动画需要在自定义视图内部,因为我正在跟踪数字和动画到返回值。 - Alin
我不明白,在applyTransformation中什么都不做,只需使用Log.d打印interpolatedTime,然后一切就会变得很明显。 - pskink
显示剩余2条评论
3个回答

10

你是否尝试过使用ValueAnimator?它专门用于管理与你的动画类似的动画。它将为您处理线程和帧速率。

// Create a new value animator that will use the range 0 to 1
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);

// It will take 5000ms for the animator to go from 0 to 1
animator.setDuration(5000);

// Callback that executes on animation steps. 
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = ((Float) (animation.getAnimatedValue())).floatValue();

        Log.d("ValueAnimator", "value=" + value);

        // Here you can now translate or redraw your view
        // You need to map 'value' to your animation in regards to time
        // eg) mDigitY = value; invalidate();
    }
});

你还可以尝试使用OvershootInterpolator,可能会在动画结束时实现弹性效果。

animator.setInterpolator(new OvershootInterpolator());

有趣,所以我可以在我的自定义视图中使用这个动画器吗?我会看一下的。 - Alin
我已经更新了代码,以更好地解释需要完成的任务。需要对您当前的算法进行一些重新设计。例如,您可以删除“delta”的概念。 - Kyle Ivey
addUpdateListenerValueAnimator 的一个方法。 - grandouassou

0

如果您有其他同时运行的AsyncTasks,可以尝试使用task.executeOnExecutor在单独的线程上启动AsyncTask以与它们解耦。但绘图仍将在主线程上完成。

我建议搜索其他可能阻塞UI线程或导致垃圾收集器启动的大内存分配的问题。在logcat中查找GC_FOR_ALLOC freed 190K, 13% free 5409K/6164K, paused 67ms, total 71ms

此外,drawText是相对昂贵的操作。您可以缓存字体绘制的结果,并从位图中绘制它。

但是,如果地图阻塞了UI线程,则除非直接使用OpenGL,否则无法做太多事情。


异步任务使用executeOnExecutor启动,并在Android Maps V2的地图控件上显示视图。当地图正在加载瓦片时,会出现抖动现象。 - Alin
我添加了另外一件你可以尝试的东西 :) - atok

0

把这些图片放在应用程序的drawable文件夹中怎么样?然后您可以有一个animation文件夹,其中包含一个定义从0到9的图像转换的XML文件。将此动画设置为imageView。最后,当您从Web服务返回数字时,请将imageView关联的图像重置为与该数字对应的图像。

动画XML(number_slider.xml)

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:drawable="@drawable/num_0"
        android:duration="150"/>
    <item
        android:drawable="@drawable/num_1"
        android:duration="150"/>
    <item
        android:drawable="@drawable/num_2"
        android:duration="150"/>
    <item
        android:drawable="@drawable/num_3"
        android:duration="150"/>
</animation-list> 

代码: a)开始动画

imageView.setBackgroundResource(R.anim.number_slider);
AnimationDrawable  loadingViewAnim = (AnimationDrawable) thumbnail.getBackground();
loadingViewAnim.start();

b) 停止动画并将从 Web 服务返回的数字设置为图像视图

loadingViewAnim.stop();
imageView.setBackgroundColor(Color.WHITE);
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.num_3));

最后,您可以在number_slider.xml文件中定义各种效果,使动画更加“生动”。您可以引入“弹跳效果”、“速度控制”等任何您能想象到的效果。此外,不需要序列化,我认为这种方法将使动画在所有设备配置上更加流畅。


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