如何正确创建自定义动画可绘制?

6

背景

我在许多地方搜索了如何在不使用内置的可绘制对象并且不对视图进行动画处理的情况下动画化可绘制对象。

其中的原因是我需要准备一个定制化的动画,以后可能会有不同的要求。

现在,我正在创建一个基本的可动画化的可绘制对象,它只是旋转其中包含的某个位图。

我已经将其设置在imageView上,但是我希望能够在任何类型的视图上使用它,甚至是重写了onDraw函数的自定义视图。

问题

无论视图大小如何,我都无法找到如何显示可绘制对象而不被切割的方法。这是我看到的:

enter image description here

代码

以下是代码:

private class CircularAnimatedDrawable extends Drawable implements Animatable {
    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();
    private static final int ANGLE_ANIMATOR_DURATION = 2000;
    private final RectF fBounds = new RectF();
    private float angle = 0;
    private ObjectAnimator mObjectAnimatorAngle;
    private final Paint mPaint;
    private boolean mRunning;
    private final Bitmap mBitmap;

    public CircularAnimatedDrawable(final Bitmap bitmap) {
        this.mBitmap = bitmap;
        mPaint = new Paint();
        setupAnimations();
    }

    public float getAngle() {
        return this.angle;
    }

    public void setAngle(final float angle) {
        this.angle = angle;
        invalidateSelf();
    }

    @Override
    public Callback getCallback() {
        return mCallback;
    }

    @Override
    public void draw(final Canvas canvas) {
        canvas.save();
        canvas.rotate(angle);
        canvas.drawBitmap(mBitmap, 0, 0, mPaint);
        canvas.restore();
    }

    @Override
    public void setAlpha(final int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(final ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }

    @Override
    protected void onBoundsChange(final Rect bounds) {
        super.onBoundsChange(bounds);
        fBounds.left = bounds.left;
        fBounds.right = bounds.right;
        fBounds.top = bounds.top;
        fBounds.bottom = bounds.bottom;
    }

    private void setupAnimations() {
        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, "angle", 360f);
        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
    }

    @Override
    public void start() {
        if (isRunning())
            return;
        mRunning = true;
        mObjectAnimatorAngle.start();
        invalidateSelf();
    }

    @Override
    public void stop() {
        if (!isRunning())
            return;
        mRunning = false;
        mObjectAnimatorAngle.cancel();
        invalidateSelf();
    }

    @Override
    public boolean isRunning() {
        return mRunning;
    }

}

使用方式:

    final ImageView imageView = (ImageView) findViewById(R.id.imageView);
    final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.spinner_76_inner_holo);

    final CircularAnimatedDrawable circularAnimatedDrawable = new CircularAnimatedDrawable(bitmap);
    circularAnimatedDrawable.setCallback(imageView);
    circularAnimatedDrawable.start();
    imageView.setImageDrawable(circularAnimatedDrawable);

这个问题

我该如何设置使可绘制对象适应视图大小?

我应该使用位图大小?fBounds?还是两者都要用?或者可能有其他方法?


尝试缩放 - Sagar Pilkhwal
1
重写getIntrisic*方法,顺便说一句,在我看来你不需要一个回调。 - pskink
如果我不使用回调函数,我认为动画将无法正常工作,就像这里所写的一样:http://developer.android.com/reference/android/graphics/drawable/Drawable.html#invalidateSelf()。关于您的解决方案,请展示一个应该做什么的例子。 - android developer
1
在 getIntrinsicWidth 中返回 mBitmap 的宽度,同样适用于高度。 - pskink
@pskink 我也应该使用 fBounds 吗? - android developer
显示剩余10条评论
2个回答

3

尝试使用这个修改过的Drawable版本:

class CircularAnimatedDrawable extends Drawable implements Animatable, TimeAnimator.TimeListener {
    private static final float TURNS_PER_SECOND = 0.5f;
    private Bitmap mBitmap;
    private boolean mRunning;
    private TimeAnimator mTimeAnimator = new TimeAnimator();
    private Paint mPaint = new Paint();
    private Matrix mMatrix = new Matrix();

    public CircularAnimatedDrawable(final Bitmap bitmap) {
        mBitmap = bitmap;
        mTimeAnimator.setTimeListener(this);
    }
    @Override
    public void draw(final Canvas canvas) {
        canvas.drawBitmap(mBitmap, mMatrix, mPaint);
    }
    @Override
    protected void onBoundsChange(Rect bounds) {
        Log.d(TAG, "onBoundsChange " + bounds);
        mMatrix.setRectToRect(new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()),
                new RectF(bounds),
                Matrix.ScaleToFit.CENTER);
    }
    @Override
    public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
        Rect b = getBounds();
        mMatrix.postRotate(360 * TURNS_PER_SECOND * deltaTime / 1000, b.centerX(), b.centerY());
        invalidateSelf();
    }
    @Override
    public void setAlpha(final int alpha) {
        mPaint.setAlpha(alpha);
    }
    @Override
    public void setColorFilter(final ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }
    @Override
    public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }
    @Override
    public void start() {
        if (isRunning())
            return;
        mRunning = true;
        mTimeAnimator.start();
        invalidateSelf();
    }
    @Override
    public void stop() {
        if (!isRunning())
            return;
        mRunning = false;
        mTimeAnimator.cancel();
        invalidateSelf();
    }
    @Override
    public boolean isRunning() {
        return mRunning;
    }
}

编辑:没有使用Animator的版本(使用[un]scheduleSelf),注意它使用View的Drawable.Callback机制,因此通常不能直接从onCreate中启动,因为View还没有附加Handler。

class CircularAnimatedDrawable extends Drawable implements Animatable, Runnable {
    private static final float TURNS_PER_SECOND = 0.5f;
    private static final long DELAY = 50;
    private Bitmap mBitmap;
    private long mLastTime;
    private boolean mRunning;
    private Paint mPaint = new Paint();
    private Matrix mMatrix = new Matrix();

    public CircularAnimatedDrawable(final Bitmap bitmap) {
        mBitmap = bitmap;
    }
    @Override
    public void draw(final Canvas canvas) {
        canvas.drawBitmap(mBitmap, mMatrix, mPaint);
    }
    @Override
    protected void onBoundsChange(Rect bounds) {
        Log.d(TAG, "onBoundsChange " + bounds);
        mMatrix.setRectToRect(new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()),
                new RectF(bounds),
                Matrix.ScaleToFit.CENTER);
    }
    @Override
    public void setAlpha(final int alpha) {
        mPaint.setAlpha(alpha);
    }
    @Override
    public void setColorFilter(final ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }
    @Override
    public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }
    @Override
    public void start() {
        if (isRunning())
            return;
        mRunning = true;
        mLastTime = SystemClock.uptimeMillis();
        scheduleSelf(this, 0);
        invalidateSelf();
    }
    @Override
    public void stop() {
        if (!isRunning())
            return;
        mRunning = false;
        unscheduleSelf(this);
        invalidateSelf();
    }
    @Override
    public boolean isRunning() {
        return mRunning;
    }
    @Override
    public void run() {
        long now = SystemClock.uptimeMillis();
        Rect b = getBounds();
        long deltaTime = now - mLastTime;
        mLastTime = now;
        mMatrix.postRotate(360 * TURNS_PER_SECOND * deltaTime / 1000, b.centerX(), b.centerY());
        scheduleSelf(this, now + DELAY);
        invalidateSelf();
    }
}

谢谢。它有效。然而,正如我所说的,TimeAnimator仅在API 16及以上版本中可用,而我使用的是API 14,因此我只更新了矩阵的部分。 - android developer
无论如何,我已经更新了我的原始答案,以防有人希望支持旧版本。 - android developer
增加了非动画版本。 - pskink
我认为我更喜欢TimeListener或ObjectAnimator,但还是谢谢。 - android developer

2

好的,解决方案如下:

    @Override
    public void draw(final Canvas canvas) {
        canvas.save();
        canvas.rotate(angle, fBounds.width() / 2 + fBounds.left, fBounds.height() / 2 + fBounds.top);
        canvas.translate(fBounds.left, fBounds.top);
        canvas.drawBitmap(mBitmap, null, new Rect(0, 0, (int) fBounds.width(), (int) fBounds.height()), mPaint);
        canvas.restore();
    }

    @Override
    public int getIntrinsicHeight() {
        return mBitmap.getHeight();
    }

    @Override
    public int getIntrinsicWidth() {
        return mBitmap.getWidth();
    }

它运行良好。我希望它足以应对未来的更改。

编辑:以下是对上述内容的优化,包括所有更改:

class CircularAnimatedDrawable extends Drawable implements Animatable {
    private static final Interpolator ANGLE_INTERPOLATOR = new LinearInterpolator();
    private static final int ANGLE_ANIMATOR_DURATION = 2000;
    private float angle = 0;
    private ObjectAnimator mObjectAnimatorAngle;
    private final Paint mPaint;
    private boolean mRunning;
    private final Bitmap mBitmap;
    private final Matrix mMatrix = new Matrix();

    public CircularAnimatedDrawable(final Bitmap bitmap) {
        this.mBitmap = bitmap;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        setupAnimations();
    }

    @SuppressWarnings("unused")
    public float getAngle() {
        return this.angle;
    }

    @SuppressWarnings("unused")
    public void setAngle(final float angle) {
        this.angle = angle;
        invalidateSelf();
    }

    @Override
    public void draw(final Canvas canvas) {
        final Rect b = getBounds();
        canvas.save();
        canvas.rotate(angle, b.centerX(), b.centerY());
        canvas.drawBitmap(mBitmap, mMatrix, mPaint);
        canvas.restore();
    }

    @Override
    public void setAlpha(final int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(final ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSPARENT;
    }

    @Override
    protected void onBoundsChange(final Rect bounds) {
        super.onBoundsChange(bounds);
        mMatrix.setRectToRect(new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()), new RectF(bounds),
                Matrix.ScaleToFit.CENTER);
    }

    @Override
    public int getIntrinsicHeight() {
        return mBitmap.getHeight();
    }

    @Override
    public int getIntrinsicWidth() {
        return mBitmap.getWidth();
    }

    private void setupAnimations() {
        mObjectAnimatorAngle = ObjectAnimator.ofFloat(this, "angle", 360f);
        mObjectAnimatorAngle.setInterpolator(ANGLE_INTERPOLATOR);
        mObjectAnimatorAngle.setDuration(ANGLE_ANIMATOR_DURATION);
        mObjectAnimatorAngle.setRepeatMode(ValueAnimator.RESTART);
        mObjectAnimatorAngle.setRepeatCount(ValueAnimator.INFINITE);
    }

    @Override
    public void start() {
        if (isRunning())
            return;
        mRunning = true;
        mObjectAnimatorAngle.start();
        invalidateSelf();
    }

    @Override
    public void stop() {
        if (!isRunning())
            return;
        mRunning = false;
        mObjectAnimatorAngle.cancel();
        invalidateSelf();
    }

    @Override
    public boolean isRunning() {
        return mRunning;
    }

}

你把它搞得太复杂了,特别是你绘制位图的方式很奇怪... - pskink
@pskink 也许吧,但我没有太多选择,因为我不得不改变一个广泛使用它们的库:https://github.com/dmytrodanylyk/circular-progress-button,并且我必须在短时间内完成自定义。 - android developer
@pskink,我的任务是定制一个库。我必须更改库中此视图的进度动画,并将其边界作为其中一个参数使用。为了帮助人们帮助我,我必须简化问题。您的解决方案对于这个任务不起作用,这就是为什么我说我需要包括边界和自定义视图。抱歉。 - android developer
我发布的可绘制对象可以对任何边界做出反应,尝试使用10x10或1000x1000的视图,您将看到它如何工作。使用矩阵,您可以将位图缩放到任何所需的边界。 - pskink
@pskink 抱歉,我没有仔细阅读你的解决方案,因为我已经找到了一个解决方案。如果视图希望在其特定范围内绘制可绘制对象(例如,向左并在垂直居中),您的代码是否也适用?如果是,您可以提供另一个答案供人们使用。 - android developer
显示剩余12条评论

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