diff --git a/app/src/main/java/ru/noties/markwon/MainActivity.java b/app/src/main/java/ru/noties/markwon/MainActivity.java index d4b7cbad..f6113a61 100644 --- a/app/src/main/java/ru/noties/markwon/MainActivity.java +++ b/app/src/main/java/ru/noties/markwon/MainActivity.java @@ -1,36 +1,26 @@ package ru.noties.markwon; import android.app.Activity; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; import android.support.annotation.NonNull; import android.text.method.LinkMovementMethod; import android.widget.TextView; -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Target; - import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.Scanner; import ru.noties.debug.AndroidLogDebugOutput; import ru.noties.debug.Debug; -import ru.noties.markwon.renderer.*; +import ru.noties.markwon.renderer.SpannableConfiguration; +import ru.noties.markwon.renderer.SpannableRenderer; import ru.noties.markwon.spans.AsyncDrawable; -import ru.noties.markwon.spans.CodeSpan; -import ru.noties.markwon.spans.AsyncDrawableSpanUtils; public class MainActivity extends Activity { @@ -38,7 +28,7 @@ public class MainActivity extends Activity { Debug.init(new AndroidLogDebugOutput(true)); } - private List targets = new ArrayList<>(); +// private List targets = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { @@ -47,15 +37,15 @@ public class MainActivity extends Activity { final TextView textView = (TextView) findViewById(R.id.activity_main); - - final Picasso picasso = new Picasso.Builder(this) - .listener(new Picasso.Listener() { - @Override - public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { - Debug.i(exception, uri); - } - }) - .build(); +// +// final Picasso picasso = new Picasso.Builder(this) +// .listener(new Picasso.Listener() { +// @Override +// public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) { +// Debug.i(exception, uri); +// } +// }) +// .build(); new Thread(new Runnable() { @Override @@ -64,8 +54,8 @@ public class MainActivity extends Activity { Scanner scanner = null; String md = null; try { -// stream = getAssets().open("scrollable.md"); - stream = getAssets().open("test.md"); + stream = getAssets().open("scrollable.md"); +// stream = getAssets().open("test.md"); scanner = new Scanner(stream).useDelimiter("\\A"); if (scanner.hasNext()) { md = scanner.next(); @@ -74,7 +64,10 @@ public class MainActivity extends Activity { Debug.e(t); } finally { if (stream != null) { - try { stream.close(); } catch (IOException e) {} + try { + stream.close(); + } catch (IOException e) { + } } if (scanner != null) { scanner.close(); @@ -88,76 +81,36 @@ public class MainActivity extends Activity { .build(); final Node node = parser.parse(md); -// final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this) -// .setAsyncDrawableLoader(new AsyncDrawable.Loader() { -// @Override -// public void load(@NonNull String destination, @NonNull final AsyncDrawable drawable) { -// Debug.i(destination); -// final Target target = new Target() { -// @Override -// public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { -// Debug.i(); -// final Drawable d = new BitmapDrawable(getResources(), bitmap); -// d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); -// drawable.setResult(d); -//// textView.setText(textView.getText()); -// } -// -// @Override -// public void onBitmapFailed(Drawable errorDrawable) { -// Debug.i(); -// } -// -// @Override -// public void onPrepareLoad(Drawable placeHolderDrawable) { -// Debug.i(); -// } -// }; -// targets.add(target); -// -// picasso.load(destination) -// .tag(destination) -// .into(target); -// -// } -// -// @Override -// public void cancel(@NonNull String destination) { -// Debug.i(destination); -// picasso -// .cancelTag(destination); -// } -// }) -// .setCodeConfig(CodeSpan.Config.builder().setTextSize( -// (int) (getResources().getDisplayMetrics().density * 14 + .5F) -// ).setMultilineMargin((int) (getResources().getDisplayMetrics().density * 8 + .5F)).build()) -// .build(); + final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this) + .asyncDrawableLoader(new AsyncDrawable.Loader() { + @Override + public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { + Debug.i("destination: %s, drawable: %s", destination, drawable); + } - final SpannableConfiguration configuration = SpannableConfiguration.create(MainActivity.this); + @Override + public void cancel(@NonNull String destination) { + Debug.i("destination: %s", destination); + } + }) + .build(); - final CharSequence text = new ru.noties.markwon.renderer.SpannableRenderer().render( + final CharSequence text = new SpannableRenderer().render( configuration, node ); -// final CharSequence text = new SpannableRenderer()._render(node/*, new Runnable() { -// @Override -// public void run() { -// textView.setText(textView.getText()); -// final Drawable drawable = null; -// drawable.setCallback(textView); -// } -// }*/); final long end = SystemClock.uptimeMillis(); Debug.i("Rendered: %d ms, length: %d", end - start, text.length()); -// Debug.i(text); + textView.post(new Runnable() { @Override public void run() { // NB! LinkMovementMethod forces frequent updates... textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setText(text); - AsyncDrawableSpanUtils.scheduleDrawables(textView); + SpannableRenderer.scheduleDrawables(textView); +// AsyncDrawableSpanUtils.scheduleDrawables(textView); } }); } diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/DrawablesScheduler.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/DrawablesScheduler.java new file mode 100644 index 00000000..2b2d50cf --- /dev/null +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/DrawablesScheduler.java @@ -0,0 +1,142 @@ +package ru.noties.markwon.renderer; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.text.Spanned; +import android.text.style.DynamicDrawableSpan; +import android.view.View; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ru.noties.markwon.spans.AsyncDrawable; +import ru.noties.markwon.spans.AsyncDrawableSpan; + +abstract class DrawablesScheduler { + + static void schedule(@NonNull final TextView textView) { + + final List list = extract(textView); + if (list.size() > 0) { + textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + + } + + @Override + public void onViewDetachedFromWindow(View v) { + // we obtain a new list in case text was changed + unschedule(textView); + } + }); + + for (AsyncDrawable d : list) { + d.setCallback2(new DrawableCallbackImpl(textView, d.getBounds())); + } + } + } + + // must be called when text manually changed in TextView + static void unschedule(@NonNull TextView view) { + for (AsyncDrawable d : extract(view)) { + d.setCallback2(null); + } + } + + private static List extract(@NonNull TextView view) { + + final List list; + + final CharSequence cs = view.getText(); + final int length = cs != null + ? cs.length() + : 0; + + if (length == 0 || !(cs instanceof Spanned)) { + //noinspection unchecked + list = Collections.EMPTY_LIST; + } else { + + final Object[] spans = ((Spanned) cs).getSpans(0, length, Object.class); + if (spans != null + && spans.length > 0) { + + list = new ArrayList<>(2); + + for (Object span : spans) { + if (span instanceof AsyncDrawableSpan) { + list.add(((AsyncDrawableSpan) span).getDrawable()); + } else if (span instanceof DynamicDrawableSpan) { + // it's really not optimal thing because it stores Drawable in WeakReference... + // which is why it will be most likely already de-referenced... + final Drawable d = ((DynamicDrawableSpan) span).getDrawable(); + if (d != null + && d instanceof AsyncDrawable) { + list.add((AsyncDrawable) d); + } + } + } + } else { + //noinspection unchecked + list = Collections.EMPTY_LIST; + } + } + + return list; + } + + private DrawablesScheduler() { + } + + private static class DrawableCallbackImpl implements Drawable.Callback { + + private final TextView view; + private Rect previousBounds; + + DrawableCallbackImpl(TextView view, Rect initialBounds) { + this.view = view; + this.previousBounds = new Rect(initialBounds); + } + + @Override + public void invalidateDrawable(@NonNull Drawable who) { + + // okay... teh thing is IF we do not change bounds size, normal invalidate would do + // but if the size has changed, then we need to update the whole layout... + + final Rect rect = who.getBounds(); + + if (!previousBounds.equals(rect)) { + // the only method that seems to work when bounds have changed + view.setText(view.getText()); + previousBounds = new Rect(rect); + } else { + // if bounds are the same then simple invalidate would do + final int scrollX = view.getScrollX(); + final int scrollY = view.getScrollY(); + view.postInvalidate( + scrollX + rect.left, + scrollY + rect.top, + scrollX + rect.right, + scrollY + rect.bottom + ); + } + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + final long delay = when - SystemClock.uptimeMillis(); + view.postDelayed(what, delay); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + view.removeCallbacks(what); + } + } +} diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java index 24f8206e..8de774c6 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableConfiguration.java @@ -105,7 +105,7 @@ public class SpannableConfiguration { linkResolver = new LinkResolverDef(); } if (htmlParser == null) { - htmlParser = SpannableHtmlParser.create(theme); + htmlParser = SpannableHtmlParser.create(theme, asyncDrawableLoader); } return new SpannableConfiguration(this); } diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java index 60c49bf7..dd71605a 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -3,6 +3,7 @@ package ru.noties.markwon.renderer; import android.support.annotation.NonNull; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.TextUtils; import android.text.style.StrikethroughSpan; import org.commonmark.ext.gfm.strikethrough.Strikethrough; @@ -330,38 +331,48 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { public void visit(HtmlInline htmlInline) { final SpannableHtmlParser htmlParser = configuration.htmlParser(); final SpannableHtmlParser.Tag tag = htmlParser.parseTag(htmlInline.getLiteral()); + Debug.i(tag); if (tag != null) { - if (tag.opening()) { + + final boolean voidTag = tag.voidTag(); + if (!voidTag && tag.opening()) { // push in stack htmlInlineItems.push(new HtmlInlineItem(tag.name(), builder.length())); visitChildren(htmlInline); } else { - // pop last item - if (htmlInlineItems.size() > 0) { - final HtmlInlineItem item = htmlInlineItems.pop(); - final int start = item.start; - final Object span = htmlParser.handleTag(item.tag); - if (span != null) { - setSpan(start, span); - } else { - final String content = builder.subSequence(start, builder.length()).toString(); - final String html = String.format(HTML_CONTENT, item.tag, content); - final Object[] spans = htmlParser.htmlSpans(html); - final int length = spans != null - ? spans.length - : 0; - for (int i = 0; i < length; i++) { - setSpan(start, spans[i]); + + if (!voidTag) { + if (htmlInlineItems.size() > 0) { + final HtmlInlineItem item = htmlInlineItems.pop(); + final Object span = htmlParser.handleTag(item.tag); + final int start = item.start; + if (span != null) { + setSpan(item.start, span); + } else { + final String content = builder.subSequence(start, builder.length()).toString(); + final String html = String.format(HTML_CONTENT, item.tag, content); + final Object[] spans = htmlParser.htmlSpans(html); + final int length = spans != null + ? spans.length + : 0; + for (int i = 0; i < length; i++) { + setSpan(start, spans[i]); + } } } } else { - throw new IllegalStateException("Unexpected closing html tag: " + tag.name() - + ", at position: " + builder.length()); + final String content = htmlInline.getLiteral(); + if (!TextUtils.isEmpty(content)) { + final Spanned html = htmlParser.html(content); + if (!TextUtils.isEmpty(html)) { + builder.append(html); + } + } } } } else { - // let's add what we have - builder.append(htmlInline.getLiteral()); + // todo, should we append just literal? +// builder.append(htmlInline.getLiteral()); visitChildren(htmlInline); } } @@ -399,28 +410,10 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { private static class HtmlInlineItem { final String tag; final int start; + HtmlInlineItem(String tag, int start) { this.tag = tag; this.start = start; } } - -// private static String dump(Node node) { -// final StringBuilder builder = new StringBuilder(); -// node.accept(new DumpVisitor(builder)); -// return builder.toString(); -// } -// -// private static class DumpVisitor extends AbstractVisitor { -// private final StringBuilder builder; -// -// DumpVisitor(StringBuilder builder) { -// this.builder = builder; -// } -// -// @Override -// public void visit(Text text) { -// builder.append(text.getLiteral()); -// } -// } } diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java index 88265eeb..32c81e6a 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java @@ -3,12 +3,21 @@ package ru.noties.markwon.renderer; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.SpannableStringBuilder; +import android.widget.TextView; import org.commonmark.node.Node; // please note that this class does not implement Renderer in order to return CharSequence (instead of String) public class SpannableRenderer { + public static void scheduleDrawables(@NonNull TextView view) { + DrawablesScheduler.schedule(view); + } + + public static void unscheduleDrawables(@NonNull TextView view) { + DrawablesScheduler.unschedule(view); + } + // todo // * LinkDrawableSpan, that draws link whilst image is still loading (it must be clickable...) // * Common interface for images (in markdown & inline-html) diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/html/HtmlImageGetter.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/html/HtmlImageGetter.java new file mode 100644 index 00000000..1f0a2893 --- /dev/null +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/html/HtmlImageGetter.java @@ -0,0 +1,21 @@ +package ru.noties.markwon.renderer.html; + +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.text.Html; + +import ru.noties.markwon.spans.AsyncDrawable; + +class HtmlImageGetter implements Html.ImageGetter { + + private final AsyncDrawable.Loader loader; + + HtmlImageGetter(@NonNull AsyncDrawable.Loader loader) { + this.loader = loader; + } + + @Override + public Drawable getDrawable(String source) { + return new AsyncDrawable(source, loader); + } +} diff --git a/library-renderer/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java b/library-renderer/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java index 99286b52..b919b703 100644 --- a/library-renderer/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java +++ b/library-renderer/src/main/java/ru/noties/markwon/renderer/html/SpannableHtmlParser.java @@ -7,9 +7,13 @@ import android.support.annotation.Nullable; import android.text.Html; import android.text.Spanned; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import ru.noties.markwon.spans.AsyncDrawable; import ru.noties.markwon.spans.SpannableTheme; @SuppressWarnings("WeakerAccess") @@ -18,8 +22,8 @@ public class SpannableHtmlParser { // we need to handle images independently (in order to parse alt, width, height, etc) // creates default parser - public static SpannableHtmlParser create(@NonNull SpannableTheme theme) { - return builderWithDefaults(theme) + public static SpannableHtmlParser create(@NonNull SpannableTheme theme, @NonNull AsyncDrawable.Loader loader) { + return builderWithDefaults(theme, loader) .build(); } @@ -27,12 +31,22 @@ public class SpannableHtmlParser { return new Builder(); } - public static Builder builderWithDefaults(@NonNull SpannableTheme theme) { + public static Builder builderWithDefaults( + @NonNull SpannableTheme theme, + @Nullable AsyncDrawable.Loader asyncDrawableLoader + ) { final BoldProvider boldProvider = new BoldProvider(); final ItalicsProvider italicsProvider = new ItalicsProvider(); final StrikeProvider strikeProvider = new StrikeProvider(); + final HtmlParser parser; + if (asyncDrawableLoader != null) { + parser = DefaultHtmlParser.create(new HtmlImageGetter(asyncDrawableLoader), null); + } else { + parser = DefaultHtmlParser.create(null, null); + } + return new Builder() .customTag("b", boldProvider) .customTag("strong", boldProvider) @@ -45,7 +59,8 @@ public class SpannableHtmlParser { .customTag("u", new UnderlineProvider()) .customTag("del", strikeProvider) .customTag("s", strikeProvider) - .customTag("strike", strikeProvider); + .customTag("strike", strikeProvider) + .parser(parser); } // for simple tags without arguments @@ -56,13 +71,16 @@ public class SpannableHtmlParser { public interface HtmlParser { Object[] getSpans(@NonNull String html); + Spanned parse(@NonNull String html); } private final Map customTags; + private final Set voidTags; private final HtmlParser parser; private SpannableHtmlParser(Builder builder) { this.customTags = builder.customTags; + this.voidTags = voidTags(); this.parser = builder.parser; } @@ -79,11 +97,33 @@ public class SpannableHtmlParser { if (length < 3) { tag = null; } else { + // okay, we will consider a tag a void one if it's in our void list tag or if it ends with `/>` final boolean closing = '<' == html.charAt(0) && '/' == html.charAt(1); + final boolean voidTag; + if (closing) { + voidTag = false; + } else { + int firstNonChar = -1; + for (int i = 1; i < length; i++) { + if (!Character.isLetterOrDigit(html.charAt(i))) { + firstNonChar = i; + break; + } + } + if (firstNonChar > 1) { + final String name = html.substring(1, firstNonChar); + voidTag = voidTags.contains(name); + } else { + voidTag = false; + } + } + + // todo, we do not strip to void tag name, so it can be possibly ended with `/` final String name = closing ? html.substring(2, length - 1) : html.substring(1, length - 1); - tag = new Tag(name, !closing); + + tag = new Tag(name, !closing, voidTag); } return tag; @@ -107,6 +147,20 @@ public class SpannableHtmlParser { return parser.getSpans(html); } + public Spanned html(String html) { + return parser.parse(html); + } + + private static Set voidTags() { + final String[] tags = { + "area", "base", "br", "col", "embed", "hr", "img", "input", + "keygen", "link", "meta", "param", "source", "track", "wbr" + }; + final Set set = new HashSet<>(tags.length); + Collections.addAll(set, tags); + return set; + } + public static class Builder { private final Map customTags = new HashMap<>(3); @@ -134,10 +188,12 @@ public class SpannableHtmlParser { private final String name; private final boolean opening; + private final boolean voidTag; - public Tag(String name, boolean opening) { + public Tag(String name, boolean opening, boolean voidTag) { this.name = name; this.opening = opening; + this.voidTag = voidTag; } public String name() { @@ -148,11 +204,16 @@ public class SpannableHtmlParser { return opening; } + public boolean voidTag() { + return voidTag; + } + @Override public String toString() { return "Tag{" + "name='" + name + '\'' + ", opening=" + opening + + ", voidTag=" + voidTag + '}'; } } @@ -197,7 +258,12 @@ public class SpannableHtmlParser { @Override public Object[] getSpans(@NonNull String html) { - return getSpans(Html.fromHtml(html, imageGetter, tagHandler)); + return getSpans(parse(html)); + } + + @Override + public Spanned parse(@NonNull String html) { + return Html.fromHtml(html, imageGetter, tagHandler); } } @@ -210,7 +276,12 @@ public class SpannableHtmlParser { @Override public Object[] getSpans(@NonNull String html) { - return getSpans(Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT, imageGetter, tagHandler)); + return getSpans(parse(html)); + } + + @Override + public Spanned parse(@NonNull String html) { + return Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT, imageGetter, tagHandler); } } } diff --git a/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpanUtils.java b/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpanUtils.java deleted file mode 100644 index 872183e1..00000000 --- a/library-renderer/src/main/java/ru/noties/markwon/spans/AsyncDrawableSpanUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -package ru.noties.markwon.spans; - -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.text.Spanned; -import android.view.View; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -public class AsyncDrawableSpanUtils { - - // todo, add `unschedule` method (to be used when new text is set, so - // drawables are removed from callbacks) - - // this method is not completely valid because DynamicDrawableSpan stores - // a drawable in weakReference & it could easily be freed, thus we might need - // to re-schedule a new one, but we have no means to do it - public static void scheduleDrawables(@NonNull final TextView textView) { - - final CharSequence cs = textView.getText(); - final int length = cs != null - ? cs.length() - : 0; - if (length == 0 || !(cs instanceof Spanned)) { - return; - } - - final Object[] spans = ((Spanned) cs).getSpans(0, length, Object.class); - if (spans != null - && spans.length > 0) { - - final List list = new ArrayList<>(2); - - for (Object span: spans) { - if (span instanceof AsyncDrawableSpan) { - list.add(((AsyncDrawableSpan) span).getDrawable()); - } - } - - if (list.size() > 0) { - textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - // can it happen that the same view first detached & them attached with all previous content? hm.. - // no op for now - } - - @Override - public void onViewDetachedFromWindow(View v) { - // remove callbacks... - textView.removeOnAttachStateChangeListener(this); - for (AsyncDrawable drawable: list) { - drawable.setCallback2(null); - } - } - }); - - for (AsyncDrawable drawable: list) { - drawable.setCallback2(new DrawableCallbackImpl(textView, drawable.getBounds())); - } - } - } - } - - private AsyncDrawableSpanUtils() {} - - private static class DrawableCallbackImpl implements Drawable.Callback { - - private final TextView view; - private Rect previousBounds; - - DrawableCallbackImpl(TextView view, Rect initialBounds) { - this.view = view; - this.previousBounds = new Rect(initialBounds); - } - - @Override - public void invalidateDrawable(@NonNull Drawable who) { - - // okay... teh thing is IF we do not change bounds size, normal invalidate would do - // but if the size has changed, then we need to update the whole layout... - - final Rect rect = who.getBounds(); - - if (!previousBounds.equals(rect)) { - // the only method that seems to work when bounds have changed - view.setText(view.getText()); - previousBounds = new Rect(rect); - } else { - // if bounds are the same then simple invalidate would do - final int scrollX = view.getScrollX(); - final int scrollY = view.getScrollY(); - view.postInvalidate( - scrollX + rect.left, - scrollY + rect.top, - scrollX + rect.right, - scrollY + rect.bottom - ); - } - } - - @Override - public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { - final long delay = when - SystemClock.uptimeMillis(); - view.postDelayed(what, delay); - } - - @Override - public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { - view.removeCallbacks(what); - } - } -}