Working on documentation. Table rendering sample

This commit is contained in:
Dimitry Ivanov 2019-02-06 15:44:46 +03:00
parent cc75a92c7f
commit 96ca96fa70
18 changed files with 357 additions and 54 deletions

View File

@ -1 +1,125 @@
# Images # Images
Starting with <Badge text="3.0.0" /> `Markwon` comes with `ImagesPlugin`
which supports `http(s)`, `file` and `data` schemes and default media
decoder (for simple images, no [SVG](/docs/image/svg.md) or [GIF](/docs/image/gif.md) which
are defined in standalone modules).
## ImagesPlugin
`ImagePlugin` takes care of _obtaining_ image resource, decoding it and displaying it in a `TextView`.
:::warning
Although `core` artifact contains `ImagesPlugin` one must
still **explicitly** register the `ImagesPlugin` on resulting `Markwon`
instance.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create())
```
:::
There are 2 factory methods to obtain `ImagesPlugin`:
* `ImagesPlugin#create(Context)`
* `ImagesPlugin#createWithAssets(Context)`
The first one `#create(Context)` configures:
* `FileSchemeHandler` that allows obtaining images from `file://` uris
* `DataUriSchemeHandler` that allows _inlining_ images with `data:`
scheme (`data:image/svg+xml;base64,MTIz`)
* `NetworkSchemeHandler` that allows obtaining images from `http://` and `https://` uris
(internally it uses `HttpURLConnection`)
* `ImageMediaDecoder` which _tries_ to decode all encountered images as regular ones (png, jpg, etc)
The second one `#createWithAssets(Context)` does the same but also adds support
for images that reside in `assets` folder of your application and
referenced by `file:///android_asset/{path}` uri.
`ImagesPlugin` also _prepares_ a TextView to display images. Due to asynchronous
nature of image loading, there must be a way to invalidate resulting Spanned
content after an image is loaded.
:::warning
Images come with few limitations. For of all, they work with a **TextView only**.
This is due to the fact that there is no way to invalidate a `Spanned` content
by itself (without context in which it is displayed). So, if `Markwon` is used,
for example, to display a `Toast` with an image:
```java
final Spanned spanned = markwon.toMarkdown("Hello ![alt](https://my.image/1.JPG)");
Toast.makeText(context, spanned, Toast.LENGTH_LONG).show();
```
Image _probably_ won't be displayed. As a workaround for `Toast` a custom `View`
can be used:
```java
final Spanned spanned = markwon.toMarkdown("Hello ![alt](https://my.image/1.JPG)");
final View view = createToastView();
final TextView textView = view.findViewById(R.id.text_view);
markwon.setParsedMarkdown(textView, spanned);
final Toast toast = new Toast(context);
toast.setView(view);
// other Toast configurations
toast.show();
```
:::
## SchemeHandler
To add support for different schemes (or customize provided) a `SchemeHandler` must be used.
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(ImagesPlugin.create(context))
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
// example only, Markwon doesn't come with a ftp scheme handler
builder.addSchemeHandler("ftp", new FtpSchemeHandler());
}
})
.build();
```
It's a class to _convert_ an URI into an `InputStream`:
```java
public abstract class SchemeHandler {
@Nullable
public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri);
}
```
`ImageItem` is a holder class for resulting `InputStream` and (optional)
content type:
```java
public class ImageItem {
private final String contentType;
private final InputStream inputStream;
/* rest omitted */
}
```
Based on `contentType` returned a corresponding `MediaDecoder` will be matched.
If no `MediaDecoder` can handle given `contentType` then a default media decoder will
be used.
## MediaDecoder
:::tip
If you are using [html](/docs/html/) you do not have to additionally setup
images displayed via `<img>` tag, as `HtmlPlugin` automatically uses configured
image loader. But images referenced in HTML come with additional support for
sizes, which is not supported natively by markdown, allowing absolute or relative sizes:
```html
<img src="./assets/my-image" width="100%">
```
:::

View File

@ -2,6 +2,23 @@
Here is the list of properties that can be configured via `MarkwonTheme.Builder` class. Here is the list of properties that can be configured via `MarkwonTheme.Builder` class.
:::tip
Starting with <Badge text="3.0.0" /> there is no need to manually construct a `MarkwonTheme`.
Instead a `Plugin` should be used:
```java
final Markwon markwon = Markwon.builder(context)
.usePlugin(new AbstractMarkwonPlugin() {
@Override
public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
builder
.codeTextColor(Color.BLACK)
.codeBackgroundColor(Color.GREEN);
}
})
.build();
```
:::
## Link color ## Link color
Controls the color of a [link](#) Controls the color of a [link](#)

View File

@ -10,7 +10,6 @@ import org.commonmark.ext.gfm.tables.TableHead;
import org.commonmark.ext.gfm.tables.TableRow; import org.commonmark.ext.gfm.tables.TableRow;
import org.commonmark.node.AbstractVisitor; import org.commonmark.node.AbstractVisitor;
import org.commonmark.node.CustomNode; import org.commonmark.node.CustomNode;
import org.commonmark.node.Node;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -160,20 +159,12 @@ public class Table {
final TableCell cell = (TableCell) customNode; final TableCell cell = (TableCell) customNode;
final Node firstChild = cell.getFirstChild();
// need to investigate why... (most likely initial node is modified by someone)
if (firstChild != null) {
if (pendingRow == null) { if (pendingRow == null) {
pendingRow = new ArrayList<>(2); pendingRow = new ArrayList<>(2);
} }
// let's TRY to not visit this node but instead try to render its first child pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(cell)));
pendingRow.add(new Table.Column(alignment(cell.getAlignment()), markwon.render(firstChild)));
pendingRowIsHeader = cell.isHeader(); pendingRowIsHeader = cell.isHeader();
}
return; return;
} }
@ -215,6 +206,4 @@ public class Table {
return out; return out;
} }
} }
} }

View File

@ -67,7 +67,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
* @see SimpleEntry * @see SimpleEntry
*/ */
@NonNull @NonNull
public static MarkwonAdapter create(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry) { public static MarkwonAdapter create(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) {
return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build(); return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build();
} }
@ -109,7 +109,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
@NonNull @NonNull
<N extends Node> Builder include( <N extends Node> Builder include(
@NonNull Class<N> node, @NonNull Class<N> node,
@NonNull Entry<? extends Holder, ? super N> entry); @NonNull Entry<? super N, ? extends Holder> entry);
/** /**
* Specify which {@link Entry} to use for all non-explicitly registered nodes * Specify which {@link Entry} to use for all non-explicitly registered nodes
@ -119,7 +119,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
* @see SimpleEntry * @see SimpleEntry
*/ */
@NonNull @NonNull
Builder defaultEntry(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry); Builder defaultEntry(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry);
/** /**
* Specify which layout {@link SimpleEntry} will use to render all non-explicitly * Specify which layout {@link SimpleEntry} will use to render all non-explicitly
@ -156,7 +156,7 @@ public abstract class MarkwonAdapter extends RecyclerView.Adapter<MarkwonAdapter
/** /**
* @see SimpleEntry * @see SimpleEntry
*/ */
public interface Entry<H extends Holder, N extends Node> { public interface Entry<N extends Node, H extends Holder> {
@NonNull @NonNull
H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent); H createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent);

View File

@ -15,8 +15,8 @@ import ru.noties.markwon.Markwon;
class MarkwonAdapterImpl extends MarkwonAdapter { class MarkwonAdapterImpl extends MarkwonAdapter {
private final SparseArray<Entry<Holder, Node>> entries; private final SparseArray<Entry<Node, Holder>> entries;
private final Entry<Holder, Node> defaultEntry; private final Entry<Node, Holder> defaultEntry;
private final Reducer reducer; private final Reducer reducer;
private LayoutInflater layoutInflater; private LayoutInflater layoutInflater;
@ -26,8 +26,8 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
MarkwonAdapterImpl( MarkwonAdapterImpl(
@NonNull SparseArray<Entry<Holder, Node>> entries, @NonNull SparseArray<Entry<Node, Holder>> entries,
@NonNull Entry<Holder, Node> defaultEntry, @NonNull Entry<Node, Holder> defaultEntry,
@NonNull Reducer reducer) { @NonNull Reducer reducer) {
this.entries = entries; this.entries = entries;
this.defaultEntry = defaultEntry; this.defaultEntry = defaultEntry;
@ -67,7 +67,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
layoutInflater = LayoutInflater.from(parent.getContext()); layoutInflater = LayoutInflater.from(parent.getContext());
} }
final Entry<Holder, Node> entry = getEntry(viewType); final Entry<Node, Holder> entry = getEntry(viewType);
return entry.createHolder(layoutInflater, parent); return entry.createHolder(layoutInflater, parent);
} }
@ -78,7 +78,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
final Node node = nodes.get(position); final Node node = nodes.get(position);
final int viewType = getNodeViewType(node.getClass()); final int viewType = getNodeViewType(node.getClass());
final Entry<Holder, Node> entry = getEntry(viewType); final Entry<Node, Holder> entry = getEntry(viewType);
entry.bindHolder(markwon, holder, node); entry.bindHolder(markwon, holder, node);
} }
@ -107,7 +107,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
public long getItemId(int position) { public long getItemId(int position) {
final Node node = nodes.get(position); final Node node = nodes.get(position);
final int type = getNodeViewType(node.getClass()); final int type = getNodeViewType(node.getClass());
final Entry<Holder, Node> entry = getEntry(type); final Entry<Node, Holder> entry = getEntry(type);
return entry.id(node); return entry.id(node);
} }
@ -122,7 +122,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
} }
@NonNull @NonNull
private Entry<Holder, Node> getEntry(int viewType) { private Entry<Node, Holder> getEntry(int viewType) {
return viewType == 0 return viewType == 0
? defaultEntry ? defaultEntry
: entries.get(viewType); : entries.get(viewType);
@ -130,26 +130,26 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
static class BuilderImpl implements Builder { static class BuilderImpl implements Builder {
private final SparseArray<Entry<Holder, Node>> entries = new SparseArray<>(3); private final SparseArray<Entry<Node, Holder>> entries = new SparseArray<>(3);
private Entry<Holder, Node> defaultEntry; private Entry<Node, Holder> defaultEntry;
private Reducer reducer; private Reducer reducer;
@NonNull @NonNull
@Override @Override
public <N extends Node> Builder include( public <N extends Node> Builder include(
@NonNull Class<N> node, @NonNull Class<N> node,
@NonNull Entry<? extends Holder, ? super N> entry) { @NonNull Entry<? super N, ? extends Holder> entry) {
//noinspection unchecked //noinspection unchecked
entries.append(node.hashCode(), (Entry<Holder, Node>) entry); entries.append(node.hashCode(), (Entry<Node, Holder>) entry);
return this; return this;
} }
@NonNull @NonNull
@Override @Override
public Builder defaultEntry(@NonNull Entry<? extends Holder, ? extends Node> defaultEntry) { public Builder defaultEntry(@NonNull Entry<? extends Node, ? extends Holder> defaultEntry) {
//noinspection unchecked //noinspection unchecked
this.defaultEntry = (Entry<Holder, Node>) defaultEntry; this.defaultEntry = (Entry<Node, Holder>) defaultEntry;
return this; return this;
} }
@ -157,7 +157,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
@Override @Override
public Builder defaultEntry(int layoutResId) { public Builder defaultEntry(int layoutResId) {
//noinspection unchecked //noinspection unchecked
this.defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleEntry(layoutResId); this.defaultEntry = (Entry<Node, Holder>) (Entry) new SimpleEntry(layoutResId);
return this; return this;
} }
@ -174,7 +174,7 @@ class MarkwonAdapterImpl extends MarkwonAdapter {
if (defaultEntry == null) { if (defaultEntry == null) {
//noinspection unchecked //noinspection unchecked
defaultEntry = (Entry<Holder, Node>) (Entry) new SimpleEntry(); defaultEntry = (Entry<Node, Holder>) (Entry) new SimpleEntry();
} }
if (reducer == null) { if (reducer == null) {

View File

@ -21,7 +21,7 @@ import ru.noties.markwon.Markwon;
* @since 3.0.0 * @since 3.0.0
*/ */
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public class SimpleEntry implements MarkwonAdapter.Entry<SimpleEntry.Holder, Node> { public class SimpleEntry implements MarkwonAdapter.Entry<Node, SimpleEntry.Holder> {
public static final Spannable.Factory NO_COPY_SPANNABLE_FACTORY = new NoCopySpannableFactory(); public static final Spannable.Factory NO_COPY_SPANNABLE_FACTORY = new NoCopySpannableFactory();

View File

@ -8,8 +8,8 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="AllowBackup,GoogleAppIndexingWarning"> tools:ignore="AllowBackup,GoogleAppIndexingWarning">

View File

@ -11,27 +11,29 @@ import android.support.v7.widget.RecyclerView;
import android.text.TextUtils; import android.text.TextUtils;
import org.commonmark.ext.gfm.tables.TableBlock; import org.commonmark.ext.gfm.tables.TableBlock;
import org.commonmark.ext.gfm.tables.TablesExtension;
import org.commonmark.node.FencedCodeBlock; import org.commonmark.node.FencedCodeBlock;
import org.commonmark.parser.Parser;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.Collections;
import ru.noties.debug.AndroidLogDebugOutput; import ru.noties.debug.AndroidLogDebugOutput;
import ru.noties.debug.Debug; import ru.noties.debug.Debug;
import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.Markwon; import ru.noties.markwon.Markwon;
import ru.noties.markwon.MarkwonConfiguration; import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonVisitor;
import ru.noties.markwon.core.CorePlugin; import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.ext.tables.TablePlugin;
import ru.noties.markwon.html.HtmlPlugin; import ru.noties.markwon.html.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.image.svg.SvgPlugin; import ru.noties.markwon.image.svg.SvgPlugin;
import ru.noties.markwon.recycler.MarkwonAdapter; import ru.noties.markwon.recycler.MarkwonAdapter;
import ru.noties.markwon.recycler.SimpleEntry; import ru.noties.markwon.recycler.SimpleEntry;
import ru.noties.markwon.sample.R; import ru.noties.markwon.sample.R;
import ru.noties.markwon.syntax.SyntaxHighlightPlugin;
import ru.noties.markwon.urlprocessor.UrlProcessor; import ru.noties.markwon.urlprocessor.UrlProcessor;
import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute; import ru.noties.markwon.urlprocessor.UrlProcessorRelativeToAbsolute;
@ -54,7 +56,7 @@ public class RecyclerActivity extends Activity {
// with `@+id/text` id // with `@+id/text` id
.include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block)) .include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block))
// create own implementation of entry for different rendering // create own implementation of entry for different rendering
.include(TableBlock.class, new TableEntry()) .include(TableBlock.class, new TableEntry2())
// specify default entry (for all other blocks) // specify default entry (for all other blocks)
.defaultEntry(new SimpleEntry(R.layout.adapter_default_entry)) .defaultEntry(new SimpleEntry(R.layout.adapter_default_entry))
.build(); .build();
@ -81,15 +83,35 @@ public class RecyclerActivity extends Activity {
.usePlugin(CorePlugin.create()) .usePlugin(CorePlugin.create())
.usePlugin(ImagesPlugin.createWithAssets(context)) .usePlugin(ImagesPlugin.createWithAssets(context))
.usePlugin(SvgPlugin.create(context.getResources())) .usePlugin(SvgPlugin.create(context.getResources()))
// although we will be rendering table differently we still need .usePlugin(new AbstractMarkwonPlugin() {
// to register commonmark-java tables extension (which TablePlugin does) @Override
.usePlugin(TablePlugin.create(context)) public void configureParser(@NonNull Parser.Builder builder) {
// it's important NOT to use TablePlugin
// the only thing we want from it is commonmark-java parser extension
builder.extensions(Collections.singleton(TablesExtension.create()));
}
})
.usePlugin(HtmlPlugin.create()) .usePlugin(HtmlPlugin.create())
// .usePlugin(SyntaxHighlightPlugin.create())
.usePlugin(new AbstractMarkwonPlugin() { .usePlugin(new AbstractMarkwonPlugin() {
@Override @Override
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
builder.urlProcessor(new UrlProcessorInitialReadme()); builder.urlProcessor(new UrlProcessorInitialReadme());
} }
@Override
public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
builder.on(FencedCodeBlock.class, (visitor, fencedCodeBlock) -> {
// we actually won't be applying code spans here, as our custom view will
// draw background and apply mono typeface
//
// NB the `trim` operation on literal (as code will have a new line at the end)
final CharSequence code = visitor.configuration()
.syntaxHighlight()
.highlight(fencedCodeBlock.getInfo(), fencedCodeBlock.getLiteral().trim());
visitor.builder().append(code);
});
}
}) })
.build(); .build();
} }

View File

@ -17,7 +17,7 @@ import ru.noties.markwon.recycler.MarkwonAdapter;
import ru.noties.markwon.sample.R; import ru.noties.markwon.sample.R;
// do not use in real applications, this is just a showcase // do not use in real applications, this is just a showcase
public class TableEntry implements MarkwonAdapter.Entry<TableEntry.TableNodeHolder, TableBlock> { public class TableEntry implements MarkwonAdapter.Entry<TableBlock, TableEntry.TableNodeHolder> {
private final Map<TableBlock, Table> cache = new HashMap<>(2); private final Map<TableBlock, Table> cache = new HashMap<>(2);

View File

@ -0,0 +1,121 @@
package ru.noties.markwon.sample.recycler;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import org.commonmark.ext.gfm.tables.TableBlock;
import java.util.HashMap;
import java.util.Map;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.ext.tables.Table;
import ru.noties.markwon.recycler.MarkwonAdapter;
import ru.noties.markwon.sample.R;
public class TableEntry2 implements MarkwonAdapter.Entry<TableBlock, TableEntry2.TableHolder> {
private final Map<TableBlock, Table> map = new HashMap<>(3);
@NonNull
@Override
public TableHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
return new TableHolder(inflater.inflate(R.layout.adapter_table_block_2, parent, false));
}
@Override
public void bindHolder(@NonNull Markwon markwon, @NonNull TableHolder holder, @NonNull TableBlock node) {
Table table = map.get(node);
if (table == null) {
table = Table.parse(markwon, node);
map.put(node, table);
}
// check if this exact TableBlock was already
final TableLayout layout = holder.layout;
if (table == null
|| table == layout.getTag(R.id.table_layout)) {
return;
}
layout.setTag(R.id.table_layout, table);
layout.removeAllViews();
layout.setBackgroundResource(R.drawable.bg_table_cell);
final Context context = layout.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
TableRow tableRow;
TextView textView;
for (Table.Row row : table.rows()) {
tableRow = new TableRow(context);
for (Table.Column column : row.columns()) {
textView = (TextView) inflater.inflate(R.layout.view_table_entry_cell, tableRow, false);
textView.setGravity(textGravity(column.alignment()));
markwon.setParsedMarkdown(textView, column.content());
textView.getPaint().setFakeBoldText(row.header());
textView.setBackgroundResource(R.drawable.bg_table_cell);
tableRow.addView(textView);
}
layout.addView(tableRow);
}
}
@Override
public long id(@NonNull TableBlock node) {
return node.hashCode();
}
@Override
public void clear() {
map.clear();
}
static class TableHolder extends MarkwonAdapter.Holder {
final TableLayout layout;
TableHolder(@NonNull View itemView) {
super(itemView);
this.layout = requireView(R.id.table_layout);
}
}
// 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 | Gravity.CENTER_VERTICAL;
}
}

View File

@ -54,7 +54,6 @@ 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); final int stroke = array.getDimensionPixelSize(R.styleable.TableEntryView_tev_borderWidth, 0);
// half of requested // half of requested
@ -124,6 +123,8 @@ public class TableEntryView extends LinearLayout {
textView.setText(column.content()); textView.setText(column.content());
textView.getPaint().setFakeBoldText(row.header()); textView.getPaint().setFakeBoldText(row.header());
} }
group.requestLayout();
} }
@NonNull @NonNull

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/text" android:id="@+id/text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -10,4 +11,5 @@
android:paddingBottom="8dip" android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#000" android:textColor="#000"
android:textSize="16sp" /> android:textSize="16sp"
tools:text="Hello" />

View File

@ -14,11 +14,15 @@
android:id="@+id/text" android:id="@+id/text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="#0f000000"
android:fontFamily="monospace"
android:lineSpacingExtra="2dip" android:lineSpacingExtra="2dip"
android:paddingLeft="16dip"
android:paddingTop="8dip" android:paddingTop="8dip"
android:paddingRight="16dip"
android:paddingBottom="8dip" android:paddingBottom="8dip"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="16sp" android:textSize="14sp"
tools:text="# Hello there! and taskjs" /> tools:text="# Hello there! and tasks" />
</HorizontalScrollView> </HorizontalScrollView>

View File

@ -5,7 +5,6 @@
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:paddingLeft="16dip" android:paddingLeft="16dip"
android:paddingTop="8dip" android:paddingTop="8dip"
android:paddingRight="16dip" android:paddingRight="16dip"

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingLeft="16dip"
android:paddingTop="8dip"
android:paddingRight="16dip"
android:paddingBottom="8dip"
android:scrollbarStyle="outsideInset">
<TableLayout
android:id="@+id/table_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</HorizontalScrollView>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="0px" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:gravity="center_vertical"
android:padding="4dip" android:padding="8dip"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#000" android:textColor="#000"
android:textSize="16sp" android:textSize="16sp"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" /> android:orientation="horizontal" />

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="0.5dip"
android:color="#000" />
</shape>