From 2593539b65c5b454d69773f4ef1ce7e6bb7748c3 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Wed, 26 Dec 2018 00:06:15 +0300 Subject: [PATCH] Documenting recycler module (work in progress) --- .../markwon/recycler/MarkwonAdapter.java | 80 ++++++++- .../recycler/MarkwonRecyclerActivity.java | 2 +- .../{TableNodeEntry.java => TableEntry.java} | 16 +- .../extension/recycler/TableEntryView.java | 153 ++++++++++++++++++ .../main/res/layout/adapter_table_block.xml | 13 +- .../main/res/layout/view_table_entry_cell.xml | 11 ++ .../main/res/layout/view_table_entry_row.xml | 5 + .../src/main/res/values/attrs.xml | 8 + 8 files changed, 269 insertions(+), 19 deletions(-) rename sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/{TableNodeEntry.java => TableEntry.java} (78%) create mode 100644 sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableEntryView.java create mode 100644 sample-custom-extension/src/main/res/layout/view_table_entry_cell.xml create mode 100644 sample-custom-extension/src/main/res/layout/view_table_entry_row.xml create mode 100644 sample-custom-extension/src/main/res/values/attrs.xml diff --git a/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java b/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java index 92f22605..fc0d4cb9 100644 --- a/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java +++ b/markwon-recycler/src/main/java/ru/noties/markwon/recycler/MarkwonAdapter.java @@ -16,7 +16,7 @@ import java.util.List; import ru.noties.markwon.Markwon; /** - * Class to display markdown in a RecyclerView. It is done by extracting root blocks from a + * Adapter to display markdown in a RecyclerView. It is done by extracting root blocks from a * parsed markdown document and rendering each block in a standalone RecyclerView entry. Provides * ability to customize rendering of blocks. For example display certain blocks in a horizontal * scrolling container or display tables in a specific widget designed for it ({@link Builder#include(Class, Entry)}). @@ -36,43 +36,119 @@ import ru.noties.markwon.Markwon; */ public abstract class MarkwonAdapter extends RecyclerView.Adapter { + /** + * Factory method to obtain {@link Builder} instance. + * + * @see Builder + */ @NonNull public static Builder builder() { return new MarkwonAdapterImpl.BuilderImpl(); } + /** + * Factory method to create a {@link MarkwonAdapter} for evaluation purposes. Resulting + * adapter will use default layout for all blocks. Default layout has no styling and should + * be specified explicitly. + * + * @see #create(int) + * @see #create(Entry) + */ @NonNull public static MarkwonAdapter create() { return new MarkwonAdapterImpl.BuilderImpl().build(); } - // for an adapter with only one entry (all blocks are rendered the same with this entry) + /** + * Factory method to create a {@link MarkwonAdapter} that uses supplied entry to render all + * nodes. + * + * @param defaultEntry {@link Entry} to be used for node rendering + * @see SimpleEntry + */ @NonNull public static MarkwonAdapter create(@NonNull Entry defaultEntry) { return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(defaultEntry).build(); } + /** + * Factory method to create a {@link MarkwonAdapter} that will use supplied layoutResId view + * to display all nodes. + * + * Please note that supplied layout must have a TextView inside + * with {@code android:id="@+id/text"} + * + * @param layoutResId layout to be used to display all nodes + * @see SimpleEntry + */ @NonNull public static MarkwonAdapter create(@LayoutRes int layoutResId) { return new MarkwonAdapterImpl.BuilderImpl().defaultEntry(layoutResId).build(); } + /** + * Builder to create an instance of {@link MarkwonAdapter} + * + * @see #include(Class, Entry) + * @see #defaultEntry(int) + * @see #defaultEntry(Entry) + * @see #reducer(Reducer) + */ public interface Builder { + /** + * Include a custom {@link Entry} rendering for a Node. Please note that `node` argument + * must be exact type, as internally there is no validation for inheritance. if multiple + * nodes should be rendered with the same {@link Entry} they must specify so explicitly. + * By calling this method for each. + * + * @param node type of the node to register + * @param entry {@link Entry} to be used for `node` rendering + * @return self + */ @NonNull Builder include( @NonNull Class node, @NonNull Entry entry); + /** + * Specify which {@link Entry} to use for all non-explicitly registered nodes + * + * @param defaultEntry {@link Entry} + * @return self + * @see SimpleEntry + */ @NonNull Builder defaultEntry(@NonNull Entry defaultEntry); + /** + * Specify which layout {@link SimpleEntry} will use to render all non-explicitly + * registered nodes. + * + * Please note that supplied layout must have a TextView inside + * with {@code android:id="@+id/text"} + * + * @return self + * @see SimpleEntry + */ @NonNull Builder defaultEntry(@LayoutRes int layoutResId); + /** + * Specify how root Node will be reduced to a list of nodes. There is a default + * {@link Reducer} that will be used if not provided explicitly (there is no need to + * register your own unless you require it). + * + * @param reducer {@link Reducer} + * @return self + * @see Reducer + */ @NonNull Builder reducer(@NonNull Reducer reducer); + /** + * @return {@link MarkwonAdapter} + */ @NonNull MarkwonAdapter build(); } diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/MarkwonRecyclerActivity.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/MarkwonRecyclerActivity.java index 34d07ec8..eb4dc039 100644 --- a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/MarkwonRecyclerActivity.java +++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/MarkwonRecyclerActivity.java @@ -49,7 +49,7 @@ public class MarkwonRecyclerActivity extends Activity { final MarkwonAdapter adapter = MarkwonAdapter.builder() .include(FencedCodeBlock.class, new SimpleEntry(R.layout.adapter_fenced_code_block)) - .include(TableBlock.class, new TableNodeEntry()) + .include(TableBlock.class, new TableEntry()) .defaultEntry(new SimpleEntry(R.layout.adapter_default_entry)) .build(); diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableNodeEntry.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableEntry.java similarity index 78% rename from sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableNodeEntry.java rename to sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableEntry.java index c8c7b67b..aa3d79ff 100644 --- a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableNodeEntry.java +++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/recycler/TableEntry.java @@ -4,7 +4,6 @@ import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TableLayout; import org.commonmark.ext.gfm.tables.TableBlock; @@ -17,7 +16,7 @@ import ru.noties.markwon.recycler.MarkwonAdapter; import ru.noties.markwon.sample.extension.R; // do not use in real applications, this is just a showcase -public class TableNodeEntry implements MarkwonAdapter.Entry { +public class TableEntry implements MarkwonAdapter.Entry { private final Map cache = new HashMap<>(2); @@ -37,18 +36,11 @@ public class TableNodeEntry implements MarkwonAdapter.Entry rows = new ArrayList<>(); + for (String row : data.split("\\|")) { + final List 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(); + } + } + } + + public void setTable(@NonNull Table table) { + final List rows = table.rows(); + for (int i = 0, size = rows.size(); i < size; i++) { + addRow(i, rows.get(i)); + } + } + + 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 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.setTextAlignment(textAlignment(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); + } + + private static int textAlignment(@NonNull Table.Alignment alignment) { + final int out; + switch (alignment) { + case LEFT: + out = TextView.TEXT_ALIGNMENT_TEXT_START; + break; + case CENTER: + out = TextView.TEXT_ALIGNMENT_CENTER; + break; + case RIGHT: + out = TextView.TEXT_ALIGNMENT_TEXT_END; + break; + default: + throw new IllegalStateException("Unexpected alignment: " + alignment); + } + return out; + } +} diff --git a/sample-custom-extension/src/main/res/layout/adapter_table_block.xml b/sample-custom-extension/src/main/res/layout/adapter_table_block.xml index 8574d797..4e032c05 100644 --- a/sample-custom-extension/src/main/res/layout/adapter_table_block.xml +++ b/sample-custom-extension/src/main/res/layout/adapter_table_block.xml @@ -1,16 +1,21 @@ + android:paddingTop="8dip" + android:paddingRight="16dip" + android:paddingBottom="8dip"> - + android:layout_height="wrap_content" + app:tev_debugData="head1,head2,head3|col1,col2,col3|col1,col2,col3|col1,col2,col3" + app:tev_rowEvenBackgroundColor="#40ff0000" /> \ No newline at end of file diff --git a/sample-custom-extension/src/main/res/layout/view_table_entry_cell.xml b/sample-custom-extension/src/main/res/layout/view_table_entry_cell.xml new file mode 100644 index 00000000..88654d3d --- /dev/null +++ b/sample-custom-extension/src/main/res/layout/view_table_entry_cell.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/sample-custom-extension/src/main/res/layout/view_table_entry_row.xml b/sample-custom-extension/src/main/res/layout/view_table_entry_row.xml new file mode 100644 index 00000000..24e7fb9e --- /dev/null +++ b/sample-custom-extension/src/main/res/layout/view_table_entry_row.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/sample-custom-extension/src/main/res/values/attrs.xml b/sample-custom-extension/src/main/res/values/attrs.xml new file mode 100644 index 00000000..05f86e93 --- /dev/null +++ b/sample-custom-extension/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file