Raw impl of task lists (parser, span, drawable)
This commit is contained in:
parent
8aea662f50
commit
f95918104b
@ -6,6 +6,9 @@
|
||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%markwon-image-loader%22)
|
||||
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%markwon-view%22)
|
||||
|
||||
- [ ] one **one** _one_
|
||||
- [X] ~~two~~
|
||||
|
||||
**Markwon** is a library for Android that renders markdown as system-native Spannables. It gives ability to display markdown in all TextView widgets (**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Notifications**, **Toasts**, etc. <u>**No WebView is required**</u>. Library provides reasonable defaults for display style of markdown but also gives all the means to tweak the appearance if desired. All markdown features are supported (including limited support for inlined HTML code, markdown tables and images).
|
||||
|
||||
<sup>*</sup>*This file is displayed by default in the [sample-apk] application. Which is a generic markdown viewer with support to display markdown via `http`, `https` & `file` schemes and 2 themes included: Light & Dark*
|
||||
|
@ -0,0 +1,74 @@
|
||||
package ru.noties.markwon.debug;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import ru.noties.markwon.R;
|
||||
import ru.noties.markwon.spans.TaskListDrawable;
|
||||
|
||||
public class DebugCheckboxDrawableView extends View {
|
||||
|
||||
private Drawable drawable;
|
||||
|
||||
public DebugCheckboxDrawableView(Context context) {
|
||||
super(context);
|
||||
init(context, null);
|
||||
}
|
||||
|
||||
public DebugCheckboxDrawableView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||
|
||||
int checkedColor = 0;
|
||||
int normalColor = 0;
|
||||
int checkMarkColor = 0;
|
||||
|
||||
boolean checked = false;
|
||||
|
||||
if (attrs != null) {
|
||||
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.DebugCheckboxDrawableView);
|
||||
try {
|
||||
|
||||
checkedColor = array.getColor(R.styleable.DebugCheckboxDrawableView_fcdv_checkedFillColor, checkedColor);
|
||||
normalColor = array.getColor(R.styleable.DebugCheckboxDrawableView_fcdv_normalOutlineColor, normalColor);
|
||||
checkMarkColor = array.getColor(R.styleable.DebugCheckboxDrawableView_fcdv_checkMarkColor, checkMarkColor);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
checked = array.getBoolean(R.styleable.DebugCheckboxDrawableView_fcdv_checked, checked);
|
||||
} finally {
|
||||
array.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
final TaskListDrawable drawable = new TaskListDrawable(checkedColor, normalColor, checkMarkColor);
|
||||
final int[] state;
|
||||
if (checked) {
|
||||
state = new int[]{android.R.attr.state_checked};
|
||||
} else {
|
||||
state = new int[0];
|
||||
}
|
||||
drawable.setState(state);
|
||||
|
||||
this.drawable = drawable;
|
||||
|
||||
setLayerType(LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
drawable.setBounds(0, 0, getWidth(), getHeight());
|
||||
drawable.draw(canvas);
|
||||
}
|
||||
}
|
16
app/src/debug/res/layout/debug_checkbox.xml
Normal file
16
app/src/debug/res/layout/debug_checkbox.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout 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="match_parent">
|
||||
|
||||
<ru.noties.markwon.debug.DebugCheckboxDrawableView
|
||||
android:layout_width="128dip"
|
||||
android:layout_height="128dip"
|
||||
android:layout_gravity="center"
|
||||
app:fcdv_checkMarkColor="#000"
|
||||
app:fcdv_checked="true"
|
||||
app:fcdv_checkedFillColor="#F00"
|
||||
app:fcdv_normalOutlineColor="#ccc" />
|
||||
|
||||
</FrameLayout>
|
11
app/src/debug/res/values/attrs.xml
Normal file
11
app/src/debug/res/values/attrs.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="DebugCheckboxDrawableView">
|
||||
<attr name="fcdv_checkedFillColor" format="color" />
|
||||
<attr name="fcdv_normalOutlineColor" format="color" />
|
||||
<attr name="fcdv_checkMarkColor" format="color" />
|
||||
<attr name="fcdv_checked" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
10
library-task-list/build.gradle
Normal file
10
library-task-list/build.gradle
Normal file
@ -0,0 +1,10 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = "1.7"
|
||||
targetCompatibility = "1.7"
|
||||
|
||||
dependencies {
|
||||
compile SUPPORT_ANNOTATIONS
|
||||
compile COMMON_MARK
|
||||
compile 'ru.noties:debug:3.0.0@jar'
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package ru.noties.markwon.tasklist;
|
||||
|
||||
import org.commonmark.node.CustomBlock;
|
||||
|
||||
public class TaskListBlock extends CustomBlock {
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package ru.noties.markwon.tasklist;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.commonmark.node.Block;
|
||||
import org.commonmark.parser.InlineParser;
|
||||
import org.commonmark.parser.block.AbstractBlockParser;
|
||||
import org.commonmark.parser.block.AbstractBlockParserFactory;
|
||||
import org.commonmark.parser.block.BlockContinue;
|
||||
import org.commonmark.parser.block.BlockStart;
|
||||
import org.commonmark.parser.block.MatchedBlockParser;
|
||||
import org.commonmark.parser.block.ParserState;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ru.noties.debug.Debug;
|
||||
|
||||
class TaskListBlockParser extends AbstractBlockParser {
|
||||
|
||||
private static final Pattern PATTERN = Pattern.compile("\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)");
|
||||
// private static final Pattern PATTERN_2 = Pattern.compile("^\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)");
|
||||
|
||||
private final TaskListBlock block = new TaskListBlock();
|
||||
|
||||
private final List<String> lines;
|
||||
|
||||
TaskListBlockParser(@NonNull String startLine) {
|
||||
this.lines = new ArrayList<>(3);
|
||||
this.lines.add(startLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockContinue tryContinue(ParserState parserState) {
|
||||
|
||||
final BlockContinue blockContinue;
|
||||
|
||||
final String line = line(parserState);
|
||||
|
||||
// Debug.i("line: %s, find: %s", line, PATTERN.matcher(line).find());
|
||||
Debug.i("isBlank: %s, line: `%s`", parserState.isBlank(), line);
|
||||
|
||||
if (line != null
|
||||
&& line.length() > 0
|
||||
&& PATTERN.matcher(line).matches()) {
|
||||
Debug.e();
|
||||
blockContinue = BlockContinue.atIndex(parserState.getIndex());
|
||||
} else {
|
||||
Debug.e();
|
||||
blockContinue = BlockContinue.finished();
|
||||
}
|
||||
|
||||
return blockContinue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLine(CharSequence line) {
|
||||
Debug.i("line: %s", line);
|
||||
if (line != null
|
||||
&& line.length() > 0) {
|
||||
lines.add(line.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseInlines(InlineParser inlineParser) {
|
||||
|
||||
Debug.i(lines);
|
||||
|
||||
Matcher matcher;
|
||||
|
||||
TaskListItem item;
|
||||
|
||||
for (String line : lines) {
|
||||
|
||||
matcher = PATTERN.matcher(line);
|
||||
|
||||
if (!matcher.matches()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
item = new TaskListItem().done(isDone(matcher.group(1)));
|
||||
|
||||
inlineParser.parse(matcher.group(2), item);
|
||||
|
||||
block.appendChild(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canContain(Block block) {
|
||||
Debug.i("block: %s", block);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeBlock() {
|
||||
Debug.e(block);
|
||||
Debug.trace();
|
||||
}
|
||||
|
||||
static class Factory extends AbstractBlockParserFactory {
|
||||
|
||||
@Override
|
||||
public BlockStart tryStart(ParserState state, MatchedBlockParser matchedBlockParser) {
|
||||
|
||||
final String line = line(state);
|
||||
|
||||
if (line != null
|
||||
&& line.length() > 0
|
||||
&& PATTERN.matcher(line).matches()) {
|
||||
|
||||
return BlockStart.of(new TaskListBlockParser(line))
|
||||
.atIndex(state.getIndex() + line.length());
|
||||
}
|
||||
|
||||
return BlockStart.none();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String line(@NonNull ParserState state) {
|
||||
final CharSequence lineRaw = state.getLine();
|
||||
return lineRaw != null
|
||||
? lineRaw.toString()
|
||||
: null;
|
||||
}
|
||||
|
||||
private static boolean isDone(@NonNull String value) {
|
||||
return "X".equals(value)
|
||||
|| "x".equals(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package ru.noties.markwon.tasklist;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.commonmark.parser.Parser;
|
||||
|
||||
public class TaskListExtension implements Parser.ParserExtension {
|
||||
|
||||
@NonNull
|
||||
public static TaskListExtension create() {
|
||||
return new TaskListExtension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extend(Parser.Builder parserBuilder) {
|
||||
parserBuilder.customBlockParserFactory(new TaskListBlockParser.Factory());
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package ru.noties.markwon.tasklist;
|
||||
|
||||
import org.commonmark.node.CustomNode;
|
||||
|
||||
public class TaskListItem extends CustomNode {
|
||||
|
||||
private boolean done;
|
||||
|
||||
public boolean done() {
|
||||
return done;
|
||||
}
|
||||
|
||||
public TaskListItem done(boolean done) {
|
||||
this.done = done;
|
||||
return this;
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ dependencies {
|
||||
compile COMMON_MARK
|
||||
compile COMMON_MARK_STRIKETHROUGHT
|
||||
compile COMMON_MARK_TABLE
|
||||
|
||||
compile project(':library-task-list')
|
||||
}
|
||||
|
||||
if (project.hasProperty('release')) {
|
||||
|
@ -15,6 +15,7 @@ import org.commonmark.parser.Parser;
|
||||
import java.util.Arrays;
|
||||
|
||||
import ru.noties.markwon.renderer.SpannableRenderer;
|
||||
import ru.noties.markwon.tasklist.TaskListExtension;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public abstract class Markwon {
|
||||
@ -27,7 +28,7 @@ public abstract class Markwon {
|
||||
*/
|
||||
public static Parser createParser() {
|
||||
return new Parser.Builder()
|
||||
.extensions(Arrays.asList(StrikethroughExtension.create(), TablesExtension.create()))
|
||||
.extensions(Arrays.asList(StrikethroughExtension.create(), TablesExtension.create(), TaskListExtension.create()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import org.commonmark.node.AbstractVisitor;
|
||||
import org.commonmark.node.BlockQuote;
|
||||
import org.commonmark.node.BulletList;
|
||||
import org.commonmark.node.Code;
|
||||
import org.commonmark.node.CustomBlock;
|
||||
import org.commonmark.node.CustomNode;
|
||||
import org.commonmark.node.Emphasis;
|
||||
import org.commonmark.node.FencedCodeBlock;
|
||||
@ -51,7 +52,10 @@ import ru.noties.markwon.spans.LinkSpan;
|
||||
import ru.noties.markwon.spans.OrderedListItemSpan;
|
||||
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||
import ru.noties.markwon.spans.TableRowSpan;
|
||||
import ru.noties.markwon.spans.TaskListSpan;
|
||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||
import ru.noties.markwon.tasklist.TaskListBlock;
|
||||
import ru.noties.markwon.tasklist.TaskListItem;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
@ -269,6 +273,19 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
newLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(CustomBlock customBlock) {
|
||||
if (customBlock instanceof TaskListBlock) {
|
||||
blockQuoteIndent += 1;
|
||||
visitChildren(customBlock);
|
||||
blockQuoteIndent -= 1;
|
||||
newLine();
|
||||
builder.append('\n');
|
||||
} else {
|
||||
super.visit(customBlock);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(CustomNode customNode) {
|
||||
|
||||
@ -279,9 +296,19 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||
setSpan(length, new StrikethroughSpan());
|
||||
|
||||
} else if (!handleTableNodes(customNode)) {
|
||||
|
||||
if (customNode instanceof TaskListItem) {
|
||||
|
||||
final int length = builder.length();
|
||||
visitChildren(customNode);
|
||||
setSpan(length, new TaskListSpan(configuration.theme(), blockQuoteIndent, length, ((TaskListItem) customNode).done()));
|
||||
newLine();
|
||||
|
||||
} else {
|
||||
super.visit(customNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleTableNodes(CustomNode node) {
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.Dimension;
|
||||
@ -39,9 +40,13 @@ public class SpannableTheme {
|
||||
}
|
||||
|
||||
public static Builder builderWithDefaults(@NonNull Context context) {
|
||||
|
||||
final int linkColor = resolve(context, android.R.attr.textColorLink);
|
||||
final int backgroundColor = resolve(context, android.R.attr.colorBackground);
|
||||
|
||||
final Dip dip = new Dip(context);
|
||||
return new Builder()
|
||||
.linkColor(resolve(context, android.R.attr.textColorLink))
|
||||
.linkColor(linkColor)
|
||||
.codeMultilineMargin(dip.toPx(8))
|
||||
.blockMargin(dip.toPx(24))
|
||||
.blockQuoteWidth(dip.toPx(4))
|
||||
@ -49,7 +54,8 @@ public class SpannableTheme {
|
||||
.headingBreakHeight(dip.toPx(1))
|
||||
.thematicBreakHeight(dip.toPx(4))
|
||||
.tableCellPadding(dip.toPx(4))
|
||||
.tableBorderWidth(dip.toPx(1));
|
||||
.tableBorderWidth(dip.toPx(1))
|
||||
.taskListDrawable(new TaskListDrawable(linkColor, linkColor, backgroundColor));
|
||||
}
|
||||
|
||||
private static int resolve(Context context, @AttrRes int attr) {
|
||||
@ -147,6 +153,8 @@ public class SpannableTheme {
|
||||
// by default paint.color * TABLE_ODD_ROW_DEF_ALPHA
|
||||
protected final int tableOddRowBackgroundColor;
|
||||
|
||||
protected final Drawable taskListDrawable;
|
||||
|
||||
protected SpannableTheme(@NonNull Builder builder) {
|
||||
this.linkColor = builder.linkColor;
|
||||
this.blockMargin = builder.blockMargin;
|
||||
@ -169,6 +177,7 @@ public class SpannableTheme {
|
||||
this.tableBorderColor = builder.tableBorderColor;
|
||||
this.tableBorderWidth = builder.tableBorderWidth;
|
||||
this.tableOddRowBackgroundColor = builder.tableOddRowBackgroundColor;
|
||||
this.taskListDrawable = builder.taskListDrawable;
|
||||
}
|
||||
|
||||
|
||||
@ -363,6 +372,11 @@ public class SpannableTheme {
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Drawable getTaskListDrawable() {
|
||||
return taskListDrawable;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private int linkColor;
|
||||
@ -386,6 +400,7 @@ public class SpannableTheme {
|
||||
private int tableBorderColor;
|
||||
private int tableBorderWidth;
|
||||
private int tableOddRowBackgroundColor;
|
||||
private Drawable taskListDrawable;
|
||||
|
||||
Builder() {
|
||||
}
|
||||
@ -412,6 +427,7 @@ public class SpannableTheme {
|
||||
this.tableBorderColor = theme.tableBorderColor;
|
||||
this.tableBorderWidth = theme.tableBorderWidth;
|
||||
this.tableOddRowBackgroundColor = theme.tableOddRowBackgroundColor;
|
||||
this.taskListDrawable = theme.taskListDrawable;
|
||||
}
|
||||
|
||||
public Builder linkColor(@ColorInt int linkColor) {
|
||||
@ -519,6 +535,11 @@ public class SpannableTheme {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder taskListDrawable(@NonNull Drawable taskListDrawable) {
|
||||
this.taskListDrawable = taskListDrawable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpannableTheme build() {
|
||||
return new SpannableTheme(this);
|
||||
}
|
||||
|
@ -0,0 +1,177 @@
|
||||
package ru.noties.markwon.spans;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.IntRange;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class TaskListDrawable extends Drawable {
|
||||
|
||||
// represent ratios (not exact coordinates)
|
||||
private static final Point POINT_0 = new Point(2.75F / 18, 8.25F / 18);
|
||||
private static final Point POINT_1 = new Point(7.F / 18, 12.5F / 18);
|
||||
private static final Point POINT_2 = new Point(15.25F / 18, 4.75F / 18);
|
||||
|
||||
private final int checkedFillColor;
|
||||
private final int normalOutlineColor;
|
||||
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final RectF rectF = new RectF();
|
||||
|
||||
private final Paint checkMarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path checkMarkPath = new Path();
|
||||
|
||||
private boolean isChecked;
|
||||
|
||||
// unfortunately we cannot rely on TextView to be LAYER_TYPE_SOFTWARE
|
||||
// if we could we would draw our checkMarkPath with PorterDuff.CLEAR
|
||||
public TaskListDrawable(@ColorInt int checkedFillColor, @ColorInt int normalOutlineColor, @ColorInt int checkMarkColor) {
|
||||
this.checkedFillColor = checkedFillColor;
|
||||
this.normalOutlineColor = normalOutlineColor;
|
||||
|
||||
checkMarkPaint.setColor(checkMarkColor);
|
||||
checkMarkPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
|
||||
// we should exclude stroke with from final bounds (half of the strokeWidth from both sides)
|
||||
|
||||
// we should have square shape
|
||||
final float min = Math.min(bounds.width(), bounds.height());
|
||||
final float stroke = min / 8;
|
||||
|
||||
final float side = min - stroke;
|
||||
rectF.set(0, 0, side, side);
|
||||
|
||||
paint.setStrokeWidth(stroke);
|
||||
checkMarkPaint.setStrokeWidth(stroke);
|
||||
|
||||
checkMarkPath.reset();
|
||||
|
||||
POINT_0.moveTo(checkMarkPath, side);
|
||||
POINT_1.lineTo(checkMarkPath, side);
|
||||
POINT_2.lineTo(checkMarkPath, side);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
|
||||
final Paint.Style style;
|
||||
final int color;
|
||||
|
||||
if (isChecked) {
|
||||
style = Paint.Style.FILL_AND_STROKE;
|
||||
color = checkedFillColor;
|
||||
} else {
|
||||
style = Paint.Style.STROKE;
|
||||
color = normalOutlineColor;
|
||||
}
|
||||
paint.setStyle(style);
|
||||
paint.setColor(color);
|
||||
|
||||
final Rect bounds = getBounds();
|
||||
|
||||
final float left = (bounds.width() - rectF.width()) / 2;
|
||||
final float top = (bounds.height() - rectF.height()) / 2;
|
||||
|
||||
final float radius = rectF.width() / 8;
|
||||
|
||||
final int save = canvas.save();
|
||||
try {
|
||||
|
||||
canvas.translate(left, top);
|
||||
|
||||
canvas.drawRoundRect(rectF, radius, radius, paint);
|
||||
|
||||
if (isChecked) {
|
||||
canvas.drawPath(checkMarkPath, checkMarkPaint);
|
||||
}
|
||||
} finally {
|
||||
canvas.restoreToCount(save);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
|
||||
paint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||
paint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
|
||||
final boolean checked;
|
||||
|
||||
final int length = state != null
|
||||
? state.length
|
||||
: 0;
|
||||
|
||||
if (length > 0) {
|
||||
|
||||
boolean inner = false;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (android.R.attr.state_checked == state[i]) {
|
||||
inner = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
checked = inner;
|
||||
} else {
|
||||
checked = false;
|
||||
}
|
||||
|
||||
final boolean result = checked != isChecked;
|
||||
if (result) {
|
||||
invalidateSelf();
|
||||
isChecked = checked;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class Point {
|
||||
|
||||
final float x;
|
||||
final float y;
|
||||
|
||||
Point(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
void moveTo(@NonNull Path path, float side) {
|
||||
path.moveTo(side * x, side * y);
|
||||
}
|
||||
|
||||
void lineTo(@NonNull Path path, float side) {
|
||||
path.lineTo(side * x, side * y);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package ru.noties.markwon.spans;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Layout;
|
||||
import android.text.style.LeadingMarginSpan;
|
||||
|
||||
public class TaskListSpan implements LeadingMarginSpan {
|
||||
|
||||
private final SpannableTheme theme;
|
||||
private final int blockIndent;
|
||||
private final int start;
|
||||
private final boolean isDone;
|
||||
|
||||
public TaskListSpan(@NonNull SpannableTheme theme, int blockIndent, int start, boolean isDone) {
|
||||
this.theme = theme;
|
||||
this.blockIndent = blockIndent;
|
||||
this.start = start;
|
||||
this.isDone = isDone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first) {
|
||||
return theme.getBlockMargin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
|
||||
|
||||
if (this.start != start) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int save = c.save();
|
||||
try {
|
||||
|
||||
final int width = theme.getBlockMargin();
|
||||
final int height = bottom - top;
|
||||
|
||||
final Drawable drawable = theme.getTaskListDrawable();
|
||||
|
||||
final int w = (int) (width * .75F + .5F);
|
||||
final int h = (int) (height * .75F + .5F);
|
||||
|
||||
drawable.setBounds(0, 0, w, h);
|
||||
|
||||
if (drawable.isStateful()) {
|
||||
final int[] state;
|
||||
if (isDone) {
|
||||
state = new int[]{android.R.attr.state_checked};
|
||||
} else {
|
||||
state = new int[0];
|
||||
}
|
||||
drawable.setState(state);
|
||||
}
|
||||
|
||||
final int l = (width * (blockIndent - 1)) + ((width - w) / 2);
|
||||
final int t = top + ((height - h) / 2);
|
||||
|
||||
c.translate(l, t);
|
||||
drawable.draw(c);
|
||||
|
||||
} finally {
|
||||
c.restoreToCount(save);
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
include ':app', ':library', ':library-image-loader', ':library-view'
|
||||
include ':app', ':library', ':library-image-loader', ':library-view', ':library-task-list'
|
||||
|
Loading…
x
Reference in New Issue
Block a user