ext-tables, table aware movement method
This commit is contained in:
parent
4c3fba8929
commit
f8eaac6197
@ -2,12 +2,16 @@
|
|||||||
|
|
||||||
# SNAPSHOT
|
# SNAPSHOT
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
* `ext-tables` - `TableAwareMovementMethod` a special movement method to handle clicks inside tables ([#289])
|
||||||
|
|
||||||
#### Changed
|
#### Changed
|
||||||
* `image-glide` - update to `4.11.0` version
|
* `image-glide` - update to `4.11.0` version
|
||||||
* `inline-parser` - revert parsing index when `InlineProcessor` returns `null` as result
|
* `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]
|
* `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
|
[#284]: https://github.com/noties/Markwon/pull/284
|
||||||
|
[#289]: https://github.com/noties/Markwon/issues/289
|
||||||
|
|
||||||
[@magnusvs]: https://github.com/magnusvs
|
[@magnusvs]: https://github.com/magnusvs
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@
|
|||||||
"javaClassName": "io.noties.markwon.app.samples.table.TableLinkifySample",
|
"javaClassName": "io.noties.markwon.app.samples.table.TableLinkifySample",
|
||||||
"id": "20200702135739",
|
"id": "20200702135739",
|
||||||
"title": "Linkify table",
|
"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": [
|
"artifacts": [
|
||||||
"EXT_TABLES",
|
"EXT_TABLES",
|
||||||
"LINKIFY"
|
"LINKIFY"
|
||||||
@ -894,12 +894,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueOnTextAddedSample",
|
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueInlineParsingSample",
|
||||||
"id": "20200629162024",
|
"id": "20200629162024",
|
||||||
"title": "User mention and issue (via text)",
|
"title": "User mention and issue (via text)",
|
||||||
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
|
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
"CORE"
|
"CORE",
|
||||||
|
"INLINE_PARSER"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"parsing",
|
"parsing",
|
||||||
@ -908,13 +909,12 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueInlineParsingSample",
|
"javaClassName": "io.noties.markwon.app.samples.GithubUserIssueOnTextAddedSample",
|
||||||
"id": "20200629162024",
|
"id": "20200629162024",
|
||||||
"title": "User mention and issue (via text)",
|
"title": "User mention and issue (via text)",
|
||||||
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
|
"description": "Github-like user mention and issue rendering via `CorePlugin.OnTextAddedListener`",
|
||||||
"artifacts": [
|
"artifacts": [
|
||||||
"CORE",
|
"CORE"
|
||||||
"INLINE_PARSER"
|
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"parsing",
|
"parsing",
|
||||||
|
@ -3,8 +3,10 @@ package io.noties.markwon.app.samples.table;
|
|||||||
import io.noties.markwon.Markwon;
|
import io.noties.markwon.Markwon;
|
||||||
import io.noties.markwon.app.sample.Tags;
|
import io.noties.markwon.app.sample.Tags;
|
||||||
import io.noties.markwon.app.sample.ui.MarkwonTextViewSample;
|
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.ext.tables.TablePlugin;
|
||||||
import io.noties.markwon.linkify.LinkifyPlugin;
|
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.MarkwonArtifact;
|
||||||
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
|
import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
|
||||||
|
|
||||||
@ -12,7 +14,7 @@ import io.noties.markwon.sample.annotations.MarkwonSampleInfo;
|
|||||||
id = "20200702135739",
|
id = "20200702135739",
|
||||||
title = "Linkify table",
|
title = "Linkify table",
|
||||||
description = "Automatically linkify markdown content " +
|
description = "Automatically linkify markdown content " +
|
||||||
"including content inside tables",
|
"including content inside tables, handle clicks inside tables",
|
||||||
artifacts = {MarkwonArtifact.EXT_TABLES, MarkwonArtifact.LINKIFY},
|
artifacts = {MarkwonArtifact.EXT_TABLES, MarkwonArtifact.LINKIFY},
|
||||||
tags = {Tags.links}
|
tags = {Tags.links}
|
||||||
)
|
)
|
||||||
@ -26,7 +28,8 @@ public class TableLinkifySample extends MarkwonTextViewSample {
|
|||||||
"| 测试 | 测试 | 测测测12345试测试测试 |\n" +
|
"| 测试 | 测试 | 测测测12345试测试测试 |\n" +
|
||||||
"| 测试 | 测试 | 123445 |\n" +
|
"| 测试 | 测试 | 123445 |\n" +
|
||||||
"| 测试 | 测试 | (650) 555-1212 |\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" +
|
"测试\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
@ -35,6 +38,8 @@ public class TableLinkifySample extends MarkwonTextViewSample {
|
|||||||
final Markwon markwon = Markwon.builder(context)
|
final Markwon markwon = Markwon.builder(context)
|
||||||
.usePlugin(LinkifyPlugin.create())
|
.usePlugin(LinkifyPlugin.create())
|
||||||
.usePlugin(TablePlugin.create(context))
|
.usePlugin(TablePlugin.create(context))
|
||||||
|
// use TableAwareLinkMovementMethod to handle clicks inside tables
|
||||||
|
.usePlugin(MovementMethodPlugin.create(TableAwareMovementMethod.create()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
markwon.setMarkdown(textView, md);
|
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
|
Please note, that _by default_ tables have limitations. For example, table contents won't be copied to clipboard if a TextView
|
||||||
for images inside table cells. And 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.
|
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
|
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
|
functionality which can answer some needs. These all come from the limited nature of the TextView
|
||||||
to display such content.
|
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.
|
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
|
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
|
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;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Cell{" +
|
return "Cell{" +
|
||||||
@ -170,7 +171,10 @@ public class TableRowSpan extends ReplacementSpan {
|
|||||||
|
|
||||||
final int size = layouts.size();
|
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
|
// @since 1.1.1
|
||||||
// draw backgrounds
|
// draw backgrounds
|
||||||
@ -264,7 +268,13 @@ public class TableRowSpan extends ReplacementSpan {
|
|||||||
canvas.drawRect(rect, paint);
|
canvas.drawRect(rect, paint);
|
||||||
|
|
||||||
if (i == (size - 1)) {
|
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);
|
canvas.drawRect(rect, paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,7 +308,7 @@ public class TableRowSpan extends ReplacementSpan {
|
|||||||
|
|
||||||
final int columns = cells.size();
|
final int columns = cells.size();
|
||||||
final int padding = theme.tableCellPadding() * 2;
|
final int padding = theme.tableCellPadding() * 2;
|
||||||
final int w = (width / columns) - padding;
|
final int w = cellWidth(columns) - padding;
|
||||||
|
|
||||||
this.layouts.clear();
|
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")
|
@SuppressLint("SwitchIntDef")
|
||||||
private static Layout.Alignment alignment(@Alignment int alignment) {
|
private static Layout.Alignment alignment(@Alignment int alignment) {
|
||||||
final Layout.Alignment out;
|
final Layout.Alignment out;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user