Image
In order to display images in your markdown ImagesPlugin can be used.
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create())
:::tip There are also modules that add image loading capabilities to markdown based on image-loading libraries: image-glide and image-picasso :::
ImagesPlugin splits the image-loading into 2 parts: scheme-handling and media-decoding.
SchemeHandler
To add a scheme-handler to ImagesPlugin:
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configure(@NonNull Registry registry) {
registry.require(ImagesPlugin.class, new Action<ImagesPlugin>() {
@Override
public void apply(@NonNull ImagesPlugin imagesPlugin) {
imagesPlugin.addSchemeHandler(DataUriSchemeHandler.create());
}
});
}
})
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.addSchemeHandler(DataUriSchemeHandler.create());
}
}))
ImagesPlugin comes with a set of predefined scheme-handlers:
FileSchemeHandler-file://DataUriSchemeHandler-data:NetworkSchemeHandler-http,httpsOkHttpNetworkSchemeHandler-http,https
FileSchemeHandler
Loads images via file:// scheme. Allows loading images from assets folder.
// default implementation, no assets handling
FileSchemeHandler.create();
// assets loading
FileSchemeHandler.createWithAssets(context);
:::warning
Assets loading will work when your URL will include android_asset in the path,
for example: file:///android_asset/image.png (mind the 3 slashes ///). If you wish
to assume all images without proper scheme to point to assets folder, then you can use
UrlProcessorAndroidAssets
:::
By default ImagesPlugin includes plain FileSchemeHandler (without assets support),
so if you wish to change that you can explicitly specify it:
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.addSchemeHandler(FileSchemeHandler.createWithAssets(context));
}
}))
DataUriSchemeHandler
DataUriSchemeHandler allows inlining images with data: scheme (data:image/svg+xml;base64,MTIz).
This scheme-handler is registered by default, so you do not need to add it explicitly.
NetworkSchemeHandler
NetworkSchemeHandler allows obtaining images from http:// and https:// uris
(internally it uses HttpURLConnection). This scheme-handler is registered by default
OkHttpNetworkSchemeHandler
OkHttpNetworkSchemeHandler allows obtaining images from http:// and https:// uris
via okhttp library. Please note that in order to use
this scheme-handler you must explicitly add okhttp library to your project.
// default instance
OkHttpNetworkSchemeHandler.create();
// specify OkHttpClient to use
OkHttpNetworkSchemeHandler.create(new OkHttpClient());
// @since 4.0.0
OkHttpNetworkSchemeHandler.create(Call.Factory);
Custom SchemeHandler
public abstract class SchemeHandler {
@NonNull
public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
@NonNull
public abstract Collection<String> supportedSchemes();
}
Starting with SchemeHandler can return a result (when no
further decoding is required):
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// for example to return a drawable resource
plugin.addSchemeHandler(new SchemeHandler() {
@NonNull
@Override
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
// will handle URLs like `drawable://ic_account_24dp_white`
final int resourceId = context.getResources().getIdentifier(
raw.substring("drawable://".length()),
"drawable",
context.getPackageName());
// it's fine if it throws, async-image-loader will catch exception
final Drawable drawable = context.getDrawable(resourceId);
return ImageItem.withResult(drawable);
}
@NonNull
@Override
public Collection<String> supportedSchemes() {
return Collections.singleton("drawable");
}
});
}
}))
Otherwise SchemeHandler must return an InputStream with proper content-type information
for further processing by a MediaDecoder:
imagesPlugin.addSchemeHandler(new SchemeHandler() {
@NonNull
@Override
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
return ImageItem.withDecodingNeeded("image/png", load(raw));
}
@NonNull
private InputStream load(@NonNull String raw) {...}
});
MediaDecoder
ImagesPlugin comes with predefined media-decoders:
GifMediaDecoderadds support for GIFSvgMediaDecoderadds support for SVGDefaultMediaDecoder
:::warning If you wish to add support for SVG or GIF you must explicitly add these dependencies to your project:
- for
SVG:com.caverock:androidsvg:1.4 - for
GIF:pl.droidsonroids.gif:android-gif-drawable:1.2.14
You can try more recent versions of these libraries, but make sure that they doesn't introduce any unexpected behavior. :::
GifMediaDecoder
Adds support for GIF media in markdown. If pl.droidsonroids.gif:android-gif-drawable:* dependency
is found in the classpath, then registration will happen automatically.
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// autoplayGif controls if GIF should be automatically started
plugin.addMediaDecoder(GifMediaDecoder.create(/*autoplayGif*/false));
}
}))
.build();
SvgMediaDecoder
Adds support for SVG media in markdown. If com.caverock:androidsvg:* dependency is found
in the classpath, then registration will happen automatically.
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// uses supplied Resources
plugin.addMediaDecoder(SvgMediaDecoder.create(context.getResources()));
// uses Resources.getSystem()
plugin.addMediaDecoder(SvgMediaDecoder.create());
}
}))
.build();
DefaultMediaDecoder
DefaultMediaDecoder tries to decode supplied InputStream
as Bitmap (via BitmapFactory.decodeStream(inputStream)). This decoder is registered automatically.
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
// uses supplied Resources
plugin.defaultMediaDecoder(DefaultMediaDecoder.create(context.getResources()));
// uses Resources.getSystem()
plugin.defaultMediaDecoder(DefaultMediaDecoder.create());
}
}))
.build();
AsyncDrawableScheduler
AsyncDrawableScheduler is used in order to give AsyncDrawable a way to invalidate TextView
that is holding it. A plugin that is dealing with AsyncDrawable should always call these methods:
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
AsyncDrawableScheduler.unschedule(textView);
}
@Override
public void afterSetText(@NonNull TextView textView) {
AsyncDrawableScheduler.schedule(textView);
}
})
.build();
:::tip
Starting with multiple plugins can call AsyncDrawableScheduler#schedule
method without the penalty to process AsyncDrawable callbacks multiple times (internally caches
state which ensures that a TextView is processed only once the text has changed).
:::
ErrorHandler
An ErrorHandler can be used to receive an error that has happened during image loading
and (optionally) return an error drawable to be displayed instead
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.errorHandler(new ImagesPlugin.ErrorHandler() {
@Nullable
@Override
public Drawable handleError(@NonNull String url, @NonNull Throwable throwable) {
return null;
}
});
}
}))
.build();
PlaceholderProvider
To display a placeholder during image loading PlaceholderProvider can be used:
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
@Override
public void configureImages(@NonNull ImagesPlugin plugin) {
plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
@Nullable
@Override
public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
return null;
}
});
}
}))
.build();
:::tip If your placeholder drawable has specific size which is not the same an image that is being loaded, you can manually assign bounds to the placeholder:
plugin.placeholderProvider(new ImagesPlugin.PlaceholderProvider() {
@Override
public Drawable providePlaceholder(@NonNull AsyncDrawable drawable) {
final ColorDrawable placeholder = new ColorDrawable(Color.BLUE);
// these bounds will be used to display a placeholder,
// so even if loading image has size `width=100%`, placeholder
// bounds won't be affected by it
placeholder.setBounds(0, 0, 48, 48);
return placeholder;
}
});
:::
:::tip
If you are using html you do not have to additionally setup
images displayed via <img> tag, as HtmlPlugin automatically uses configured
image loader. But images referenced in HTML come with additional support for
sizes, which is not supported natively by markdown, allowing absolute or relative sizes:
<img src="./assets/my-image" width="100%">
:::