Working with sample application
This commit is contained in:
parent
e01787982f
commit
6f8c1dfaee
@ -17,11 +17,15 @@ import java.util.Map;
|
|||||||
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class SimpleEntry implements MarkwonAdapter.Entry<SimpleEntry.Holder, Node> {
|
public class SimpleEntry implements MarkwonAdapter.Entry<SimpleEntry.Holder, Node> {
|
||||||
|
|
||||||
private static final NoCopySpannableFactory FACTORY = new NoCopySpannableFactory();
|
public static final Spannable.Factory NO_COPY_SPANNABLE_FACTORY = new NoCopySpannableFactory();
|
||||||
|
|
||||||
// small cache, maybe add pre-compute of text, also spannableFactory (so no copying of spans)
|
// small cache for already rendered nodes
|
||||||
private final Map<Node, Spanned> cache = new HashMap<>();
|
private final Map<Node, Spanned> cache = new HashMap<>();
|
||||||
|
|
||||||
private final int layoutResId;
|
private final int layoutResId;
|
||||||
@ -60,15 +64,15 @@ public class SimpleEntry implements MarkwonAdapter.Entry<SimpleEntry.Holder, Nod
|
|||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Holder extends MarkwonAdapter.Holder {
|
public static class Holder extends MarkwonAdapter.Holder {
|
||||||
|
|
||||||
final TextView textView;
|
final TextView textView;
|
||||||
|
|
||||||
Holder(@NonNull View itemView) {
|
protected Holder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
|
||||||
this.textView = requireView(R.id.text);
|
this.textView = requireView(R.id.text);
|
||||||
this.textView.setSpannableFactory(FACTORY);
|
this.textView.setSpannableFactory(NO_COPY_SPANNABLE_FACTORY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +170,8 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// important thing here is to check if corePlugin is added
|
||||||
|
// add it _only_ if it's not present
|
||||||
if (hasCoreDependents && !hasCore) {
|
if (hasCoreDependents && !hasCore) {
|
||||||
final List<MarkwonPlugin> out = new ArrayList<>(plugins.size() + 1);
|
final List<MarkwonPlugin> out = new ArrayList<>(plugins.size() + 1);
|
||||||
// add default instance of CorePlugin
|
// add default instance of CorePlugin
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.method.MovementMethod;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public class MovementMethodPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates plugin that will ensure that there is movement method registered on a TextView.
|
||||||
|
* Uses Android system LinkMovementMethod as default
|
||||||
|
*
|
||||||
|
* @see #create(MovementMethod)
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static MovementMethodPlugin create() {
|
||||||
|
return create(LinkMovementMethod.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static MovementMethodPlugin create(@NonNull MovementMethod movementMethod) {
|
||||||
|
return new MovementMethodPlugin(movementMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final MovementMethod movementMethod;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
MovementMethodPlugin(@NonNull MovementMethod movementMethod) {
|
||||||
|
this.movementMethod = movementMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) {
|
||||||
|
textView.setMovementMethod(movementMethod);
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,11 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
|||||||
return new ImagesPlugin(context, false);
|
return new ImagesPlugin(context, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special scheme that is used {@code file:///android_asset/}
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static ImagesPlugin createWithAssets(@NonNull Context context) {
|
public static ImagesPlugin createWithAssets(@NonNull Context context) {
|
||||||
return new ImagesPlugin(context, true);
|
return new ImagesPlugin(context, true);
|
||||||
|
@ -2,11 +2,16 @@ package ru.noties.markwon.sample.extension.recycler;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.SpannedString;
|
import android.text.SpannedString;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@ -19,6 +24,10 @@ import ru.noties.markwon.sample.extension.R;
|
|||||||
|
|
||||||
public class TableEntryView extends LinearLayout {
|
public class TableEntryView extends LinearLayout {
|
||||||
|
|
||||||
|
// paint and rect to draw borders
|
||||||
|
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Rect rect = new Rect();
|
||||||
|
|
||||||
private LayoutInflater inflater;
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
private int rowEvenBackgroundColor;
|
private int rowEvenBackgroundColor;
|
||||||
@ -43,6 +52,18 @@ public class TableEntryView extends LinearLayout {
|
|||||||
|
|
||||||
rowEvenBackgroundColor = array.getColor(R.styleable.TableEntryView_tev_rowEvenBackgroundColor, 0);
|
rowEvenBackgroundColor = array.getColor(R.styleable.TableEntryView_tev_rowEvenBackgroundColor, 0);
|
||||||
|
|
||||||
|
|
||||||
|
final int stroke = array.getDimensionPixelSize(R.styleable.TableEntryView_tev_borderWidth, 0);
|
||||||
|
|
||||||
|
// half of requested
|
||||||
|
final float strokeWidth = stroke > 0
|
||||||
|
? stroke / 2.F
|
||||||
|
: context.getResources().getDisplayMetrics().density / 2.F;
|
||||||
|
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeWidth(strokeWidth);
|
||||||
|
paint.setColor(array.getColor(R.styleable.TableEntryView_tev_borderColor, Color.BLACK));
|
||||||
|
|
||||||
if (isInEditMode()) {
|
if (isInEditMode()) {
|
||||||
final String data = array.getString(R.styleable.TableEntryView_tev_debugData);
|
final String data = array.getString(R.styleable.TableEntryView_tev_debugData);
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
@ -67,6 +88,8 @@ public class TableEntryView extends LinearLayout {
|
|||||||
array.recycle();
|
array.recycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setWillNotDraw(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTable(@NonNull Table table) {
|
public void setTable(@NonNull Table table) {
|
||||||
@ -133,6 +156,35 @@ public class TableEntryView extends LinearLayout {
|
|||||||
return (TextView) group.getChildAt(index);
|
return (TextView) group.getChildAt(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
final int rows = getChildCount();
|
||||||
|
if (rows == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first draw the whole border
|
||||||
|
rect.set(0, 0, getWidth(), getHeight());
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
|
||||||
|
ViewGroup group;
|
||||||
|
View view;
|
||||||
|
|
||||||
|
int top;
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
group = (ViewGroup) getChildAt(row);
|
||||||
|
top = group.getTop();
|
||||||
|
for (int col = 0, cols = group.getChildCount(); col < cols; col++) {
|
||||||
|
view = group.getChildAt(col);
|
||||||
|
rect.set(view.getLeft(), top + view.getTop(), view.getRight(), top + view.getBottom());
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int textAlignment(@NonNull Table.Alignment alignment) {
|
private static int textAlignment(@NonNull Table.Alignment alignment) {
|
||||||
final int out;
|
final int out;
|
||||||
switch (alignment) {
|
switch (alignment) {
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<!--<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"-->
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
<!--xmlns:app="http://schemas.android.com/apk/res-auto"-->
|
||||||
android:layout_width="match_parent"
|
<!--android:layout_width="match_parent"-->
|
||||||
android:layout_height="wrap_content"
|
<!--android:layout_height="wrap_content"-->
|
||||||
android:clipChildren="false"
|
<!--android:clipChildren="false"-->
|
||||||
android:clipToPadding="false"
|
<!--android:clipToPadding="false"-->
|
||||||
android:fillViewport="true"
|
<!--android:fillViewport="true"-->
|
||||||
android:paddingLeft="16dip"
|
<!--android:paddingLeft="16dip"-->
|
||||||
android:paddingTop="8dip"
|
<!--android:paddingTop="8dip"-->
|
||||||
android:paddingRight="16dip"
|
<!--android:paddingRight="16dip"-->
|
||||||
android:paddingBottom="8dip">
|
<!--android:paddingBottom="8dip">-->
|
||||||
|
|
||||||
<ru.noties.markwon.sample.extension.recycler.TableEntryView
|
<ru.noties.markwon.sample.extension.recycler.TableEntryView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/table_entry"
|
android:id="@+id/table_entry"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:tev_debugData="head1,head2,head3|col1,col2,col3|col1,col2,col3|col1,col2,col3"
|
app:tev_debugData="head1,head2,head3|col1,col2,col3|col1,col2,col3|col1,col2,col3"
|
||||||
app:tev_rowEvenBackgroundColor="#40ff0000" />
|
app:tev_rowEvenBackgroundColor="#40ff0000" />
|
||||||
|
|
||||||
</HorizontalScrollView>
|
<!--</HorizontalScrollView>-->
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
<declare-styleable name="TableEntryView">
|
<declare-styleable name="TableEntryView">
|
||||||
<attr name="tev_rowEvenBackgroundColor" format="color" />
|
<attr name="tev_rowEvenBackgroundColor" format="color" />
|
||||||
|
<attr name="tev_borderColor" format="color" />
|
||||||
|
<attr name="tev_borderWidth" format="dimension" />
|
||||||
<attr name="tev_debugData" format="string" />
|
<attr name="tev_debugData" format="string" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
@ -29,6 +29,14 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
// let's use different res directory so sample will have _isolated_ resources from others
|
||||||
|
res.srcDirs += [ './src/main/recycler/res' ]
|
||||||
|
assets.srcDirs += ['./src/main/recycler/assets']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -41,6 +49,7 @@ dependencies {
|
|||||||
implementation project(':markwon-image-gif')
|
implementation project(':markwon-image-gif')
|
||||||
implementation project(':markwon-image-svg')
|
implementation project(':markwon-image-svg')
|
||||||
implementation project(':markwon-syntax-highlight')
|
implementation project(':markwon-syntax-highlight')
|
||||||
|
implementation project(':markwon-recycler')
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
implementation it['support-recycler-view']
|
implementation it['support-recycler-view']
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="ru.noties.markwon.sample">
|
package="ru.noties.markwon.sample">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@ -21,6 +23,8 @@
|
|||||||
<activity android:name=".core.CoreActivity" />
|
<activity android:name=".core.CoreActivity" />
|
||||||
<activity android:name=".latex.LatexActivity" />
|
<activity android:name=".latex.LatexActivity" />
|
||||||
<activity android:name=".customextension.CustomExtensionActivity" />
|
<activity android:name=".customextension.CustomExtensionActivity" />
|
||||||
|
<activity android:name=".basicplugins.BasicPluginsActivity" />
|
||||||
|
<activity android:name=".recycler.RecyclerActivity" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -17,9 +17,11 @@ import ru.noties.adapt.OnClickViewProcessor;
|
|||||||
import ru.noties.debug.AndroidLogDebugOutput;
|
import ru.noties.debug.AndroidLogDebugOutput;
|
||||||
import ru.noties.debug.Debug;
|
import ru.noties.debug.Debug;
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
|
import ru.noties.markwon.sample.basicplugins.BasicPluginsActivity;
|
||||||
import ru.noties.markwon.sample.core.CoreActivity;
|
import ru.noties.markwon.sample.core.CoreActivity;
|
||||||
import ru.noties.markwon.sample.customextension.CustomExtensionActivity;
|
import ru.noties.markwon.sample.customextension.CustomExtensionActivity;
|
||||||
import ru.noties.markwon.sample.latex.LatexActivity;
|
import ru.noties.markwon.sample.latex.LatexActivity;
|
||||||
|
import ru.noties.markwon.sample.recycler.RecyclerActivity;
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
@ -79,6 +81,10 @@ public class MainActivity extends Activity {
|
|||||||
activity = CoreActivity.class;
|
activity = CoreActivity.class;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BASIC_PLUGINS:
|
||||||
|
activity = BasicPluginsActivity.class;
|
||||||
|
break;
|
||||||
|
|
||||||
case LATEX:
|
case LATEX:
|
||||||
activity = LatexActivity.class;
|
activity = LatexActivity.class;
|
||||||
break;
|
break;
|
||||||
@ -87,6 +93,10 @@ public class MainActivity extends Activity {
|
|||||||
activity = CustomExtensionActivity.class;
|
activity = CustomExtensionActivity.class;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RECYCLER:
|
||||||
|
activity = RecyclerActivity.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);
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,14 @@ public enum SampleItem {
|
|||||||
// all usages of markwon without plugins (parse, render, setMarkwon, etc)
|
// all usages of markwon without plugins (parse, render, setMarkwon, etc)
|
||||||
CORE(R.string.sample_core),
|
CORE(R.string.sample_core),
|
||||||
|
|
||||||
|
BASIC_PLUGINS(R.string.sample_basic_plugins),
|
||||||
|
|
||||||
LATEX(R.string.sample_latex),
|
LATEX(R.string.sample_latex),
|
||||||
|
|
||||||
CUSTOM_EXTENSION(R.string.sample_custom_extension),
|
CUSTOM_EXTENSION(R.string.sample_custom_extension),
|
||||||
|
|
||||||
|
RECYCLER(R.string.sample_recycler),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
private final int textResId;
|
private final int textResId;
|
||||||
|
@ -0,0 +1,204 @@
|
|||||||
|
package ru.noties.markwon.sample.basicplugins;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.commonmark.node.Heading;
|
||||||
|
import org.commonmark.node.Paragraph;
|
||||||
|
|
||||||
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
|
import ru.noties.markwon.Markwon;
|
||||||
|
import ru.noties.markwon.MarkwonConfiguration;
|
||||||
|
import ru.noties.markwon.MarkwonPlugin;
|
||||||
|
import ru.noties.markwon.MarkwonSpansFactory;
|
||||||
|
import ru.noties.markwon.MarkwonVisitor;
|
||||||
|
import ru.noties.markwon.MovementMethodPlugin;
|
||||||
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
|
import ru.noties.markwon.image.ImageItem;
|
||||||
|
import ru.noties.markwon.image.ImagesPlugin;
|
||||||
|
import ru.noties.markwon.image.SchemeHandler;
|
||||||
|
import ru.noties.markwon.image.network.NetworkSchemeHandler;
|
||||||
|
|
||||||
|
public class BasicPluginsActivity extends Activity {
|
||||||
|
|
||||||
|
private TextView textView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
textView = new TextView(this);
|
||||||
|
setContentView(textView);
|
||||||
|
|
||||||
|
step_1();
|
||||||
|
|
||||||
|
step_2();
|
||||||
|
|
||||||
|
step_3();
|
||||||
|
|
||||||
|
step_4();
|
||||||
|
|
||||||
|
step_5();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to apply paragraph spans a custom plugin should be created (CorePlugin will take care
|
||||||
|
* of everything else).
|
||||||
|
* <p>
|
||||||
|
* Please note that when a plugin is registered and it <em>depends</em> on CorePlugin, there is no
|
||||||
|
* need to explicitly specify it. By default all plugins that extend AbstractMarkwonPlugin do declare
|
||||||
|
* it\'s dependency on CorePlugin ({@link MarkwonPlugin#priority()}).
|
||||||
|
* <p>
|
||||||
|
* Order in which plugins are specified to the builder is of little importance as long as each
|
||||||
|
* plugin clearly states what dependencies it has
|
||||||
|
*/
|
||||||
|
private void step_1() {
|
||||||
|
|
||||||
|
final String markdown = "# Hello!\n\nA paragraph?\n\nIt should be!";
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
|
builder.setFactory(Paragraph.class, (configuration, props) ->
|
||||||
|
new ForegroundColorSpan(Color.GREEN));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
markwon.setMarkdown(textView, markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To disable some nodes from rendering another custom plugin can be used
|
||||||
|
*/
|
||||||
|
private void step_2() {
|
||||||
|
|
||||||
|
final String markdown = "# Heading 1\n\n## Heading 2\n\n**other** content [here](#)";
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
||||||
|
|
||||||
|
// for example to disable rendering of heading:
|
||||||
|
// try commenting this out to see that otherwise headings will be rendered
|
||||||
|
builder.on(Heading.class, null);
|
||||||
|
|
||||||
|
// same method can be used to override existing visitor by specifying
|
||||||
|
// a new NodeVisitor instance
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
markwon.setMarkdown(textView, markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To customize core theme plugin can be used again
|
||||||
|
*/
|
||||||
|
private void step_3() {
|
||||||
|
|
||||||
|
final String markdown = "`A code` that is rendered differently\n\n```\nHello!\n```";
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
||||||
|
builder
|
||||||
|
.codeBackgroundColor(Color.BLACK)
|
||||||
|
.codeTextColor(Color.RED);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
markwon.setMarkdown(textView, markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MarkwonConfiguration contains these <em>utilities</em>:
|
||||||
|
* <ul>
|
||||||
|
* <li>SyntaxHighlight</li>
|
||||||
|
* <li>LinkSpan.Resolver</li>
|
||||||
|
* <li>UrlProcessor</li>
|
||||||
|
* <li>ImageSizeResolver</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* In order to customize them a custom plugin should be used
|
||||||
|
*/
|
||||||
|
private void step_4() {
|
||||||
|
|
||||||
|
final String markdown = "[a link without scheme](github.com)";
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
// please note that Markwon does not handle MovementMethod,
|
||||||
|
// so if your markdown has links your should apply MovementMethod manually
|
||||||
|
// or use MovementMethodPlugin (which uses system LinkMovementMethod by default)
|
||||||
|
.usePlugin(MovementMethodPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
// for example if specified destination has no scheme info, we will
|
||||||
|
// _assume_ that it's network request and append HTTPS scheme
|
||||||
|
builder.urlProcessor(destination -> {
|
||||||
|
final Uri uri = Uri.parse(destination);
|
||||||
|
if (TextUtils.isEmpty(uri.getScheme())) {
|
||||||
|
return "https://" + destination;
|
||||||
|
}
|
||||||
|
return destination;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
markwon.setMarkdown(textView, markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Images configuration. Can be used with (or without) ImagesPlugin, which does some basic
|
||||||
|
* images handling (parsing markdown containing images, obtain an image from network
|
||||||
|
* file system or assets). Please note that
|
||||||
|
*/
|
||||||
|
private void step_5() {
|
||||||
|
|
||||||
|
final String markdown = "";
|
||||||
|
|
||||||
|
final Markwon markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(ImagesPlugin.create(this))
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
|
||||||
|
// we can have a custom SchemeHandler
|
||||||
|
// here we will just use networkSchemeHandler to redirect call
|
||||||
|
builder.addSchemeHandler("myownscheme", new SchemeHandler() {
|
||||||
|
|
||||||
|
final NetworkSchemeHandler networkSchemeHandler = NetworkSchemeHandler.create();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
raw = raw.replace("myownscheme", "https");
|
||||||
|
return networkSchemeHandler.handle(raw, Uri.parse(raw));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
markwon.setMarkdown(textView, markdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
// text lifecycle (after/before)
|
||||||
|
// rendering lifecycle (before/after)
|
||||||
|
// renderProps
|
||||||
|
// process
|
||||||
|
// priority
|
||||||
|
}
|
@ -1,26 +1,16 @@
|
|||||||
package ru.noties.markwon.sample.core;
|
package ru.noties.markwon.sample.core;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.ForegroundColorSpan;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.commonmark.node.Heading;
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.node.Paragraph;
|
|
||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
import ru.noties.markwon.MarkwonPlugin;
|
|
||||||
import ru.noties.markwon.MarkwonSpansFactory;
|
|
||||||
import ru.noties.markwon.MarkwonVisitor;
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
|
||||||
|
|
||||||
public class CoreActivity extends Activity {
|
public class CoreActivity extends Activity {
|
||||||
|
|
||||||
@ -40,12 +30,6 @@ public class CoreActivity extends Activity {
|
|||||||
step_3();
|
step_3();
|
||||||
|
|
||||||
step_4();
|
step_4();
|
||||||
|
|
||||||
step_5();
|
|
||||||
|
|
||||||
step_6();
|
|
||||||
|
|
||||||
step_7();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,74 +119,4 @@ public class CoreActivity extends Activity {
|
|||||||
// apply parsed markdown
|
// apply parsed markdown
|
||||||
markwon.setParsedMarkdown(textView, spanned);
|
markwon.setParsedMarkdown(textView, spanned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In order to apply paragraph spans a custom plugin should be created (CorePlugin will take care
|
|
||||||
* of everything else).
|
|
||||||
* <p>
|
|
||||||
* Please note that when a plugin is registered and it <em>depends</em> on CorePlugin, there is no
|
|
||||||
* need to explicitly specify it. By default all plugins that extend AbstractMarkwonPlugin do declare
|
|
||||||
* it\'s dependency on CorePlugin ({@link MarkwonPlugin#priority()}).
|
|
||||||
* <p>
|
|
||||||
* Order in which plugins are specified to the builder is of little importance as long as each
|
|
||||||
* plugin clearly states what dependencies it has
|
|
||||||
*/
|
|
||||||
private void step_5() {
|
|
||||||
|
|
||||||
final String markdown = "# Hello!\n\nA paragraph?\n\nIt should be!";
|
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(this)
|
|
||||||
.usePlugin(new AbstractMarkwonPlugin() {
|
|
||||||
@Override
|
|
||||||
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
|
||||||
builder.setFactory(Paragraph.class, (configuration, props) ->
|
|
||||||
new ForegroundColorSpan(Color.GREEN));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
|
||||||
|
|
||||||
markwon.setMarkdown(textView, markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To disable some nodes from rendering another custom plugin can be used
|
|
||||||
*/
|
|
||||||
private void step_6() {
|
|
||||||
|
|
||||||
final String markdown = "# Heading 1\n\n## Heading 2\n\n**other** content [here](#)";
|
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(this)
|
|
||||||
.usePlugin(new AbstractMarkwonPlugin() {
|
|
||||||
@Override
|
|
||||||
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
|
|
||||||
// for example to disable rendering of heading:
|
|
||||||
// try commenting this out to see that otherwise headings will be rendered
|
|
||||||
builder.on(Heading.class, null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
|
||||||
|
|
||||||
markwon.setMarkdown(textView, markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To customize core theme plugins can be used again
|
|
||||||
*/
|
|
||||||
private void step_7() {
|
|
||||||
|
|
||||||
final String markdown = "`A code` that is rendered differently\n\n```\nHello!\n```";
|
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(this)
|
|
||||||
.usePlugin(new AbstractMarkwonPlugin() {
|
|
||||||
@Override
|
|
||||||
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
|
|
||||||
builder
|
|
||||||
.codeBackgroundColor(Color.BLACK)
|
|
||||||
.codeTextColor(Color.RED);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.build();
|
|
||||||
|
|
||||||
markwon.setMarkdown(textView, markdown);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,162 @@
|
|||||||
|
package ru.noties.markwon.sample.recycler;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.commonmark.ext.gfm.tables.TableBlock;
|
||||||
|
import org.commonmark.node.FencedCodeBlock;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import ru.noties.debug.AndroidLogDebugOutput;
|
||||||
|
import ru.noties.debug.Debug;
|
||||||
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
|
import ru.noties.markwon.Markwon;
|
||||||
|
import ru.noties.markwon.MarkwonConfiguration;
|
||||||
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
|
import ru.noties.markwon.ext.tables.TablePlugin;
|
||||||
|
import ru.noties.markwon.html.HtmlPlugin;
|
||||||
|
import ru.noties.markwon.image.ImagesPlugin;
|
||||||
|
import ru.noties.markwon.image.svg.SvgPlugin;
|
||||||
|
import ru.noties.markwon.recycler.MarkwonAdapter;
|
||||||
|
import ru.noties.markwon.recycler.SimpleEntry;
|
||||||
|
import ru.noties.markwon.sample.R;
|
||||||
|
import ru.noties.markwon.urlprocessor.UrlProcessor;
|
||||||
|
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
|
||||||
|
|
||||||
|
public class RecyclerActivity extends Activity {
|
||||||
|
|
||||||
|
static {
|
||||||
|
Debug.init(new AndroidLogDebugOutput(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_recycler);
|
||||||
|
|
||||||
|
// create MarkwonAdapter and register two blocks that will be rendered differently
|
||||||
|
// * fenced code block (can also specify the same Entry for indended code block)
|
||||||
|
// * table block
|
||||||
|
final MarkwonAdapter adapter = MarkwonAdapter.builder()
|
||||||
|
// we can simply use bundled SimpleEntry, that will lookup a TextView
|
||||||
|
// with `@+id/text` id
|
||||||
|
.include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block))
|
||||||
|
// create own implementation of entry for different rendering
|
||||||
|
.include(TableBlock.class, new TableEntry())
|
||||||
|
// specify default entry (for all other blocks)
|
||||||
|
.defaultEntry(new SimpleEntry(R.layout.adapter_default_entry))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final RecyclerView recyclerView = findViewById(R.id.recycler_view);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
recyclerView.setHasFixedSize(true);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
final Markwon markwon = markwon(this);
|
||||||
|
adapter.setMarkdown(markwon, loadReadMe(this));
|
||||||
|
|
||||||
|
// please note that we should notify updates (adapter doesn't do it implicitly)
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
// NB, there is no currently available widget to render tables gracefully
|
||||||
|
// TableEntryView is here for demonstration purposes only (to show that rendering
|
||||||
|
// tables
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Markwon markwon(@NonNull Context context) {
|
||||||
|
return Markwon.builder(context)
|
||||||
|
.usePlugin(CorePlugin.create())
|
||||||
|
.usePlugin(ImagesPlugin.createWithAssets(context))
|
||||||
|
.usePlugin(SvgPlugin.create(context.getResources()))
|
||||||
|
.usePlugin(TablePlugin.create(context))
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
.usePlugin(new AbstractMarkwonPlugin() {
|
||||||
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.urlProcessor(new UrlProcessorInitialReadme());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String loadReadMe(@NonNull Context context) {
|
||||||
|
InputStream stream = null;
|
||||||
|
try {
|
||||||
|
stream = context.getAssets().open("README.md");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return readStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String readStream(@Nullable InputStream inputStream) {
|
||||||
|
|
||||||
|
String out = null;
|
||||||
|
|
||||||
|
if (inputStream != null) {
|
||||||
|
BufferedReader reader = null;
|
||||||
|
//noinspection TryFinallyCanBeTryWithResources
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
builder.append(line)
|
||||||
|
.append('\n');
|
||||||
|
}
|
||||||
|
out = builder.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out == null) {
|
||||||
|
throw new RuntimeException("Cannot read stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UrlProcessorInitialReadme implements UrlProcessor {
|
||||||
|
|
||||||
|
private static final String GITHUB_BASE = "https://github.com/noties/Markwon/raw/master/";
|
||||||
|
|
||||||
|
private final UrlProcessorRelativeToAbsolute processor
|
||||||
|
= new UrlProcessorRelativeToAbsolute(GITHUB_BASE);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String process(@NonNull String destination) {
|
||||||
|
String out;
|
||||||
|
final Uri uri = Uri.parse(destination);
|
||||||
|
if (TextUtils.isEmpty(uri.getScheme())) {
|
||||||
|
out = processor.process(destination);
|
||||||
|
} else {
|
||||||
|
out = destination;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package ru.noties.markwon.sample.recycler;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.commonmark.ext.gfm.tables.TableBlock;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.noties.debug.Debug;
|
||||||
|
import ru.noties.markwon.Markwon;
|
||||||
|
import ru.noties.markwon.ext.tables.Table;
|
||||||
|
import ru.noties.markwon.recycler.MarkwonAdapter;
|
||||||
|
import ru.noties.markwon.sample.R;
|
||||||
|
|
||||||
|
// do not use in real applications, this is just a showcase
|
||||||
|
public class TableEntry implements MarkwonAdapter.Entry<TableEntry.TableNodeHolder, TableBlock> {
|
||||||
|
|
||||||
|
private final Map<TableBlock, Table> cache = new HashMap<>(2);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public TableNodeHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
|
return new TableNodeHolder(inflater.inflate(R.layout.adapter_table_block, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindHolder(@NonNull Markwon markwon, @NonNull TableNodeHolder holder, @NonNull TableBlock node) {
|
||||||
|
|
||||||
|
Table table = cache.get(node);
|
||||||
|
if (table == null) {
|
||||||
|
table = Table.parse(markwon, node);
|
||||||
|
cache.put(node, table);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.i(table);
|
||||||
|
|
||||||
|
if (table != null) {
|
||||||
|
holder.tableEntryView.setTable(table);
|
||||||
|
// render table
|
||||||
|
} // we need to do something with null table...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long id(@NonNull TableBlock node) {
|
||||||
|
return node.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TableNodeHolder extends MarkwonAdapter.Holder {
|
||||||
|
|
||||||
|
final TableEntryView tableEntryView;
|
||||||
|
|
||||||
|
TableNodeHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
this.tableEntryView = requireView(R.id.table_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
package ru.noties.markwon.sample.recycler;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.SpannedString;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.noties.markwon.ext.tables.Table;
|
||||||
|
import ru.noties.markwon.sample.R;
|
||||||
|
|
||||||
|
public class TableEntryView extends LinearLayout {
|
||||||
|
|
||||||
|
// paint and rect to draw borders
|
||||||
|
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private final Rect rect = new Rect();
|
||||||
|
|
||||||
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
|
private int rowEvenBackgroundColor;
|
||||||
|
|
||||||
|
public TableEntryView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableEntryView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
inflater = LayoutInflater.from(context);
|
||||||
|
setOrientation(VERTICAL);
|
||||||
|
|
||||||
|
if (attrs != null) {
|
||||||
|
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TableEntryView);
|
||||||
|
try {
|
||||||
|
|
||||||
|
rowEvenBackgroundColor = array.getColor(R.styleable.TableEntryView_tev_rowEvenBackgroundColor, 0);
|
||||||
|
|
||||||
|
|
||||||
|
final int stroke = array.getDimensionPixelSize(R.styleable.TableEntryView_tev_borderWidth, 0);
|
||||||
|
|
||||||
|
// half of requested
|
||||||
|
final float strokeWidth = stroke > 0
|
||||||
|
? stroke / 2.F
|
||||||
|
: context.getResources().getDisplayMetrics().density / 2.F;
|
||||||
|
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeWidth(strokeWidth);
|
||||||
|
paint.setColor(array.getColor(R.styleable.TableEntryView_tev_borderColor, Color.BLACK));
|
||||||
|
|
||||||
|
if (isInEditMode()) {
|
||||||
|
final String data = array.getString(R.styleable.TableEntryView_tev_debugData);
|
||||||
|
if (data != null) {
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
|
||||||
|
final List<Table.Row> rows = new ArrayList<>();
|
||||||
|
for (String row : data.split("\\|")) {
|
||||||
|
final List<Table.Column> columns = new ArrayList<>();
|
||||||
|
for (String column : row.split(",")) {
|
||||||
|
columns.add(new Table.Column(Table.Alignment.LEFT, new SpannedString(column)));
|
||||||
|
}
|
||||||
|
final boolean header = first;
|
||||||
|
first = false;
|
||||||
|
rows.add(new Table.Row(header, columns));
|
||||||
|
}
|
||||||
|
final Table table = new Table(rows);
|
||||||
|
setTable(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setWillNotDraw(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTable(@NonNull Table table) {
|
||||||
|
final List<Table.Row> rows = table.rows();
|
||||||
|
for (int i = 0, size = rows.size(); i < size; i++) {
|
||||||
|
addRow(i, rows.get(i));
|
||||||
|
}
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRow(int index, @NonNull Table.Row row) {
|
||||||
|
|
||||||
|
final ViewGroup group = ensureRow(index);
|
||||||
|
|
||||||
|
final int backgroundColor = !row.header() && (index % 2) == 0
|
||||||
|
? rowEvenBackgroundColor
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
group.setBackgroundColor(backgroundColor);
|
||||||
|
|
||||||
|
final List<Table.Column> columns = row.columns();
|
||||||
|
|
||||||
|
TextView textView;
|
||||||
|
Table.Column column;
|
||||||
|
|
||||||
|
for (int i = 0, size = columns.size(); i < size; i++) {
|
||||||
|
textView = ensureCell(group, i);
|
||||||
|
column = columns.get(i);
|
||||||
|
textView.setGravity(textGravity(column.alignment()));
|
||||||
|
textView.setText(column.content());
|
||||||
|
textView.getPaint().setFakeBoldText(row.header());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ViewGroup ensureRow(int index) {
|
||||||
|
|
||||||
|
final int count = getChildCount();
|
||||||
|
if (index >= count) {
|
||||||
|
|
||||||
|
// count=0,index=1, diff=2
|
||||||
|
// count=0,index=5, diff=6
|
||||||
|
// count=1,index=2, diff=2
|
||||||
|
int diff = index - count + 1;
|
||||||
|
while (diff > 0) {
|
||||||
|
addView(inflater.inflate(R.layout.view_table_entry_row, this, false));
|
||||||
|
diff -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ViewGroup) getChildAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private TextView ensureCell(@NonNull ViewGroup group, int index) {
|
||||||
|
|
||||||
|
final int count = group.getChildCount();
|
||||||
|
if (index >= count) {
|
||||||
|
int diff = index - count + 1;
|
||||||
|
while (diff > 0) {
|
||||||
|
group.addView(inflater.inflate(R.layout.view_table_entry_cell, group, false));
|
||||||
|
diff -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TextView) group.getChildAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
final int rows = getChildCount();
|
||||||
|
if (rows == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first draw the whole border
|
||||||
|
rect.set(0, 0, getWidth(), getHeight());
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
|
||||||
|
ViewGroup group;
|
||||||
|
View view;
|
||||||
|
|
||||||
|
int top;
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
group = (ViewGroup) getChildAt(row);
|
||||||
|
top = group.getTop();
|
||||||
|
for (int col = 0, cols = group.getChildCount(); col < cols; col++) {
|
||||||
|
view = group.getChildAt(col);
|
||||||
|
rect.set(view.getLeft(), top + view.getTop(), view.getRight(), top + view.getBottom());
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we will use gravity instead of textAlignment because min sdk is 16 (textAlignment starts at 17)
|
||||||
|
@SuppressLint("RtlHardcoded")
|
||||||
|
private static int textGravity(@NonNull Table.Alignment alignment) {
|
||||||
|
|
||||||
|
final int gravity;
|
||||||
|
|
||||||
|
switch (alignment) {
|
||||||
|
|
||||||
|
case LEFT:
|
||||||
|
gravity = Gravity.LEFT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CENTER:
|
||||||
|
gravity = Gravity.CENTER_HORIZONTAL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RIGHT:
|
||||||
|
gravity = Gravity.RIGHT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unknown table alignment: " + alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gravity;
|
||||||
|
}
|
||||||
|
}
|
1
sample/src/main/recycler/assets/README.md
Symbolic link
1
sample/src/main/recycler/assets/README.md
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../../../README.md
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dip"
|
||||||
|
android:layout_marginRight="16dip"
|
||||||
|
android:lineSpacingExtra="2dip"
|
||||||
|
android:paddingTop="8dip"
|
||||||
|
android:paddingBottom="8dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="#000"
|
||||||
|
android:textSize="16sp" />
|
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:paddingLeft="16dip"
|
||||||
|
android:paddingRight="16dip"
|
||||||
|
android:scrollbarStyle="outsideInset">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:lineSpacingExtra="2dip"
|
||||||
|
android:paddingTop="8dip"
|
||||||
|
android:paddingBottom="8dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="# Hello there! and taskjs" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
22
sample/src/main/recycler/res/layout/adapter_table_block.xml
Normal file
22
sample/src/main/recycler/res/layout/adapter_table_block.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:paddingLeft="16dip"
|
||||||
|
android:paddingTop="8dip"
|
||||||
|
android:paddingRight="16dip"
|
||||||
|
android:paddingBottom="8dip"
|
||||||
|
android:scrollbarStyle="outsideInset">
|
||||||
|
|
||||||
|
<ru.noties.markwon.sample.recycler.TableEntryView
|
||||||
|
android:id="@+id/table_entry"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:tev_debugData="head1,head2,head3|col1,col2,col3|col1,col2,col3|col1,col2,col3"
|
||||||
|
app:tev_rowEvenBackgroundColor="#40ff0000" />
|
||||||
|
|
||||||
|
</HorizontalScrollView>
|
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:padding="4dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textColor="#000"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Table content" />
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal" />
|
9
sample/src/main/recycler/res/values/attrs.xml
Normal file
9
sample/src/main/recycler/res/values/attrs.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="TableEntryView">
|
||||||
|
<attr name="tev_rowEvenBackgroundColor" format="color" />
|
||||||
|
<attr name="tev_borderColor" format="color" />
|
||||||
|
<attr name="tev_borderWidth" format="dimension" />
|
||||||
|
<attr name="tev_debugData" format="string" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
@ -4,7 +4,15 @@
|
|||||||
<!--Ignore missing translation warning-->
|
<!--Ignore missing translation warning-->
|
||||||
|
|
||||||
<string name="sample_core"># \# Core\n\nSimple usage example</string>
|
<string name="sample_core"># \# Core\n\nSimple usage example</string>
|
||||||
<string name="sample_latex"># \# LaTeX\n\nShows how to display a **LaTeX** formula in a Markwon powered application</string>
|
|
||||||
<string name="sample_custom_extension"># \# Custom extension\n\nShows how to create a custom extension to display an icon referenced in markdown as `@ic-android-black-24`</string>
|
<string name="sample_basic_plugins"># \# Basic plugins\n\nShows basic usage of plugins</string>
|
||||||
|
|
||||||
|
<string name="sample_latex"># \# LaTeX\n\nShows how to display a **LaTeX** formula</string>
|
||||||
|
|
||||||
|
<string name="sample_custom_extension"># \# Custom extension\n\nShows how to create a custom
|
||||||
|
extension to display an icon referenced in markdown as `@ic-android-black-24`</string>
|
||||||
|
|
||||||
|
<string name="sample_recycler"># \# Recycler\n\nShow how to render markdown in a RecyclerView.
|
||||||
|
Renders code blocks wrapped in a HorizontalScrollView. Renders tables in a custom view.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user