Android library for rendering Adobe After Effects animations exported as JSON through Bodymovin with native performance and extensive customization options.
—
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.
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());
}
});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";
}
}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;
}
}
};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