CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-airbnb-android--lottie

Android library for rendering Adobe After Effects animations exported as JSON through Bodymovin with native performance and extensive customization options.

Pending
Overview
Eval results
Files

asset-management.mddocs/

Asset Management

Delegate interfaces for handling custom image assets, fonts, and dynamic text replacement. This system allows you to provide custom implementations for loading images, fonts, and replacing text content at runtime.

Capabilities

Image Asset Management

System for providing custom bitmap loading for image assets referenced in Lottie animations. Useful for loading images from network, custom storage, or applying transformations.

/**
 * Delegate for handling custom image asset loading
 */
public interface ImageAssetDelegate {
    /**
     * Fetch bitmap for the given image asset
     * @param asset The image asset to load
     * @return Bitmap to use, or null to use default loading
     */
    Bitmap fetchBitmap(LottieImageAsset asset);
}

/**
 * Image asset model containing metadata
 */
public class LottieImageAsset {
    // Asset identification
    public String getId();
    public String getFileName();
    public String getDirName();
    
    // Dimensions
    public int getWidth();
    public int getHeight();
    
    // Bitmap management
    public Bitmap getBitmap();
    public void setBitmap(Bitmap bitmap);
    
    // Internal methods
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public boolean hasBitmap();
}

Usage Examples:

// Basic image asset delegate
animationView.setImageAssetDelegate(new ImageAssetDelegate() {
    @Override
    public Bitmap fetchBitmap(LottieImageAsset asset) {
        // Load from custom location
        String imagePath = "custom/path/" + asset.getFileName();
        return BitmapFactory.decodeFile(imagePath);
    }
});

// Network image loading delegate
animationView.setImageAssetDelegate(new ImageAssetDelegate() {
    @Override
    public Bitmap fetchBitmap(LottieImageAsset asset) {
        try {
            String imageUrl = "https://cdn.example.com/images/" + asset.getFileName();
            URL url = new URL(imageUrl);
            InputStream inputStream = url.openConnection().getInputStream();
            return BitmapFactory.decodeStream(inputStream);
        } catch (IOException e) {
            Log.e("Lottie", "Failed to load image: " + asset.getFileName(), e);
            return null; // Use default loading
        }
    }
});

// Cached image loading with transformations
public class CachedImageDelegate implements ImageAssetDelegate {
    private final LruCache<String, Bitmap> imageCache;
    private final Context context;
    
    public CachedImageDelegate(Context context) {
        this.context = context;
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8; // Use 1/8th of available memory
        this.imageCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount() / 1024;
            }
        };
    }
    
    @Override
    public Bitmap fetchBitmap(LottieImageAsset asset) {
        String cacheKey = asset.getId();
        Bitmap cached = imageCache.get(cacheKey);
        if (cached != null) {
            return cached;
        }
        
        // Load and process image
        Bitmap original = loadImageFromAssets(asset.getFileName());
        if (original != null) {
            // Apply transformations
            Bitmap processed = applyImageTransformations(original, asset);
            imageCache.put(cacheKey, processed);
            return processed;
        }
        
        return null;
    }
    
    private Bitmap loadImageFromAssets(String fileName) {
        try (InputStream inputStream = context.getAssets().open("images/" + fileName)) {
            return BitmapFactory.decodeStream(inputStream);
        } catch (IOException e) {
            return null;
        }
    }
    
    private Bitmap applyImageTransformations(Bitmap original, LottieImageAsset asset) {
        // Apply scaling, filters, etc.
        if (original.getWidth() != asset.getWidth() || original.getHeight() != asset.getHeight()) {
            return Bitmap.createScaledBitmap(original, asset.getWidth(), asset.getHeight(), true);
        }
        return original;
    }
}

// Dynamic image replacement based on user data
animationView.setImageAssetDelegate(new ImageAssetDelegate() {
    @Override
    public Bitmap fetchBitmap(LottieImageAsset asset) {
        switch (asset.getId()) {
            case "user_avatar":
                return getUserAvatarBitmap();
            case "product_image":
                return getProductImageBitmap();
            default:
                return null; // Use default loading
        }
    }
    
    private Bitmap getUserAvatarBitmap() {
        // Load user's profile picture
        return ImageLoader.loadUserAvatar(getCurrentUserId());
    }
    
    private Bitmap getProductImageBitmap() {
        // Load current product image
        return ImageLoader.loadProductImage(getCurrentProductId());
    }
});

Font Asset Management

System for providing custom font loading for text layers in Lottie animations. Allows loading fonts from custom locations or applying font substitutions.

/**
 * Delegate for handling custom font asset loading
 */
public interface FontAssetDelegate {
    /**
     * Fetch typeface for the given font family
     * @param fontFamily Font family name from animation
     * @return Typeface to use, or null to use default loading
     */
    Typeface fetchFont(String fontFamily);
    
    /**
     * Get file path for font family (optional)
     * @param fontFamily Font family name
     * @return Font file path, or null
     */
    String getFontPath(String fontFamily);
}

/**
 * Font asset model
 */
public class Font {
    // Font identification
    public String getName();
    public String getFontFamily();
    public String getStyle();
    public String getFontPath();
    
    // Font metrics
    public float getAscent();
    
    // Internal methods
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public Font(String fontFamily, String name, String style, float ascent);
}

Usage Examples:

// Basic font asset delegate
animationView.setFontAssetDelegate(new FontAssetDelegate() {
    @Override
    public Typeface fetchFont(String fontFamily) {
        try {
            return Typeface.createFromAsset(getAssets(), "fonts/" + fontFamily + ".ttf");
        } catch (Exception e) {
            Log.w("Lottie", "Failed to load font: " + fontFamily, e);
            return null; // Use default font
        }
    }
    
    @Override
    public String getFontPath(String fontFamily) {
        return "fonts/" + fontFamily + ".ttf";
    }
});

// Font mapping delegate
animationView.setFontAssetDelegate(new FontAssetDelegate() {
    private final Map<String, String> fontMappings = new HashMap<String, String>() {{
        put("CustomFont-Regular", "Roboto-Regular");
        put("CustomFont-Bold", "Roboto-Bold");
        put("DisplayFont", "Oswald-Regular");
    }};
    
    @Override
    public Typeface fetchFont(String fontFamily) {
        String mappedFont = fontMappings.get(fontFamily);
        if (mappedFont != null) {
            try {
                return Typeface.createFromAsset(getAssets(), "fonts/" + mappedFont + ".ttf");
            } catch (Exception e) {
                Log.w("Lottie", "Failed to load mapped font: " + mappedFont, e);
            }
        }
        
        // Try original font name
        try {
            return Typeface.createFromAsset(getAssets(), "fonts/" + fontFamily + ".ttf");
        } catch (Exception e) {
            return Typeface.DEFAULT;
        }
    }
    
    @Override
    public String getFontPath(String fontFamily) {
        String mappedFont = fontMappings.get(fontFamily);
        return "fonts/" + (mappedFont != null ? mappedFont : fontFamily) + ".ttf";
    }
});

// System font delegate
animationView.setFontAssetDelegate(new FontAssetDelegate() {
    @Override
    public Typeface fetchFont(String fontFamily) {
        switch (fontFamily.toLowerCase()) {
            case "roboto":
            case "roboto-regular":
                return Typeface.DEFAULT;
            case "roboto-bold":
                return Typeface.DEFAULT_BOLD;
            case "monospace":
                return Typeface.MONOSPACE;
            case "serif":
                return Typeface.SERIF;
            case "sans-serif":
                return Typeface.SANS_SERIF;
            default:
                // Try to create from system
                return Typeface.create(fontFamily, Typeface.NORMAL);
        }
    }
    
    @Override
    public String getFontPath(String fontFamily) {
        return null; // System fonts don't have file paths
    }
});

// Font caching delegate
public class CachedFontDelegate implements FontAssetDelegate {
    private final Map<String, Typeface> fontCache = new HashMap<>();
    private final Context context;
    
    public CachedFontDelegate(Context context) {
        this.context = context;
    }
    
    @Override
    public Typeface fetchFont(String fontFamily) {
        Typeface cached = fontCache.get(fontFamily);
        if (cached != null) {
            return cached;
        }
        
        try {
            Typeface typeface = Typeface.createFromAsset(
                context.getAssets(), 
                "fonts/" + fontFamily + ".ttf"
            );
            fontCache.put(fontFamily, typeface);
            return typeface;
        } catch (Exception e) {
            Log.w("Lottie", "Failed to load font: " + fontFamily, e);
            return Typeface.DEFAULT;
        }
    }
    
    @Override
    public String getFontPath(String fontFamily) {
        return "fonts/" + fontFamily + ".ttf";
    }
}

Text Replacement

System for dynamically replacing text content in text layers. Allows localization, personalization, and real-time text updates.

/**
 * Delegate for dynamic text replacement
 */
public class TextDelegate {
    // Text replacement methods
    public String getText(String layerName, String sourceText);
    public void setText(String layerName, String text);
    
    // Internal text storage
    private final Map<String, String> textMap = new HashMap<>();
    
    // Internal callback handling
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public final LottieValueCallback<String> getTextCallback(String layerName, String sourceText);
}

Usage Examples:

// Basic text replacement
TextDelegate textDelegate = new TextDelegate() {
    @Override
    public String getText(String layerName, String sourceText) {
        switch (layerName) {
            case "WelcomeText":
                return "Welcome, " + getCurrentUserName() + "!";
            case "StatusText":
                return getApplicationStatus();
            case "CounterText":
                return String.valueOf(getCurrentCount());
            default:
                return sourceText; // Use original text
        }
    }
};
animationView.setTextDelegate(textDelegate);

// Localized text replacement
TextDelegate localizedDelegate = new TextDelegate() {
    private final Map<String, String> localizedTexts = loadLocalizedTexts();
    
    @Override
    public String getText(String layerName, String sourceText) {
        String localized = localizedTexts.get(layerName);
        return localized != null ? localized : sourceText;
    }
    
    private Map<String, String> loadLocalizedTexts() {
        Map<String, String> texts = new HashMap<>();
        // Load from resources based on current locale
        String locale = Locale.getDefault().getLanguage();
        switch (locale) {
            case "es":
                texts.put("HelloText", "¡Hola!");
                texts.put("GoodbyeText", "¡Adiós!");
                break;
            case "fr":
                texts.put("HelloText", "Bonjour!");
                texts.put("GoodbyeText", "Au revoir!");
                break;
            default:
                texts.put("HelloText", "Hello!");
                texts.put("GoodbyeText", "Goodbye!");
                break;
        }
        return texts;
    }
};

// Dynamic text updates
TextDelegate dynamicDelegate = new TextDelegate();
animationView.setTextDelegate(dynamicDelegate);

// Update text dynamically
dynamicDelegate.setText("ScoreText", "Score: " + currentScore);
dynamicDelegate.setText("TimeText", "Time: " + formatTime(remainingTime));
dynamicDelegate.setText("MessageText", "Level " + currentLevel + " Complete!");

// Template-based text replacement
TextDelegate templateDelegate = new TextDelegate() {
    @Override
    public String getText(String layerName, String sourceText) {
        // Support template variables in source text
        return replaceTemplateVariables(sourceText);
    }
    
    private String replaceTemplateVariables(String template) {
        return template
            .replace("${username}", getCurrentUserName())
            .replace("${score}", String.valueOf(getCurrentScore()))
            .replace("${level}", String.valueOf(getCurrentLevel()))
            .replace("${date}", getCurrentDate());
    }
};

// Real-time updating text delegate
public class LiveTextDelegate extends TextDelegate {
    private Timer updateTimer;
    private LottieAnimationView animationView;
    
    public LiveTextDelegate(LottieAnimationView animationView) {
        this.animationView = animationView;
        startUpdates();
    }
    
    @Override
    public String getText(String layerName, String sourceText) {
        switch (layerName) {
            case "ClockText":
                return getCurrentTime();
            case "CountdownText":
                return String.valueOf(getRemainingSeconds());
            case "LiveDataText":
                return getLiveDataValue();
            default:
                return sourceText;
        }
    }
    
    private void startUpdates() {
        updateTimer = new Timer();
        updateTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // Trigger redraw to update text
                if (animationView != null) {
                    animationView.post(() -> animationView.invalidate());
                }
            }
        }, 0, 1000); // Update every second
    }
    
    public void stopUpdates() {
        if (updateTimer != null) {
            updateTimer.cancel();
            updateTimer = null;
        }
    }
    
    private String getCurrentTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
        return sdf.format(new Date());
    }
    
    private int getRemainingSeconds() {
        // Calculate remaining time
        return Math.max(0, (int) ((endTime - System.currentTimeMillis()) / 1000));
    }
    
    private String getLiveDataValue() {
        // Fetch live data from server, database, etc.
        return String.valueOf(DataProvider.getCurrentValue());
    }
}

// Conditional text replacement
TextDelegate conditionalDelegate = new TextDelegate() {
    @Override
    public String getText(String layerName, String sourceText) {
        switch (layerName) {
            case "StatusIndicator":
                return isConnected() ? "ONLINE" : "OFFLINE";
            case "ProgressText":
                int progress = getCurrentProgress();
                if (progress < 25) return "Starting...";
                else if (progress < 75) return "In Progress...";
                else if (progress < 100) return "Almost Done...";
                else return "Complete!";
            case "UserGreeting":
                return isUserLoggedIn() ? 
                    "Welcome back!" : 
                    "Please sign in";
            default:
                return sourceText;
        }
    }
};

Font Character Model

public class FontCharacter {
    // Character properties
    public String getCharacter();
    public double getSize();
    public double getWidth();
    public String getStyle();
    public String getFontFamily();
    
    // Shape data
    public List<ShapeGroup> getShapes();
    
    // Internal construction
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public FontCharacter(List<ShapeGroup> shapes, String character, double size, 
                        double width, String style, String fontFamily);
}

Install with Tessl CLI

npx tessl i tessl/maven-com-airbnb-android--lottie

docs

asset-management.md

composition-loading.md

configuration-performance.md

dynamic-properties.md

index.md

view-components.md

tile.json