ellipse handle for type.

This commit is contained in:
cgspine
2020-05-25 22:57:16 +08:00
parent aeab58d474
commit 31bdc66ee0
11 changed files with 698 additions and 165 deletions
@@ -18,6 +18,8 @@ package com.qmuiteam.qmuidemo.fragment.components.qqface;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
@@ -35,11 +37,11 @@ import com.qmuiteam.qmuidemo.QDQQFaceManager;
public class QMUIQQFaceView2 extends View {
private TypeEnvironment mTypeEnvironment = new TypeEnvironment();
private LineLayout mLineLayout = new LineLayout.Builder(mTypeEnvironment)
private LineLayout mLineLayout = new LineLayout(mTypeEnvironment)
.setCalculateWholeLines(true)
.setEllipsize(TextUtils.TruncateAt.END)
.setMaxLines(10)
.build();
.setEllipsize(TextUtils.TruncateAt.MIDDLE)
.setMoreText("更多", Color.RED, Typeface.DEFAULT_BOLD)
.setMaxLines(10);
private TextParser mTextParser = new EmojiTextParser(QDQQFaceManager.getInstance());
public QMUIQQFaceView2(Context context) {
@@ -21,10 +21,12 @@ import android.graphics.Canvas;
import androidx.core.util.Pools;
import com.qmuiteam.qmui.type.element.BreakWordLineElement;
import com.qmuiteam.qmui.type.element.CharElement;
import com.qmuiteam.qmui.type.element.CharOrPhraseElement;
import com.qmuiteam.qmui.type.element.Element;
import com.qmuiteam.qmui.type.element.NextParagraphElement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -49,7 +51,8 @@ public class Line {
private int mContentWidth;
private int mContentHeight;
private int mLayoutWidth;
private List<Element> mElements = new ArrayList<>();
private List<Element> mElements = new LinkedList<>();
private HashMap<Element, Integer> mVisibleChanged;
public void init(int x, int y, int widthLimit) {
mX = x;
@@ -58,12 +61,21 @@ public class Line {
}
public void add(Element element) {
element.setVisible(Element.VISIBLE);
mElements.add(element);
mContentWidth += element.getMeasureWidth();
mContentHeight = (int) Math.max(mContentHeight, element.getMeasureHeight());
}
public void addFirst(Element element){
mElements.add(0, element);
mContentWidth += element.getMeasureWidth();
mContentHeight = (int) Math.max(mContentHeight, element.getMeasureHeight());
}
public Element first(){
return mElements.isEmpty() ? null : mElements.get(0);
}
public int getSize() {
return mElements.size();
}
@@ -76,6 +88,10 @@ public class Line {
return mLayoutWidth;
}
public int getWidthLimit() {
return mWidthLimit;
}
public int getContentHeight() {
return mContentHeight;
}
@@ -88,6 +104,20 @@ public class Line {
return mY;
}
public void setX(int x) {
mX = x;
}
public void setY(int y) {
mY = y;
}
void move(TypeEnvironment environment){
for(Element el: mElements){
el.move(environment);
}
}
public List<Element> handleWordBreak(TypeEnvironment environment) {
if (mElements.size() == 0) {
return null;
@@ -142,28 +172,46 @@ public class Line {
return back;
}
private void hideLastIfSpaceIfNeeded(boolean dropLastIfSpace){
private boolean hideLastIfSpaceIfNeeded(boolean dropLastIfSpace) {
Element last = mElements.get(mElements.size() - 1);
if (dropLastIfSpace && last instanceof CharElement && last.getChar() == ' ') {
last.setVisible(Element.GONE);
if (dropLastIfSpace && last instanceof CharOrPhraseElement && last.getChar() == ' ' && last.getVisible() != Element.GONE) {
changeVisibleInner(last, Element.GONE);
mContentWidth -= last.getMeasureWidth();
return true;
}
return false;
}
private int calculateGapCount(){
private void changeVisibleInner(Element element, int visible) {
int oldVal = element.getVisible();
if (visible == oldVal) {
return;
}
if (mVisibleChanged == null) {
mVisibleChanged = new HashMap<>();
}
mVisibleChanged.put(element, oldVal);
element.setVisible(visible);
}
private int calculateGapCount() {
int ret = 0;
for(int i = 1; i < mElements.size(); i++){
for (int i = 1; i < mElements.size(); i++) {
Element el = mElements.get(i);
if(el.getVisible() != Element.GONE &&
if (el.getVisible() != Element.GONE &&
(el.getWordPart() == Element.WORD_PART_WHOLE ||
el.getWordPart() == Element.WORD_PART_START)){
ret ++;
el.getWordPart() == Element.WORD_PART_START)) {
ret++;
}
}
return ret;
}
public void layout(TypeEnvironment env, boolean dropLastIfSpace, boolean isParagraphLast) {
public boolean isMiddleParagraphEndLine(){
return !mElements.isEmpty() && mElements.get(mElements.size() - 1) instanceof NextParagraphElement;
}
public void layout(TypeEnvironment env, boolean dropLastIfSpace, boolean isEnd) {
if (mElements.isEmpty()) {
return;
}
@@ -178,9 +226,9 @@ public class Line {
start = mX + (mWidthLimit - mContentWidth) / 2f;
} else if (alignment == TypeEnvironment.Alignment.JUSTIFY) {
float remain = mWidthLimit - mContentWidth;
if (!isParagraphLast || remain < env.getLastLineJustifyMaxWidth()) {
if (!(isEnd || isMiddleParagraphEndLine()) || remain < env.getLastLineJustifyMaxWidth()) {
int gapCount = calculateGapCount();
if(gapCount > 0){
if (gapCount > 0) {
addSpace = remain / gapCount;
mLayoutWidth = mWidthLimit;
}
@@ -205,6 +253,36 @@ public class Line {
}
}
void restoreVisibleChange(){
if (mVisibleChanged != null) {
for (Element entry : mVisibleChanged.keySet()) {
Integer visible = mVisibleChanged.get(entry);
if (visible != null) {
entry.setVisible(visible);
}
}
mVisibleChanged.clear();
}
}
public List<Element> popAll(){
List<Element> elements = new ArrayList<>(mElements);
mElements.clear();
restoreVisibleChange();
mContentWidth = 0;
mContentHeight = 0;
mLayoutWidth = 0;
return elements;
}
public void clear(){
mElements.clear();
restoreVisibleChange();
mContentWidth = 0;
mContentHeight = 0;
mLayoutWidth = 0;
}
public void release() {
mX = 0;
mY = 0;
@@ -213,6 +291,7 @@ public class Line {
mContentHeight = 0;
mLayoutWidth = 0;
mElements.clear();
restoreVisibleChange();
sLinePool.release(this);
}
}
@@ -17,35 +17,83 @@
package com.qmuiteam.qmui.type;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.qmuiteam.qmui.type.element.BreakWordLineElement;
import com.qmuiteam.qmui.type.element.CharOrPhraseElement;
import com.qmuiteam.qmui.type.element.EffectElement;
import com.qmuiteam.qmui.type.element.Element;
import com.qmuiteam.qmui.type.element.NextParagraphElement;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class LineLayout {
private int mMaxLines;
private int mMaxLines = Integer.MAX_VALUE;
private TextUtils.TruncateAt mEllipsize;
private boolean mCalculateWholeLines;
private boolean mCalculateWholeLines = true;
private TypeEnvironment mTypeEnvironment;
private TypeModel mTypeModel;
private List<Line> mLines = new ArrayList<>();
private boolean mDropLastIfSpace;
private boolean mDropLastIfSpace = true;
private String mMoreText = null;
private int mMoreTextColor = 0;
private Typeface mMoreTextTypeface = null;
private int mMoreUnderlineColor = Color.TRANSPARENT;
private int mMoreBgColor = 0;
private int mMoreUnderlineHeight = 0;
private int mTotalLineCount = 0;
private LineLayout(Builder builder) {
mTypeEnvironment = builder.mTypeEnvironment;
mMaxLines = builder.mMaxLines;
mEllipsize = builder.mEllipsize;
mCalculateWholeLines = builder.mCalculateWholeLines;
mDropLastIfSpace = builder.mDropLastIfSpace;
public LineLayout(TypeEnvironment environment) {
mTypeEnvironment = environment;
}
public void setTypeModel(TypeModel typeModel) {
public LineLayout setMaxLines(int maxLines) {
mMaxLines = maxLines;
return this;
}
public LineLayout setEllipsize(TextUtils.TruncateAt ellipsize) {
mEllipsize = ellipsize;
return this;
}
public LineLayout setCalculateWholeLines(boolean calculateWholeLines) {
mCalculateWholeLines = calculateWholeLines;
return this;
}
public LineLayout setDropLastIfSpace(boolean dropLastIfSpace) {
mDropLastIfSpace = dropLastIfSpace;
return this;
}
public LineLayout setMoreText(String text, int color, Typeface typeface) {
mMoreText = text;
mMoreTextColor = color;
mMoreTextTypeface = typeface;
return this;
}
public LineLayout setMoreBackgroundColor(int color) {
mMoreBgColor = color;
return this;
}
public LineLayout setUnderline(int height, int color) {
mMoreUnderlineHeight = height;
mMoreUnderlineColor = color;
return this;
}
public LineLayout setTypeModel(TypeModel typeModel) {
mTypeModel = typeModel;
return this;
}
public void measureAndLayout() {
@@ -64,14 +112,14 @@ public class LineLayout {
while (element != null) {
element.measure(mTypeEnvironment);
if (element instanceof NextParagraphElement) {
line.layout(mTypeEnvironment, mDropLastIfSpace, true);
line.add(element);
line.layout(mTypeEnvironment, mDropLastIfSpace, false);
mLines.add(line);
if (canInterrupt()) {
return;
}
y += line.getContentHeight() + mTypeEnvironment.getParagraphSpace();
line = Line.acquire();
line.init(0, y, mTypeEnvironment.getWidthLimit());
line = createNewLine(y);
} else if (line.getContentWidth() + element.getMeasureWidth() > mTypeEnvironment.getWidthLimit()) {
if (mLines.size() == 0 && line.getSize() == 0) {
// the width is too small.
@@ -82,11 +130,11 @@ public class LineLayout {
line.layout(mTypeEnvironment, mDropLastIfSpace, false);
mLines.add(line);
if (canInterrupt()) {
handleEllipse(true);
return;
}
y += line.getContentHeight() + mTypeEnvironment.getLineSpace();
line = Line.acquire();
line.init(0, y, mTypeEnvironment.getWidthLimit());
line = createNewLine(y);
if (back != null && !back.isEmpty()) {
for (Element el : back) {
line.add(el);
@@ -104,18 +152,294 @@ public class LineLayout {
} else {
line.release();
}
mTotalLineCount = mLines.size();
handleEllipse(false);
}
public int getMaxLayoutWidth(){
private Line createNewLine(int y) {
Line line = Line.acquire();
line.init(0, y, mTypeEnvironment.getWidthLimit());
return line;
}
private void handleEllipse(boolean fromInterrupt) {
if (mLines.isEmpty() || mLines.size() < mMaxLines || (mLines.size() == mMaxLines && !fromInterrupt)) {
return;
}
if (mEllipsize == TextUtils.TruncateAt.END) {
handleEllipseEnd();
} else if (mEllipsize == TextUtils.TruncateAt.START) {
handleEllipseStart();
} else if (mEllipsize == TextUtils.TruncateAt.MIDDLE) {
handleEllipseMiddle();
}
}
private void handleEllipseEnd() {
for (int i = mLines.size() - 1; i >= mMaxLines; i--) {
mLines.remove(mLines.get(i));
}
Line lastLine = mLines.get(mLines.size() - 1);
int limitWidth = lastLine.getWidthLimit();
Element ellipseElement = new CharOrPhraseElement("...", -1, -1);
ellipseElement.unsafeSingleEnvironmentUpdater(null, new EnvironmentUpdater() {
@Override
public void update(TypeEnvironment env) {
env.clear();
}
});
ellipseElement.measure(mTypeEnvironment);
limitWidth -= ellipseElement.getMeasureWidth();
Element moreElement = null;
if (mMoreText != null && !mMoreText.isEmpty()) {
moreElement = new CharOrPhraseElement(mMoreText, -1, -1);
List<Integer> changeTypes = new ArrayList<>();
changeTypes.add(TypeEnvironment.TYPE_TEXT_COLOR);
changeTypes.add(TypeEnvironment.TYPE_BG_COLOR);
changeTypes.add(TypeEnvironment.TYPE_TYPEFACE);
changeTypes.add(TypeEnvironment.TYPE_BORDER_BOTTOM_COLOR);
changeTypes.add(TypeEnvironment.TYPE_BORDER_BOTTOM_WIDTH);
moreElement.unsafeSingleEnvironmentUpdater(changeTypes, new EnvironmentUpdater() {
@Override
public void update(TypeEnvironment env) {
if (mMoreTextColor != 0) {
env.setTextColor(mMoreTextColor);
}
if (mMoreBgColor != 0) {
env.setBackgroundColor(mMoreBgColor);
}
if (mMoreTextTypeface != null) {
env.setTypeface(mMoreTextTypeface);
}
if (mMoreUnderlineHeight > 0) {
env.setBorderBottom(mMoreUnderlineHeight, mMoreUnderlineColor);
}
}
});
moreElement.measure(mTypeEnvironment);
limitWidth -= moreElement.getMeasureWidth();
}
int contentWidth = lastLine.getContentWidth();
if (contentWidth < limitWidth) {
lastLine.restoreVisibleChange();
} else {
List<Element> elements = lastLine.popAll();
for (Element el : elements) {
if (el.getMeasureWidth() <= limitWidth) {
lastLine.add(el);
limitWidth -= el.getMeasureWidth();
} else {
break;
}
}
}
lastLine.add(ellipseElement);
if (moreElement != null) {
lastLine.add(moreElement);
}
lastLine.layout(mTypeEnvironment, mDropLastIfSpace, true);
}
private void handleEllipseStart() {
mTypeEnvironment.clear();
for (int i = mLines.size() - 1; i >= mMaxLines; i--) {
mLines.remove(mLines.get(i));
}
Element ellipseElement = new CharOrPhraseElement("...", -1, -1);
ellipseElement.unsafeSingleEnvironmentUpdater(null, new EnvironmentUpdater() {
@Override
public void update(TypeEnvironment env) {
env.clear();
}
});
ellipseElement.measure(mTypeEnvironment);
Queue<Element> elements = new LinkedList<>();
elements.add(ellipseElement);
for (int i = 0; i < mLines.size(); i++) {
Line line = mLines.get(i);
int limitWidth = line.getWidthLimit();
elements.addAll(line.popAll());
while (!elements.isEmpty()) {
Element el = elements.peek();
if (el != null) {
if (el instanceof NextParagraphElement) {
elements.poll();
line.add(el);
el.move(mTypeEnvironment);
break;
}
if (el instanceof BreakWordLineElement) {
elements.poll();
continue;
}
if (line.getContentWidth() + el.getMeasureWidth() <= limitWidth) {
elements.poll();
line.add(el);
el.move(mTypeEnvironment);
} else {
break;
}
} else {
elements.poll();
}
}
line.handleWordBreak(mTypeEnvironment);
line.layout(mTypeEnvironment, mDropLastIfSpace, false);
if (elements.isEmpty()) {
return;
}
}
}
private void handleEllipseMiddle() {
mTypeEnvironment.clear();
List<Line> lines = new ArrayList<>(mLines);
mLines.clear();
Element ellipseElement = new CharOrPhraseElement("...", -1, -1);
ellipseElement.measure(mTypeEnvironment);
int ellipseLine = mMaxLines % 2 == 0 ? mMaxLines / 2 : (mMaxLines + 1) / 2;
for (int i = 0; i < ellipseLine; i++) {
mLines.add(lines.get(i));
}
Line handleLine = lines.get(ellipseLine - 1);
int limitWidth = handleLine.getWidthLimit();
Deque<Element> unHandled = new LinkedList<>(handleLine.popAll());
while (!unHandled.isEmpty()) {
Element el = unHandled.peek();
if (el != null) {
if (handleLine.getContentWidth() + el.getMeasureWidth() <= limitWidth / 2f - ellipseElement.getMeasureWidth() / 2) {
unHandled.poll();
handleLine.add(el);
el.move(mTypeEnvironment);
} else {
break;
}
} else {
unHandled.poll();
}
}
ellipseElement.measure(mTypeEnvironment);
handleLine.add(ellipseElement);
int nextFullShowLine = lines.size() - mMaxLines + ellipseLine;
int startLine = lines.size() - 1;
// find the latest paragraph end line.
for (int i = lines.size() - 2; i > nextFullShowLine; i--) {
if (lines.get(i).isMiddleParagraphEndLine()) {
startLine = i;
}
}
for (int i = ellipseLine; i <= startLine; i++) {
unHandled.addAll(lines.get(i).popAll());
}
for (int i = startLine; i >= nextFullShowLine; i--) {
Line line = lines.get(i);
while (!unHandled.isEmpty()) {
Element element = unHandled.peekLast();
if (element != null) {
if (element instanceof NextParagraphElement) {
unHandled.pollLast();
continue;
}
if (element instanceof BreakWordLineElement) {
unHandled.pollLast();
continue;
}
if (line.getContentWidth() + element.getMeasureWidth() <= line.getWidthLimit()) {
unHandled.pollLast();
line.addFirst(element);
} else {
break;
}
} else {
unHandled.pollLast();
}
}
}
List<Element> toAdd = new LinkedList<>();
int toAddWidth = 0;
while (!unHandled.isEmpty()) {
Element element = unHandled.peekLast();
if (element != null) {
if (element instanceof NextParagraphElement) {
unHandled.pollLast();
continue;
}
if (element instanceof BreakWordLineElement) {
unHandled.pollLast();
continue;
}
if (handleLine.getContentWidth() + toAddWidth + element.getMeasureWidth() <= handleLine.getWidthLimit()) {
unHandled.pollLast();
toAdd.add(0, element);
toAddWidth += element.getMeasureWidth();
} else {
break;
}
} else {
unHandled.pollLast();
}
}
Element firstUnHandle = unHandled.peekFirst();
Element lastUnHandle = unHandled.peekLast();
Element effect = mTypeModel.getFirstEffect();
if (firstUnHandle != null && lastUnHandle != null) {
List<Element> ellipseEffect = new ArrayList<>();
while (effect != null && effect.getIndex() <= lastUnHandle.getIndex()) {
if (effect.getIndex() >= firstUnHandle.getIndex()) {
ellipseEffect.add(effect);
}
effect = effect.getNext();
}
if (ellipseEffect.size() > 0) {
EffectElement effectElement = new EffectElement(ellipseEffect);
effectElement.move(mTypeEnvironment);
handleLine.add(effectElement);
}
}
for (Element el : toAdd) {
el.move(mTypeEnvironment);
handleLine.add(el);
}
handleLine.handleWordBreak(mTypeEnvironment);
handleLine.layout(mTypeEnvironment, mDropLastIfSpace, ellipseLine == lines.size());
int lastEnd = handleLine.getY() + handleLine.getContentHeight();
for (int i = nextFullShowLine; i < lines.size(); i++) {
Line line = lines.get(i);
Line prev = lines.get(i - 1);
if (prev.isMiddleParagraphEndLine()) {
line.setY(lastEnd + mTypeEnvironment.getParagraphSpace());
} else {
line.setY(lastEnd + mTypeEnvironment.getLineSpace());
}
lastEnd = line.getY() + line.getContentHeight();
line.move(mTypeEnvironment);
line.handleWordBreak(mTypeEnvironment);
line.layout(mTypeEnvironment, mDropLastIfSpace, i == lines.size() - 1);
mLines.add(line);
}
}
public int getMaxLayoutWidth() {
int maxWidth = 0;
for(Line line: mLines){
for (Line line : mLines) {
maxWidth = Math.max(maxWidth, line.getLayoutWidth());
}
return maxWidth;
}
public int getContentHeight(){
if(mLines.isEmpty()){
public int getContentHeight() {
if (mLines.isEmpty()) {
return 0;
}
Line last = mLines.get(mLines.size() - 1);
@@ -124,7 +448,7 @@ public class LineLayout {
public void draw(Canvas canvas) {
mTypeEnvironment.clear();
for(Line line: mLines){
for (Line line : mLines) {
line.draw(mTypeEnvironment, canvas);
}
}
@@ -140,40 +464,4 @@ public class LineLayout {
}
mLines.clear();
}
public static class Builder {
private TypeEnvironment mTypeEnvironment;
private int mMaxLines = Integer.MAX_VALUE;
private TextUtils.TruncateAt mEllipsize;
private boolean mCalculateWholeLines = true;
private boolean mDropLastIfSpace = true;
public Builder(@NonNull TypeEnvironment typeEnvironment) {
mTypeEnvironment = typeEnvironment;
}
public Builder setMaxLines(int maxLines) {
mMaxLines = maxLines;
return this;
}
public Builder setCalculateWholeLines(boolean calculateWholeLines) {
mCalculateWholeLines = calculateWholeLines;
return this;
}
public Builder setEllipsize(TextUtils.TruncateAt ellipsize) {
mEllipsize = ellipsize;
return this;
}
public Builder setDropLastIfSpace(boolean dropLastIfSpace) {
mDropLastIfSpace = dropLastIfSpace;
return this;
}
public LineLayout build() {
return new LineLayout(this);
}
}
}
@@ -17,11 +17,14 @@
package com.qmuiteam.qmui.type;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.util.Log;
import android.util.SparseArray;
import com.qmuiteam.qmui.type.element.Element;
import java.util.Stack;
public class TypeEnvironment {
@@ -33,6 +36,15 @@ public class TypeEnvironment {
public static final int TYPE_ALIGNMENT = -5;
public static final int TYPE_LINE_SPACE = -6;
public static final int TYPE_PARAGRAPH_SPACE = -7;
public static final int TYPE_BORDER_TOP_WIDTH = -8;
public static final int TYPE_BORDER_TOP_COLOR = -9;
public static final int TYPE_BORDER_RIGHT_WIDTH = -10;
public static final int TYPE_BORDER_RIGHT_COLOR = -11;
public static final int TYPE_BORDER_BOTTOM_WIDTH = -12;
public static final int TYPE_BORDER_BOTTOM_COLOR = -13;
public static final int TYPE_BORDER_LEFT_WIDTH = -14;
public static final int TYPE_BORDER_LEFT_COLOR = -15;
public static final int TYPE_BORDER_PAINT = -16;
public enum Alignment {
LEFT,
@@ -51,24 +63,25 @@ public class TypeEnvironment {
private Alignment mAlignment = Alignment.JUSTIFY;
private int mLastLineJustifyMaxWidth = (int) (Resources.getSystem().getDisplayMetrics().density * 36);
private int mTextColor;
private int mTextColor = Color.BLACK;
private int mBackgroundColor;
private Paint mPaint = new Paint();
private Paint mBgPaint = new Paint();
private SparseArray<Object> mCustomProp;
private SparseArray<Object> mCustomProp = new SparseArray<>();
private SparseArray<Stack<Object>> mStack = new SparseArray<>();
private Element mLastRunElement = null;
public TypeEnvironment() {
mPaint.setAntiAlias(false);
mBgPaint.setAntiAlias(false);
mPaint.setAntiAlias(true);
mBgPaint.setAntiAlias(true);
}
public void setMeasureLimit(int widthLimit, int heightLimit){
public void setMeasureLimit(int widthLimit, int heightLimit) {
mWidthLimit = widthLimit;
mHeightLimit = heightLimit;
}
@@ -154,19 +167,86 @@ public class TypeEnvironment {
}
public void setCustomProp(int type, Object value) {
if (mCustomProp == null) {
mCustomProp = new SparseArray<>();
}
mCustomProp.put(type, value);
}
public Object getCustomProp(int type) {
if (mCustomProp == null) {
return null;
public void setBorderTop(int width, int color) {
setCustomProp(TYPE_BORDER_TOP_WIDTH, width);
setCustomProp(TYPE_BORDER_TOP_COLOR, color);
}
public int getBorderTopWidth() {
return getIntCustomProp(TYPE_BORDER_TOP_WIDTH);
}
public int getBorderTopColor() {
return getIntCustomProp(TYPE_BORDER_TOP_COLOR);
}
public void setBorderRight(int width, int color) {
setCustomProp(TYPE_BORDER_RIGHT_WIDTH, width);
setCustomProp(TYPE_BORDER_RIGHT_COLOR, color);
}
public int getBorderRightWidth() {
return getIntCustomProp(TYPE_BORDER_RIGHT_WIDTH);
}
public int getBorderRightColor() {
return getIntCustomProp(TYPE_BORDER_RIGHT_COLOR);
}
public void setBorderBottom(int width, int color) {
setCustomProp(TYPE_BORDER_BOTTOM_WIDTH, width);
setCustomProp(TYPE_BORDER_BOTTOM_COLOR, color);
}
public int getBorderBottomWidth() {
return getIntCustomProp(TYPE_BORDER_BOTTOM_WIDTH);
}
public int getBorderBottomColor() {
return getIntCustomProp(TYPE_BORDER_BOTTOM_COLOR);
}
public void setBorderLeft(int width, int color) {
setCustomProp(TYPE_BORDER_LEFT_WIDTH, width);
setCustomProp(TYPE_BORDER_LEFT_COLOR, color);
}
public int getBorderLeftWidth() {
return getIntCustomProp(TYPE_BORDER_LEFT_WIDTH);
}
public int getBorderLeftColor() {
return getIntCustomProp(TYPE_BORDER_LEFT_COLOR);
}
public Paint getBorderPaint() {
Object obj = getCustomProp(TYPE_BORDER_PAINT);
Paint paint;
if (obj == null) {
paint = new Paint();
paint.setAntiAlias(true);
setCustomProp(TYPE_BORDER_PAINT, paint);
} else {
paint = (Paint) obj;
}
return paint;
}
public Object getCustomProp(int type) {
return mCustomProp.get(type);
}
public int getIntCustomProp(int type) {
Object obj = mCustomProp.get(type);
if (!(obj instanceof Integer)) {
return 0;
}
return (int) obj;
}
public TypeEnvironment snapshot() {
TypeEnvironment env = new TypeEnvironment();
env.setMeasureLimit(mWidthLimit, mHeightLimit);
@@ -178,6 +258,9 @@ public class TypeEnvironment {
env.setTextColor(mTextColor);
env.setBackgroundColor(mBackgroundColor);
for(int i =0; i< mStack.size(); i++){
env.mStack.put(mStack.keyAt(i), (Stack<Object>) mStack.valueAt(i).clone());
}
if (mCustomProp != null) {
for (int i = 0; i < mCustomProp.size(); i++) {
@@ -187,6 +270,13 @@ public class TypeEnvironment {
return env;
}
void setLastRunElement(Element lastRunElement) {
mLastRunElement = lastRunElement;
}
Element getLastRunElement() {
return mLastRunElement;
}
public void save(int type) {
Stack<Object> stack = mStack.get(type);
@@ -196,7 +286,7 @@ public class TypeEnvironment {
}
if (type == TYPE_TEXT_COLOR) {
stack.push(mTextColor);
} else if(type == TYPE_BG_COLOR){
} else if (type == TYPE_BG_COLOR) {
stack.push(mBackgroundColor);
} else if (type == TYPE_TYPEFACE) {
stack.push(mTypeface);
@@ -220,9 +310,13 @@ public class TypeEnvironment {
return;
}
Object v = stack.pop();
restore(type, v);
}
private void restore(int type, Object v){
if (type == TYPE_TEXT_COLOR) {
setTextColor((Integer) v);
} else if(type == TYPE_BG_COLOR){
} else if (type == TYPE_BG_COLOR) {
setBackgroundColor((Integer) v);
} else if (type == TYPE_TYPEFACE) {
setTypeface((Typeface) v);
@@ -239,11 +333,14 @@ public class TypeEnvironment {
}
}
public void clear(){
for(int i = 0; i < mStack.size(); i++){
Stack<Object> stack = mStack.get(i);
if(stack != null){
stack.clear();
public void clear() {
for (int i = 0; i < mStack.size(); i++) {
Stack<Object> stack = mStack.valueAt(i);
if (stack != null && stack.size() > 0) {
while (stack.size() > 1){
stack.pop();
}
restore(mStack.keyAt(i), stack.pop());
}
}
}
@@ -17,6 +17,7 @@
package com.qmuiteam.qmui.type;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.qmuiteam.qmui.type.element.Element;
@@ -27,15 +28,23 @@ public class TypeModel {
private final Map<Integer, Element> mElementMap;
private Element mFirstElement;
private Element mLastElement;
@Nullable
private Element mFirstEffect;
public TypeModel(@NonNull Map<Integer, Element> elementMap,
Element firstElement,
Element lastElement) {
Element lastElement,
@Nullable Element firstEffect) {
mElementMap = elementMap;
mFirstElement = firstElement;
mLastElement = lastElement;
mFirstEffect = firstEffect;
}
@Nullable
public Element getFirstEffect() {
return mFirstEffect;
}
public void insertAfterElement(Element element, @NonNull Element toInsert) {
Element next = element.getNext();
@@ -16,7 +16,7 @@
package com.qmuiteam.qmui.type.element;
public class BreakWordLineElement extends CharElement {
public class BreakWordLineElement extends CharOrPhraseElement {
public BreakWordLineElement() {
super('-', -1, -1);
@@ -25,16 +25,20 @@ import androidx.annotation.Nullable;
import com.qmuiteam.qmui.type.TypeEnvironment;
public class CharElement extends Element {
public class CharOrPhraseElement extends Element {
public CharElement(char singleChar, int index, int originIndex) {
public CharOrPhraseElement(char singleChar, int index, int originIndex) {
super(singleChar, null, index, originIndex);
}
public CharElement(char singleChar, int index, int originIndex, @Nullable String description) {
public CharOrPhraseElement(char singleChar, int index, int originIndex, @Nullable String description) {
super(singleChar, null, index, originIndex, description);
}
public CharOrPhraseElement(String text, int index, int originIndex) {
super('\u0000', text, index, originIndex);
}
@Override
protected void onMeasure(TypeEnvironment env) {
Paint paint = env.getPaint();
@@ -43,12 +47,8 @@ public class CharElement extends Element {
-paint.getFontMetrics().ascent);
}
static Paint sPaint = new Paint();
@Override
protected void onDraw(TypeEnvironment env, Canvas canvas) {
sPaint.setColor(Color.RED);
// canvas.drawRect(getX(), getY(), getX() + getMeasureWidth(), getY() + getMeasureHeight(), sPaint);
canvas.drawText(toString(), getX(), getY() + getBaseLine(), env.getPaint());
}
}
@@ -0,0 +1,49 @@
/*
* Tencent is pleased to support the open source community by making QMUI_Android available.
*
* Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.qmuiteam.qmui.type.element;
import android.graphics.Canvas;
import com.qmuiteam.qmui.type.EnvironmentUpdater;
import com.qmuiteam.qmui.type.TypeEnvironment;
import java.util.List;
public class EffectElement extends Element {
public EffectElement(final List<Element> list) {
super(' ', null, -1, -1, "");
addEnvironmentUpdater(new EnvironmentUpdater() {
@Override
public void update(TypeEnvironment env) {
for (Element element : list) {
element.move(env);
}
}
});
}
@Override
protected void onMeasure(TypeEnvironment env) {
setMeasureDimen(0, 0, 0);
}
@Override
protected void onDraw(TypeEnvironment env, Canvas canvas) {
}
}
@@ -25,7 +25,6 @@ import com.qmuiteam.qmui.type.EnvironmentUpdater;
import com.qmuiteam.qmui.type.TypeEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class Element {
@@ -41,12 +40,11 @@ public abstract class Element {
private final int mIndex;
private final int mOriginIndex;
private final String mDescription;
private Element mDep;
private List<Element> mSubs;
private Element mNextEffect;
private Element mPrev;
private Element mNext;
private int wordPart = WORD_PART_WHOLE;
private boolean canBreakWord = false;
private int mWordPart = WORD_PART_WHOLE;
private boolean mCanBreakWord = false;
private int mVisible = VISIBLE;
@@ -73,15 +71,12 @@ public abstract class Element {
mDescription = description;
}
public void depOn(Element element) {
if (mDep != null && mDep.mSubs != null) {
mDep.mSubs.remove(this);
public void insetNextEffect(Element element) {
Element origin = mNextEffect;
mNextEffect = element;
if (element != null) {
element.mNextEffect = origin;
}
mDep = element;
if (element.mSubs == null) {
element.mSubs = new ArrayList<>();
}
element.mSubs.add(this);
}
public void setPrev(Element element) {
@@ -99,32 +94,32 @@ public abstract class Element {
}
public void setWordPart(int wordPart) {
this.wordPart = wordPart;
this.mWordPart = wordPart;
}
public int getWordPart() {
return wordPart;
return mWordPart;
}
public void setCanBreakWord(boolean canBreakWord) {
this.canBreakWord = canBreakWord;
this.mCanBreakWord = canBreakWord;
}
public boolean isCanBreakWord() {
return canBreakWord;
return mCanBreakWord;
}
void addSaveType(int type){
if(mSaveType == null){
void addSaveType(int type) {
if (mSaveType == null) {
mSaveType = new ArrayList<>();
}
mSaveType.add(type);
}
void removeSaveType(int type){
if(mSaveType != null){
for(int i = 0; i <mSaveType.size(); i++){
if(mSaveType.get(i) == type){
void removeSaveType(int type) {
if (mSaveType != null) {
for (int i = 0; i < mSaveType.size(); i++) {
if (mSaveType.get(i) == type) {
mSaveType.remove(i);
break;
}
@@ -132,17 +127,17 @@ public abstract class Element {
}
}
void addRestoreType(int type){
if(mRestoreType == null){
void addRestoreType(int type) {
if (mRestoreType == null) {
mRestoreType = new ArrayList<>();
}
mRestoreType.add(type);
}
void removeStoreType(int type){
if(mRestoreType != null){
for(int i = 0; i <mRestoreType.size(); i++){
if(mRestoreType.get(i) == type){
void removeStoreType(int type) {
if (mRestoreType != null) {
for (int i = 0; i < mRestoreType.size(); i++) {
if (mRestoreType.get(i) == type) {
mRestoreType.remove(i);
break;
}
@@ -150,23 +145,40 @@ public abstract class Element {
}
}
public boolean hasEnvironmentUpdater(){
public boolean hasEnvironmentUpdater() {
return mEnvironmentUpdater != null && mEnvironmentUpdater.size() > 0;
}
void addEnvironmentUpdater(@NonNull EnvironmentUpdater environmentUpdater) {
if(mEnvironmentUpdater == null){
if (mEnvironmentUpdater == null) {
mEnvironmentUpdater = new ArrayList<>();
}
mEnvironmentUpdater.add(environmentUpdater);
}
void removeEnvironmentUpdater(@NonNull EnvironmentUpdater environmentUpdater){
if(mEnvironmentUpdater != null){
void removeEnvironmentUpdater(@NonNull EnvironmentUpdater environmentUpdater) {
if (mEnvironmentUpdater != null) {
mEnvironmentUpdater.remove(environmentUpdater);
}
}
public void unsafeSingleEnvironmentUpdater(@Nullable List<Integer> changedTypes, @NonNull EnvironmentUpdater environmentUpdater) {
if (changedTypes != null) {
for (Integer type : changedTypes) {
addRestoreType(type);
addSaveType(type);
}
}
addEnvironmentUpdater(environmentUpdater);
}
public void move(TypeEnvironment environment) {
if (hasEnvironmentUpdater()) {
updateEnv(environment);
restoreEnv(environment);
}
}
public int getIndex() {
return mIndex;
}
@@ -183,7 +195,7 @@ public abstract class Element {
return mChar;
}
public boolean isSingleChar(){
public boolean isSingleChar() {
return mText == null;
}
@@ -198,15 +210,7 @@ public abstract class Element {
}
public String getDescription() {
return mDescription != null ?mDescription : toString();
}
public Element getDep() {
return mDep;
}
public List<Element> getSubs() {
return Collections.unmodifiableList(mSubs);
return mDescription != null ? mDescription : toString();
}
protected void setMeasureDimen(float measureWidth, float measureHeight, float baseline) {
@@ -262,34 +266,39 @@ public abstract class Element {
public void measure(TypeEnvironment env) {
updateEnv(env);
onMeasure(env);
restoreEnv(env);
}
public void draw(TypeEnvironment env, Canvas canvas) {
updateEnv(env);
if(mVisible != VISIBLE){
return;
if (mVisible == VISIBLE) {
onDraw(env, canvas);
}
onDraw(env, canvas);
restoreEnv(env);
}
private void updateEnv(TypeEnvironment env){
if(mRestoreType != null){
for(Integer type: mRestoreType){
env.restore(type);
}
}
if(mSaveType != null){
for(Integer type: mSaveType){
void updateEnv(TypeEnvironment env) {
if (mSaveType != null) {
for (Integer type : mSaveType) {
env.save(type);
}
}
if (mEnvironmentUpdater != null) {
for(EnvironmentUpdater updater: mEnvironmentUpdater){
for (EnvironmentUpdater updater : mEnvironmentUpdater) {
updater.update(env);
}
}
}
void restoreEnv(TypeEnvironment env) {
if (mRestoreType != null) {
for (Integer type : mRestoreType) {
env.restore(type);
}
}
}
protected abstract void onMeasure(TypeEnvironment env);
protected abstract void onDraw(TypeEnvironment env, Canvas canvas);
}
@@ -19,7 +19,7 @@ package com.qmuiteam.qmui.type.parser;
import android.graphics.drawable.Drawable;
import com.qmuiteam.qmui.type.TypeModel;
import com.qmuiteam.qmui.type.element.CharElement;
import com.qmuiteam.qmui.type.element.CharOrPhraseElement;
import com.qmuiteam.qmui.type.element.DrawableElement;
import com.qmuiteam.qmui.type.element.Element;
import com.qmuiteam.qmui.type.element.EmojiElement;
@@ -73,7 +73,7 @@ public class EmojiTextParser implements TextParser {
j++;
}
if (!find) {
tmp = new CharElement(c, index, i);
tmp = new CharOrPhraseElement(c, index, i);
}
} else {
boolean handled = false;
@@ -107,7 +107,7 @@ public class EmojiTextParser implements TextParser {
}
if (!handled) {
tmp = new CharElement(c, index, i);
tmp = new CharOrPhraseElement(c, index, i);
}
}
@@ -123,6 +123,6 @@ public class EmojiTextParser implements TextParser {
}
map.put(tmp.getIndex(), tmp);
}
return new TypeModel(map, first, last);
return new TypeModel(map, first, last, null);
}
}
@@ -17,7 +17,7 @@
package com.qmuiteam.qmui.type.parser;
import com.qmuiteam.qmui.type.TypeModel;
import com.qmuiteam.qmui.type.element.CharElement;
import com.qmuiteam.qmui.type.element.CharOrPhraseElement;
import com.qmuiteam.qmui.type.element.Element;
import com.qmuiteam.qmui.type.element.NextParagraphElement;
@@ -44,7 +44,7 @@ public class PlainTextParser implements TextParser {
tmp = new NextParagraphElement(c, null, index, i);
}
} else {
tmp = new CharElement(c, index, i);
tmp = new CharOrPhraseElement(c, index, i);
}
ParserHelper.handleWordPart(c, last, tmp);
@@ -59,6 +59,6 @@ public class PlainTextParser implements TextParser {
}
map.put(tmp.getIndex(), tmp);
}
return new TypeModel(map, first, last);
return new TypeModel(map, first, last, null);
}
}