
- variables names are modified to better match the [google codestyle](https://source.android.com/source/code-style#follow-field-naming-conventions)
256 lines
6.9 KiB
Java
256 lines
6.9 KiB
Java
package ru.noties.markwon.spans;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Rect;
|
|
import android.support.annotation.IntDef;
|
|
import android.support.annotation.IntRange;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.text.Layout;
|
|
import android.text.StaticLayout;
|
|
import android.text.TextPaint;
|
|
import android.text.style.ReplacementSpan;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class TableRowSpan extends ReplacementSpan {
|
|
|
|
public static final int ALIGN_LEFT = 0;
|
|
public static final int ALIGN_CENTER = 1;
|
|
public static final int ALIGN_RIGHT = 2;
|
|
|
|
@IntDef(value = {ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface Alignment {
|
|
}
|
|
|
|
public interface Invalidator {
|
|
void invalidate();
|
|
}
|
|
|
|
public static class Cell {
|
|
|
|
final int mAlignment;
|
|
final CharSequence mText;
|
|
|
|
public Cell(@Alignment int alignment, CharSequence text) {
|
|
mAlignment = alignment;
|
|
mText = text;
|
|
}
|
|
|
|
@Alignment
|
|
public int alignment() {
|
|
return mAlignment;
|
|
}
|
|
|
|
public CharSequence text() {
|
|
return mText;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Cell{" +
|
|
"mAlignment=" + mAlignment +
|
|
", mText=" + mText +
|
|
'}';
|
|
}
|
|
}
|
|
|
|
private final SpannableTheme mTheme;
|
|
private final List<Cell> mCells;
|
|
private final List<StaticLayout> mLayouts;
|
|
private final TextPaint mTextPaint;
|
|
private final boolean mHeader;
|
|
private final boolean mOdd;
|
|
|
|
private final Rect mRect = ObjectsPool.rect();
|
|
private final Paint mPaint = ObjectsPool.paint();
|
|
|
|
private int mWidth;
|
|
private int mHeight;
|
|
private Invalidator mInvalidator;
|
|
|
|
public TableRowSpan(
|
|
@NonNull SpannableTheme theme,
|
|
@NonNull List<Cell> cells,
|
|
boolean header,
|
|
boolean odd) {
|
|
mTheme = theme;
|
|
mCells = cells;
|
|
mLayouts = new ArrayList<>(cells.size());
|
|
mTextPaint = new TextPaint();
|
|
mHeader = header;
|
|
mOdd = odd;
|
|
}
|
|
|
|
@Override
|
|
public int getSize(
|
|
@NonNull Paint paint,
|
|
CharSequence text,
|
|
@IntRange(from = 0) int start,
|
|
@IntRange(from = 0) int end,
|
|
@Nullable Paint.FontMetricsInt fm) {
|
|
|
|
// it's our absolute requirement to have width of the canvas here... because, well, it changes
|
|
// the way we draw mText. So, if we do not know the width of canvas we cannot correctly measure our mText
|
|
|
|
if (mLayouts.size() > 0) {
|
|
|
|
if (fm != null) {
|
|
|
|
int max = 0;
|
|
for (StaticLayout layout : mLayouts) {
|
|
final int height = layout.getHeight();
|
|
if (height > max) {
|
|
max = height;
|
|
}
|
|
}
|
|
|
|
// we store actual height
|
|
mHeight = max;
|
|
|
|
// but apply height with padding
|
|
final int padding = mTheme.tableCellPadding() * 2;
|
|
|
|
fm.ascent = -(max + padding);
|
|
fm.descent = 0;
|
|
|
|
fm.top = fm.ascent;
|
|
fm.bottom = 0;
|
|
}
|
|
}
|
|
|
|
return mWidth;
|
|
}
|
|
|
|
@Override
|
|
public void draw(
|
|
@NonNull Canvas canvas,
|
|
CharSequence text,
|
|
@IntRange(from = 0) int start,
|
|
@IntRange(from = 0) int end,
|
|
float x,
|
|
int top,
|
|
int y,
|
|
int bottom,
|
|
@NonNull Paint paint) {
|
|
|
|
if (recreateLayouts(canvas.getWidth())) {
|
|
mWidth = canvas.getWidth();
|
|
mTextPaint.set(paint);
|
|
makeNewLayouts();
|
|
}
|
|
|
|
int maxHeight = 0;
|
|
|
|
final int padding = mTheme.tableCellPadding();
|
|
|
|
final int size = mLayouts.size();
|
|
|
|
final int w = mWidth / size;
|
|
|
|
// feels like magic...
|
|
final int heightDiff = (bottom - top - mHeight) / 4;
|
|
|
|
if (mOdd) {
|
|
final int save = canvas.save();
|
|
try {
|
|
mRect.set(0, 0, mWidth, bottom - top);
|
|
mTheme.applyTableOddRowStyle(mPaint);
|
|
canvas.translate(x, top - heightDiff);
|
|
canvas.drawRect(mRect, mPaint);
|
|
} finally {
|
|
canvas.restoreToCount(save);
|
|
}
|
|
}
|
|
|
|
mRect.set(0, 0, w, bottom - top);
|
|
|
|
mTheme.applyTableBorderStyle(mPaint);
|
|
|
|
StaticLayout layout;
|
|
for (int i = 0; i < size; i++) {
|
|
layout = mLayouts.get(i);
|
|
final int save = canvas.save();
|
|
try {
|
|
|
|
canvas.translate(x + (i * w), top - heightDiff);
|
|
canvas.drawRect(mRect, mPaint);
|
|
|
|
canvas.translate(padding, padding + heightDiff);
|
|
layout.draw(canvas);
|
|
|
|
if (layout.getHeight() > maxHeight) {
|
|
maxHeight = layout.getHeight();
|
|
}
|
|
|
|
} finally {
|
|
canvas.restoreToCount(save);
|
|
}
|
|
}
|
|
|
|
if (mHeight != maxHeight) {
|
|
if (mInvalidator != null) {
|
|
mInvalidator.invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean recreateLayouts(int newWidth) {
|
|
return mWidth != newWidth;
|
|
}
|
|
|
|
private void makeNewLayouts() {
|
|
|
|
mTextPaint.setFakeBoldText(mHeader);
|
|
|
|
final int columns = mCells.size();
|
|
final int padding = mTheme.tableCellPadding() * 2;
|
|
final int w = (mWidth / columns) - padding;
|
|
|
|
mLayouts.clear();
|
|
Cell cell;
|
|
StaticLayout layout;
|
|
for (int i = 0, size = mCells.size(); i < size; i++) {
|
|
cell = mCells.get(i);
|
|
layout = new StaticLayout(
|
|
cell.mText,
|
|
mTextPaint,
|
|
w,
|
|
alignment(cell.mAlignment),
|
|
1.F,
|
|
.0F,
|
|
false
|
|
);
|
|
mLayouts.add(layout);
|
|
}
|
|
}
|
|
|
|
@SuppressLint("SwitchIntDef")
|
|
private static Layout.Alignment alignment(@Alignment int alignment) {
|
|
final Layout.Alignment out;
|
|
switch (alignment) {
|
|
case ALIGN_CENTER:
|
|
out = Layout.Alignment.ALIGN_CENTER;
|
|
break;
|
|
case ALIGN_RIGHT:
|
|
out = Layout.Alignment.ALIGN_OPPOSITE;
|
|
break;
|
|
default:
|
|
out = Layout.Alignment.ALIGN_NORMAL;
|
|
break;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
public TableRowSpan invalidator(Invalidator invalidator) {
|
|
mInvalidator = invalidator;
|
|
return this;
|
|
}
|
|
}
|