You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Canvas上间隔延迟绘制多张Bitmap并实现淡入动画效果?

Fixing Bitmap Fade-In Sequence with Delay on Android Canvas

Hey there! I get it—trying to nail that smooth delayed fade-in sequence for multiple Bitmaps on Canvas can be frustrating when postInvalidateDelay() doesn't behave like you expect. Let's break down why that's happening and walk through a solid, working solution.

Why postInvalidateDelay() Isn't Working for You

The core issue is that postInvalidateDelay() only tells the View to redraw itself after a delay—but if you're not tracking when each Bitmap should start fading in and its current transparency level, that single delayed redraw will just render all Bitmaps at once, no animation included. You need to manage each Bitmap's animation state independently to make the sequence work.

Step-by-Step Solution

Here's a complete custom View implementation that handles delayed fade-ins properly. We'll use a Handler to schedule each Bitmap's start time, and track transparency to create the smooth fade effect.

Kotlin Implementation

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.View

// Data class to track each Bitmap's animation state
data class BitmapAnimationState(
    val bitmap: Bitmap,
    var startTime: Long = 0L,
    val delay: Long = 500L, // Delay before this Bitmap starts fading in
    val fadeDuration: Long = 1000L // How long the fade-in takes
)

class FadeInBitmapView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val bitmapAnimationStates = mutableListOf<BitmapAnimationState>()
    private val mainHandler = Handler(Looper.getMainLooper())
    private var currentBitmapIndex = 0

    // Call this to set the list of Bitmaps you want to display
    fun setBitmaps(bitmaps: List<Bitmap>) {
        bitmapAnimationStates.clear()
        // Assign incremental delays to each Bitmap (500ms apart here)
        bitmaps.forEachIndexed { index, bitmap ->
            bitmapAnimationStates.add(BitmapAnimationState(bitmap, delay = 500L * index))
        }
        currentBitmapIndex = 0
        startNextBitmapAnimation()
    }

    private fun startNextBitmapAnimation() {
        if (currentBitmapIndex >= bitmapAnimationStates.size) return

        val currentState = bitmapAnimationStates[currentBitmapIndex]
        currentState.startTime = System.currentTimeMillis()

        // Schedule the next Bitmap's animation to start after this one's delay
        mainHandler.postDelayed({
            currentBitmapIndex++
            startNextBitmapAnimation()
            invalidate()
        }, currentState.delay)

        // Keep redrawing until this Bitmap's fade-in is complete
        mainHandler.post(object : Runnable {
            override fun run() {
                invalidate()
                val elapsedTime = System.currentTimeMillis() - currentState.startTime
                if (elapsedTime < currentState.fadeDuration) {
                    // Schedule next frame (~60fps)
                    mainHandler.postDelayed(this, 16)
                }
            }
        })
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val paint = Paint()

        bitmapAnimationStates.forEach { state ->
            val elapsedTime = System.currentTimeMillis() - state.startTime
            // Skip if this Bitmap hasn't started its animation yet
            if (elapsedTime < 0) return@forEach

            // Calculate current alpha (0 = fully transparent, 255 = fully opaque)
            val alpha = when {
                elapsedTime < state.fadeDuration -> {
                    ((elapsedTime.toFloat() / state.fadeDuration) * 255).toInt()
                }
                else -> 255 // Fade-in complete
            }

            paint.alpha = alpha
            // Draw the Bitmap centered on the View (adjust position as needed)
            val drawX = (width - state.bitmap.width) / 2f
            val drawY = (height - state.bitmap.height) / 2f
            canvas.drawBitmap(state.bitmap, drawX, drawY, paint)
        }
    }

    // Clean up Handler callbacks to avoid memory leaks
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mainHandler.removeCallbacksAndMessages(null)
    }
}

Java Implementation

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import java.util.ArrayList;
import java.util.List;

public class FadeInBitmapView extends View {

    // Helper class to track each Bitmap's animation state
    private static class BitmapAnimationState {
        Bitmap bitmap;
        long startTime;
        long delay;
        long fadeDuration;

        BitmapAnimationState(Bitmap bitmap, long delay) {
            this.bitmap = bitmap;
            this.delay = delay;
            this.fadeDuration = 1000L;
        }
    }

    private List<BitmapAnimationState> bitmapAnimationStates = new ArrayList<>();
    private Handler mainHandler = new Handler(Looper.getMainLooper());
    private int currentBitmapIndex = 0;

    public FadeInBitmapView(Context context) {
        super(context);
    }

    public FadeInBitmapView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FadeInBitmapView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    // Call this to set the list of Bitmaps you want to display
    public void setBitmaps(List<Bitmap> bitmaps) {
        bitmapAnimationStates.clear();
        // Assign incremental delays to each Bitmap (500ms apart here)
        for (int i = 0; i < bitmaps.size(); i++) {
            bitmapAnimationStates.add(new BitmapAnimationState(bitmaps.get(i), 500L * i));
        }
        currentBitmapIndex = 0;
        startNextBitmapAnimation();
    }

    private void startNextBitmapAnimation() {
        if (currentBitmapIndex >= bitmapAnimationStates.size()) return;

        BitmapAnimationState currentState = bitmapAnimationStates.get(currentBitmapIndex);
        currentState.startTime = System.currentTimeMillis();

        // Schedule the next Bitmap's animation to start after this one's delay
        mainHandler.postDelayed(() -> {
            currentBitmapIndex++;
            startNextBitmapAnimation();
            invalidate();
        }, currentState.delay);

        // Keep redrawing until this Bitmap's fade-in is complete
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                invalidate();
                long elapsedTime = System.currentTimeMillis() - currentState.startTime;
                if (elapsedTime < currentState.fadeDuration) {
                    // Schedule next frame (~60fps)
                    mainHandler.postDelayed(this, 16);
                }
            }
        });
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();

        for (BitmapAnimationState state : bitmapAnimationStates) {
            long elapsedTime = System.currentTimeMillis() - state.startTime;
            // Skip if this Bitmap hasn't started its animation yet
            if (elapsedTime < 0) continue;

            // Calculate current alpha (0 = fully transparent, 255 = fully opaque)
            int alpha;
            if (elapsedTime < state.fadeDuration) {
                alpha = (int) ((elapsedTime / (float) state.fadeDuration) * 255);
            } else {
                alpha = 255; // Fade-in complete
            }

            paint.setAlpha(alpha);
            // Draw the Bitmap centered on the View (adjust position as needed)
            float drawX = (getWidth() - state.bitmap.getWidth()) / 2f;
            float drawY = (getHeight() - state.bitmap.getHeight()) / 2f;
            canvas.drawBitmap(state.bitmap, drawX, drawY, paint);
        }
    }

    // Clean up Handler callbacks to avoid memory leaks
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mainHandler.removeCallbacksAndMessages(null);
    }
}

Key Notes to Make This Work

  • Independent Animation Tracking: Each Bitmap has its own startTime, delay, and fadeDuration so we can control when it starts fading and how long the effect lasts.
  • Smooth Redraws: We use a Handler to schedule redraws every ~16ms (to hit 60fps) until each Bitmap's fade-in is complete.
  • Memory Safety: We clear Handler callbacks when the View is detached from the window to prevent memory leaks.
  • Customizable Timing: Adjust the delay and fadeDuration values to match your desired animation speed.

To use this, just call setBitmaps() on your FadeInBitmapView instance with your list of loaded Bitmaps.

内容的提问来源于stack exchange,提问作者Wajee ul Hassan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:30:38