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
,https
OkHttpNetworkSchemeHandler
-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:
GifMediaDecoder
adds support for GIFSvgMediaDecoder
adds 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%">
:::