Documenting recycler module (work in progress)
This commit is contained in:
parent
24b95e2ffb
commit
2593539b65
@ -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<MarkwonAdapter.Holder> {
|
||||
|
||||
/**
|
||||
* 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<? extends Holder, ? extends Node> 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.
|
||||
*
|
||||
* <strong>Please note</strong> 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 <em>exact</em> 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
|
||||
<N extends Node> Builder include(
|
||||
@NonNull Class<N> node,
|
||||
@NonNull Entry<? extends Holder, ? super N> 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<? extends Holder, ? extends Node> defaultEntry);
|
||||
|
||||
/**
|
||||
* Specify which layout {@link SimpleEntry} will use to render all non-explicitly
|
||||
* registered nodes.
|
||||
*
|
||||
* <strong>Please note</strong> 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 <em>reduced</em> 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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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<TableNodeEntry.TableNodeHolder, TableBlock> {
|
||||
public class TableEntry implements MarkwonAdapter.Entry<TableEntry.TableNodeHolder, TableBlock> {
|
||||
|
||||
private final Map<TableBlock, Table> cache = new HashMap<>(2);
|
||||
|
||||
@ -37,18 +36,11 @@ public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.Table
|
||||
}
|
||||
|
||||
if (table != null) {
|
||||
holder.tableEntryView.setTable(table);
|
||||
// render table
|
||||
renderTable(markwon, holder, table);
|
||||
} // we need to do something with null table...
|
||||
}
|
||||
|
||||
private void renderTable(
|
||||
@NonNull Markwon markwon,
|
||||
@NonNull TableNodeHolder holder,
|
||||
@NonNull Table table) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long id(@NonNull TableBlock node) {
|
||||
return node.hashCode();
|
||||
@ -61,12 +53,12 @@ public class TableNodeEntry implements MarkwonAdapter.Entry<TableNodeEntry.Table
|
||||
|
||||
static class TableNodeHolder extends MarkwonAdapter.Holder {
|
||||
|
||||
final TableLayout layout;
|
||||
final TableEntryView tableEntryView;
|
||||
|
||||
TableNodeHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.layout = requireView(R.id.table_layout);
|
||||
this.tableEntryView = requireView(R.id.table_entry);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package ru.noties.markwon.sample.extension.recycler;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.SpannedString;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
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.extension.R;
|
||||
|
||||
public class TableEntryView extends LinearLayout {
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
@ -1,16 +1,21 @@
|
||||
<?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:paddingRight="16dip">
|
||||
android:paddingTop="8dip"
|
||||
android:paddingRight="16dip"
|
||||
android:paddingBottom="8dip">
|
||||
|
||||
<TableLayout
|
||||
android:id="@+id/table_layout"
|
||||
<ru.noties.markwon.sample.extension.recycler.TableEntryView
|
||||
android:id="@+id/table_entry"
|
||||
android:layout_width="wrap_content"
|
||||
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_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="wrap_content"
|
||||
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" />
|
8
sample-custom-extension/src/main/res/values/attrs.xml
Normal file
8
sample-custom-extension/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="TableEntryView">
|
||||
<attr name="tev_rowEvenBackgroundColor" format="color" />
|
||||
<attr name="tev_debugData" format="string" />
|
||||
</declare-styleable>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user