ext-tables, table aware movement method
This commit is contained in:
parent
4c3fba8929
commit
f8eaac6197
@ -2,12 +2,16 @@
|
||||
|
||||
# SNAPSHOT
|
||||
|
||||
#### Added
|
||||
* `ext-tables` - `TableAwareMovementMethod` a special movement method to handle clicks inside tables ([#289])
|
||||
|
||||
#### Changed
|
||||
* `image-glide` - update to `4.11.0` version
|
||||
* `inline-parser` - revert parsing index when `InlineProcessor` returns `null` as result
|
||||
* `image-coil` - update `Coil` to `0.12.0` ([Coil changelog](https://coil-kt.github.io/coil/changelog/)) ([#284])<br>Thanks [@magnusvs]
|
||||
|
||||
[#284]: https://github.com/noties/Markwon/pull/284
|
||||
[#289]: https://github.com/noties/Markwon/issues/289
|
||||
|
||||
[@magnusvs]: https://github.com/magnusvs
|
||||
|
||||
|
@ -216,7 +216,7 @@
|
||||
"javaClassName": "io.noties.markwon.app.samples.table.TableLinkifySample",
|
||||
"id": "20200702135739",
|
||||
"title": "Linkify table",
|
||||
"description": "Automatically linkify markdown content including content inside tables",
|
||||
"description": "Automatically linkify markdown content including content inside tables, handle clicks inside tables",
|
||||
"artifacts": [
|
||||
"EXT_TABLES",
|
||||
"LINKIFY"
|
||||
@ -894,12 +894,13 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueOnTextAddedSample",
|
||||
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueInlineParsingSample",
|
||||
"id": "20200629162024",
|
||||
"title": "User mention and issue (via text)",
|
||||
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
|
||||
"artifacts": [
|
||||
"CORE"
|
||||
"CORE",
|
||||
"INLINE_PARSER"
|
||||
],
|
||||
"tags": [
|
||||
"parsing",
|
||||
@ -908,13 +909,12 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueInlineParsingSample",
|
||||
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueOnTextAddedSample",
|
||||
"id": "20200629162024",
|
||||
"title": "User mention and issue (via text)",
|
||||
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
|
||||
"artifacts": [
|
||||
"CORE",
|
||||
"INLINE_PARSER"
|
||||
"CORE"
|
||||
],
|
||||
"tags": [
|
||||
"parsing",
|
||||
|
@ -3,8 +3,10 @@ package io.noties.markwon.app.samples.table;
|
||||
import io.noties.markwon.Markwon;
|
||||
import io.noties.markwon.app.sample.Tags;
|
||||
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
|
||||
import io.noties.markwon.ext.tables.TableAwareMovementMethod;
|
||||
import io.noties.markwon.ext.tables.TablePlugin;
|
||||
import io.noties.markwon.linkify.LinkifyPlugin;
|
||||
import io.noties.markwon.movement.MovementMethodPlugin;
|
||||
import io.noties.markwon.sample.annotations.MarkwonArtifact;
|
||||
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
|
||||
|
||||
@ -12,7 +14,7 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
|
||||
id = "20200702135739",
|
||||
title = "Linkify table",
|
||||
description = "Automatically linkify markdown content " +
|
||||
"including content inside tables",
|
||||
"including content inside tables, handle clicks inside tables",
|
||||
artifacts = {MarkwonArtifact.EXT_TABLES, MarkwonArtifact.LINKIFY},
|
||||
tags = {Tags.links}
|
||||
)
|
||||
@ -23,10 +25,11 @@ public class TableLinkifySample extends MarkwonTextViewSample {
|
||||
"| HEADER | HEADER | HEADER |\n" +
|
||||
"|:----:|:----:|:----:|\n" +
|
||||
"| 测试 | 测试 | 测试 |\n" +
|
||||
"| 测试 | 测试 | 测测测12345试测试测试 |\n" +
|
||||
"| 测试 | 测试 | 测测测12345试测试测试 |\n" +
|
||||
"| 测试 | 测试 | 123445 |\n" +
|
||||
"| 测试 | 测试 | (650) 555-1212 |\n" +
|
||||
"| 测试 | 测试 | [link](#) |\n" +
|
||||
"| 测试 | 测试 | mail@ma.il |\n" +
|
||||
"| 测试 | 测试 | some text that goes here is very very very very important [link](https://noties.io/Markwon) |\n" +
|
||||
"\n" +
|
||||
"测试\n" +
|
||||
"\n" +
|
||||
@ -35,6 +38,8 @@ public class TableLinkifySample extends MarkwonTextViewSample {
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.usePlugin(TablePlugin.create(context))
|
||||
// use TableAwareLinkMovementMethod to handle clicks inside tables
|
||||
.usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create()))
|
||||
.build();
|
||||
|
||||
markwon.setMarkdown(textView, md);
|
||||
|
@ -40,13 +40,26 @@ final Markwon markwon = Markwon.builder(context)
|
||||
))
|
||||
```
|
||||
|
||||
Please note, that _by default_ tables have limitations. For example, there is no support
|
||||
for images inside table cells. And table contents won't be copied to clipboard if a TextView
|
||||
Please note, that _by default_ tables have limitations. For example, table contents won't be copied to clipboard if a TextView
|
||||
has such functionality. Table will always take full width of a TextView in which it is displayed.
|
||||
All columns will always be of the same width. So, _default_ implementation provides basic
|
||||
functionality which can answer some needs. These all come from the limited nature of the TextView
|
||||
to display such content.
|
||||
|
||||
:::warning
|
||||
If table contains links a special `MovementMethod` must be used on a `TextView` widget - `TableAwareMovementMethod`,
|
||||
for example with `MovementMethodPlugin`:
|
||||
```java
|
||||
final Markwon markwon = Markwon.builder(context)
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.usePlugin(TablePlugin.create(context))
|
||||
// use TableAwareLinkMovementMethod to handle clicks inside tables,
|
||||
// wraps LinkMovementMethod internally
|
||||
.usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create()))
|
||||
.build();
|
||||
```
|
||||
:::
|
||||
|
||||
In order to provide full-fledged experience, tables must be displayed in a special widget.
|
||||
Since version `3.0.0` Markwon provides a special artifact `markwon-recycler` that allows
|
||||
to render markdown in a set of widgets in a RecyclerView. It also gives ability to change
|
||||
|
@ -0,0 +1,131 @@
|
||||
package io.noties.markwon.ext.tables;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.method.MovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* @since $SNAPSHOT;
|
||||
*/
|
||||
public class TableAwareMovementMethod implements MovementMethod {
|
||||
|
||||
@NonNull
|
||||
public static TableAwareMovementMethod wrap(@NonNull MovementMethod movementMethod) {
|
||||
return new TableAwareMovementMethod(movementMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps LinkMovementMethod
|
||||
*/
|
||||
@NonNull
|
||||
public static TableAwareMovementMethod create() {
|
||||
return new TableAwareMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
public static boolean handleTableRowTouchEvent(
|
||||
@NonNull TextView widget,
|
||||
@NonNull Spannable buffer,
|
||||
@NonNull MotionEvent event) {
|
||||
// handle only action up (originally action down is used in order to handle selection,
|
||||
// which tables do no have)
|
||||
if (event.getAction() != MotionEvent.ACTION_UP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
x -= widget.getTotalPaddingLeft();
|
||||
y -= widget.getTotalPaddingTop();
|
||||
x += widget.getScrollX();
|
||||
y += widget.getScrollY();
|
||||
|
||||
final Layout layout = widget.getLayout();
|
||||
final int line = layout.getLineForVertical(y);
|
||||
final int off = layout.getOffsetForHorizontal(line, x);
|
||||
|
||||
final TableRowSpan[] spans = buffer.getSpans(off, off, TableRowSpan.class);
|
||||
if (spans.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final TableRowSpan span = spans[0];
|
||||
|
||||
// okay, we can calculate the x to obtain span, but what about y?
|
||||
final Layout rowLayout = span.findLayoutForHorizontalOffset(x);
|
||||
if (rowLayout != null) {
|
||||
// line top as basis
|
||||
final int rowY = layout.getLineTop(line);
|
||||
final int rowLine = rowLayout.getLineForVertical(y - rowY);
|
||||
final int rowOffset = rowLayout.getOffsetForHorizontal(rowLine, x % span.cellWidth());
|
||||
final ClickableSpan[] rowClickableSpans = ((Spanned) rowLayout.getText())
|
||||
.getSpans(rowOffset, rowOffset, ClickableSpan.class);
|
||||
if (rowClickableSpans.length > 0) {
|
||||
rowClickableSpans[0].onClick(widget);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private final MovementMethod wrapped;
|
||||
|
||||
public TableAwareMovementMethod(@NonNull MovementMethod wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(TextView widget, Spannable text) {
|
||||
wrapped.initialize(widget, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) {
|
||||
return wrapped.onKeyDown(widget, text, keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) {
|
||||
return wrapped.onKeyUp(widget, text, keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
|
||||
return wrapped.onKeyOther(view, text, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTakeFocus(TextView widget, Spannable text, int direction) {
|
||||
wrapped.onTakeFocus(widget, text, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
|
||||
return wrapped.onTrackballEvent(widget, text, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
||||
// let wrapped handle first, then if super handles nothing, search for table row spans
|
||||
return wrapped.onTouchEvent(widget, buffer, event)
|
||||
|| handleTableRowTouchEvent(widget, buffer, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event) {
|
||||
return wrapped.onGenericMotionEvent(widget, text, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSelectArbitrarily() {
|
||||
return wrapped.canSelectArbitrarily();
|
||||
}
|
||||
}
|
@ -63,6 +63,7 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
return text;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cell{" +
|
||||
@ -170,7 +171,10 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
|
||||
final int size = layouts.size();
|
||||
|
||||
final int w = (int) (1F * width / size + 0.5F);
|
||||
final int w = cellWidth(size);
|
||||
|
||||
// @since $SNAPSHOT; roundingDiff to offset last vertical border
|
||||
final int roundingDiff = w - (width / size);
|
||||
|
||||
// @since 1.1.1
|
||||
// draw backgrounds
|
||||
@ -264,7 +268,13 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
canvas.drawRect(rect, paint);
|
||||
|
||||
if (i == (size - 1)) {
|
||||
rect.set(w - borderWidth, borderTop, w, borderBottom);
|
||||
// @since $SNAPSHOT; subtract rounding offset for the last vertical divider
|
||||
rect.set(
|
||||
w - borderWidth - roundingDiff,
|
||||
borderTop,
|
||||
w - roundingDiff,
|
||||
borderBottom
|
||||
);
|
||||
canvas.drawRect(rect, paint);
|
||||
}
|
||||
}
|
||||
@ -298,7 +308,7 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
|
||||
final int columns = cells.size();
|
||||
final int padding = theme.tableCellPadding() * 2;
|
||||
final int w = (width / columns) - padding;
|
||||
final int w = cellWidth(columns) - padding;
|
||||
|
||||
this.layouts.clear();
|
||||
|
||||
@ -374,6 +384,34 @@ public class TableRowSpan extends ReplacementSpan {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain Layout given horizontal offset. Primary usage target - MovementMethod
|
||||
*
|
||||
* @since $SNAPSHOT;
|
||||
*/
|
||||
@Nullable
|
||||
public Layout findLayoutForHorizontalOffset(int x) {
|
||||
final int size = layouts.size();
|
||||
final int w = cellWidth(size);
|
||||
final int i = x / w;
|
||||
if (i >= size) {
|
||||
return null;
|
||||
}
|
||||
return layouts.get(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since $SNAPSHOT;
|
||||
*/
|
||||
public int cellWidth() {
|
||||
return cellWidth(layouts.size());
|
||||
}
|
||||
|
||||
// @since $SNAPSHOT;
|
||||
protected int cellWidth(int size) {
|
||||
return (int) (1F * width / size + 0.5F);
|
||||
}
|
||||
|
||||
@SuppressLint("SwitchIntDef")
|
||||
private static Layout.Alignment alignment(@Alignment int alignment) {
|
||||
final Layout.Alignment out;
|
||||
|
Loading…
x
Reference in New Issue
Block a user