zTrap 92ccf583c5 **Markwon view
- variables names are modified to better match the [google codestyle](https://source.android.com/source/code-style#follow-field-naming-conventions)
2017-10-26 10:01:38 +03:00

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;
}
}