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() {
|
Prism4jThemeDarkula prism4jThemeDarkula() {
|
||||||
return Prism4jThemeDarkula.create();
|
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
|
@Inject
|
||||||
UriProcessor uriProcessor;
|
UriProcessor uriProcessor;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
GifProcessor gifProcessor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
App.component(this)
|
App.component(this)
|
||||||
@ -67,7 +70,11 @@ public class MainActivity extends Activity {
|
|||||||
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
|
markdownRenderer.render(MainActivity.this, themes.isLight(), uri(), text, new MarkdownRenderer.MarkdownReadyListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onMarkdownReady(CharSequence markdown) {
|
public void onMarkdownReady(CharSequence markdown) {
|
||||||
|
|
||||||
Markwon.setText(textView, markdown, BetterLinkMovementMethod.getInstance());
|
Markwon.setText(textView, markdown, BetterLinkMovementMethod.getInstance());
|
||||||
|
|
||||||
|
gifProcessor.process(textView);
|
||||||
|
|
||||||
Views.setVisible(progress, false);
|
Views.setVisible(progress, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -15,9 +15,9 @@ import javax.inject.Inject;
|
|||||||
import ru.noties.debug.Debug;
|
import ru.noties.debug.Debug;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
|
||||||
import ru.noties.markwon.syntax.Prism4jSyntaxHighlight;
|
import ru.noties.markwon.syntax.Prism4jSyntaxHighlight;
|
||||||
import ru.noties.markwon.syntax.Prism4jTheme;
|
import ru.noties.markwon.syntax.Prism4jTheme;
|
||||||
|
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
import ru.noties.prism4j.Prism4j;
|
import ru.noties.prism4j.Prism4j;
|
||||||
|
|
||||||
@ -82,6 +82,11 @@ public class MarkdownRenderer {
|
|||||||
? prism4jTheme.background()
|
? prism4jTheme.background()
|
||||||
: 0x0Fffffff;
|
: 0x0Fffffff;
|
||||||
|
|
||||||
|
final GifPlaceholder gifPlaceholder = new GifPlaceholder(
|
||||||
|
context.getResources().getDrawable(R.drawable.ic_play_circle_filled_18dp_white),
|
||||||
|
0x20000000
|
||||||
|
);
|
||||||
|
|
||||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
final SpannableConfiguration configuration = SpannableConfiguration.builder(context)
|
||||||
.asyncDrawableLoader(loader)
|
.asyncDrawableLoader(loader)
|
||||||
.urlProcessor(urlProcessor)
|
.urlProcessor(urlProcessor)
|
||||||
@ -90,6 +95,7 @@ public class MarkdownRenderer {
|
|||||||
.codeBackgroundColor(background)
|
.codeBackgroundColor(background)
|
||||||
.codeTextColor(prism4jTheme.textColor())
|
.codeTextColor(prism4jTheme.textColor())
|
||||||
.build())
|
.build())
|
||||||
|
.factory(new GifAwareSpannableFactory(gifPlaceholder))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final long start = SystemClock.uptimeMillis();
|
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