2020-01-14 16:33:08 +03:00
..

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 GIF
  • SvgMediaDecoder adds support for SVG
  • DefaultMediaDecoder

:::warning If you wish to add support for SVG or GIF you must explicitly add these dependencies to your project:

For security reasons it's advisable to use latest versions of these libraries. If you notice compilation and/or runtime issues when used with Markwon, please create an issue specifying library and library version used. :::

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%">

:::