Testing if we can display svg & gif, yeah...
This commit is contained in:
parent
87d03793a8
commit
99f2879f6a
@ -18,5 +18,7 @@ dependencies {
|
|||||||
compile project(':library-renderer')
|
compile project(':library-renderer')
|
||||||
compile 'ru.noties:debug:3.0.0@jar'
|
compile 'ru.noties:debug:3.0.0@jar'
|
||||||
compile 'com.squareup.picasso:picasso:2.5.2'
|
compile 'com.squareup.picasso:picasso:2.5.2'
|
||||||
|
compile 'com.caverock:androidsvg:1.2.1'
|
||||||
|
compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7'
|
||||||
compile 'com.squareup.okhttp3:okhttp:3.8.0'
|
compile 'com.squareup.okhttp3:okhttp:3.8.0'
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
package ru.noties.markwon;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.support.test.InstrumentationRegistry;
|
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumentation test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() throws Exception {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
|
||||||
|
|
||||||
assertEquals("ru.noties.markwon", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||

|

|
||||||
|
|
||||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22scrollable%22)
|
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22scrollable%22)
|
||||||
|
|
||||||
@ -11,7 +11,7 @@
|
|||||||
All GIFs here are taken from `sample` application module.
|
All GIFs here are taken from `sample` application module.
|
||||||
|
|
||||||
|
|
||||||
<img src="art/scrollable_colorful.gif" width="30%" alt="colorful_sample"/> <img src="art/scrollable_custom_overscroll.gif" width="30%" alt="custom_overscroll_sample"/> <img src="art/scrollable_dialog.gif" width="30%" alt="dialog_sample"/>
|
<img src="https://github.com/noties/Scrollable/raw/master/art/scrollable_colorful.gif" width="30%" alt="colorful_sample"/> <img src="https://github.com/noties/Scrollable/raw/master/art/scrollable_custom_overscroll.gif" width="30%" alt="custom_overscroll_sample"/> <img src="https://github.com/noties/Scrollable/raw/master/art/scrollable_dialog.gif" width="30%" alt="dialog_sample"/>
|
||||||
|
|
||||||
<sup>*Serving suggestion</sup>
|
<sup>*Serving suggestion</sup>
|
||||||
|
|
||||||
|
126
app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java
Normal file
126
app/src/main/java/ru/noties/markwon/AsyncDrawableLoader.java
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.graphics.Picture;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.PictureDrawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.caverock.androidsvg.SVG;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
|
import ru.noties.debug.Debug;
|
||||||
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
|
|
||||||
|
public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
||||||
|
|
||||||
|
private final TextView view;
|
||||||
|
private final Picasso picasso;
|
||||||
|
private final OkHttpClient client;
|
||||||
|
private final ExecutorService executorService;
|
||||||
|
private final Map<String, Future<?>> requests;
|
||||||
|
|
||||||
|
// sh*t..
|
||||||
|
public AsyncDrawableLoader(TextView view) {
|
||||||
|
this.view = view;
|
||||||
|
this.picasso = new Picasso.Builder(view.getContext())
|
||||||
|
.listener(new Picasso.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onImageLoadFailed(Picasso picasso, Uri uri, Exception exception) {
|
||||||
|
Debug.e(exception, picasso, uri);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
this.client = new OkHttpClient();
|
||||||
|
this.executorService = Executors.newCachedThreadPool();
|
||||||
|
this.requests = new HashMap<>(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
|
||||||
|
|
||||||
|
Debug.i("destination: %s", destination);
|
||||||
|
|
||||||
|
if (destination.endsWith(".svg")) {
|
||||||
|
// load svg
|
||||||
|
requests.put(destination, loadSvg(destination, drawable));
|
||||||
|
} else if (destination.endsWith(".gif")) {
|
||||||
|
requests.put(destination, loadGif(destination, drawable));
|
||||||
|
} else {
|
||||||
|
picasso
|
||||||
|
.load(destination)
|
||||||
|
.tag(destination)
|
||||||
|
.into(new TextViewTarget(view, drawable));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancel(@NonNull String destination) {
|
||||||
|
Debug.i("destination: %s", destination);
|
||||||
|
picasso.cancelTag(destination);
|
||||||
|
|
||||||
|
final Future<?> future = requests.get(destination);
|
||||||
|
if (future != null) {
|
||||||
|
future.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<?> loadSvg(final String destination, final AsyncDrawable asyncDrawable) {
|
||||||
|
return executorService.submit(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final URL url = new URL(destination);
|
||||||
|
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
final InputStream inputStream = connection.getInputStream();
|
||||||
|
final SVG svg = SVG.getFromInputStream(inputStream);
|
||||||
|
final Picture picture = svg.renderToPicture();
|
||||||
|
final Drawable drawable = new PictureDrawable(picture);
|
||||||
|
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||||
|
asyncDrawable.setResult(drawable);
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Debug.e(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Future<?> loadGif(final String destination, final AsyncDrawable asyncDrawable) {
|
||||||
|
return executorService.submit(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final URL url = new URL(destination);
|
||||||
|
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
final InputStream inputStream = connection.getInputStream();
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
final byte[] buffer = new byte[1024 * 8];
|
||||||
|
int read;
|
||||||
|
while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) {
|
||||||
|
baos.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
final GifDrawable drawable = new GifDrawable(baos.toByteArray());
|
||||||
|
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||||
|
asyncDrawable.setResult(drawable);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Debug.e(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ package ru.noties.markwon;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@ -33,15 +32,7 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
final TextView textView = (TextView) findViewById(R.id.activity_main);
|
final TextView textView = (TextView) findViewById(R.id.activity_main);
|
||||||
|
|
||||||
//
|
final AsyncDrawable.Loader loader = new AsyncDrawableLoader(textView);
|
||||||
// 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() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -75,17 +66,7 @@ public class MainActivity extends Activity {
|
|||||||
final long start = SystemClock.uptimeMillis();
|
final long start = SystemClock.uptimeMillis();
|
||||||
|
|
||||||
final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this)
|
final SpannableConfiguration configuration = SpannableConfiguration.builder(MainActivity.this)
|
||||||
.asyncDrawableLoader(new AsyncDrawable.Loader() {
|
.asyncDrawableLoader(loader)
|
||||||
@Override
|
|
||||||
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
|
|
||||||
Debug.i("destination: %s, drawable: %s", destination, drawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel(@NonNull String destination) {
|
|
||||||
Debug.i("destination: %s", destination);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final CharSequence text = Markwon.markdown(configuration, md);
|
final CharSequence text = Markwon.markdown(configuration, md);
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
//package ru.noties.markwon;
|
|
||||||
//
|
|
||||||
//public class Markwon {
|
|
||||||
//
|
|
||||||
// // todo, annotation processor to PRE_COMPILE markdown!! no... multiple lnguages and you are out, forget about it
|
|
||||||
// // view for debugging (to view in preview) x3!
|
|
||||||
//}
|
|
@ -1,444 +0,0 @@
|
|||||||
//package ru.noties.markwon;
|
|
||||||
//
|
|
||||||
//import android.graphics.Canvas;
|
|
||||||
//import android.graphics.ColorFilter;
|
|
||||||
//import android.graphics.drawable.Drawable;
|
|
||||||
//import android.os.Handler;
|
|
||||||
//import android.os.Looper;
|
|
||||||
//import android.support.annotation.IntRange;
|
|
||||||
//import android.support.annotation.NonNull;
|
|
||||||
//import android.support.annotation.Nullable;
|
|
||||||
//import android.text.Html;
|
|
||||||
//import android.text.SpannableStringBuilder;
|
|
||||||
//import android.text.Spanned;
|
|
||||||
//import android.text.style.AbsoluteSizeSpan;
|
|
||||||
//import android.text.style.StrikethroughSpan;
|
|
||||||
//import android.text.style.URLSpan;
|
|
||||||
//
|
|
||||||
//import org.commonmark.ext.gfm.strikethrough.Strikethrough;
|
|
||||||
//import org.commonmark.node.AbstractVisitor;
|
|
||||||
//import org.commonmark.node.BlockQuote;
|
|
||||||
//import org.commonmark.node.BulletList;
|
|
||||||
//import org.commonmark.node.Code;
|
|
||||||
//import org.commonmark.node.CustomBlock;
|
|
||||||
//import org.commonmark.node.CustomNode;
|
|
||||||
//import org.commonmark.node.Document;
|
|
||||||
//import org.commonmark.node.Emphasis;
|
|
||||||
//import org.commonmark.node.FencedCodeBlock;
|
|
||||||
//import org.commonmark.node.HardLineBreak;
|
|
||||||
//import org.commonmark.node.Heading;
|
|
||||||
//import org.commonmark.node.HtmlBlock;
|
|
||||||
//import org.commonmark.node.HtmlInline;
|
|
||||||
//import org.commonmark.node.Image;
|
|
||||||
//import org.commonmark.node.IndentedCodeBlock;
|
|
||||||
//import org.commonmark.node.Link;
|
|
||||||
//import org.commonmark.node.ListItem;
|
|
||||||
//import org.commonmark.node.Node;
|
|
||||||
//import org.commonmark.node.OrderedList;
|
|
||||||
//import org.commonmark.node.Paragraph;
|
|
||||||
//import org.commonmark.node.SoftLineBreak;
|
|
||||||
//import org.commonmark.node.StrongEmphasis;
|
|
||||||
//import org.commonmark.node.Text;
|
|
||||||
//import org.commonmark.node.ThematicBreak;
|
|
||||||
//import org.commonmark.renderer.Renderer;
|
|
||||||
//
|
|
||||||
//import java.util.ArrayDeque;
|
|
||||||
//import java.util.Arrays;
|
|
||||||
//import java.util.Deque;
|
|
||||||
//
|
|
||||||
//import ru.noties.debug.Debug;
|
|
||||||
//import ru.noties.markwon.spans.BlockQuoteSpan;
|
|
||||||
//import ru.noties.markwon.spans.CodeSpan;
|
|
||||||
//import ru.noties.markwon.spans.AsyncDrawableSpan;
|
|
||||||
//import ru.noties.markwon.spans.EmphasisSpan;
|
|
||||||
//import ru.noties.markwon.spans.BulletListItemSpan;
|
|
||||||
//import ru.noties.markwon.spans.StrongEmphasisSpan;
|
|
||||||
//import ru.noties.markwon.spans.SubScriptSpan;
|
|
||||||
//import ru.noties.markwon.spans.SuperScriptSpan;
|
|
||||||
//import ru.noties.markwon.spans.ThematicBreakSpan;
|
|
||||||
//
|
|
||||||
//public class SpannableRenderer implements Renderer {
|
|
||||||
//
|
|
||||||
// // todo, util to extract all drawables and attach to textView (gif, animations, lazyLoading, etc)
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void render(Node node, Appendable output) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public String render(Node node) {
|
|
||||||
// // hm.. doesn't make sense to render to string
|
|
||||||
// throw null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// public CharSequence _render(Node node) {
|
|
||||||
// final SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
||||||
// node.accept(new SpannableNodeRenderer(builder));
|
|
||||||
// return builder;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static class SpannableNodeRenderer extends AbstractVisitor {
|
|
||||||
//
|
|
||||||
//// private static final float[] HEADING_SIZES = {
|
|
||||||
//// 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f,
|
|
||||||
//// };
|
|
||||||
//
|
|
||||||
// private final SpannableStringBuilder builder;
|
|
||||||
//
|
|
||||||
// private int blockQuoteIndent;
|
|
||||||
// private int listLevel;
|
|
||||||
//
|
|
||||||
// SpannableNodeRenderer(SpannableStringBuilder builder) {
|
|
||||||
// this.builder = builder;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(HardLineBreak hardLineBreak) {
|
|
||||||
// // todo
|
|
||||||
// Debug.i(hardLineBreak);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Text text) {
|
|
||||||
// builder.append(text.getLiteral());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(StrongEmphasis strongEmphasis) {
|
|
||||||
// final int length = builder.length();
|
|
||||||
// visitChildren(strongEmphasis);
|
|
||||||
// builder.setSpan(new StrongEmphasisSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Emphasis emphasis) {
|
|
||||||
// final int length = builder.length();
|
|
||||||
// visitChildren(emphasis);
|
|
||||||
// builder.setSpan(new EmphasisSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(IndentedCodeBlock indentedCodeBlock) {
|
|
||||||
// // todo
|
|
||||||
// Debug.i(indentedCodeBlock);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(BlockQuote blockQuote) {
|
|
||||||
// builder.append('\n');
|
|
||||||
// final int length = builder.length();
|
|
||||||
// blockQuoteIndent += 1;
|
|
||||||
// visitChildren(blockQuote);
|
|
||||||
// builder.setSpan(new BlockQuoteSpan(blockQuoteIndent), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// blockQuoteIndent -= 1;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Code code) {
|
|
||||||
// final int length = builder.length();
|
|
||||||
// builder.append(code.getLiteral());
|
|
||||||
//// builder.setSpan(new ForegroundColorSpan(0xff00ff00), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// builder.setSpan(new CodeSpan(false, length, builder.length()), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(BulletList bulletList) {
|
|
||||||
// Debug.i(bulletList, bulletList.getBulletMarker());
|
|
||||||
// visitChildren(bulletList);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(ListItem listItem) {
|
|
||||||
// Debug.i(listItem);
|
|
||||||
//// builder.append('\n');
|
|
||||||
// if (builder.charAt(builder.length() - 1) != '\n') {
|
|
||||||
// builder.append('\n');
|
|
||||||
// }
|
|
||||||
// final int length = builder.length();
|
|
||||||
// blockQuoteIndent += 1;
|
|
||||||
// listLevel += 1;
|
|
||||||
// visitChildren(listItem);
|
|
||||||
//// builder.setSpan(new BulletSpan(4, 0xff0000ff), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// builder.setSpan(new BulletListItemSpan(blockQuoteIndent, listLevel > 1, length), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// blockQuoteIndent -= 1;
|
|
||||||
// listLevel -= 1;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(ThematicBreak thematicBreak) {
|
|
||||||
// final int length = builder.length();
|
|
||||||
// builder.append('\n')
|
|
||||||
// .append(' '); // without space it won't render
|
|
||||||
// builder.setSpan(new ThematicBreakSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// builder.append('\n');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(OrderedList orderedList) {
|
|
||||||
// Debug.i(orderedList, orderedList.getDelimiter(), orderedList.getStartNumber());
|
|
||||||
// // todo, ordering numbers
|
|
||||||
// super.visit(orderedList);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(SoftLineBreak softLineBreak) {
|
|
||||||
// Debug.i(softLineBreak);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Heading heading) {
|
|
||||||
// Debug.i(heading);
|
|
||||||
// if (builder.length() != 0 && builder.charAt(builder.length() - 1) != '\n') {
|
|
||||||
// builder.append('\n');
|
|
||||||
// }
|
|
||||||
// final int length = builder.length();
|
|
||||||
// visitChildren(heading);
|
|
||||||
// final int max = 120;
|
|
||||||
// final int one = 20; // total is 6
|
|
||||||
// final int size = max - ((heading.getLevel() - 1) * one);
|
|
||||||
// builder.setSpan(new AbsoluteSizeSpan(size), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// builder.append('\n');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(FencedCodeBlock fencedCodeBlock) {
|
|
||||||
// builder.append('\n');
|
|
||||||
// final int length = builder.length();
|
|
||||||
// builder.append(fencedCodeBlock.getLiteral());
|
|
||||||
// builder.setSpan(new CodeSpan(true, length, builder.length() - 1), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Paragraph paragraph) {
|
|
||||||
// Debug.i(paragraph);
|
|
||||||
// if (listLevel == 0
|
|
||||||
// && blockQuoteIndent == 0) {
|
|
||||||
// builder.append('\n')
|
|
||||||
// .append('\n');
|
|
||||||
// }
|
|
||||||
// visitChildren(paragraph);
|
|
||||||
//
|
|
||||||
// if (listLevel == 0
|
|
||||||
// && blockQuoteIndent == 0) {
|
|
||||||
// builder.append('\n')
|
|
||||||
// .append('\n');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//// private int htmlStart = -1;
|
|
||||||
// private final Deque<HtmlInlineItem> htmlStack = new ArrayDeque<>();
|
|
||||||
//
|
|
||||||
// private static class HtmlInlineItem {
|
|
||||||
//
|
|
||||||
// final int start;
|
|
||||||
// final String tag;
|
|
||||||
//
|
|
||||||
// private HtmlInlineItem(int start, String tag) {
|
|
||||||
// this.start = start;
|
|
||||||
// this.tag = tag;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(HtmlInline htmlInline) {
|
|
||||||
//
|
|
||||||
//// Debug.i(htmlInline, htmlStart);
|
|
||||||
//// Debug.i(htmlInline.getLiteral(), htmlInline.toString());
|
|
||||||
//
|
|
||||||
// // okay, it's seems that we desperately need to understand if it's opening tag or closing
|
|
||||||
//
|
|
||||||
// final HtmlTag tag = parseTag(htmlInline.getLiteral());
|
|
||||||
//
|
|
||||||
// Debug.i(htmlInline.getLiteral(), tag);
|
|
||||||
//
|
|
||||||
// if (tag != null) {
|
|
||||||
// Debug.i("tag: %s, closing: %s", tag.tag, tag.closing);
|
|
||||||
// if (!tag.closing) {
|
|
||||||
// htmlStack.push(new HtmlInlineItem(builder.length(), tag.tag));
|
|
||||||
// visitChildren(htmlInline);
|
|
||||||
// } else {
|
|
||||||
// final HtmlInlineItem item = htmlStack.pop();
|
|
||||||
// final int start = item.start;
|
|
||||||
// final int end = builder.length();
|
|
||||||
// // here, additionally, we can render some tags ourselves (sup/sub)
|
|
||||||
// if ("sup".equals(item.tag)) {
|
|
||||||
// builder.setSpan(new SuperScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// } else if("sub".equals(item.tag)) {
|
|
||||||
// builder.setSpan(new SubScriptSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// } else if("del".equals(item.tag)) {
|
|
||||||
// // weird, but `Html` class does not return a spannable for `<del>o</del>`
|
|
||||||
// // seems like a bug
|
|
||||||
// builder.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// } else {
|
|
||||||
// final String html = "<" + item.tag + ">" + (builder.subSequence(start, end).toString()) + "</" + item.tag + ">";
|
|
||||||
// final Spanned spanned = Html.fromHtml(html);
|
|
||||||
// final Object[] spans = spanned.getSpans(0, spanned.length(), Object.class);
|
|
||||||
//
|
|
||||||
// Debug.i("html: %s, start: %d, end: %d, spans: %s", html, start, end, Arrays.toString(spans));
|
|
||||||
//
|
|
||||||
// if (spans != null
|
|
||||||
// && spans.length > 0) {
|
|
||||||
// for (Object span: spans) {
|
|
||||||
// Debug.i(span);
|
|
||||||
// builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// super.visit(htmlInline);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static class HtmlTag {
|
|
||||||
// final String tag;
|
|
||||||
// final boolean closing;
|
|
||||||
// HtmlTag(String tag, boolean closing) {
|
|
||||||
// this.tag = tag;
|
|
||||||
// this.closing = closing;
|
|
||||||
// }
|
|
||||||
// @Override
|
|
||||||
// public String toString() {
|
|
||||||
// return "HtmlTag{" +
|
|
||||||
// "tag='" + tag + '\'' +
|
|
||||||
// ", closing=" + closing +
|
|
||||||
// '}';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static HtmlTag parseTag(String in) {
|
|
||||||
//
|
|
||||||
// final HtmlTag out;
|
|
||||||
//
|
|
||||||
// final int length = in != null
|
|
||||||
// ? in.length()
|
|
||||||
// : 0;
|
|
||||||
//
|
|
||||||
// Debug.i(in, length);
|
|
||||||
//
|
|
||||||
// if (length == 0 || length < 3) {
|
|
||||||
// out = null;
|
|
||||||
// } else {
|
|
||||||
//
|
|
||||||
// final boolean closing = '<' == in.charAt(0) && '/' == in.charAt(1);
|
|
||||||
// final String tag = closing
|
|
||||||
// ? in.substring(2, in.length() - 1)
|
|
||||||
// : in.substring(1, in.length() - 1);
|
|
||||||
// out = new HtmlTag(tag, closing);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return out;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(HtmlBlock htmlBlock) {
|
|
||||||
// // interestring thing... what is it also?
|
|
||||||
// Debug.i(htmlBlock);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(CustomBlock customBlock) {
|
|
||||||
// // not supported, what is it anyway?
|
|
||||||
// Debug.i(customBlock);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Document document) {
|
|
||||||
// // the whole document, no need to do anything
|
|
||||||
// Debug.i(document);
|
|
||||||
// super.visit(document);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Link link) {
|
|
||||||
// Debug.i(link);
|
|
||||||
// final int length = builder.length();
|
|
||||||
// visitChildren(link);
|
|
||||||
// builder.setSpan(new URLSpan(link.getDestination()), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(Image image) {
|
|
||||||
// // not supported... maybe for now?
|
|
||||||
// Debug.i(image);
|
|
||||||
// final int length = builder.length();
|
|
||||||
// super.visit(image);
|
|
||||||
//
|
|
||||||
//// final int length = builder.length();
|
|
||||||
// final TestDrawable drawable = new TestDrawable();
|
|
||||||
// final AsyncDrawableSpan span = new AsyncDrawableSpan(drawable);
|
|
||||||
// builder.append(" ");
|
|
||||||
// builder.setSpan(span, length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void visit(CustomNode customNode) {
|
|
||||||
//
|
|
||||||
// Debug.i(customNode);
|
|
||||||
//
|
|
||||||
// if (customNode instanceof Strikethrough) {
|
|
||||||
// final int length = builder.length();
|
|
||||||
// visitChildren(customNode);
|
|
||||||
// builder.setSpan(new StrikethroughSpan(), length, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// } else {
|
|
||||||
// super.visit(customNode);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// private static class TestDrawable extends Drawable {
|
|
||||||
//
|
|
||||||
// private final Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
// private boolean called;
|
|
||||||
//
|
|
||||||
// TestDrawable() {
|
|
||||||
// setBounds(0, 0, 50, 50);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void draw(@NonNull final Canvas canvas) {
|
|
||||||
// canvas.clipRect(getBounds());
|
|
||||||
// if (!called) {
|
|
||||||
// canvas.drawColor(0xFF00ff00);
|
|
||||||
// handler.removeCallbacksAndMessages(null);
|
|
||||||
// handler.postDelayed(new Runnable() {
|
|
||||||
// @Override
|
|
||||||
// public void run() {
|
|
||||||
// called = true;
|
|
||||||
// setBounds(0, 0, 400, 400);
|
|
||||||
// invalidateSelf();
|
|
||||||
// }
|
|
||||||
// }, 2000L);
|
|
||||||
// } else {
|
|
||||||
// canvas.drawColor(0xFFff0000);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public int getOpacity() {
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public int getIntrinsicWidth() {
|
|
||||||
// return called ? 400 : 50;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public int getIntrinsicHeight() {
|
|
||||||
// return called ? 400 : 50;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
74
app/src/main/java/ru/noties/markwon/TextViewTarget.java
Normal file
74
app/src/main/java/ru/noties/markwon/TextViewTarget.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
import com.squareup.picasso.Target;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
|
|
||||||
|
public class TextViewTarget implements Target {
|
||||||
|
|
||||||
|
private final TextView view;
|
||||||
|
private final AsyncDrawable asyncDrawable;
|
||||||
|
|
||||||
|
public TextViewTarget(TextView view, AsyncDrawable asyncDrawable) {
|
||||||
|
this.view = view;
|
||||||
|
this.asyncDrawable = asyncDrawable;
|
||||||
|
|
||||||
|
attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
|
||||||
|
if (bitmap != null) {
|
||||||
|
final Drawable drawable = new BitmapDrawable(view.getResources(), bitmap);
|
||||||
|
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||||
|
asyncDrawable.setResult(drawable);
|
||||||
|
}
|
||||||
|
detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBitmapFailed(Drawable errorDrawable) {
|
||||||
|
if (errorDrawable != null) {
|
||||||
|
asyncDrawable.setResult(errorDrawable);
|
||||||
|
}
|
||||||
|
detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareLoad(Drawable placeHolderDrawable) {
|
||||||
|
if (placeHolderDrawable != null) {
|
||||||
|
asyncDrawable.setResult(placeHolderDrawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attach() {
|
||||||
|
|
||||||
|
// amazing stuff here, in order to keep this target alive (picasso stores target in a WeakReference)
|
||||||
|
// we need to do this
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
List<TextViewTarget> list = (List<TextViewTarget>) view.getTag(R.id.amazing);
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<>(2);
|
||||||
|
view.setTag(R.id.amazing, list);
|
||||||
|
}
|
||||||
|
list.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void detach() {
|
||||||
|
//noinspection unchecked
|
||||||
|
final List<TextViewTarget> list = (List<TextViewTarget>) view.getTag(R.id.amazing);
|
||||||
|
if (list != null) {
|
||||||
|
list.remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,45 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
import ru.noties.debug.Debug;
|
|
||||||
|
|
||||||
public class BlockQuoteSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
private final int indent;
|
|
||||||
|
|
||||||
public BlockQuoteSpan(int indent) {
|
|
||||||
this.indent = indent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
return 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
// Debug.i("x: %d, dir: %d, top: %d, baseline: %d, bottom: %d, first: %s",
|
|
||||||
// x, dir, top, baseline, bottom, first
|
|
||||||
// );
|
|
||||||
|
|
||||||
final int save = c.save();
|
|
||||||
try {
|
|
||||||
final int left = 24 * (indent - 1);
|
|
||||||
// final RectF rectF = new RectF(0, 0, 16, 16);
|
|
||||||
final Rect rect = new Rect(left, top, left + 8, bottom);
|
|
||||||
final Paint paint = new Paint();
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
paint.setColor(0xFFf0f0f0);
|
|
||||||
c.drawRect(rect, paint);
|
|
||||||
// c.translate(x, .0F);
|
|
||||||
// c.drawOval(rectF, paint);
|
|
||||||
} finally {
|
|
||||||
c.restoreToCount(save);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,270 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.support.annotation.IntRange;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.style.ReplacementSpan;
|
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
|
||||||
|
|
||||||
// we will use Replacement span because code blocks cannot contain other markdown
|
|
||||||
// so we will render the string (not a charSequence with possible metric affecting spans)
|
|
||||||
public class CodeSpan extends ReplacementSpan/* implements LeadingMarginSpan*/ {
|
|
||||||
|
|
||||||
private final boolean multiline;
|
|
||||||
private final int start;
|
|
||||||
private final int end;
|
|
||||||
|
|
||||||
private final Rect rect = new Rect();
|
|
||||||
private final Rect borderRect = new Rect();
|
|
||||||
private final Paint paint = new Paint();
|
|
||||||
|
|
||||||
public CodeSpan(boolean multiline, int start, int end) {
|
|
||||||
this.multiline = multiline;
|
|
||||||
this.start = start;
|
|
||||||
this.end = end;
|
|
||||||
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize(
|
|
||||||
@NonNull Paint paint,
|
|
||||||
CharSequence text,
|
|
||||||
@IntRange(from = 0) int start,
|
|
||||||
@IntRange(from = 0) int end,
|
|
||||||
@Nullable Paint.FontMetricsInt fm
|
|
||||||
) {
|
|
||||||
|
|
||||||
final CharSequence cs = text.subSequence(start, end);
|
|
||||||
final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F);
|
|
||||||
|
|
||||||
// final StaticLayout layout = new StaticLayout(cs, new TextPaint(paint), 10000, Layout.Alignment.ALIGN_NORMAL, 1.F, .0F, false);
|
|
||||||
// final float width = layout.getLineWidth(0);
|
|
||||||
// final int out = 32 + (int) (width + .5F);
|
|
||||||
|
|
||||||
// Debug.i("text: %s, width: %s", cs, width);
|
|
||||||
|
|
||||||
if (fm != null) {
|
|
||||||
// we add a padding top & bottom
|
|
||||||
Debug.i("a: %s, d: %s, t: %s, b: %s", fm.ascent, fm.descent, fm.top, fm.bottom);
|
|
||||||
final float ratio = .62F; // golden ratio
|
|
||||||
fm.ascent = fm.ascent - 8;
|
|
||||||
fm.descent = (int) (-fm.ascent * ratio);
|
|
||||||
fm.top = fm.ascent;
|
|
||||||
fm.bottom = fm.descent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(
|
|
||||||
@NonNull Canvas canvas,
|
|
||||||
CharSequence text,
|
|
||||||
@IntRange(from = 0) int start,
|
|
||||||
@IntRange(from = 0) int end,
|
|
||||||
float x,
|
|
||||||
int top,
|
|
||||||
int y,
|
|
||||||
int bottom,
|
|
||||||
@NonNull Paint paint
|
|
||||||
) {
|
|
||||||
|
|
||||||
final int left = (int) (x + .5F);
|
|
||||||
|
|
||||||
final int right;
|
|
||||||
if (multiline) {
|
|
||||||
right = canvas.getWidth();
|
|
||||||
} else {
|
|
||||||
final int width = (16 * 2) + (int) (paint.measureText(text, start, end) + .5F);
|
|
||||||
right = left + width;
|
|
||||||
}
|
|
||||||
|
|
||||||
rect.set(left, top, right, bottom);
|
|
||||||
|
|
||||||
|
|
||||||
// okay, draw background first
|
|
||||||
drawBackground(canvas);
|
|
||||||
|
|
||||||
// then, if any, draw borders
|
|
||||||
drawBorders(canvas, this.start == start, this.end == end);
|
|
||||||
|
|
||||||
// draw text
|
|
||||||
// y center position
|
|
||||||
final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2);
|
|
||||||
// if (config.textColor != 0) {
|
|
||||||
// // we will use Paint object that is used to draw all the text (textSize, textColor, typeface, etc)
|
|
||||||
// paint.setColor(config.textColor);
|
|
||||||
// }
|
|
||||||
canvas.drawText(text, start, end, x + 16, b, paint);
|
|
||||||
|
|
||||||
|
|
||||||
// Debug.i("text: %s, x: %s, top: %s, y: %s, bottom: %s", text.subSequence(start, end), x, top, y, bottom);
|
|
||||||
//
|
|
||||||
// final CharSequence cs = text.subSequence(start, end);
|
|
||||||
//
|
|
||||||
// final int width = 32 + (int) (paint.measureText(cs, 0, cs.length()) + .5F);
|
|
||||||
//
|
|
||||||
// final int left = (int) (x + .5F);
|
|
||||||
// final int right = multiline
|
|
||||||
// ? canvas.getWidth()
|
|
||||||
// : left + width;
|
|
||||||
//
|
|
||||||
// final Rect rect = new Rect(
|
|
||||||
// left,
|
|
||||||
// top,
|
|
||||||
// right,
|
|
||||||
// bottom
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// final Paint p = new Paint();
|
|
||||||
// p.setStyle(Paint.Style.FILL);
|
|
||||||
// p.setColor(0x80ff0000);
|
|
||||||
// canvas.drawRect(rect, p);
|
|
||||||
//
|
|
||||||
// // y center position
|
|
||||||
// final int b = bottom - ((bottom - top) / 2) - (int) ((paint.descent() + paint.ascent()) / 2);
|
|
||||||
// p.setColor(0xFF000000);
|
|
||||||
// canvas.drawText(cs, 0, cs.length(), x + 16, b, paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawBackground(Canvas canvas) {
|
|
||||||
// final int color = config.backgroundColor;
|
|
||||||
// if (color != 0) {
|
|
||||||
paint.setColor(0x40ff0000);
|
|
||||||
canvas.drawRect(rect, paint);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawBorders(Canvas canvas, boolean top, boolean bottom) {
|
|
||||||
|
|
||||||
final int color = 0xFFff0000;
|
|
||||||
final int width = 4;
|
|
||||||
// if (color == 0
|
|
||||||
// || width == 0) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
paint.setColor(color);
|
|
||||||
|
|
||||||
// left and right are always drawn
|
|
||||||
|
|
||||||
// LEFT
|
|
||||||
borderRect.set(rect.left, rect.top, rect.left + width, rect.bottom);
|
|
||||||
canvas.drawRect(borderRect, paint);
|
|
||||||
|
|
||||||
// RIGHT
|
|
||||||
borderRect.set(rect.right - width, rect.top, rect.right, rect.bottom);
|
|
||||||
canvas.drawRect(borderRect, paint);
|
|
||||||
|
|
||||||
// TOP
|
|
||||||
if (top) {
|
|
||||||
borderRect.set(rect.left, rect.top, rect.right, rect.top + width);
|
|
||||||
canvas.drawRect(borderRect, paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BOTTOM
|
|
||||||
if (bottom) {
|
|
||||||
borderRect.set(rect.left, rect.bottom - width, rect.right, rect.bottom);
|
|
||||||
canvas.drawRect(borderRect, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public int getLeadingMargin(boolean first) {
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
//// Debug.i("x: %d, top: %d, bottom: %d", x, top, bottom);
|
|
||||||
//
|
|
||||||
//// Debug.i("this: [%d, %d], came: [%d, %d]", this.start, this.end, start, end);
|
|
||||||
// Debug.i("x: %d, canvas: [%d-%d], text: %s", x, c.getWidth(), c.getHeight(), (text.subSequence(start, end)));
|
|
||||||
//
|
|
||||||
// // the thing is... if we do not draw, then text won't be drawn also
|
|
||||||
// final Rect rect = new Rect();
|
|
||||||
//
|
|
||||||
// final Paint paint = new Paint();
|
|
||||||
// paint.setStyle(Paint.Style.FILL);
|
|
||||||
// paint.setColor(0xffcccccc);
|
|
||||||
//
|
|
||||||
// rect.set(x, top, c.getWidth(), bottom);
|
|
||||||
// c.drawRect(rect, paint);
|
|
||||||
//
|
|
||||||
// if (this.start == start) {
|
|
||||||
// this.top = top;
|
|
||||||
//
|
|
||||||
//// final int save = c.save();
|
|
||||||
//// try {
|
|
||||||
//// c.drawColor(0x00ffffff);
|
|
||||||
//// } finally {
|
|
||||||
//// c.restoreToCount(save);
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
//// c.drawColor(0x00ffffff);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (this.end == end) {
|
|
||||||
// // draw borders
|
|
||||||
// final Rect r = new Rect(x + 1, this.top, c.getWidth() - x, bottom);
|
|
||||||
// final Paint pa = new Paint();
|
|
||||||
// pa.setStyle(Paint.Style.STROKE);
|
|
||||||
// pa.setColor(0xff999999);
|
|
||||||
// c.drawRect(r, pa);
|
|
||||||
// }
|
|
||||||
//// rect.inset((int) paint.getStrokeWidth(), (int) paint.getStrokeWidth());
|
|
||||||
//// paint.setStyle(Paint.Style.STROKE);
|
|
||||||
//// paint.setColor(0xff333333);
|
|
||||||
//// c.drawRect(rect, paint);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) {
|
|
||||||
//// int ht = mDrawable.getIntrinsicHeight();
|
|
||||||
////
|
|
||||||
//// int need = ht - (v + fm.descent - fm.ascent - istartv);
|
|
||||||
//// if (need > 0)
|
|
||||||
//// fm.descent += need;
|
|
||||||
////
|
|
||||||
//// need = ht - (v + fm.bottom - fm.top - istartv);
|
|
||||||
//// if (need > 0)
|
|
||||||
//// fm.bottom += need;
|
|
||||||
////
|
|
||||||
//
|
|
||||||
//// final int lineOffset = v - spanstartv;
|
|
||||||
//// final int desired = 128;
|
|
||||||
//// final int currentLineHeight = -fm.ascent + fm.descent;
|
|
||||||
//// final float ratio = (float) desired / currentLineHeight;
|
|
||||||
////
|
|
||||||
//// Debug.i("fm, came: %s", fm);
|
|
||||||
//// Debug.i("lineOffset: %d, current: %d, ratio: %s", lineOffset, currentLineHeight, ratio);
|
|
||||||
////
|
|
||||||
//// fm.ascent = (int) (ratio * fm.ascent + .5F);
|
|
||||||
//// fm.descent = (int) (ratio * fm.descent + .5F);
|
|
||||||
////
|
|
||||||
//// Debug.i("fm, out: %s", fm);
|
|
||||||
//
|
|
||||||
//// Debug.i("top: %d, bottom: %d, ascent: %d, descent: %d", fm.top, fm.bottom, fm.ascent, fm.descent);
|
|
||||||
//// Debug.i("lineHeight: %d, v: %d, spanstartv: %d", lineOffset, v, spanstartv);
|
|
||||||
////
|
|
||||||
//// final int h = 128;
|
|
||||||
//// final int descentNeed = h - (v + fm.descent - fm.ascent - spanstartv);
|
|
||||||
//// if (descentNeed > 0) {
|
|
||||||
//// fm.ascent -= descentNeed / 2;
|
|
||||||
//// fm.descent += descentNeed / 2;
|
|
||||||
//// }
|
|
||||||
//// final int bottomNeed = h - (v + fm.bottom - fm.top - spanstartv);
|
|
||||||
//// if (bottomNeed > 0) {
|
|
||||||
//// fm.top -= bottomNeed;
|
|
||||||
//// fm.bottom += bottomNeed;
|
|
||||||
//// }
|
|
||||||
////
|
|
||||||
//// Debug.i("out, ascent: %d, descent: %d, bottom: %d", fm.ascent, fm.descent, fm.bottom);
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.support.annotation.IntDef;
|
|
||||||
import android.support.annotation.IntRange;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.style.ReplacementSpan;
|
|
||||||
|
|
||||||
public class DrawableSpan extends ReplacementSpan {
|
|
||||||
|
|
||||||
@IntDef({ ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER })
|
|
||||||
@interface Alignment {}
|
|
||||||
|
|
||||||
public static final int ALIGN_BOTTOM = 0;
|
|
||||||
public static final int ALIGN_BASELINE = 1;
|
|
||||||
public static final int ALIGN_CENTER = 2;
|
|
||||||
|
|
||||||
private final Drawable drawable;
|
|
||||||
private final int alignment;
|
|
||||||
|
|
||||||
public DrawableSpan(@NonNull Drawable drawable) {
|
|
||||||
this(drawable, ALIGN_BOTTOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawableSpan(@NonNull Drawable drawable, @Alignment int alignment) {
|
|
||||||
this.drawable = drawable;
|
|
||||||
this.alignment = alignment;
|
|
||||||
|
|
||||||
// additionally set intrinsic bounds if empty
|
|
||||||
final Rect rect = drawable.getBounds();
|
|
||||||
if (rect.isEmpty()) {
|
|
||||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize(
|
|
||||||
@NonNull Paint paint,
|
|
||||||
CharSequence text,
|
|
||||||
@IntRange(from = 0) int start,
|
|
||||||
@IntRange(from = 0) int end,
|
|
||||||
@Nullable Paint.FontMetricsInt fm) {
|
|
||||||
|
|
||||||
final Rect rect = drawable.getBounds();
|
|
||||||
|
|
||||||
if (fm != null) {
|
|
||||||
fm.ascent = -rect.bottom;
|
|
||||||
fm.descent = 0;
|
|
||||||
|
|
||||||
fm.top = fm.ascent;
|
|
||||||
fm.bottom = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rect.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(
|
|
||||||
@NonNull Canvas canvas,
|
|
||||||
CharSequence text,
|
|
||||||
@IntRange(from = 0) int start,
|
|
||||||
@IntRange(from = 0) int end,
|
|
||||||
float x,
|
|
||||||
int top,
|
|
||||||
int y,
|
|
||||||
int bottom,
|
|
||||||
@NonNull Paint paint) {
|
|
||||||
|
|
||||||
final Drawable drawable = this.drawable;
|
|
||||||
|
|
||||||
final int b = bottom - drawable.getBounds().bottom;
|
|
||||||
|
|
||||||
final int save = canvas.save();
|
|
||||||
try {
|
|
||||||
final int translationY;
|
|
||||||
if (ALIGN_CENTER == alignment) {
|
|
||||||
translationY = (int) (b / 2.F + .5F);
|
|
||||||
} else if (ALIGN_BASELINE == alignment) {
|
|
||||||
translationY = b - paint.getFontMetricsInt().descent;
|
|
||||||
} else {
|
|
||||||
translationY = b;
|
|
||||||
}
|
|
||||||
canvas.translate(x, translationY);
|
|
||||||
drawable.draw(canvas);
|
|
||||||
} finally {
|
|
||||||
canvas.restoreToCount(save);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Drawable getDrawable() {
|
|
||||||
return drawable;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
|
||||||
|
|
||||||
public class DrawableSpanUtils {
|
|
||||||
|
|
||||||
// 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<Drawable> list = new ArrayList<>(2);
|
|
||||||
|
|
||||||
for (Object span: spans) {
|
|
||||||
if (span instanceof DrawableSpan) {
|
|
||||||
list.add(((DrawableSpan) 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 (Drawable drawable: list) {
|
|
||||||
drawable.setCallback(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (Drawable drawable: list) {
|
|
||||||
drawable.setCallback(new DrawableCallbackImpl(textView, drawable.getBounds()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DrawableSpanUtils() {}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class EmphasisSpan extends MetricAffectingSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint p) {
|
|
||||||
p.setTextSkewX(-0.25f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setTextSkewX(-0.25f);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
import ru.noties.debug.Debug;
|
|
||||||
|
|
||||||
public class ListItemSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
private final int blockIndent;
|
|
||||||
private final boolean nested;
|
|
||||||
private final int start;
|
|
||||||
|
|
||||||
public ListItemSpan(int blockIndent, boolean nested, int start) {
|
|
||||||
this.blockIndent = blockIndent;
|
|
||||||
this.nested = nested;
|
|
||||||
this.start = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
return 36;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
// Debug.i("x: %d, dir: %d, top: %d, baseline: %d, bottom: %d, first: %s",
|
|
||||||
// x, dir, top, baseline, bottom, first
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if there was a line break, we don't need to draw it
|
|
||||||
if (this.start != start) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int save = c.save();
|
|
||||||
try {
|
|
||||||
final int left = 24 * (blockIndent - 1) + (first ? 12 : 0);
|
|
||||||
final RectF rectF = new RectF(left, top, left + 16, bottom);
|
|
||||||
final Paint paint = new Paint();
|
|
||||||
paint.setStyle(nested ? Paint.Style.STROKE : Paint.Style.FILL);
|
|
||||||
paint.setColor(0xFFff0000);
|
|
||||||
c.drawOval(rectF, paint);
|
|
||||||
} finally {
|
|
||||||
c.restoreToCount(save);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class StrongEmphasisSpan extends MetricAffectingSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint p) {
|
|
||||||
p.setFakeBoldText(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setFakeBoldText(true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class SubSpan extends MetricAffectingSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift -= (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift -= (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.text.TextPaint;
|
|
||||||
import android.text.style.MetricAffectingSpan;
|
|
||||||
|
|
||||||
public class SupSpan extends MetricAffectingSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateDrawState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift += (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMeasureState(TextPaint tp) {
|
|
||||||
tp.setTextSize(tp.getTextSize() * .75F);
|
|
||||||
tp.baselineShift += (int) (tp.ascent() / 2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package ru.noties.markwon.spans2;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.text.Layout;
|
|
||||||
import android.text.style.LeadingMarginSpan;
|
|
||||||
|
|
||||||
public class ThematicBreakSpan implements LeadingMarginSpan {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getLeadingMargin(boolean first) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
|
||||||
final int middle = (bottom - top) / 2;
|
|
||||||
final Rect rect = new Rect(0, top + middle - 2, c.getWidth(), top + middle + 2);
|
|
||||||
final Paint paint = new Paint();
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
paint.setColor(0x80000000);
|
|
||||||
c.drawRect(rect, paint);
|
|
||||||
}
|
|
||||||
}
|
|
4
app/src/main/res/values/ids.xml
Normal file
4
app/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<item name="amazing" type="id" />
|
||||||
|
</resources>
|
@ -1,17 +0,0 @@
|
|||||||
package ru.noties.markwon;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() throws Exception {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ package ru.noties.markwon;
|
|||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@ -13,6 +14,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.noties.debug.Debug;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
||||||
|
|
||||||
@ -21,6 +23,7 @@ abstract class DrawablesScheduler {
|
|||||||
static void schedule(@NonNull final TextView textView) {
|
static void schedule(@NonNull final TextView textView) {
|
||||||
|
|
||||||
final List<AsyncDrawable> list = extract(textView);
|
final List<AsyncDrawable> list = extract(textView);
|
||||||
|
Debug.i(list);
|
||||||
if (list.size() > 0) {
|
if (list.size() > 0) {
|
||||||
textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
textView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -36,13 +39,15 @@ abstract class DrawablesScheduler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (AsyncDrawable d : list) {
|
for (AsyncDrawable d : list) {
|
||||||
d.setCallback2(new DrawableCallbackImpl(textView, d.getBounds()));
|
Debug.i(d);
|
||||||
|
d.setCallback2(new DrawableCallbackImpl(textView, null, d.getBounds()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// must be called when text manually changed in TextView
|
// must be called when text manually changed in TextView
|
||||||
static void unschedule(@NonNull TextView view) {
|
static void unschedule(@NonNull TextView view) {
|
||||||
|
Debug.i();
|
||||||
for (AsyncDrawable d : extract(view)) {
|
for (AsyncDrawable d : extract(view)) {
|
||||||
d.setCallback2(null);
|
d.setCallback2(null);
|
||||||
}
|
}
|
||||||
@ -93,38 +98,61 @@ abstract class DrawablesScheduler {
|
|||||||
private DrawablesScheduler() {
|
private DrawablesScheduler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface CoordinatesProvider {
|
||||||
|
int getX();
|
||||||
|
int getY();
|
||||||
|
}
|
||||||
|
|
||||||
private static class DrawableCallbackImpl implements Drawable.Callback {
|
private static class DrawableCallbackImpl implements Drawable.Callback {
|
||||||
|
|
||||||
private final TextView view;
|
private final TextView view;
|
||||||
|
private final CoordinatesProvider coordinatesProvider;
|
||||||
private Rect previousBounds;
|
private Rect previousBounds;
|
||||||
|
|
||||||
DrawableCallbackImpl(TextView view, Rect initialBounds) {
|
DrawableCallbackImpl(TextView view, CoordinatesProvider provider, Rect initialBounds) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
this.coordinatesProvider = provider;
|
||||||
this.previousBounds = new Rect(initialBounds);
|
this.previousBounds = new Rect(initialBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateDrawable(@NonNull Drawable who) {
|
public void invalidateDrawable(@NonNull final Drawable who) {
|
||||||
|
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
view.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
invalidateDrawable(who);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Rect rect = who.getBounds();
|
||||||
|
|
||||||
// okay... teh thing is IF we do not change bounds size, normal invalidate would do
|
// 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...
|
// but if the size has changed, then we need to update the whole layout...
|
||||||
|
|
||||||
final Rect rect = who.getBounds();
|
|
||||||
|
|
||||||
if (!previousBounds.equals(rect)) {
|
if (!previousBounds.equals(rect)) {
|
||||||
// the only method that seems to work when bounds have changed
|
// the only method that seems to work when bounds have changed
|
||||||
view.setText(view.getText());
|
view.setText(view.getText());
|
||||||
previousBounds = new Rect(rect);
|
previousBounds = new Rect(rect);
|
||||||
} else {
|
} else {
|
||||||
// if bounds are the same then simple invalidate would do
|
// // if bounds are the same then simple invalidate would do
|
||||||
final int scrollX = view.getScrollX();
|
// if (coordinatesProvider != null) {
|
||||||
final int scrollY = view.getScrollY();
|
// final int x = coordinatesProvider.getX();
|
||||||
view.postInvalidate(
|
// final int y = coordinatesProvider.getY();
|
||||||
scrollX + rect.left,
|
// view.postInvalidate(
|
||||||
scrollY + rect.top,
|
// x + rect.left,
|
||||||
scrollX + rect.right,
|
// y + rect.top,
|
||||||
scrollY + rect.bottom
|
// x + rect.right,
|
||||||
);
|
// y + rect.bottom
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// // else all we can do is request full re-draw... maybe system is smart enough not re-draw what is not on screen?
|
||||||
|
// view.postInvalidate();
|
||||||
|
// }
|
||||||
|
view.postInvalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,4 +167,23 @@ abstract class DrawablesScheduler {
|
|||||||
view.removeCallbacks(what);
|
view.removeCallbacks(what);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class AsyncDrawableCoordinatesProvider implements CoordinatesProvider {
|
||||||
|
|
||||||
|
private final AsyncDrawableSpan span;
|
||||||
|
|
||||||
|
private AsyncDrawableCoordinatesProvider(AsyncDrawableSpan span) {
|
||||||
|
this.span = span;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getX() {
|
||||||
|
return span.lastKnownDrawX();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getY() {
|
||||||
|
return span.lastKnownDrawY();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ public class AsyncDrawable extends Drawable {
|
|||||||
private final Loader loader;
|
private final Loader loader;
|
||||||
|
|
||||||
private Drawable result;
|
private Drawable result;
|
||||||
|
private Callback callback;
|
||||||
|
|
||||||
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
public AsyncDrawable(@NonNull String destination, @NonNull Loader loader) {
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
@ -45,6 +46,7 @@ public class AsyncDrawable extends Drawable {
|
|||||||
// yeah
|
// yeah
|
||||||
public void setCallback2(@Nullable Callback callback) {
|
public void setCallback2(@Nullable Callback callback) {
|
||||||
|
|
||||||
|
this.callback = callback;
|
||||||
super.setCallback(callback);
|
super.setCallback(callback);
|
||||||
|
|
||||||
// if not null -> means we are attached
|
// if not null -> means we are attached
|
||||||
@ -58,7 +60,7 @@ public class AsyncDrawable extends Drawable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResult(Drawable result) {
|
public void setResult(@NonNull Drawable result) {
|
||||||
|
|
||||||
// if we have previous one, detach it
|
// if we have previous one, detach it
|
||||||
if (this.result != null) {
|
if (this.result != null) {
|
||||||
@ -66,7 +68,7 @@ public class AsyncDrawable extends Drawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.result = result;
|
this.result = result;
|
||||||
this.result.setCallback(getCallback());
|
this.result.setCallback(callback);
|
||||||
|
|
||||||
// should we copy the data here? like bounds etc?
|
// should we copy the data here? like bounds etc?
|
||||||
// if we are async and we load some image from some source
|
// if we are async and we load some image from some source
|
||||||
|
@ -25,6 +25,9 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
private final int alignment;
|
private final int alignment;
|
||||||
private final boolean replacementTextIsLink;
|
private final boolean replacementTextIsLink;
|
||||||
|
|
||||||
|
private int lastKnownDrawX;
|
||||||
|
private int lastKnownDrawY;
|
||||||
|
|
||||||
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
|
public AsyncDrawableSpan(@NonNull SpannableTheme theme, @NonNull AsyncDrawable drawable) {
|
||||||
this(theme, drawable, ALIGN_BOTTOM);
|
this(theme, drawable, ALIGN_BOTTOM);
|
||||||
}
|
}
|
||||||
@ -105,6 +108,9 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
int bottom,
|
int bottom,
|
||||||
@NonNull Paint paint) {
|
@NonNull Paint paint) {
|
||||||
|
|
||||||
|
this.lastKnownDrawX = (int) (x + .5F);
|
||||||
|
this.lastKnownDrawY = y;
|
||||||
|
|
||||||
final AsyncDrawable drawable = this.drawable;
|
final AsyncDrawable drawable = this.drawable;
|
||||||
|
|
||||||
if (drawable.hasResult()) {
|
if (drawable.hasResult()) {
|
||||||
@ -144,4 +150,12 @@ public class AsyncDrawableSpan extends ReplacementSpan {
|
|||||||
public AsyncDrawable getDrawable() {
|
public AsyncDrawable getDrawable() {
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int lastKnownDrawX() {
|
||||||
|
return lastKnownDrawX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int lastKnownDrawY() {
|
||||||
|
return lastKnownDrawY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
apply plugin: 'com.android.library'
|
|
||||||
|
|
||||||
android {
|
|
||||||
|
|
||||||
compileSdkVersion TARGET_SDK
|
|
||||||
buildToolsVersion BUILD_TOOLS
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion MIN_SDK
|
|
||||||
targetSdkVersion TARGET_SDK
|
|
||||||
versionCode 1
|
|
||||||
versionName version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':library-renderer')
|
|
||||||
compile SUPPORT_ANNOTATIONS
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<manifest package="ru.noties.markwon.view"/>
|
|
@ -1,59 +0,0 @@
|
|||||||
package ru.noties.markwon.view;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
|
||||||
import org.commonmark.node.Node;
|
|
||||||
import org.commonmark.parser.Parser;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableConfiguration;
|
|
||||||
import ru.noties.markwon.renderer.SpannableRenderer;
|
|
||||||
|
|
||||||
public class MarkdownTextView extends TextView {
|
|
||||||
|
|
||||||
private Parser parser;
|
|
||||||
private SpannableRenderer renderer;
|
|
||||||
private SpannableConfiguration configuration;
|
|
||||||
|
|
||||||
public MarkdownTextView(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MarkdownTextView(Context context, @Nullable AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MarkdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
public MarkdownTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setText(CharSequence text, BufferType type) {
|
|
||||||
if (parser == null) {
|
|
||||||
parser = Parser.builder()
|
|
||||||
.extensions(Collections.singletonList(StrikethroughExtension.create()))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
if (renderer == null) {
|
|
||||||
renderer = new SpannableRenderer();
|
|
||||||
}
|
|
||||||
if (configuration == null) {
|
|
||||||
configuration = SpannableConfiguration.create(getContext());
|
|
||||||
}
|
|
||||||
final Node node = parser.parse(text.toString());
|
|
||||||
final CharSequence cs = renderer.render(configuration, node);
|
|
||||||
super.setText(cs, type);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
include ':app', ':library-renderer', ':library-view'
|
include ':app', ':library-renderer'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user