Change text-setter to use precomputed-text-compat (androix.core)
This commit is contained in:
parent
7e12552060
commit
54335dce6e
@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 4.1.0-SNAPSHOT
|
||||||
|
* Add `Markwon.TextSetter` interface to be able to use PrecomputedText/PrecomputedTextCompat
|
||||||
|
* Add `PrecomputedTextSetterCompat` and `compileOnly` dependency on `androidx.core:core`
|
||||||
|
(clients must have this dependency in the classpath)
|
||||||
|
|
||||||
# 4.0.2
|
# 4.0.2
|
||||||
* Fix `JLatexMathPlugin` formula placeholder (cannot have line breaks) ([#149])
|
* Fix `JLatexMathPlugin` formula placeholder (cannot have line breaks) ([#149])
|
||||||
* Fix `JLatexMathPlugin` to update resulting formula bounds when `fitCanvas=true` and
|
* Fix `JLatexMathPlugin` to update resulting formula bounds when `fitCanvas=true` and
|
||||||
|
@ -59,6 +59,7 @@ ext {
|
|||||||
deps = [
|
deps = [
|
||||||
'x-annotations' : 'androidx.annotation:annotation:1.1.0',
|
'x-annotations' : 'androidx.annotation:annotation:1.1.0',
|
||||||
'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0',
|
'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0',
|
||||||
|
'x-core' : 'androidx.core:core:1.0.2',
|
||||||
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
|
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
|
||||||
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
|
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
|
||||||
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",
|
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",
|
||||||
|
@ -18,6 +18,10 @@ dependencies {
|
|||||||
deps.with {
|
deps.with {
|
||||||
api it['x-annotations']
|
api it['x-annotations']
|
||||||
api it['commonmark']
|
api it['commonmark']
|
||||||
|
|
||||||
|
// @since 4.1.0-SNAPSHOT to allow PrecomputedTextSetterCompat
|
||||||
|
// note that this dependency must be added on a client side explicitly
|
||||||
|
compileOnly it['x-core']
|
||||||
}
|
}
|
||||||
|
|
||||||
deps['test'].with {
|
deps['test'].with {
|
||||||
|
@ -123,7 +123,7 @@ public abstract class Markwon {
|
|||||||
* Interface to set text on a TextView. Primary goal is to give a way to use PrecomputedText
|
* Interface to set text on a TextView. Primary goal is to give a way to use PrecomputedText
|
||||||
* functionality
|
* functionality
|
||||||
*
|
*
|
||||||
* @see PrecomputedTextSetter
|
* @see PrecomputedTextSetterCompat
|
||||||
* @since 4.1.0-SNAPSHOT
|
* @since 4.1.0-SNAPSHOT
|
||||||
*/
|
*/
|
||||||
public interface TextSetter {
|
public interface TextSetter {
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
package io.noties.markwon;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.text.PrecomputedText;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see io.noties.markwon.Markwon.TextSetter
|
|
||||||
* @since 4.1.0-SNAPSHOT
|
|
||||||
*/
|
|
||||||
@RequiresApi(Build.VERSION_CODES.P)
|
|
||||||
public class PrecomputedTextSetter implements Markwon.TextSetter {
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static PrecomputedTextSetter create() {
|
|
||||||
return create(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static PrecomputedTextSetter create(@NonNull Executor executor) {
|
|
||||||
return new PrecomputedTextSetter(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Executor executor;
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
PrecomputedTextSetter(@NonNull Executor executor) {
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setText(
|
|
||||||
@NonNull TextView textView,
|
|
||||||
@NonNull final Spanned markdown,
|
|
||||||
@NonNull final TextView.BufferType bufferType,
|
|
||||||
@NonNull final Runnable onComplete) {
|
|
||||||
final WeakReference<TextView> reference = new WeakReference<>(textView);
|
|
||||||
executor.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
final PrecomputedText precomputedText = precomputedText(reference.get(), markdown);
|
|
||||||
if (precomputedText != null) {
|
|
||||||
apply(reference.get(), precomputedText, bufferType, onComplete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static PrecomputedText precomputedText(@Nullable TextView textView, @NonNull Spanned spanned) {
|
|
||||||
return textView == null
|
|
||||||
? null
|
|
||||||
: PrecomputedText.create(spanned, textView.getTextMetricsParams());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void apply(
|
|
||||||
@Nullable final TextView textView,
|
|
||||||
@NonNull final PrecomputedText precomputedText,
|
|
||||||
@NonNull final TextView.BufferType bufferType,
|
|
||||||
@NonNull final Runnable onComplete) {
|
|
||||||
if (textView != null) {
|
|
||||||
textView.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
textView.setText(precomputedText, bufferType);
|
|
||||||
onComplete.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,120 @@
|
|||||||
|
package io.noties.markwon;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.text.PrecomputedTextCompat;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please note this class requires `androidx.core:core` artifact being explicitly added to your dependencies.
|
||||||
|
* Please do not use with `markwon-recycler` as it will lead to bad item rendering (due to async nature)
|
||||||
|
*
|
||||||
|
* @see io.noties.markwon.Markwon.TextSetter
|
||||||
|
* @since 4.1.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
public class PrecomputedTextSetterCompat implements Markwon.TextSetter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param executor for background execution of text pre-computation
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static PrecomputedTextSetterCompat create(@NonNull Executor executor) {
|
||||||
|
return new PrecomputedTextSetterCompat(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Executor executor;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
PrecomputedTextSetterCompat(@NonNull Executor executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setText(
|
||||||
|
@NonNull TextView textView,
|
||||||
|
@NonNull final Spanned markdown,
|
||||||
|
@NonNull final TextView.BufferType bufferType,
|
||||||
|
@NonNull final Runnable onComplete) {
|
||||||
|
|
||||||
|
// insert version check and do not execute on a device < 21
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// it's still no-op, so there is no need to start background execution
|
||||||
|
applyText(textView, markdown, bufferType, onComplete);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final WeakReference<TextView> reference = new WeakReference<>(textView);
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final PrecomputedTextCompat precomputedTextCompat = precomputedText(reference.get(), markdown);
|
||||||
|
if (precomputedTextCompat != null) {
|
||||||
|
applyText(reference.get(), precomputedTextCompat, bufferType, onComplete);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e("PrecomputdTxtSetterCmpt", "Exception during pre-computing text", t);
|
||||||
|
// apply initial markdown
|
||||||
|
applyText(reference.get(), markdown, bufferType, onComplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static PrecomputedTextCompat precomputedText(@Nullable TextView textView, @NonNull Spanned spanned) {
|
||||||
|
|
||||||
|
if (textView == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final PrecomputedTextCompat.Params params;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
// use native parameters on P
|
||||||
|
params = new PrecomputedTextCompat.Params(textView.getTextMetricsParams());
|
||||||
|
} else {
|
||||||
|
|
||||||
|
final PrecomputedTextCompat.Params.Builder builder =
|
||||||
|
new PrecomputedTextCompat.Params.Builder(textView.getPaint());
|
||||||
|
|
||||||
|
// please note that text-direction initialization is omitted
|
||||||
|
// by default it will be determined by the first locale-specific character
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
// another miss on API surface, this can easily be done by the compat class itself
|
||||||
|
builder
|
||||||
|
.setBreakStrategy(textView.getBreakStrategy())
|
||||||
|
.setHyphenationFrequency(textView.getHyphenationFrequency());
|
||||||
|
}
|
||||||
|
|
||||||
|
params = builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PrecomputedTextCompat.create(spanned, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyText(
|
||||||
|
@Nullable final TextView textView,
|
||||||
|
@NonNull final Spanned text,
|
||||||
|
@NonNull final TextView.BufferType bufferType,
|
||||||
|
@NonNull final Runnable onComplete) {
|
||||||
|
Log.e("TXT", String.format("thread: %s, attached: %s", Thread.currentThread(), textView.isAttachedToWindow()));
|
||||||
|
if (textView != null) {
|
||||||
|
textView.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
textView.setText(text, bufferType);
|
||||||
|
onComplete.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,7 @@ dependencies {
|
|||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
implementation it['x-recycler-view']
|
implementation it['x-recycler-view']
|
||||||
|
implementation it['x-core'] // for precomputedTextCompat
|
||||||
implementation it['okhttp']
|
implementation it['okhttp']
|
||||||
implementation it['prism4j']
|
implementation it['prism4j']
|
||||||
implementation it['debug']
|
implementation it['debug']
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
<activity android:name=".html.HtmlActivity" />
|
<activity android:name=".html.HtmlActivity" />
|
||||||
<activity android:name=".simpleext.SimpleExtActivity" />
|
<activity android:name=".simpleext.SimpleExtActivity" />
|
||||||
<activity android:name=".customextension2.CustomExtensionActivity2" />
|
<activity android:name=".customextension2.CustomExtensionActivity2" />
|
||||||
|
<activity android:name=".precomputed.PrecomputedActivity" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import io.noties.markwon.sample.customextension.CustomExtensionActivity;
|
|||||||
import io.noties.markwon.sample.customextension2.CustomExtensionActivity2;
|
import io.noties.markwon.sample.customextension2.CustomExtensionActivity2;
|
||||||
import io.noties.markwon.sample.html.HtmlActivity;
|
import io.noties.markwon.sample.html.HtmlActivity;
|
||||||
import io.noties.markwon.sample.latex.LatexActivity;
|
import io.noties.markwon.sample.latex.LatexActivity;
|
||||||
|
import io.noties.markwon.sample.precomputed.PrecomputedActivity;
|
||||||
import io.noties.markwon.sample.recycler.RecyclerActivity;
|
import io.noties.markwon.sample.recycler.RecyclerActivity;
|
||||||
import io.noties.markwon.sample.simpleext.SimpleExtActivity;
|
import io.noties.markwon.sample.simpleext.SimpleExtActivity;
|
||||||
|
|
||||||
@ -112,6 +113,10 @@ public class MainActivity extends Activity {
|
|||||||
activity = CustomExtensionActivity2.class;
|
activity = CustomExtensionActivity2.class;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PRECOMPUTED_TEXT:
|
||||||
|
activity = PrecomputedActivity.class;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("No Activity is associated with sample-item: " + item);
|
throw new IllegalStateException("No Activity is associated with sample-item: " + item);
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ public enum Sample {
|
|||||||
|
|
||||||
SIMPLE_EXT(R.string.sample_simple_ext),
|
SIMPLE_EXT(R.string.sample_simple_ext),
|
||||||
|
|
||||||
CUSTOM_EXTENSION_2(R.string.sample_custom_extension_2);
|
CUSTOM_EXTENSION_2(R.string.sample_custom_extension_2),
|
||||||
|
|
||||||
|
PRECOMPUTED_TEXT(R.string.sample_precomputed_text);
|
||||||
|
|
||||||
private final int textResId;
|
private final int textResId;
|
||||||
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package io.noties.markwon.sample.precomputed;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import io.noties.markwon.Markwon;
|
||||||
|
import io.noties.markwon.PrecomputedTextSetterCompat;
|
||||||
|
import io.noties.markwon.sample.R;
|
||||||
|
|
||||||
|
public class PrecomputedActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_text_view);
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
// please note that precomputedTextCompat is no-op on devices lower than L (21)
|
||||||
|
.textSetter(PrecomputedTextSetterCompat.create(Executors.newCachedThreadPool()))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final TextView textView = findViewById(R.id.text_view);
|
||||||
|
final String markdown = "# Hello!\n\n" +
|
||||||
|
"This _displays_ how to implement and use `PrecomputedTextCompat` with the **Markwon**\n\n" +
|
||||||
|
"> consider using PrecomputedText only if your markdown content is large enough\n> \n" +
|
||||||
|
"> **please note** that it works badly with `markwon-recycler` due to asynchronous nature";
|
||||||
|
|
||||||
|
// please note that _sometimes_ (if done without `post` here) further `textView.post`
|
||||||
|
// (that is used in PrecomputedTextSetterCompat to deliver result to main-thread) won't be called
|
||||||
|
// making the result of pre-computation absent and text-view clear (no text)
|
||||||
|
textView.post(() -> markwon.setMarkdown(textView, markdown));
|
||||||
|
}
|
||||||
|
}
|
@ -23,4 +23,6 @@
|
|||||||
<string name="sample_custom_extension_2"># \# Custom extension 2\n\nAutomatically
|
<string name="sample_custom_extension_2"># \# Custom extension 2\n\nAutomatically
|
||||||
convert `#1` and `@user` to Github links</string>
|
convert `#1` and `@user` to Github links</string>
|
||||||
|
|
||||||
|
<string name="sample_precomputed_text"># \# PrecomputedText\n\nUsage of TextSetter and PrecomputedTextCompat</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user