Update sample application to add play-pause functionality for gifs
This commit is contained in:
parent
bd08178a55
commit
830ec9180b
@ -107,4 +107,10 @@ class AppModule {
|
||||
Prism4jThemeDarkula prism4jThemeDarkula() {
|
||||
return Prism4jThemeDarkula.create();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
GifProcessor gifProcessor() {
|
||||
return GifProcessor.create();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import pl.droidsonroids.gif.GifDrawable;
|
||||
import ru.noties.markwon.renderer.ImageSize;
|
||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
|
||||
public class GifAwareAsyncDrawable extends AsyncDrawable {
|
||||
|
||||
public interface OnGifResultListener {
|
||||
void onGifResult(@NonNull GifAwareAsyncDrawable drawable);
|
||||
}
|
||||
|
||||
private final Drawable gifPlaceholder;
|
||||
private OnGifResultListener onGifResultListener;
|
||||
private boolean isGif;
|
||||
|
||||
public GifAwareAsyncDrawable(
|
||||
@NonNull Drawable gifPlaceholder,
|
||||
@NonNull String destination,
|
||||
@NonNull Loader loader,
|
||||
@Nullable ImageSizeResolver imageSizeResolver,
|
||||
@Nullable ImageSize imageSize) {
|
||||
super(destination, loader, imageSizeResolver, imageSize);
|
||||
this.gifPlaceholder = gifPlaceholder;
|
||||
}
|
||||
|
||||
public void onGifResultListener(@Nullable OnGifResultListener onGifResultListener) {
|
||||
this.onGifResultListener = onGifResultListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResult(@NonNull Drawable result) {
|
||||
super.setResult(result);
|
||||
isGif = result instanceof GifDrawable;
|
||||
if (isGif && onGifResultListener != null) {
|
||||
onGifResultListener.onGifResult(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
super.draw(canvas);
|
||||
|
||||
if (isGif) {
|
||||
final GifDrawable drawable = (GifDrawable) getResult();
|
||||
if (!drawable.isPlaying()) {
|
||||
gifPlaceholder.setBounds(drawable.getBounds());
|
||||
gifPlaceholder.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import ru.noties.markwon.renderer.ImageSize;
|
||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
||||
import ru.noties.markwon.spans.SpannableTheme;
|
||||
|
||||
public class GifAwareSpannableFactory extends SpannableFactoryDef {
|
||||
|
||||
private final GifPlaceholder gifPlaceholder;
|
||||
|
||||
public GifAwareSpannableFactory(@NonNull GifPlaceholder gifPlaceholder) {
|
||||
this.gifPlaceholder = gifPlaceholder;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object image(@NonNull SpannableTheme theme, @NonNull String destination, @NonNull AsyncDrawable.Loader loader, @NonNull ImageSizeResolver imageSizeResolver, @Nullable ImageSize imageSize, boolean replacementTextIsLink) {
|
||||
return new AsyncDrawableSpan(
|
||||
theme,
|
||||
new GifAwareAsyncDrawable(
|
||||
gifPlaceholder,
|
||||
destination,
|
||||
loader,
|
||||
imageSizeResolver,
|
||||
imageSize
|
||||
),
|
||||
AsyncDrawableSpan.ALIGN_BOTTOM,
|
||||
replacementTextIsLink
|
||||
);
|
||||
}
|
||||
}
|
77
app/src/main/java/ru/noties/markwon/GifPlaceholder.java
Normal file
77
app/src/main/java/ru/noties/markwon/GifPlaceholder.java
Normal file
@ -0,0 +1,77 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public class GifPlaceholder extends Drawable {
|
||||
|
||||
private final Drawable icon;
|
||||
private final Paint paint;
|
||||
|
||||
private float left;
|
||||
private float top;
|
||||
|
||||
public GifPlaceholder(@NonNull Drawable icon, @ColorInt int background) {
|
||||
this.icon = icon;
|
||||
if (icon.getBounds().isEmpty()) {
|
||||
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
|
||||
}
|
||||
|
||||
if (background != 0) {
|
||||
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor(background);
|
||||
} else {
|
||||
paint = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
|
||||
final int w = bounds.width();
|
||||
final int h = bounds.height();
|
||||
|
||||
this.left = (w - icon.getBounds().width()) / 2;
|
||||
this.top = (h - icon.getBounds().height()) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
|
||||
if (paint != null) {
|
||||
canvas.drawRect(getBounds(), paint);
|
||||
}
|
||||
|
||||
final int save = canvas.save();
|
||||
try {
|
||||
canvas.translate(left, top);
|
||||
icon.draw(canvas);
|
||||
} finally {
|
||||
canvas.restoreToCount(save);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
}
|
125
app/src/main/java/ru/noties/markwon/GifProcessor.java
Normal file
125
app/src/main/java/ru/noties/markwon/GifProcessor.java
Normal file
@ -0,0 +1,125 @@
|
||||
package ru.noties.markwon;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import pl.droidsonroids.gif.GifDrawable;
|
||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
||||
|
||||
public abstract class GifProcessor {
|
||||
|
||||
public abstract void process(@NonNull TextView textView);
|
||||
|
||||
@NonNull
|
||||
public static GifProcessor create() {
|
||||
return new Impl();
|
||||
}
|
||||
|
||||
static class Impl extends GifProcessor {
|
||||
|
||||
@Override
|
||||
public void process(@NonNull final TextView textView) {
|
||||
|
||||
// here is what we will do additionally:
|
||||
// we query for all asyncDrawableSpans
|
||||
// we check if they are inside clickableSpan
|
||||
// if not we apply onGifListener
|
||||
|
||||
final Spannable spannable = spannable(textView);
|
||||
if (spannable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AsyncDrawableSpan[] asyncDrawableSpans =
|
||||
spannable.getSpans(0, spannable.length(), AsyncDrawableSpan.class);
|
||||
if (asyncDrawableSpans == null
|
||||
|| asyncDrawableSpans.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int start;
|
||||
int end;
|
||||
ClickableSpan[] clickableSpans;
|
||||
|
||||
for (final AsyncDrawableSpan asyncDrawableSpan : asyncDrawableSpans) {
|
||||
|
||||
start = spannable.getSpanStart(asyncDrawableSpan);
|
||||
end = spannable.getSpanEnd(asyncDrawableSpan);
|
||||
|
||||
if (start < 0
|
||||
|| end < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
clickableSpans = spannable.getSpans(start, end, ClickableSpan.class);
|
||||
if (clickableSpans != null
|
||||
&& clickableSpans.length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
((GifAwareAsyncDrawable) asyncDrawableSpan.getDrawable()).onGifResultListener(new GifAwareAsyncDrawable.OnGifResultListener() {
|
||||
@Override
|
||||
public void onGifResult(@NonNull GifAwareAsyncDrawable drawable) {
|
||||
addGifClickSpan(textView, asyncDrawableSpan, drawable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Spannable spannable(@NonNull TextView textView) {
|
||||
final CharSequence charSequence = textView.getText();
|
||||
if (charSequence instanceof Spannable) {
|
||||
return (Spannable) charSequence;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addGifClickSpan(
|
||||
@NonNull TextView textView,
|
||||
@NonNull AsyncDrawableSpan span,
|
||||
@NonNull GifAwareAsyncDrawable drawable) {
|
||||
|
||||
// important thing here is to obtain new spannable from textView
|
||||
// as with each `setText()` new spannable is created and keeping reference
|
||||
// to an older one won't affect textView
|
||||
final Spannable spannable = spannable(textView);
|
||||
if (spannable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int start = spannable.getSpanStart(span);
|
||||
final int end = spannable.getSpanEnd(span);
|
||||
if (start < 0
|
||||
|| end < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final GifDrawable gifDrawable = (GifDrawable) drawable.getResult();
|
||||
spannable.setSpan(new GifToggleClickableSpan(gifDrawable), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
private static class GifToggleClickableSpan extends ClickableSpan {
|
||||
|
||||
private final GifDrawable gifDrawable;
|
||||
|
||||
GifToggleClickableSpan(@NonNull GifDrawable gifDrawable) {
|
||||
this.gifDrawable = gifDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
if (gifDrawable.isPlaying()) {
|
||||
gifDrawable.pause();
|
||||
} else {
|
||||
gifDrawable.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -28,8 +28,11 @@ public class MainActivity extends Activity {
|
||||
@Inject
|
||||
UriProcessor uriProcessor;
|
||||
|
||||
@Inject
|
||||
GifProcessor gifProcessor;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
App.component(this)
|
||||
@ -67,7 +70,11 @@ public class MainActivity extends Activity {
|
||||
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
|
||||
@Override
|
||||
public void onMarkdownReady(CharSequence markdown) {
|
||||
|
||||
Markwon.setText(textView, markdown, BetterLinkMovementMethod.getInstance());
|
||||
|
||||
gifProcessor.process(textView);
|
||||
|
||||
Views.setVisible(progress, false);
|
||||
}
|
||||
});
|
||||
|
@ -15,9 +15,9 @@ import javax.inject.Inject;
|
||||
import ru.noties.debug.Debug;
|
||||
import ru.noties.markwon.spans.AsyncDrawable;
|
||||
import ru.noties.markwon.spans.SpannableTheme;
|
||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||
import ru.noties.markwon.syntax.Prism4jSyntaxHighlight;
|
||||
import ru.noties.markwon.syntax.Prism4jTheme;
|
||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
||||
import ru.noties.prism4j.Prism4j;
|
||||
|
||||
@ -82,6 +82,11 @@ public class MarkdownRenderer {
|
||||
? prism4jTheme.background()
|
||||
: 0x0Fffffff;
|
||||
|
||||
final GifPlaceholder gifPlaceholder = new GifPlaceholder(
|
||||
context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white),
|
||||
0x20000000
|
||||
);
|
||||
|
||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
||||
.asyncDrawableLoader(loader)
|
||||
.urlProcessor(urlProcessor)
|
||||
@ -90,6 +95,7 @@ public class MarkdownRenderer {
|
||||
.codeBackgroundColor(background)
|
||||
.codeTextColor(prism4jTheme.textColor())
|
||||
.build())
|
||||
.factory(new GifAwareSpannableFactory(gifPlaceholder))
|
||||
.build();
|
||||
|
||||
final long start = SystemClock.uptimeMillis();
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 322 B |
Binary file not shown.
After Width: | Height: | Size: 378 B |
Binary file not shown.
After Width: | Height: | Size: 536 B |
Loading…
x
Reference in New Issue
Block a user