mirror of
https://github.com/Tencent/QMUI_Android.git
synced 2026-10-21 14:52:07 +08:00
convert to kotlin
This commit is contained in:
+2
-1
@@ -1,7 +1,7 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.10'
|
||||
ext.kotlin_version = '1.4.21'
|
||||
repositories {
|
||||
mavenLocal()
|
||||
google()
|
||||
@@ -30,6 +30,7 @@ allprojects {
|
||||
minSdkVersion = 19
|
||||
targetSdkVersion = 29
|
||||
compileSdkVersion = 29
|
||||
coreVersion='1.5.0-beta01'
|
||||
appcompatVersion= '1.3.0-alpha01'
|
||||
materialVersion='1.2.1'
|
||||
annotationVersion='1.1.0-beta01'
|
||||
|
||||
+3
-1
@@ -1,4 +1,5 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
version = QMUI_TYPE_VERSION
|
||||
|
||||
@@ -25,7 +26,8 @@ android {
|
||||
|
||||
dependencies {
|
||||
api "androidx.annotation:annotation:$annotationVersion"
|
||||
api "androidx.core:core:$appcompatVersion"
|
||||
api "androidx.core:core-ktx:$appcompatVersion"
|
||||
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
|
||||
// deploy
|
||||
|
||||
@@ -13,9 +13,8 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type
|
||||
|
||||
package com.qmuiteam.qmui.type;
|
||||
|
||||
public interface EnvironmentUpdater {
|
||||
void update(TypeEnvironment env);
|
||||
}
|
||||
interface EnvironmentUpdater {
|
||||
fun update(env: TypeEnvironment)
|
||||
}
|
||||
@@ -13,293 +13,253 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type
|
||||
|
||||
package com.qmuiteam.qmui.type;
|
||||
import android.graphics.Canvas
|
||||
import androidx.core.util.Pools
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
import com.qmuiteam.qmui.type.element.BreakWordLineElement
|
||||
import com.qmuiteam.qmui.type.element.Element
|
||||
import com.qmuiteam.qmui.type.element.NextParagraphElement
|
||||
import com.qmuiteam.qmui.type.element.TextElement
|
||||
import java.util.*
|
||||
|
||||
import android.graphics.Canvas;
|
||||
class Line private constructor() {
|
||||
companion object {
|
||||
private val sLinePool: Pools.Pool<Line> = Pools.SimplePool(16)
|
||||
|
||||
import androidx.core.util.Pools;
|
||||
|
||||
import com.qmuiteam.qmui.type.element.BreakWordLineElement;
|
||||
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;
|
||||
|
||||
public class Line {
|
||||
private static Pools.Pool<Line> sLinePool = new Pools.SimplePool<>(16);
|
||||
|
||||
public static Line acquire() {
|
||||
Line line = sLinePool.acquire();
|
||||
if (line == null) {
|
||||
line = new Line();
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private Line() {
|
||||
|
||||
}
|
||||
|
||||
private int mX;
|
||||
private int mY;
|
||||
private int mWidthLimit;
|
||||
private int mContentWidth;
|
||||
private int mContentHeight;
|
||||
private int mLayoutWidth;
|
||||
private List<Element> mElements = new LinkedList<>();
|
||||
private HashMap<Element, Integer> mVisibleChanged;
|
||||
|
||||
public void init(int x, int y, int widthLimit) {
|
||||
mX = x;
|
||||
mY = y;
|
||||
mWidthLimit = widthLimit;
|
||||
}
|
||||
|
||||
public void add(Element element) {
|
||||
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();
|
||||
}
|
||||
|
||||
public int getContentWidth() {
|
||||
return mContentWidth;
|
||||
}
|
||||
|
||||
public int getLayoutWidth() {
|
||||
return mLayoutWidth;
|
||||
}
|
||||
|
||||
public int getWidthLimit() {
|
||||
return mWidthLimit;
|
||||
}
|
||||
|
||||
public int getContentHeight() {
|
||||
return mContentHeight;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return mX;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
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;
|
||||
}
|
||||
int lastIndex = mElements.size() - 1;
|
||||
Element last = mElements.get(lastIndex);
|
||||
Element next = last.getNext();
|
||||
|
||||
List<Element> back = new LinkedList<>();
|
||||
if(last.getWordPart() == Element.WORD_PART_WHOLE){
|
||||
if(last.getLineBreakType() == Element.LINE_BREAK_TYPE_NOT_END ||
|
||||
(next != null && next.getLineBreakType() == Element.LINE_BREAK_TYPE_NOT_START)){
|
||||
mElements.remove(lastIndex);
|
||||
back.add(last);
|
||||
fun acquire(): Line {
|
||||
var line = sLinePool.acquire()
|
||||
if (line == null) {
|
||||
line = Line()
|
||||
}
|
||||
}else if(last.getWordPart() == Element.WORD_PART_END && (next != null && next.getLineBreakType() != Element.LINE_BREAK_TYPE_NOT_START)){
|
||||
return line
|
||||
}
|
||||
}
|
||||
|
||||
var x = 0
|
||||
var y = 0
|
||||
var widthLimit = 0
|
||||
private set
|
||||
var contentWidth = 0
|
||||
private set
|
||||
var contentHeight = 0
|
||||
private set
|
||||
var layoutWidth = 0
|
||||
private set
|
||||
private val mElements = LinkedList<Element>()
|
||||
private var mVisibleChanged: HashMap<Element, Int>? = null
|
||||
|
||||
|
||||
val size: Int
|
||||
get() = mElements.size
|
||||
|
||||
fun init(x: Int, y: Int, widthLimit: Int) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.widthLimit = widthLimit
|
||||
}
|
||||
|
||||
fun add(element: Element) {
|
||||
mElements.add(element)
|
||||
contentWidth = (contentWidth + element.measureWidth).toInt()
|
||||
contentHeight = contentHeight.toFloat().coerceAtLeast(element.measureHeight).toInt()
|
||||
}
|
||||
|
||||
fun addFirst(element: Element) {
|
||||
mElements.add(0, element)
|
||||
contentWidth = (contentWidth + element.measureWidth).toInt()
|
||||
contentHeight = contentHeight.toFloat().coerceAtLeast(element.measureHeight).toInt()
|
||||
}
|
||||
|
||||
fun first(): Element? {
|
||||
return if (mElements.isEmpty()) null else mElements[0]
|
||||
}
|
||||
|
||||
fun move(environment: TypeEnvironment?) {
|
||||
for (el in mElements) {
|
||||
el.move(environment!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleWordBreak(environment: TypeEnvironment?): List<Element>? {
|
||||
if (mElements.size == 0) {
|
||||
return null
|
||||
}
|
||||
var lastIndex = mElements.size - 1
|
||||
val last = mElements[lastIndex]
|
||||
val next = last.next
|
||||
val back: MutableList<Element> = LinkedList()
|
||||
if (last.wordPart == Element.WORD_PART_WHOLE) {
|
||||
if (last.lineBreakType == Element.LINE_BREAK_TYPE_NOT_END ||
|
||||
next != null && next.lineBreakType == Element.LINE_BREAK_TYPE_NOT_START) {
|
||||
mElements.removeAt(lastIndex)
|
||||
back.add(last)
|
||||
}
|
||||
} else if (last.wordPart == Element.WORD_PART_END && next != null && next.lineBreakType != Element.LINE_BREAK_TYPE_NOT_START) {
|
||||
// do nothing
|
||||
}else if(last.getWordPart() == Element.WORD_PART_START){
|
||||
mElements.remove(lastIndex);
|
||||
back.add(last);
|
||||
}else{
|
||||
back.add(last);
|
||||
mElements.remove(lastIndex);
|
||||
lastIndex--;
|
||||
int min = Math.max(0, lastIndex - 30); // try 30 letter.
|
||||
boolean find = false;
|
||||
} else if (last.wordPart == Element.WORD_PART_START) {
|
||||
mElements.removeAt(lastIndex)
|
||||
back.add(last)
|
||||
} else {
|
||||
back.add(last)
|
||||
mElements.removeAt(lastIndex)
|
||||
lastIndex--
|
||||
val min = Math.max(0, lastIndex - 30) // try 30 letter.
|
||||
var find = false
|
||||
while (lastIndex > min) {
|
||||
Element el = mElements.get(lastIndex);
|
||||
if (el.getWordPart() == Element.WORD_PART_WHOLE || el.getWordPart() == Element.WORD_PART_END) {
|
||||
find = true;
|
||||
break;
|
||||
} else if (el.getLineBreakType() == Element.LINE_BREAK_WORD_BREAK_ALLOWED) {
|
||||
val el = mElements[lastIndex]
|
||||
if (el.wordPart == Element.WORD_PART_WHOLE || el.wordPart == Element.WORD_PART_END) {
|
||||
find = true
|
||||
break
|
||||
} else if (el.lineBreakType == Element.LINE_BREAK_WORD_BREAK_ALLOWED) {
|
||||
// TODO what if environment had changed after break? the measure may be wrong
|
||||
BreakWordLineElement b = new BreakWordLineElement();
|
||||
b.measure(environment);
|
||||
add(b);
|
||||
find = true;
|
||||
break;
|
||||
val b = BreakWordLineElement()
|
||||
b.measure(environment!!)
|
||||
add(b)
|
||||
find = true
|
||||
break
|
||||
} else {
|
||||
back.add(0, el);
|
||||
mElements.remove(lastIndex);
|
||||
lastIndex--;
|
||||
back.add(0, el)
|
||||
mElements.removeAt(lastIndex)
|
||||
lastIndex--
|
||||
}
|
||||
}
|
||||
if (!find) {
|
||||
// give up
|
||||
mElements.addAll(back);
|
||||
return null;
|
||||
mElements.addAll(back)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (back.isEmpty()) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
for (Element el : back) {
|
||||
mContentWidth -= el.getMeasureWidth();
|
||||
for (el in back) {
|
||||
contentWidth = (contentWidth -el.measureWidth).toInt()
|
||||
}
|
||||
return back;
|
||||
return back
|
||||
}
|
||||
|
||||
private boolean hideLastIfSpaceIfNeeded(boolean dropLastIfSpace) {
|
||||
Element last = mElements.get(mElements.size() - 1);
|
||||
if (dropLastIfSpace && last instanceof CharOrPhraseElement && last.getChar() == ' ' && last.getVisible() != Element.GONE) {
|
||||
changeVisibleInner(last, Element.GONE);
|
||||
mContentWidth -= last.getMeasureWidth();
|
||||
return true;
|
||||
private fun hideLastIfSpaceIfNeeded(dropLastIfSpace: Boolean): Boolean {
|
||||
val last = mElements[mElements.size - 1]
|
||||
if (dropLastIfSpace && last is TextElement && last.length == 1 && last.text[0] == ' ' && last.visible != Element.GONE) {
|
||||
changeVisibleInner(last, Element.GONE)
|
||||
contentWidth = (contentWidth - last.measureWidth).toInt()
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
private void changeVisibleInner(Element element, int visible) {
|
||||
int oldVal = element.getVisible();
|
||||
private fun changeVisibleInner(element: Element, visible: Int) {
|
||||
val oldVal = element.visible
|
||||
if (visible == oldVal) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (mVisibleChanged == null) {
|
||||
mVisibleChanged = new HashMap<>();
|
||||
mVisibleChanged = HashMap()
|
||||
}
|
||||
mVisibleChanged.put(element, oldVal);
|
||||
element.setVisible(visible);
|
||||
mVisibleChanged!![element] = oldVal
|
||||
element.visible = visible
|
||||
}
|
||||
|
||||
private int calculateGapCount() {
|
||||
int ret = 0;
|
||||
for (int i = 1; i < mElements.size(); i++) {
|
||||
Element el = mElements.get(i);
|
||||
if (el.getVisible() != Element.GONE &&
|
||||
(el.getWordPart() == Element.WORD_PART_WHOLE ||
|
||||
el.getWordPart() == Element.WORD_PART_START)) {
|
||||
ret++;
|
||||
private fun calculateGapCount(): Int {
|
||||
var ret = 0
|
||||
for (i in 1 until mElements.size) {
|
||||
val el = mElements[i]
|
||||
if (el.visible != Element.GONE &&
|
||||
(el.wordPart == Element.WORD_PART_WHOLE ||
|
||||
el.wordPart == Element.WORD_PART_START)) {
|
||||
ret++
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return ret
|
||||
}
|
||||
|
||||
public boolean isMiddleParagraphEndLine(){
|
||||
return !mElements.isEmpty() && mElements.get(mElements.size() - 1) instanceof NextParagraphElement;
|
||||
}
|
||||
val isMiddleParagraphEndLine: Boolean
|
||||
get() = !mElements.isEmpty() && mElements[mElements.size - 1] is NextParagraphElement
|
||||
|
||||
public void layout(TypeEnvironment env, boolean dropLastIfSpace, boolean isEnd) {
|
||||
fun layout(env: TypeEnvironment, dropLastIfSpace: Boolean, isEnd: Boolean) {
|
||||
if (mElements.isEmpty()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
hideLastIfSpaceIfNeeded(dropLastIfSpace);
|
||||
mLayoutWidth = mContentWidth;
|
||||
TypeEnvironment.Alignment alignment = env.getAlignment();
|
||||
float start = mX;
|
||||
float addSpace = 0;
|
||||
if (alignment == TypeEnvironment.Alignment.RIGHT) {
|
||||
start = mX + mWidthLimit - mContentWidth;
|
||||
} else if (alignment == TypeEnvironment.Alignment.CENTER) {
|
||||
start = mX + (mWidthLimit - mContentWidth) / 2f;
|
||||
} else if (alignment == TypeEnvironment.Alignment.JUSTIFY) {
|
||||
float remain = mWidthLimit - mContentWidth;
|
||||
if (!(isEnd || isMiddleParagraphEndLine()) || remain < env.getLastLineJustifyMaxWidth()) {
|
||||
int gapCount = calculateGapCount();
|
||||
hideLastIfSpaceIfNeeded(dropLastIfSpace)
|
||||
layoutWidth = contentWidth
|
||||
val alignment = env.alignment
|
||||
var start = x.toFloat()
|
||||
var addSpace = 0f
|
||||
if (alignment === TypeEnvironment.Alignment.RIGHT) {
|
||||
start = (x + widthLimit - contentWidth).toFloat()
|
||||
} else if (alignment === TypeEnvironment.Alignment.CENTER) {
|
||||
start = x + (widthLimit - contentWidth) / 2f
|
||||
} else if (alignment === TypeEnvironment.Alignment.JUSTIFY) {
|
||||
val remain = (widthLimit - contentWidth).toFloat()
|
||||
if (!(isEnd || isMiddleParagraphEndLine) || remain < env.lastLineJustifyMaxWidth) {
|
||||
val gapCount = calculateGapCount()
|
||||
if (gapCount > 0) {
|
||||
addSpace = remain / gapCount;
|
||||
mLayoutWidth = mWidthLimit;
|
||||
addSpace = remain / gapCount
|
||||
layoutWidth = widthLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
float x = start;
|
||||
for (int i = 0; i < mElements.size(); i++) {
|
||||
Element el = mElements.get(i);
|
||||
if (i > 0 && (el.getWordPart() == Element.WORD_PART_WHOLE
|
||||
|| el.getWordPart() == Element.WORD_PART_START)) {
|
||||
x += addSpace;
|
||||
mElements.get(i - 1).setNextGapWidth(addSpace);
|
||||
var x = start
|
||||
for (i in mElements.indices) {
|
||||
val el = mElements[i]
|
||||
if (i > 0 && (el.wordPart == Element.WORD_PART_WHOLE
|
||||
|| el.wordPart == Element.WORD_PART_START)) {
|
||||
x += addSpace
|
||||
mElements[i - 1].nextGapWidth = addSpace
|
||||
}
|
||||
el.setX((int) x);
|
||||
x += el.getMeasureWidth();
|
||||
el.setY(mY + (mContentHeight - el.getMeasureHeight()) / 2f);
|
||||
el.x = x
|
||||
x += el.measureWidth
|
||||
el.y = y + (contentHeight - el.measureHeight) / 2f
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(TypeEnvironment env, Canvas canvas) {
|
||||
for (Element element : mElements) {
|
||||
element.draw(env, canvas);
|
||||
fun draw(env: TypeEnvironment, canvas: Canvas) {
|
||||
for (element in mElements) {
|
||||
element.draw(env, canvas)
|
||||
}
|
||||
}
|
||||
|
||||
void restoreVisibleChange(){
|
||||
fun restoreVisibleChange() {
|
||||
if (mVisibleChanged != null) {
|
||||
for (Element entry : mVisibleChanged.keySet()) {
|
||||
Integer visible = mVisibleChanged.get(entry);
|
||||
for (entry in mVisibleChanged!!.keys) {
|
||||
val visible = mVisibleChanged!![entry]
|
||||
if (visible != null) {
|
||||
entry.setVisible(visible);
|
||||
entry.visible = visible
|
||||
}
|
||||
}
|
||||
mVisibleChanged.clear();
|
||||
mVisibleChanged!!.clear()
|
||||
}
|
||||
}
|
||||
|
||||
public List<Element> popAll(){
|
||||
List<Element> elements = new ArrayList<>(mElements);
|
||||
mElements.clear();
|
||||
restoreVisibleChange();
|
||||
mContentWidth = 0;
|
||||
mContentHeight = 0;
|
||||
mLayoutWidth = 0;
|
||||
return elements;
|
||||
fun popAll(): List<Element> {
|
||||
val elements: List<Element> = ArrayList(mElements)
|
||||
mElements.clear()
|
||||
restoreVisibleChange()
|
||||
contentWidth = 0
|
||||
contentHeight = 0
|
||||
layoutWidth = 0
|
||||
return elements
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
mElements.clear();
|
||||
restoreVisibleChange();
|
||||
mContentWidth = 0;
|
||||
mContentHeight = 0;
|
||||
mLayoutWidth = 0;
|
||||
fun clear() {
|
||||
mElements.clear()
|
||||
restoreVisibleChange()
|
||||
contentWidth = 0
|
||||
contentHeight = 0
|
||||
layoutWidth = 0
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mX = 0;
|
||||
mY = 0;
|
||||
mWidthLimit = 0;
|
||||
mContentWidth = 0;
|
||||
mContentHeight = 0;
|
||||
mLayoutWidth = 0;
|
||||
mElements.clear();
|
||||
restoreVisibleChange();
|
||||
sLinePool.release(this);
|
||||
fun release() {
|
||||
x = 0
|
||||
y = 0
|
||||
widthLimit = 0
|
||||
contentWidth = 0
|
||||
contentHeight = 0
|
||||
layoutWidth = 0
|
||||
mElements.clear()
|
||||
restoreVisibleChange()
|
||||
sLinePool.release(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,459 +13,425 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type
|
||||
|
||||
package com.qmuiteam.qmui.type;
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextUtils.TruncateAt
|
||||
import com.qmuiteam.qmui.type.element.*
|
||||
import java.util.*
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextUtils;
|
||||
class LineLayout(private val mTypeEnvironment: TypeEnvironment) {
|
||||
private var mMaxLines = Int.MAX_VALUE
|
||||
private var mEllipsize: TruncateAt? = null
|
||||
private var mCalculateWholeLines = true
|
||||
private val mLines: MutableList<Line> = ArrayList()
|
||||
private var mDropLastIfSpace = true
|
||||
private var mMoreText: String? = null
|
||||
private var mMoreTextColor = 0
|
||||
private var mMoreTextTypeface: Typeface? = null
|
||||
private var mMoreUnderlineColor = Color.TRANSPARENT
|
||||
private var mMoreBgColor = 0
|
||||
private var mMoreUnderlineHeight = 0
|
||||
private var mTotalLineCount = 0
|
||||
|
||||
import com.qmuiteam.qmui.type.element.BreakWordLineElement;
|
||||
import com.qmuiteam.qmui.type.element.CharOrPhraseElement;
|
||||
import com.qmuiteam.qmui.type.element.IgnoreEffectElement;
|
||||
import com.qmuiteam.qmui.type.element.Element;
|
||||
import com.qmuiteam.qmui.type.element.NextParagraphElement;
|
||||
var typeModel: TypeModel? = null
|
||||
private set
|
||||
|
||||
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 = Integer.MAX_VALUE;
|
||||
private TextUtils.TruncateAt mEllipsize;
|
||||
private boolean mCalculateWholeLines = true;
|
||||
private TypeEnvironment mTypeEnvironment;
|
||||
private TypeModel mTypeModel;
|
||||
private List<Line> mLines = new ArrayList<>();
|
||||
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;
|
||||
|
||||
public LineLayout(TypeEnvironment environment) {
|
||||
mTypeEnvironment = environment;
|
||||
fun setMaxLines(maxLines: Int): LineLayout {
|
||||
mMaxLines = maxLines
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setMaxLines(int maxLines) {
|
||||
mMaxLines = maxLines;
|
||||
return this;
|
||||
fun setEllipsize(ellipsize: TruncateAt?): LineLayout {
|
||||
mEllipsize = ellipsize
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setEllipsize(TextUtils.TruncateAt ellipsize) {
|
||||
mEllipsize = ellipsize;
|
||||
return this;
|
||||
fun setCalculateWholeLines(calculateWholeLines: Boolean): LineLayout {
|
||||
mCalculateWholeLines = calculateWholeLines
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setCalculateWholeLines(boolean calculateWholeLines) {
|
||||
mCalculateWholeLines = calculateWholeLines;
|
||||
return this;
|
||||
fun setDropLastIfSpace(dropLastIfSpace: Boolean): LineLayout {
|
||||
mDropLastIfSpace = dropLastIfSpace
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setDropLastIfSpace(boolean dropLastIfSpace) {
|
||||
mDropLastIfSpace = dropLastIfSpace;
|
||||
return this;
|
||||
fun setMoreText(text: String?, color: Int, typeface: Typeface?): LineLayout {
|
||||
mMoreText = text
|
||||
mMoreTextColor = color
|
||||
mMoreTextTypeface = typeface
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setMoreText(String text, int color, Typeface typeface) {
|
||||
mMoreText = text;
|
||||
mMoreTextColor = color;
|
||||
mMoreTextTypeface = typeface;
|
||||
return this;
|
||||
fun setMoreBackgroundColor(color: Int): LineLayout {
|
||||
mMoreBgColor = color
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setMoreBackgroundColor(int color) {
|
||||
mMoreBgColor = color;
|
||||
return this;
|
||||
fun setUnderline(height: Int, color: Int): LineLayout {
|
||||
mMoreUnderlineHeight = height
|
||||
mMoreUnderlineColor = color
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setUnderline(int height, int color) {
|
||||
mMoreUnderlineHeight = height;
|
||||
mMoreUnderlineColor = color;
|
||||
return this;
|
||||
fun setTypeModel(typeModel: TypeModel?): LineLayout {
|
||||
this.typeModel = typeModel
|
||||
return this
|
||||
}
|
||||
|
||||
public LineLayout setTypeModel(TypeModel typeModel) {
|
||||
mTypeModel = typeModel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TypeModel getTypeModel() {
|
||||
return mTypeModel;
|
||||
}
|
||||
|
||||
public void measureAndLayout() {
|
||||
mTypeEnvironment.clear();
|
||||
release();
|
||||
if (mTypeModel == null) {
|
||||
return;
|
||||
fun measureAndLayout() {
|
||||
mTypeEnvironment.clear()
|
||||
release()
|
||||
if (typeModel == null) {
|
||||
return
|
||||
}
|
||||
Element element = mTypeModel.firstElement();
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
Line line = Line.acquire();
|
||||
int y = 0;
|
||||
line.init(0, y, mTypeEnvironment.getWidthLimit());
|
||||
var element: Element? = typeModel!!.firstElement() ?: return
|
||||
var line = Line.acquire()
|
||||
var y = 0
|
||||
line.init(0, y, mTypeEnvironment.widthLimit)
|
||||
while (element != null) {
|
||||
element.measure(mTypeEnvironment);
|
||||
if (element instanceof NextParagraphElement) {
|
||||
line.add(element);
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, false);
|
||||
mLines.add(line);
|
||||
element.measure(mTypeEnvironment)
|
||||
if (element is NextParagraphElement) {
|
||||
line.add(element)
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, false)
|
||||
mLines.add(line)
|
||||
if (canInterrupt()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
y += line.getContentHeight() + mTypeEnvironment.getParagraphSpace();
|
||||
line = createNewLine(y);
|
||||
} else if (line.getContentWidth() + element.getMeasureWidth() > mTypeEnvironment.getWidthLimit()) {
|
||||
if (mLines.size() == 0 && line.getSize() == 0) {
|
||||
y += line.contentHeight + mTypeEnvironment.paragraphSpace
|
||||
line = createNewLine(y)
|
||||
} else if (line.contentWidth + element.measureWidth > mTypeEnvironment.widthLimit) {
|
||||
if (mLines.size == 0 && line.size == 0) {
|
||||
// the width is too small.
|
||||
line.release();
|
||||
return;
|
||||
line.release()
|
||||
return
|
||||
}
|
||||
List<Element> back = line.handleWordBreak(mTypeEnvironment);
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, false);
|
||||
mLines.add(line);
|
||||
val back = line.handleWordBreak(mTypeEnvironment)
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, false)
|
||||
mLines.add(line)
|
||||
if (canInterrupt()) {
|
||||
handleEllipse(true);
|
||||
return;
|
||||
handleEllipse(true)
|
||||
return
|
||||
}
|
||||
y += line.getContentHeight() + mTypeEnvironment.getLineSpace();
|
||||
line = createNewLine(y);
|
||||
y += line.contentHeight + mTypeEnvironment.lineSpace
|
||||
line = createNewLine(y)
|
||||
if (back != null && !back.isEmpty()) {
|
||||
for (Element el : back) {
|
||||
line.add(el);
|
||||
for (el in back) {
|
||||
line.add(el)
|
||||
}
|
||||
}
|
||||
line.add(element);
|
||||
line.add(element)
|
||||
} else {
|
||||
line.add(element);
|
||||
line.add(element)
|
||||
}
|
||||
element = element.getNext();
|
||||
element = element.next
|
||||
}
|
||||
if (line.getSize() > 0) {
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, true);
|
||||
mLines.add(line);
|
||||
if (line.size > 0) {
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, true)
|
||||
mLines.add(line)
|
||||
} else {
|
||||
line.release();
|
||||
line.release()
|
||||
}
|
||||
mTotalLineCount = mLines.size();
|
||||
handleEllipse(false);
|
||||
mTotalLineCount = mLines.size
|
||||
handleEllipse(false)
|
||||
}
|
||||
|
||||
private Line createNewLine(int y) {
|
||||
Line line = Line.acquire();
|
||||
line.init(0, y, mTypeEnvironment.getWidthLimit());
|
||||
return line;
|
||||
private fun createNewLine(y: Int): Line {
|
||||
val line = Line.acquire()
|
||||
line.init(0, y, mTypeEnvironment.widthLimit)
|
||||
return line
|
||||
}
|
||||
|
||||
private void handleEllipse(boolean fromInterrupt) {
|
||||
if (mLines.isEmpty() || mLines.size() < mMaxLines || (mLines.size() == mMaxLines && !fromInterrupt)) {
|
||||
return;
|
||||
private fun handleEllipse(fromInterrupt: Boolean) {
|
||||
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();
|
||||
if (mEllipsize == TruncateAt.END) {
|
||||
handleEllipseEnd()
|
||||
} else if (mEllipsize == TruncateAt.START) {
|
||||
handleEllipseStart()
|
||||
} else if (mEllipsize == TruncateAt.MIDDLE) {
|
||||
handleEllipseMiddle()
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEllipseEnd() {
|
||||
for (int i = mLines.size() - 1; i >= mMaxLines; i--) {
|
||||
mLines.remove(mLines.get(i));
|
||||
private fun handleEllipseEnd() {
|
||||
for (i in mLines.size - 1 downTo mMaxLines) {
|
||||
mLines.remove(mLines[i])
|
||||
}
|
||||
Line lastLine = mLines.get(mLines.size() - 1);
|
||||
int limitWidth = lastLine.getWidthLimit();
|
||||
Element ellipseElement = new CharOrPhraseElement("...", -1, -1);
|
||||
ellipseElement.addSingleEnvironmentUpdater(null, new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
env.clear();
|
||||
val lastLine = mLines[mLines.size - 1]
|
||||
var limitWidth = lastLine.widthLimit
|
||||
val ellipseElement: Element = TextElement("...", -1, -1)
|
||||
ellipseElement.addSingleEnvironmentUpdater(null, object : EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
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.addSingleEnvironmentUpdater(changeTypes, new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
})
|
||||
ellipseElement.measure(mTypeEnvironment)
|
||||
limitWidth = (limitWidth - ellipseElement.measureWidth).toInt()
|
||||
var moreElement: Element? = null
|
||||
if (mMoreText != null && !mMoreText!!.isEmpty()) {
|
||||
moreElement = TextElement(mMoreText!!, -1, -1)
|
||||
val changeTypes: MutableList<Int> = 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.addSingleEnvironmentUpdater(changeTypes, object : EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
if (mMoreTextColor != 0) {
|
||||
env.setTextColor(mMoreTextColor);
|
||||
env.textColor = mMoreTextColor
|
||||
}
|
||||
if (mMoreBgColor != 0) {
|
||||
env.setBackgroundColor(mMoreBgColor);
|
||||
env.backgroundColor = mMoreBgColor
|
||||
}
|
||||
if (mMoreTextTypeface != null) {
|
||||
env.setTypeface(mMoreTextTypeface);
|
||||
env.typeface = mMoreTextTypeface
|
||||
}
|
||||
|
||||
if (mMoreUnderlineHeight > 0) {
|
||||
env.setBorderBottom(mMoreUnderlineHeight, mMoreUnderlineColor);
|
||||
env.setBorderBottom(mMoreUnderlineHeight, mMoreUnderlineColor)
|
||||
}
|
||||
}
|
||||
});
|
||||
moreElement.measure(mTypeEnvironment);
|
||||
limitWidth -= moreElement.getMeasureWidth();
|
||||
})
|
||||
moreElement.measure(mTypeEnvironment)
|
||||
limitWidth = (limitWidth - moreElement.measureWidth).toInt()
|
||||
}
|
||||
|
||||
int contentWidth = lastLine.getContentWidth();
|
||||
val contentWidth = lastLine.contentWidth
|
||||
if (contentWidth < limitWidth) {
|
||||
lastLine.restoreVisibleChange();
|
||||
lastLine.restoreVisibleChange()
|
||||
} else {
|
||||
List<Element> elements = lastLine.popAll();
|
||||
for (Element el : elements) {
|
||||
if (el.getMeasureWidth() <= limitWidth) {
|
||||
lastLine.add(el);
|
||||
limitWidth -= el.getMeasureWidth();
|
||||
val elements = lastLine.popAll()
|
||||
for (el in elements) {
|
||||
if (el.measureWidth <= limitWidth) {
|
||||
lastLine.add(el)
|
||||
limitWidth -= (limitWidth - el.measureWidth).toInt()
|
||||
} else {
|
||||
break;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
lastLine.add(ellipseElement);
|
||||
lastLine.add(ellipseElement)
|
||||
if (moreElement != null) {
|
||||
lastLine.add(moreElement);
|
||||
lastLine.add(moreElement)
|
||||
}
|
||||
lastLine.layout(mTypeEnvironment, mDropLastIfSpace, true);
|
||||
lastLine.layout(mTypeEnvironment, mDropLastIfSpace, true)
|
||||
}
|
||||
|
||||
private void handleEllipseStart() {
|
||||
mTypeEnvironment.clear();
|
||||
for (int i = mLines.size() - 1; i >= mMaxLines; i--) {
|
||||
mLines.remove(mLines.get(i));
|
||||
private fun handleEllipseStart() {
|
||||
mTypeEnvironment.clear()
|
||||
for (i in mLines.size - 1 downTo mMaxLines) {
|
||||
mLines.remove(mLines[i])
|
||||
}
|
||||
Element ellipseElement = new CharOrPhraseElement("...", -1, -1);
|
||||
ellipseElement.addSingleEnvironmentUpdater(null, new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
env.clear();
|
||||
val ellipseElement: Element = TextElement("...", -1, -1)
|
||||
ellipseElement.addSingleEnvironmentUpdater(null, object : EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
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());
|
||||
})
|
||||
ellipseElement.measure(mTypeEnvironment)
|
||||
val elements: Queue<Element> = LinkedList()
|
||||
elements.add(ellipseElement)
|
||||
for (i in mLines.indices) {
|
||||
val line = mLines[i]
|
||||
val limitWidth = line.widthLimit
|
||||
elements.addAll(line.popAll())
|
||||
while (!elements.isEmpty()) {
|
||||
Element el = elements.peek();
|
||||
val el = elements.peek()
|
||||
if (el != null) {
|
||||
if (el instanceof NextParagraphElement) {
|
||||
elements.poll();
|
||||
line.add(el);
|
||||
el.move(mTypeEnvironment);
|
||||
break;
|
||||
if (el is NextParagraphElement) {
|
||||
elements.poll()
|
||||
line.add(el)
|
||||
el.move(mTypeEnvironment)
|
||||
break
|
||||
}
|
||||
|
||||
if (el instanceof BreakWordLineElement) {
|
||||
elements.poll();
|
||||
continue;
|
||||
if (el is BreakWordLineElement) {
|
||||
elements.poll()
|
||||
continue
|
||||
}
|
||||
if (line.getContentWidth() + el.getMeasureWidth() <= limitWidth) {
|
||||
elements.poll();
|
||||
line.add(el);
|
||||
el.move(mTypeEnvironment);
|
||||
|
||||
if (line.contentWidth + el.measureWidth <= limitWidth) {
|
||||
elements.poll()
|
||||
line.add(el)
|
||||
el.move(mTypeEnvironment)
|
||||
} else {
|
||||
break;
|
||||
break
|
||||
}
|
||||
} else {
|
||||
elements.poll();
|
||||
elements.poll()
|
||||
}
|
||||
}
|
||||
line.handleWordBreak(mTypeEnvironment);
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, false);
|
||||
line.handleWordBreak(mTypeEnvironment)
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, false)
|
||||
if (elements.isEmpty()) {
|
||||
return;
|
||||
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));
|
||||
private fun handleEllipseMiddle() {
|
||||
mTypeEnvironment.clear()
|
||||
val lines: List<Line> = ArrayList(mLines)
|
||||
mLines.clear()
|
||||
val ellipseElement: Element = TextElement("...", -1, -1)
|
||||
ellipseElement.measure(mTypeEnvironment)
|
||||
val ellipseLine = if (mMaxLines % 2 == 0) mMaxLines / 2 else (mMaxLines + 1) / 2
|
||||
for (i in 0 until ellipseLine) {
|
||||
mLines.add(lines[i])
|
||||
}
|
||||
Line handleLine = lines.get(ellipseLine - 1);
|
||||
int limitWidth = handleLine.getWidthLimit();
|
||||
Deque<Element> unHandled = new LinkedList<>(handleLine.popAll());
|
||||
val handleLine = lines[ellipseLine - 1]
|
||||
val limitWidth = handleLine.widthLimit
|
||||
val unHandled: Deque<Element> = LinkedList(handleLine.popAll())
|
||||
while (!unHandled.isEmpty()) {
|
||||
Element el = unHandled.peek();
|
||||
val el = unHandled.peek()
|
||||
if (el != null) {
|
||||
if (handleLine.getContentWidth() + el.getMeasureWidth() <= limitWidth / 2f - ellipseElement.getMeasureWidth() / 2) {
|
||||
unHandled.poll();
|
||||
handleLine.add(el);
|
||||
el.move(mTypeEnvironment);
|
||||
if (handleLine.contentWidth + el.measureWidth <= limitWidth / 2f - ellipseElement.measureWidth / 2) {
|
||||
unHandled.poll()
|
||||
handleLine.add(el)
|
||||
el.move(mTypeEnvironment)
|
||||
} else {
|
||||
break;
|
||||
break
|
||||
}
|
||||
} else {
|
||||
unHandled.poll();
|
||||
unHandled.poll()
|
||||
}
|
||||
}
|
||||
ellipseElement.measure(mTypeEnvironment);
|
||||
handleLine.add(ellipseElement);
|
||||
|
||||
int nextFullShowLine = lines.size() - mMaxLines + ellipseLine;
|
||||
int startLine = lines.size() - 1;
|
||||
ellipseElement.measure(mTypeEnvironment)
|
||||
handleLine.add(ellipseElement)
|
||||
val nextFullShowLine = lines.size - mMaxLines + ellipseLine
|
||||
var 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 (i in lines.size - 2 downTo nextFullShowLine + 1) {
|
||||
if (lines[i].isMiddleParagraphEndLine) {
|
||||
startLine = i
|
||||
}
|
||||
}
|
||||
for (int i = ellipseLine; i <= startLine; i++) {
|
||||
unHandled.addAll(lines.get(i).popAll());
|
||||
for (i in ellipseLine..startLine) {
|
||||
unHandled.addAll(lines[i].popAll())
|
||||
}
|
||||
|
||||
for (int i = startLine; i >= nextFullShowLine; i--) {
|
||||
Line line = lines.get(i);
|
||||
for (i in startLine downTo nextFullShowLine) {
|
||||
val line = lines[i]
|
||||
while (!unHandled.isEmpty()) {
|
||||
Element element = unHandled.peekLast();
|
||||
val element = unHandled.peekLast()
|
||||
if (element != null) {
|
||||
if (element instanceof NextParagraphElement) {
|
||||
unHandled.pollLast();
|
||||
continue;
|
||||
if (element is NextParagraphElement) {
|
||||
unHandled.pollLast()
|
||||
continue
|
||||
}
|
||||
if (element instanceof BreakWordLineElement) {
|
||||
unHandled.pollLast();
|
||||
continue;
|
||||
if (element is BreakWordLineElement) {
|
||||
unHandled.pollLast()
|
||||
continue
|
||||
}
|
||||
if (line.getContentWidth() + element.getMeasureWidth() <= line.getWidthLimit()) {
|
||||
unHandled.pollLast();
|
||||
line.addFirst(element);
|
||||
if (line.contentWidth + element.measureWidth <= line.widthLimit) {
|
||||
unHandled.pollLast()
|
||||
line.addFirst(element)
|
||||
} else {
|
||||
break;
|
||||
break
|
||||
}
|
||||
} else {
|
||||
unHandled.pollLast();
|
||||
unHandled.pollLast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Element> toAdd = new LinkedList<>();
|
||||
int toAddWidth = 0;
|
||||
val toAdd = LinkedList<Element>()
|
||||
var toAddWidth = 0
|
||||
while (!unHandled.isEmpty()) {
|
||||
Element element = unHandled.peekLast();
|
||||
val element = unHandled.peekLast()
|
||||
if (element != null) {
|
||||
if (element instanceof NextParagraphElement) {
|
||||
unHandled.pollLast();
|
||||
continue;
|
||||
if (element is NextParagraphElement) {
|
||||
unHandled.pollLast()
|
||||
continue
|
||||
}
|
||||
if (element instanceof BreakWordLineElement) {
|
||||
unHandled.pollLast();
|
||||
continue;
|
||||
if (element is BreakWordLineElement) {
|
||||
unHandled.pollLast()
|
||||
continue
|
||||
}
|
||||
if (handleLine.getContentWidth() + toAddWidth + element.getMeasureWidth() <= handleLine.getWidthLimit()) {
|
||||
unHandled.pollLast();
|
||||
toAdd.add(0, element);
|
||||
toAddWidth += element.getMeasureWidth();
|
||||
if (handleLine.contentWidth + toAddWidth + element.measureWidth <= handleLine.widthLimit) {
|
||||
unHandled.pollLast()
|
||||
toAdd.add(0, element)
|
||||
toAddWidth = (toAddWidth + element.measureWidth).toInt()
|
||||
} else {
|
||||
break;
|
||||
break
|
||||
}
|
||||
} else {
|
||||
unHandled.pollLast();
|
||||
unHandled.pollLast()
|
||||
}
|
||||
}
|
||||
|
||||
Element firstUnHandle = unHandled.peekFirst();
|
||||
Element lastUnHandle = unHandled.peekLast();
|
||||
Element effect = mTypeModel.getFirstEffect();
|
||||
val firstUnHandle = unHandled.peekFirst()
|
||||
val lastUnHandle = unHandled.peekLast()
|
||||
var effect = typeModel!!.firstEffect
|
||||
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);
|
||||
val ellipseEffect: MutableList<Element> = ArrayList()
|
||||
while (effect != null && effect.index <= lastUnHandle.index) {
|
||||
if (effect.index >= firstUnHandle.index) {
|
||||
ellipseEffect.add(effect)
|
||||
}
|
||||
effect = effect.getNext();
|
||||
effect = effect.next
|
||||
}
|
||||
if (ellipseEffect.size() > 0) {
|
||||
IgnoreEffectElement ignoreEffectElement = new IgnoreEffectElement(ellipseEffect);
|
||||
ignoreEffectElement.move(mTypeEnvironment);
|
||||
handleLine.add(ignoreEffectElement);
|
||||
if (ellipseEffect.size > 0) {
|
||||
val ignoreEffectElement = IgnoreEffectElement(ellipseEffect)
|
||||
ignoreEffectElement.move(mTypeEnvironment)
|
||||
handleLine.add(ignoreEffectElement)
|
||||
}
|
||||
}
|
||||
for (Element el : toAdd) {
|
||||
el.move(mTypeEnvironment);
|
||||
handleLine.add(el);
|
||||
for (el in 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());
|
||||
handleLine.handleWordBreak(mTypeEnvironment)
|
||||
handleLine.layout(mTypeEnvironment, mDropLastIfSpace, ellipseLine == lines.size)
|
||||
var lastEnd = handleLine.y + handleLine.contentHeight
|
||||
for (i in nextFullShowLine until lines.size) {
|
||||
val line = lines[i]
|
||||
val prev = lines[i - 1]
|
||||
if (prev.isMiddleParagraphEndLine) {
|
||||
line.y = lastEnd + mTypeEnvironment.paragraphSpace
|
||||
} else {
|
||||
line.setY(lastEnd + mTypeEnvironment.getLineSpace());
|
||||
line.y = lastEnd + mTypeEnvironment.lineSpace
|
||||
}
|
||||
lastEnd = line.getY() + line.getContentHeight();
|
||||
line.move(mTypeEnvironment);
|
||||
line.handleWordBreak(mTypeEnvironment);
|
||||
line.layout(mTypeEnvironment, mDropLastIfSpace, i == lines.size() - 1);
|
||||
mLines.add(line);
|
||||
lastEnd = line.y + line.contentHeight
|
||||
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) {
|
||||
maxWidth = Math.max(maxWidth, line.getLayoutWidth());
|
||||
val maxLayoutWidth: Int
|
||||
get() {
|
||||
var maxWidth = 0
|
||||
for (line in mLines) {
|
||||
maxWidth = Math.max(maxWidth, line.layoutWidth)
|
||||
}
|
||||
return maxWidth
|
||||
}
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
public int getContentHeight() {
|
||||
if (mLines.isEmpty()) {
|
||||
return 0;
|
||||
val contentHeight: Int
|
||||
get() {
|
||||
if (mLines.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
val last = mLines[mLines.size - 1]
|
||||
return last.y + last.contentHeight
|
||||
}
|
||||
Line last = mLines.get(mLines.size() - 1);
|
||||
return last.getY() + last.getContentHeight();
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas) {
|
||||
mTypeEnvironment.clear();
|
||||
for (Line line : mLines) {
|
||||
line.draw(mTypeEnvironment, canvas);
|
||||
fun draw(canvas: Canvas) {
|
||||
mTypeEnvironment.clear()
|
||||
for (line in mLines) {
|
||||
line.draw(mTypeEnvironment, canvas)
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canInterrupt() {
|
||||
return mLines.size() == mMaxLines && !mCalculateWholeLines &&
|
||||
(mEllipsize == null || mEllipsize == TextUtils.TruncateAt.END);
|
||||
private fun canInterrupt(): Boolean {
|
||||
return mLines.size == mMaxLines && !mCalculateWholeLines &&
|
||||
(mEllipsize == null || mEllipsize == TruncateAt.END)
|
||||
}
|
||||
|
||||
public void release() {
|
||||
for (Line line : mLines) {
|
||||
line.release();
|
||||
fun release() {
|
||||
for (line in mLines) {
|
||||
line.release()
|
||||
}
|
||||
mLines.clear();
|
||||
mLines.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,335 +13,248 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type
|
||||
|
||||
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 java.util.*
|
||||
|
||||
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;
|
||||
class TypeEnvironment {
|
||||
companion object {
|
||||
private const val TAG = "TypeEnvironment"
|
||||
const val TYPE_TEXT_COLOR = -1
|
||||
const val TYPE_BG_COLOR = -2
|
||||
const val TYPE_TYPEFACE = -3
|
||||
const val TYPE_TEXT_SIZE = -4
|
||||
const val TYPE_ALIGNMENT = -5
|
||||
const val TYPE_LINE_SPACE = -6
|
||||
const val TYPE_PARAGRAPH_SPACE = -7
|
||||
const val TYPE_BORDER_TOP_WIDTH = -8
|
||||
const val TYPE_BORDER_TOP_COLOR = -9
|
||||
const val TYPE_BORDER_RIGHT_WIDTH = -10
|
||||
const val TYPE_BORDER_RIGHT_COLOR = -11
|
||||
const val TYPE_BORDER_BOTTOM_WIDTH = -12
|
||||
const val TYPE_BORDER_BOTTOM_COLOR = -13
|
||||
const val TYPE_BORDER_LEFT_WIDTH = -14
|
||||
const val TYPE_BORDER_LEFT_COLOR = -15
|
||||
const val TYPE_BORDER_PAINT = -16
|
||||
|
||||
import com.qmuiteam.qmui.type.element.Element;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
public class TypeEnvironment {
|
||||
private static final String TAG = "TypeEnvironment";
|
||||
public static final int TYPE_TEXT_COLOR = -1;
|
||||
public static final int TYPE_BG_COLOR = -2;
|
||||
public static final int TYPE_TYPEFACE = -3;
|
||||
public static final int TYPE_TEXT_SIZE = -4;
|
||||
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,
|
||||
RIGHT,
|
||||
CENTER,
|
||||
JUSTIFY
|
||||
val DEFAULT_LAST_LINE_JUSTIFY_MAX_WIDTH = (Resources.getSystem().displayMetrics.density * 36).toInt()
|
||||
}
|
||||
|
||||
private int mWidthLimit;
|
||||
private int mHeightLimit;
|
||||
|
||||
private Typeface mTypeface;
|
||||
private float mTextSize;
|
||||
private int mLineSpace;
|
||||
private int mParagraphSpace;
|
||||
private Alignment mAlignment = Alignment.JUSTIFY;
|
||||
private int mLastLineJustifyMaxWidth = (int) (Resources.getSystem().getDisplayMetrics().density * 36);
|
||||
|
||||
private int mTextColor = Color.BLACK;
|
||||
private int mBackgroundColor = Color.TRANSPARENT;
|
||||
|
||||
private Paint mPaint = new Paint();
|
||||
private Paint mBgPaint = new Paint();
|
||||
|
||||
private SparseArray<Object> mCustomProp = new SparseArray<>();
|
||||
|
||||
private SparseArray<Stack<Object>> mStack = new SparseArray<>();
|
||||
private Element mLastRunElement = null;
|
||||
|
||||
|
||||
public TypeEnvironment() {
|
||||
mPaint.setAntiAlias(true);
|
||||
mBgPaint.setAntiAlias(true);
|
||||
enum class Alignment {
|
||||
LEFT, RIGHT, CENTER, JUSTIFY
|
||||
}
|
||||
|
||||
var widthLimit = 0
|
||||
private set
|
||||
var heightLimit = 0
|
||||
private set
|
||||
|
||||
public void setMeasureLimit(int widthLimit, int heightLimit) {
|
||||
mWidthLimit = widthLimit;
|
||||
mHeightLimit = heightLimit;
|
||||
|
||||
var alignment: Alignment = Alignment.JUSTIFY
|
||||
|
||||
var lastLineJustifyMaxWidth = DEFAULT_LAST_LINE_JUSTIFY_MAX_WIDTH
|
||||
|
||||
val paint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
public int getWidthLimit() {
|
||||
return mWidthLimit;
|
||||
val bgPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
}
|
||||
private val mCustomProp: SparseArray<Any?> = SparseArray()
|
||||
private val mStack = SparseArray<Stack<Any?>>()
|
||||
|
||||
public int getHeightLimit() {
|
||||
return mHeightLimit;
|
||||
}
|
||||
|
||||
public void setLastLineJustifyMaxWidth(int lastLineJustifyMaxWidth) {
|
||||
mLastLineJustifyMaxWidth = lastLineJustifyMaxWidth;
|
||||
}
|
||||
var lineSpace = 0
|
||||
|
||||
public int getLastLineJustifyMaxWidth() {
|
||||
return mLastLineJustifyMaxWidth;
|
||||
}
|
||||
var paragraphSpace: Int = 0
|
||||
get() = field.coerceAtLeast(lineSpace)
|
||||
|
||||
public void setTypeface(Typeface typeface) {
|
||||
mTypeface = typeface;
|
||||
mPaint.setTypeface(typeface);
|
||||
}
|
||||
|
||||
public void setTextColor(int textColor) {
|
||||
mTextColor = textColor;
|
||||
mPaint.setColor(textColor);
|
||||
}
|
||||
|
||||
public void setTextSize(float textSize) {
|
||||
mTextSize = textSize;
|
||||
mPaint.setTextSize(textSize);
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
mBackgroundColor = backgroundColor;
|
||||
mBgPaint.setColor(backgroundColor);
|
||||
}
|
||||
|
||||
public void setAlignment(Alignment alignment) {
|
||||
mAlignment = alignment;
|
||||
}
|
||||
|
||||
public void setLineSpace(int lineSpace) {
|
||||
mLineSpace = lineSpace;
|
||||
}
|
||||
|
||||
public void setParagraphSpace(int paragraphSpace) {
|
||||
mParagraphSpace = paragraphSpace;
|
||||
}
|
||||
|
||||
public int getParagraphSpace() {
|
||||
return Math.max(mParagraphSpace, mLineSpace);
|
||||
}
|
||||
|
||||
public Alignment getAlignment() {
|
||||
return mAlignment;
|
||||
}
|
||||
|
||||
public int getLineSpace() {
|
||||
return mLineSpace;
|
||||
}
|
||||
|
||||
public float getTextSize() {
|
||||
return mTextSize;
|
||||
}
|
||||
|
||||
public int getTextColor() {
|
||||
return mTextColor;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
public Paint getBgPaint() {
|
||||
return mBgPaint;
|
||||
}
|
||||
|
||||
public Paint getPaint() {
|
||||
return mPaint;
|
||||
}
|
||||
|
||||
public void setCustomProp(int type, Object value) {
|
||||
mCustomProp.put(type, value);
|
||||
}
|
||||
|
||||
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);
|
||||
env.setAlignment(mAlignment);
|
||||
env.setLineSpace(mLineSpace);
|
||||
env.setParagraphSpace(mParagraphSpace);
|
||||
env.setTextSize(mTextSize);
|
||||
env.setTypeface(mTypeface);
|
||||
|
||||
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());
|
||||
var typeface: Typeface? = null
|
||||
set(value) {
|
||||
field = value
|
||||
paint.typeface = value
|
||||
}
|
||||
|
||||
var textSize: Float = 0f
|
||||
set(value) {
|
||||
field = value
|
||||
paint.textSize = value
|
||||
}
|
||||
var textColor: Int = Color.BLACK
|
||||
set(value) {
|
||||
field = value
|
||||
paint.color = value
|
||||
}
|
||||
var backgroundColor: Int = Color.TRANSPARENT
|
||||
set(value) {
|
||||
field = value
|
||||
bgPaint.color = value
|
||||
}
|
||||
|
||||
fun setCustomProp(type: Int, value: Any?) {
|
||||
mCustomProp!!.put(type, value)
|
||||
}
|
||||
|
||||
fun setBorderTop(width: Int, color: Int) {
|
||||
setCustomProp(TYPE_BORDER_TOP_WIDTH, width)
|
||||
setCustomProp(TYPE_BORDER_TOP_COLOR, color)
|
||||
}
|
||||
|
||||
val borderTopWidth: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_TOP_WIDTH)
|
||||
val borderTopColor: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_TOP_COLOR)
|
||||
|
||||
fun setBorderRight(width: Int, color: Int) {
|
||||
setCustomProp(TYPE_BORDER_RIGHT_WIDTH, width)
|
||||
setCustomProp(TYPE_BORDER_RIGHT_COLOR, color)
|
||||
}
|
||||
|
||||
val borderRightWidth: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_RIGHT_WIDTH)
|
||||
val borderRightColor: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_RIGHT_COLOR)
|
||||
|
||||
fun setBorderBottom(width: Int, color: Int) {
|
||||
setCustomProp(TYPE_BORDER_BOTTOM_WIDTH, width)
|
||||
setCustomProp(TYPE_BORDER_BOTTOM_COLOR, color)
|
||||
}
|
||||
|
||||
val borderBottomWidth: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_BOTTOM_WIDTH)
|
||||
val borderBottomColor: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_BOTTOM_COLOR)
|
||||
|
||||
fun setBorderLeft(width: Int, color: Int) {
|
||||
setCustomProp(TYPE_BORDER_LEFT_WIDTH, width)
|
||||
setCustomProp(TYPE_BORDER_LEFT_COLOR, color)
|
||||
}
|
||||
|
||||
val borderLeftWidth: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_LEFT_WIDTH)
|
||||
val borderLeftColor: Int
|
||||
get() = getIntCustomProp(TYPE_BORDER_LEFT_COLOR)
|
||||
val borderPaint: Paint
|
||||
get() {
|
||||
val obj = getCustomProp(TYPE_BORDER_PAINT)
|
||||
val paint: Paint
|
||||
if (obj == null) {
|
||||
paint = Paint()
|
||||
paint.isAntiAlias = true
|
||||
setCustomProp(TYPE_BORDER_PAINT, paint)
|
||||
} else {
|
||||
paint = obj as Paint
|
||||
}
|
||||
return paint
|
||||
}
|
||||
|
||||
fun getCustomProp(type: Int): Any? {
|
||||
return mCustomProp!![type]
|
||||
}
|
||||
|
||||
fun getIntCustomProp(type: Int): Int {
|
||||
val obj = mCustomProp!![type]
|
||||
return if (obj !is Int) {
|
||||
0
|
||||
} else obj
|
||||
}
|
||||
|
||||
fun setMeasureLimit(widthLimit: Int, heightLimit: Int) {
|
||||
this.widthLimit = widthLimit
|
||||
this.heightLimit = heightLimit
|
||||
}
|
||||
|
||||
fun snapshot(): TypeEnvironment {
|
||||
val env = TypeEnvironment()
|
||||
env.setMeasureLimit(widthLimit, heightLimit)
|
||||
env.alignment = alignment
|
||||
env.lineSpace = lineSpace
|
||||
env.paragraphSpace = paragraphSpace
|
||||
env.textSize = textSize
|
||||
env.typeface = typeface
|
||||
env.textColor = textColor
|
||||
env.backgroundColor = backgroundColor
|
||||
for (i in 0 until mStack.size()) {
|
||||
env.mStack.put(mStack.keyAt(i), mStack.valueAt(i).clone() as Stack<Any?>)
|
||||
}
|
||||
if (mCustomProp != null) {
|
||||
for (int i = 0; i < mCustomProp.size(); i++) {
|
||||
env.setCustomProp(mCustomProp.keyAt(i), mCustomProp.valueAt(i));
|
||||
for (i in 0 until mCustomProp.size()) {
|
||||
env.setCustomProp(mCustomProp.keyAt(i), mCustomProp.valueAt(i))
|
||||
}
|
||||
}
|
||||
return env;
|
||||
return env
|
||||
}
|
||||
|
||||
void setLastRunElement(Element lastRunElement) {
|
||||
mLastRunElement = lastRunElement;
|
||||
}
|
||||
|
||||
Element getLastRunElement() {
|
||||
return mLastRunElement;
|
||||
}
|
||||
|
||||
public void save(int type) {
|
||||
Stack<Object> stack = mStack.get(type);
|
||||
fun save(type: Int) {
|
||||
var stack = mStack[type]
|
||||
if (stack == null) {
|
||||
stack = new Stack<>();
|
||||
mStack.put(type, stack);
|
||||
stack = Stack()
|
||||
mStack.put(type, stack)
|
||||
}
|
||||
if (type == TYPE_TEXT_COLOR) {
|
||||
stack.push(mTextColor);
|
||||
stack.push(textColor)
|
||||
} else if (type == TYPE_BG_COLOR) {
|
||||
stack.push(mBackgroundColor);
|
||||
stack.push(backgroundColor)
|
||||
} else if (type == TYPE_TYPEFACE) {
|
||||
stack.push(mTypeface);
|
||||
stack.push(typeface)
|
||||
} else if (type == TYPE_TEXT_SIZE) {
|
||||
stack.push(mTextSize);
|
||||
stack.push(textSize)
|
||||
} else if (type == TYPE_ALIGNMENT) {
|
||||
stack.push(mAlignment);
|
||||
stack.push(alignment)
|
||||
} else if (type == TYPE_LINE_SPACE) {
|
||||
stack.push(mLineSpace);
|
||||
stack.push(lineSpace)
|
||||
} else if (type == TYPE_PARAGRAPH_SPACE) {
|
||||
stack.push(mParagraphSpace);
|
||||
stack.push(paragraphSpace)
|
||||
} else {
|
||||
stack.push(mCustomProp.get(type));
|
||||
stack.push(mCustomProp[type])
|
||||
}
|
||||
}
|
||||
|
||||
public void restore(int type) {
|
||||
Stack<Object> stack = mStack.get(type);
|
||||
fun restore(type: Int) {
|
||||
val stack = mStack[type]
|
||||
if (stack == null || stack.isEmpty()) {
|
||||
Log.d(TAG, "restore (type = " + type + ")with a empty stack.");
|
||||
return;
|
||||
Log.d(TAG, "restore (type = $type)with a empty stack.")
|
||||
return
|
||||
}
|
||||
Object v = stack.pop();
|
||||
restore(type, v);
|
||||
val v = stack.pop()
|
||||
restore(type, v)
|
||||
}
|
||||
|
||||
private void restore(int type, Object v){
|
||||
private fun restore(type: Int, v: Any?) {
|
||||
if (type == TYPE_TEXT_COLOR) {
|
||||
setTextColor((Integer) v);
|
||||
textColor = v as Int
|
||||
} else if (type == TYPE_BG_COLOR) {
|
||||
setBackgroundColor((Integer) v);
|
||||
backgroundColor = v as Int
|
||||
} else if (type == TYPE_TYPEFACE) {
|
||||
setTypeface((Typeface) v);
|
||||
typeface = v as? Typeface
|
||||
} else if (type == TYPE_TEXT_SIZE) {
|
||||
setTextSize((Float) v);
|
||||
textSize = v as Float
|
||||
} else if (type == TYPE_ALIGNMENT) {
|
||||
setAlignment((Alignment) v);
|
||||
alignment = v as Alignment
|
||||
} else if (type == TYPE_LINE_SPACE) {
|
||||
setLineSpace((Integer) v);
|
||||
lineSpace = v as Int
|
||||
} else if (type == TYPE_PARAGRAPH_SPACE) {
|
||||
setParagraphSpace((Integer) v);
|
||||
paragraphSpace = v as Int
|
||||
} else {
|
||||
setCustomProp(type, v);
|
||||
setCustomProp(type, v)
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
fun clear() {
|
||||
for (i in 0 until mStack.size()) {
|
||||
val stack = mStack.valueAt(i)
|
||||
if (stack != null && stack.size > 0) {
|
||||
while (stack.size > 1) {
|
||||
stack.pop()
|
||||
}
|
||||
restore(mStack.keyAt(i), stack.pop());
|
||||
restore(mStack.keyAt(i), stack.pop())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,162 +13,110 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type
|
||||
|
||||
package com.qmuiteam.qmui.type;
|
||||
import com.qmuiteam.qmui.type.element.Element
|
||||
import java.util.*
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
class TypeModel(
|
||||
val origin: CharSequence,
|
||||
private val mElementMap: Map<Int, Element>,
|
||||
private val mFirstElement: Element,
|
||||
private val mLastElement: Element) {
|
||||
|
||||
import com.qmuiteam.qmui.type.element.Element;
|
||||
var firstEffect: Element? = null
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TypeModel {
|
||||
private CharSequence mOrigin;
|
||||
private final Map<Integer, Element> mElementMap;
|
||||
private Element mFirstElement;
|
||||
private Element mLastElement;
|
||||
@Nullable
|
||||
private Element mFirstEffect;
|
||||
|
||||
public TypeModel(
|
||||
CharSequence origin,
|
||||
@NonNull Map<Integer, Element> elementMap,
|
||||
Element firstElement,
|
||||
Element lastElement,
|
||||
@Nullable Element firstEffect) {
|
||||
mOrigin = origin;
|
||||
mElementMap = elementMap;
|
||||
mFirstElement = firstElement;
|
||||
mLastElement = lastElement;
|
||||
mFirstEffect = firstEffect;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Element getFirstEffect() {
|
||||
return mFirstEffect;
|
||||
}
|
||||
|
||||
public CharSequence getOrigin() {
|
||||
return mOrigin;
|
||||
}
|
||||
|
||||
|
||||
public EffectRemover addBgEffect(int start, int end, final int bgColor){
|
||||
List<Integer> types = new ArrayList<>();
|
||||
types.add(TypeEnvironment.TYPE_BG_COLOR);
|
||||
return unsafeAddEffect(start, end, types, new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
env.setBackgroundColor(bgColor);
|
||||
fun addBgEffect(start: Int, end: Int, bgColor: Int): EffectRemover? {
|
||||
val types: MutableList<Int> = ArrayList()
|
||||
types.add(TypeEnvironment.TYPE_BG_COLOR)
|
||||
return unsafeAddEffect(start, end, types, object : EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
env.backgroundColor = bgColor
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
public EffectRemover addTextColorEffect(int start, int end, final int textColor){
|
||||
List<Integer> types = new ArrayList<>();
|
||||
types.add(TypeEnvironment.TYPE_TEXT_COLOR);
|
||||
return unsafeAddEffect(start, end, types, new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
env.setTextColor(textColor);
|
||||
fun addTextColorEffect(start: Int, end: Int, textColor: Int): EffectRemover? {
|
||||
val types: MutableList<Int> = ArrayList()
|
||||
types.add(TypeEnvironment.TYPE_TEXT_COLOR)
|
||||
return unsafeAddEffect(start, end, types, object : EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
env.textColor = textColor
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
public EffectRemover addUnderLineEffect(int start, int end, final int underLineColor, final int underLineHeight){
|
||||
List<Integer> types = new ArrayList<>();
|
||||
types.add(TypeEnvironment.TYPE_BORDER_BOTTOM_WIDTH);
|
||||
types.add(TypeEnvironment.TYPE_BORDER_BOTTOM_COLOR);
|
||||
return unsafeAddEffect(start, end, types, new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
env.setBorderBottom(underLineHeight, underLineColor);
|
||||
fun addUnderLineEffect(start: Int, end: Int, underLineColor: Int, underLineHeight: Int): EffectRemover? {
|
||||
val types: MutableList<Int> = ArrayList()
|
||||
types.add(TypeEnvironment.TYPE_BORDER_BOTTOM_WIDTH)
|
||||
types.add(TypeEnvironment.TYPE_BORDER_BOTTOM_COLOR)
|
||||
return unsafeAddEffect(start, end, types, object : EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
env.setBorderBottom(underLineHeight, underLineColor)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public EffectRemover unsafeAddEffect(int start, int end, List<Integer> types, EnvironmentUpdater environmentUpdater){
|
||||
Element elementStart = mElementMap.get(start);
|
||||
Element elementEnd = mElementMap.get(end);
|
||||
if(elementStart == null || elementEnd == null){
|
||||
return null;
|
||||
fun unsafeAddEffect(start: Int, end: Int, types: List<Int>, environmentUpdater: EnvironmentUpdater): EffectRemover? {
|
||||
val elementStart = mElementMap[start]
|
||||
val elementEnd = mElementMap[end]
|
||||
if (elementStart == null || elementEnd == null) {
|
||||
return null
|
||||
}
|
||||
for(Integer type: types){
|
||||
elementStart.addSaveType(type);
|
||||
elementEnd.addRestoreType(type);
|
||||
for (type in types) {
|
||||
elementStart.addSaveType(type)
|
||||
elementEnd.addRestoreType(type)
|
||||
}
|
||||
elementStart.addEnvironmentUpdater(environmentUpdater);
|
||||
if(mFirstEffect == null){
|
||||
mFirstEffect = elementStart;
|
||||
elementStart.addEnvironmentUpdater(environmentUpdater)
|
||||
firstEffect = if (firstEffect == null) {
|
||||
elementStart
|
||||
} else {
|
||||
mFirstEffect = elementStart.insertTo(mFirstElement);
|
||||
elementStart.insertEffectTo(firstEffect!!)
|
||||
}
|
||||
mFirstEffect = elementEnd.insertTo(mFirstElement);
|
||||
return new DefaultEffectRemove(this, start, end, types, environmentUpdater);
|
||||
firstEffect = elementEnd.insertEffectTo(firstEffect!!)
|
||||
return DefaultEffectRemove(this, start, end, types, environmentUpdater)
|
||||
}
|
||||
|
||||
public boolean unsafeRemoveEffect(int start, int end, List<Integer> types, EnvironmentUpdater environmentUpdater){
|
||||
Element elementStart = mElementMap.get(start);
|
||||
Element elementEnd = mElementMap.get(end);
|
||||
if(elementStart == null || elementEnd == null){
|
||||
return false;
|
||||
fun unsafeRemoveEffect(start: Int, end: Int, types: List<Int>, environmentUpdater: EnvironmentUpdater): Boolean {
|
||||
val elementStart = mElementMap[start]
|
||||
val elementEnd = mElementMap[end]
|
||||
if (elementStart == null || elementEnd == null) {
|
||||
return false
|
||||
}
|
||||
for(Integer type: types){
|
||||
elementStart.removeSaveType(type);
|
||||
elementEnd.removeStoreType(type);
|
||||
for (type in types) {
|
||||
elementStart.removeSaveType(type)
|
||||
elementEnd.removeStoreType(type)
|
||||
}
|
||||
elementStart.removeEnvironmentUpdater(environmentUpdater);
|
||||
|
||||
mFirstEffect = elementStart.removeFromEffectListIfNeeded(mFirstEffect);
|
||||
mFirstEffect = elementEnd.removeFromEffectListIfNeeded(mFirstEffect);
|
||||
return true;
|
||||
elementStart.removeEnvironmentUpdater(environmentUpdater)
|
||||
firstEffect = elementStart.removeFromEffectListIfNeeded(firstEffect)
|
||||
firstEffect = elementEnd.removeFromEffectListIfNeeded(firstEffect)
|
||||
return true
|
||||
}
|
||||
|
||||
public Element firstElement() {
|
||||
return mFirstElement;
|
||||
fun firstElement(): Element {
|
||||
return mFirstElement
|
||||
}
|
||||
|
||||
public Element lastElement() {
|
||||
return mLastElement;
|
||||
fun lastElement(): Element {
|
||||
return mLastElement
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Element get(int pos){
|
||||
return mElementMap.get(pos);
|
||||
operator fun get(pos: Int): Element? {
|
||||
return mElementMap[pos]
|
||||
}
|
||||
|
||||
public interface EffectRemover {
|
||||
void remove();
|
||||
interface EffectRemover {
|
||||
fun remove()
|
||||
}
|
||||
}
|
||||
|
||||
static class DefaultEffectRemove implements EffectRemover{
|
||||
|
||||
private final int mStart;
|
||||
private final int mEnd;
|
||||
private final List<Integer> mTypes;
|
||||
private final EnvironmentUpdater mEnvironmentUpdater;
|
||||
private final TypeModel mTypeModel;
|
||||
|
||||
public DefaultEffectRemove(
|
||||
TypeModel typeModel,
|
||||
int start,
|
||||
int end,
|
||||
List<Integer> types,
|
||||
EnvironmentUpdater environmentUpdater) {
|
||||
mTypeModel = typeModel;
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
mTypes = types;
|
||||
mEnvironmentUpdater = environmentUpdater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
mTypeModel.unsafeRemoveEffect(mStart, mEnd, mTypes, mEnvironmentUpdater);
|
||||
}
|
||||
class DefaultEffectRemove(
|
||||
private val typeModel: TypeModel,
|
||||
private val start: Int,
|
||||
private val end: Int,
|
||||
private val types: List<Int>,
|
||||
private val environmentUpdater: EnvironmentUpdater) : TypeModel.EffectRemover {
|
||||
override fun remove() {
|
||||
typeModel.unsafeRemoveEffect(start, end, types, environmentUpdater)
|
||||
}
|
||||
}
|
||||
@@ -13,13 +13,10 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.element
|
||||
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
|
||||
public class BreakWordLineElement extends CharOrPhraseElement {
|
||||
|
||||
public BreakWordLineElement() {
|
||||
super('-', -1, -1);
|
||||
setWordPart(WORD_PART_MIDDLE);
|
||||
class BreakWordLineElement : TextElement("-", -1, -1) {
|
||||
init {
|
||||
wordPart = WORD_PART_MIDDLE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,43 @@
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
/*
|
||||
* 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 android.graphics.drawable.Drawable;
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
class DrawableElement(
|
||||
drawable: Drawable,
|
||||
text: CharSequence,
|
||||
index: Int, start: Int) : Element(text, index, start) {
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment;
|
||||
|
||||
public class DrawableElement extends Element {
|
||||
|
||||
private final Drawable mDrawable;
|
||||
|
||||
public DrawableElement(
|
||||
@NonNull Drawable drawable,
|
||||
Character singleChar,
|
||||
@Nullable CharSequence text,
|
||||
int index, int originIndex) {
|
||||
super(singleChar, text, index, originIndex,
|
||||
text != null && text.length() > 2 && text.charAt(0) == '['
|
||||
? text.subSequence(1, text.length() - 1).toString() : null);
|
||||
mDrawable = drawable.mutate();
|
||||
mDrawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
val drawable = drawable.mutate().apply {
|
||||
setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(TypeEnvironment env) {
|
||||
setMeasureDimen(
|
||||
mDrawable.getIntrinsicWidth(),
|
||||
mDrawable.getIntrinsicHeight(),
|
||||
0);
|
||||
override fun onMeasure(env: TypeEnvironment) {
|
||||
setMeasureDimen(drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat(), 0f)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(TypeEnvironment env, Canvas canvas) {
|
||||
drawBg(env, canvas);
|
||||
canvas.save();
|
||||
canvas.translate(getX(), getY());
|
||||
mDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
drawBorder(env, canvas);
|
||||
override fun onDraw(env: TypeEnvironment, canvas: Canvas) {
|
||||
drawBg(env, canvas)
|
||||
canvas.save()
|
||||
canvas.translate(x, y)
|
||||
drawable.draw(canvas)
|
||||
canvas.restore()
|
||||
drawBorder(env, canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,424 +13,291 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.element
|
||||
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import com.qmuiteam.qmui.type.EnvironmentUpdater
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
abstract class Element(val text: CharSequence, val index: Int, val start: Int) {
|
||||
companion object {
|
||||
const val VISIBLE = 0
|
||||
const val GONE = 1
|
||||
const val WORD_PART_WHOLE = 0
|
||||
const val WORD_PART_START = 1
|
||||
const val WORD_PART_MIDDLE = 2
|
||||
const val WORD_PART_END = 3
|
||||
const val LINE_BREAK_TYPE_NORMAL = 0
|
||||
const val LINE_BREAK_TYPE_NOT_START = 1
|
||||
const val LINE_BREAK_TYPE_NOT_END = 2
|
||||
const val LINE_BREAK_WORD_BREAK_ALLOWED = 3
|
||||
private val NOT_START_CHARS = charArrayOf(
|
||||
',', '.', ';', ']', '>', ')', '?', '"', '\'', '!', ':', '}', '」',
|
||||
',', '。', ';', '、', '】', '》', ')', '?', '”', '!', ':', '』')
|
||||
private val NOT_END_CHARS = charArrayOf(
|
||||
'(', '<', '[', '{', '“', '「', '『', '(', '《'
|
||||
)
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.qmuiteam.qmui.type.EnvironmentUpdater;
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class Element {
|
||||
public static final int VISIBLE = 0;
|
||||
public static final int INVISIBLE = 1;
|
||||
public static final int GONE = 2;
|
||||
|
||||
public static final int WORD_PART_WHOLE = 0;
|
||||
public static final int WORD_PART_START = 1;
|
||||
public static final int WORD_PART_MIDDLE = 2;
|
||||
public static final int WORD_PART_END = 3;
|
||||
|
||||
|
||||
public static final int LINE_BREAK_TYPE_NORMAL = 0;
|
||||
public static final int LINE_BREAK_TYPE_NOT_START = 1;
|
||||
public static final int LINE_BREAK_TYPE_NOT_END = 2;
|
||||
public static final int LINE_BREAK_WORD_BREAK_ALLOWED = 3;
|
||||
|
||||
private static final char[] NOT_START_CHARS =new char[]{
|
||||
',', '.', ';', ']', '>', ')', '?', '"', '\'', '!', ':', '}', '」',
|
||||
',', '。', ';', '、', '】', '》', ')', '?', '”', '!', ':', '』',
|
||||
};
|
||||
private static final char[] NOT_END_CHARS = new char[]{
|
||||
'(', '<', '[', '{', '“', '「', '『', '(', '《'
|
||||
};
|
||||
|
||||
static {
|
||||
Arrays.sort(NOT_START_CHARS);
|
||||
Arrays.sort(NOT_END_CHARS);
|
||||
}
|
||||
|
||||
private final char mChar;
|
||||
private final CharSequence mText;
|
||||
private final int mIndex;
|
||||
private final int mOriginIndex;
|
||||
private final String mDescription;
|
||||
private Element mPrevEffect;
|
||||
private Element mNextEffect;
|
||||
private Element mPrev;
|
||||
private Element mNext;
|
||||
private int mWordPart = WORD_PART_WHOLE;
|
||||
private int mLineBreakType = LINE_BREAK_TYPE_NORMAL;
|
||||
private int mVisible = VISIBLE;
|
||||
|
||||
|
||||
private List<Integer> mSaveType;
|
||||
private List<Integer> mRestoreType;
|
||||
@Nullable
|
||||
private List<EnvironmentUpdater> mEnvironmentUpdater;
|
||||
|
||||
private float mMeasureWidth;
|
||||
private float mMeasureHeight;
|
||||
private float mX;
|
||||
private float mY;
|
||||
private float mBaseLine;
|
||||
private float mNextGapWidth;
|
||||
private String mToStringCache;
|
||||
|
||||
public Element(Character singleChar, @Nullable CharSequence text, int index, int originIndex) {
|
||||
this(singleChar, text, index, originIndex, null);
|
||||
}
|
||||
|
||||
public Element(char singleChar, @Nullable CharSequence text, int index, int originIndex, @Nullable String description) {
|
||||
mChar = singleChar;
|
||||
mText = text;
|
||||
mIndex = index;
|
||||
mOriginIndex = originIndex;
|
||||
mDescription = description;
|
||||
if(Arrays.binarySearch(NOT_START_CHARS, singleChar) >= 0){
|
||||
mLineBreakType = LINE_BREAK_TYPE_NOT_START;
|
||||
} else if(Arrays.binarySearch(NOT_END_CHARS, singleChar) >= 0){
|
||||
mLineBreakType = LINE_BREAK_TYPE_NOT_END;
|
||||
init {
|
||||
Arrays.sort(NOT_START_CHARS)
|
||||
Arrays.sort(NOT_END_CHARS)
|
||||
}
|
||||
}
|
||||
|
||||
public Element insertTo(Element head){
|
||||
if(head == null){
|
||||
return this;
|
||||
var prevEffect: Element? = null
|
||||
private set
|
||||
var nextEffect: Element? = null
|
||||
private set
|
||||
|
||||
var wordPart = WORD_PART_WHOLE
|
||||
var lineBreakType = LINE_BREAK_TYPE_NORMAL
|
||||
var visible = VISIBLE
|
||||
|
||||
|
||||
var measureWidth = 0f
|
||||
private set
|
||||
var measureHeight = 0f
|
||||
private set
|
||||
var x = 0f
|
||||
var y = 0f
|
||||
var baseLine = 0f
|
||||
var nextGapWidth = 0f
|
||||
|
||||
private var saveTypeList: MutableList<Int>? = null
|
||||
private var restoreTypeList: MutableList<Int>? = null
|
||||
private var environmentUpdaterList: MutableList<EnvironmentUpdater>? = null
|
||||
|
||||
val length: Int = text.length
|
||||
|
||||
private var _prev: Element? = null
|
||||
private var _next: Element? = null
|
||||
|
||||
var next: Element?
|
||||
get() = _next
|
||||
set(element) {
|
||||
_next = element
|
||||
if (element != null) {
|
||||
element._prev = this
|
||||
}
|
||||
}
|
||||
if(head == this){
|
||||
return head;
|
||||
var prev: Element?
|
||||
get() = _prev
|
||||
set(element) {
|
||||
_prev = element
|
||||
if (element != null) {
|
||||
element._next = this
|
||||
}
|
||||
}
|
||||
if(mIndex < head.mIndex){
|
||||
head.setPrev(this);
|
||||
this.setNext(head);
|
||||
return this;
|
||||
|
||||
private val rightWithGap: Float
|
||||
get() = x + measureWidth + nextGapWidth + 0.5f
|
||||
|
||||
init {
|
||||
if(text.length == 1){
|
||||
if (Arrays.binarySearch(NOT_START_CHARS, text[0]) >= 0) {
|
||||
lineBreakType = LINE_BREAK_TYPE_NOT_START
|
||||
} else if (Arrays.binarySearch(NOT_END_CHARS, text[0]) >= 0) {
|
||||
lineBreakType = LINE_BREAK_TYPE_NOT_END
|
||||
}
|
||||
}
|
||||
Element current = head;
|
||||
Element next = head.mNext;
|
||||
while (next != null){
|
||||
if(next == this){
|
||||
}
|
||||
|
||||
fun insertEffectTo(head: Element): Element {
|
||||
if (head === this) {
|
||||
return head
|
||||
}
|
||||
if (index < head.index) {
|
||||
head.prevEffect = this
|
||||
nextEffect = head
|
||||
return this
|
||||
}
|
||||
var current: Element = head
|
||||
var next = head.nextEffect
|
||||
while (next != null) {
|
||||
if (next === this) {
|
||||
// already in list
|
||||
return head;
|
||||
return head
|
||||
}
|
||||
if(next.mIndex > mIndex){
|
||||
current.setNext(this);
|
||||
next.setPrev(this);
|
||||
this.setPrev(current);
|
||||
this.setNext(next);
|
||||
return head;
|
||||
if (next.index > index) {
|
||||
current.nextEffect = this
|
||||
next.prevEffect = this
|
||||
prevEffect = current
|
||||
nextEffect = next
|
||||
return head
|
||||
}
|
||||
current = next;
|
||||
next = next.mNext;
|
||||
current = next
|
||||
next = next.nextEffect
|
||||
}
|
||||
current.setNext(this);
|
||||
this.setPrev(current);
|
||||
this.setNext(null);
|
||||
return head;
|
||||
current.nextEffect = this
|
||||
prevEffect = current
|
||||
nextEffect = null
|
||||
return head
|
||||
}
|
||||
|
||||
public Element removeFromEffectListIfNeeded(Element head){
|
||||
boolean noSaveType = mSaveType == null || mSaveType.isEmpty();
|
||||
boolean noRestoreType = mRestoreType == null || mRestoreType.isEmpty();
|
||||
if(noSaveType && noRestoreType){
|
||||
Element prev = mPrevEffect;
|
||||
Element next = mNextEffect;
|
||||
if(prev != null){
|
||||
prev.mNextEffect = next;
|
||||
mPrevEffect = null;
|
||||
fun removeFromEffectListIfNeeded(head: Element?): Element? {
|
||||
if(head == null){
|
||||
return null
|
||||
}
|
||||
val noSaveType = saveTypeList.isNullOrEmpty()
|
||||
val noRestoreType = restoreTypeList.isNullOrEmpty()
|
||||
if (noSaveType && noRestoreType) {
|
||||
val prev = prevEffect
|
||||
val next = nextEffect
|
||||
if (prev != null) {
|
||||
prev.nextEffect = next
|
||||
prevEffect = null
|
||||
}
|
||||
if(next != null){
|
||||
next.mPrevEffect = prev;
|
||||
mNextEffect = null;
|
||||
if (next != null) {
|
||||
next.prevEffect = prev
|
||||
nextEffect = null
|
||||
}
|
||||
if(head == this){
|
||||
return next;
|
||||
if (head === this) {
|
||||
return next
|
||||
}
|
||||
}
|
||||
return head;
|
||||
return head
|
||||
}
|
||||
|
||||
public Element getNextEffect() {
|
||||
return mNextEffect;
|
||||
fun addSaveType(type: Int) {
|
||||
val list = saveTypeList ?: ArrayList<Int>().also { saveTypeList = it}
|
||||
list.add(type)
|
||||
}
|
||||
|
||||
public Element getPrevEffect() {
|
||||
return mPrevEffect;
|
||||
}
|
||||
|
||||
public int getLineBreakType() {
|
||||
return mLineBreakType;
|
||||
}
|
||||
|
||||
public void setLineBreakType(int lineBreakType) {
|
||||
mLineBreakType = lineBreakType;
|
||||
}
|
||||
|
||||
public void setPrev(Element element) {
|
||||
this.mPrev = element;
|
||||
if (element != null) {
|
||||
element.mNext = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNext(Element element) {
|
||||
this.mNext = element;
|
||||
if (element != null) {
|
||||
element.mPrev = this;
|
||||
}
|
||||
}
|
||||
|
||||
public void setWordPart(int wordPart) {
|
||||
this.mWordPart = wordPart;
|
||||
}
|
||||
|
||||
public int getWordPart() {
|
||||
return mWordPart;
|
||||
}
|
||||
|
||||
public void addSaveType(int type) {
|
||||
if (mSaveType == null) {
|
||||
mSaveType = new ArrayList<>();
|
||||
}
|
||||
mSaveType.add(type);
|
||||
}
|
||||
|
||||
public void removeSaveType(int type) {
|
||||
if (mSaveType != null) {
|
||||
for (int i = 0; i < mSaveType.size(); i++) {
|
||||
if (mSaveType.get(i) == type) {
|
||||
mSaveType.remove(i);
|
||||
break;
|
||||
}
|
||||
fun removeSaveType(type: Int) {
|
||||
val list = saveTypeList ?: return
|
||||
for (i in list.indices) {
|
||||
if (list[i] == type) {
|
||||
list.removeAt(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addRestoreType(int type) {
|
||||
if (mRestoreType == null) {
|
||||
mRestoreType = new ArrayList<>();
|
||||
}
|
||||
mRestoreType.add(type);
|
||||
fun addRestoreType(type: Int) {
|
||||
val list = restoreTypeList ?: ArrayList<Int>().also { restoreTypeList = it}
|
||||
list.add(type)
|
||||
}
|
||||
|
||||
public void removeStoreType(int type) {
|
||||
if (mRestoreType != null) {
|
||||
for (int i = 0; i < mRestoreType.size(); i++) {
|
||||
if (mRestoreType.get(i) == type) {
|
||||
mRestoreType.remove(i);
|
||||
break;
|
||||
}
|
||||
fun removeStoreType(type: Int) {
|
||||
val list = restoreTypeList ?: return
|
||||
for (i in list.indices) {
|
||||
if (list[i] == type) {
|
||||
list.removeAt(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasEnvironmentUpdater() {
|
||||
return mEnvironmentUpdater != null && mEnvironmentUpdater.size() > 0;
|
||||
fun hasEnvironmentUpdater(): Boolean {
|
||||
return (environmentUpdaterList?.size ?: 0) > 0
|
||||
}
|
||||
|
||||
public void addEnvironmentUpdater(@NonNull EnvironmentUpdater environmentUpdater) {
|
||||
if (mEnvironmentUpdater == null) {
|
||||
mEnvironmentUpdater = new ArrayList<>();
|
||||
}
|
||||
mEnvironmentUpdater.add(environmentUpdater);
|
||||
fun hasSaveType(): Boolean {
|
||||
return !saveTypeList.isNullOrEmpty()
|
||||
}
|
||||
|
||||
public void removeEnvironmentUpdater(@NonNull EnvironmentUpdater environmentUpdater) {
|
||||
if (mEnvironmentUpdater != null) {
|
||||
mEnvironmentUpdater.remove(environmentUpdater);
|
||||
}
|
||||
fun hasRestoreType(): Boolean {
|
||||
return !restoreTypeList.isNullOrEmpty()
|
||||
}
|
||||
|
||||
public void addSingleEnvironmentUpdater(@Nullable List<Integer> changedTypes, @NonNull EnvironmentUpdater environmentUpdater) {
|
||||
fun addEnvironmentUpdater(environmentUpdater: EnvironmentUpdater) {
|
||||
val list = environmentUpdaterList ?: ArrayList<EnvironmentUpdater>().also { environmentUpdaterList = it }
|
||||
list.add(environmentUpdater)
|
||||
}
|
||||
|
||||
fun removeEnvironmentUpdater(environmentUpdater: EnvironmentUpdater) {
|
||||
val list = environmentUpdaterList ?: return
|
||||
list.remove(environmentUpdater)
|
||||
}
|
||||
|
||||
fun addSingleEnvironmentUpdater(changedTypes: List<Int>?, environmentUpdater: EnvironmentUpdater) {
|
||||
if (changedTypes != null) {
|
||||
for (Integer type : changedTypes) {
|
||||
addRestoreType(type);
|
||||
addSaveType(type);
|
||||
for (type in changedTypes) {
|
||||
addSaveType(type)
|
||||
addRestoreType(type)
|
||||
}
|
||||
}
|
||||
addEnvironmentUpdater(environmentUpdater);
|
||||
addEnvironmentUpdater(environmentUpdater)
|
||||
}
|
||||
|
||||
public void move(TypeEnvironment environment) {
|
||||
fun move(environment: TypeEnvironment) {
|
||||
if (hasEnvironmentUpdater()) {
|
||||
updateEnv(environment);
|
||||
restoreEnv(environment);
|
||||
updateEnv(environment)
|
||||
restoreEnv(environment)
|
||||
}
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return mIndex;
|
||||
override fun toString(): String {
|
||||
return text.toString()
|
||||
}
|
||||
|
||||
public int getOriginIndex() {
|
||||
return mOriginIndex;
|
||||
|
||||
protected fun setMeasureDimen(measureWidth: Float, measureHeight: Float, baseline: Float) {
|
||||
this.measureWidth = measureWidth
|
||||
this.measureHeight = measureHeight
|
||||
baseLine = baseline
|
||||
}
|
||||
|
||||
public CharSequence getText() {
|
||||
return mText;
|
||||
|
||||
fun measure(env: TypeEnvironment) {
|
||||
updateEnv(env)
|
||||
onMeasure(env)
|
||||
restoreEnv(env)
|
||||
}
|
||||
|
||||
public char getChar() {
|
||||
return mChar;
|
||||
}
|
||||
|
||||
public boolean isSingleChar() {
|
||||
return mText == null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
if(mToStringCache != null){
|
||||
return mToStringCache;
|
||||
fun draw(env: TypeEnvironment, canvas: Canvas) {
|
||||
updateEnv(env)
|
||||
if (visible == VISIBLE) {
|
||||
onDraw(env, canvas)
|
||||
}
|
||||
mToStringCache = mText != null ? mText.toString() : String.valueOf(mChar);
|
||||
return mToStringCache;
|
||||
restoreEnv(env)
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return mText != null ? mText.length() : 1;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return mDescription != null ? mDescription : toString();
|
||||
}
|
||||
|
||||
protected void setMeasureDimen(float measureWidth, float measureHeight, float baseline) {
|
||||
mMeasureWidth = measureWidth;
|
||||
mMeasureHeight = measureHeight;
|
||||
mBaseLine = baseline;
|
||||
}
|
||||
|
||||
public void setVisible(int visible) {
|
||||
mVisible = visible;
|
||||
}
|
||||
|
||||
public int getVisible() {
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
public void setX(float x) {
|
||||
mX = x;
|
||||
}
|
||||
|
||||
public void setY(float y) {
|
||||
mY = y;
|
||||
}
|
||||
|
||||
public void setNextGapWidth(float nextGapWidth) {
|
||||
mNextGapWidth = nextGapWidth;
|
||||
}
|
||||
|
||||
public float getBaseLine() {
|
||||
return mBaseLine;
|
||||
}
|
||||
|
||||
public float getMeasureWidth() {
|
||||
return mMeasureWidth;
|
||||
}
|
||||
|
||||
public float getMeasureHeight() {
|
||||
return mMeasureHeight;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return mX;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return mY;
|
||||
}
|
||||
|
||||
public Element getNext() {
|
||||
return mNext;
|
||||
}
|
||||
|
||||
public Element getPrev() {
|
||||
return mPrev;
|
||||
}
|
||||
|
||||
public final void measure(TypeEnvironment env) {
|
||||
updateEnv(env);
|
||||
onMeasure(env);
|
||||
restoreEnv(env);
|
||||
}
|
||||
|
||||
public final void draw(TypeEnvironment env, Canvas canvas) {
|
||||
updateEnv(env);
|
||||
if (mVisible == VISIBLE) {
|
||||
onDraw(env, canvas);
|
||||
fun updateEnv(env: TypeEnvironment) {
|
||||
saveTypeList?.forEach {
|
||||
env.save(it)
|
||||
}
|
||||
restoreEnv(env);
|
||||
// environmentUpdaterList?.forEach {
|
||||
// it.update(env)
|
||||
// }
|
||||
}
|
||||
|
||||
void updateEnv(TypeEnvironment env) {
|
||||
if (mSaveType != null) {
|
||||
for (Integer type : mSaveType) {
|
||||
env.save(type);
|
||||
}
|
||||
}
|
||||
if (mEnvironmentUpdater != null) {
|
||||
for (EnvironmentUpdater updater : mEnvironmentUpdater) {
|
||||
updater.update(env);
|
||||
}
|
||||
fun restoreEnv(env: TypeEnvironment) {
|
||||
restoreTypeList?.forEach {
|
||||
env.restore(it)
|
||||
}
|
||||
}
|
||||
|
||||
void restoreEnv(TypeEnvironment env) {
|
||||
if (mRestoreType != null) {
|
||||
for (Integer type : mRestoreType) {
|
||||
env.restore(type);
|
||||
}
|
||||
protected abstract fun onMeasure(env: TypeEnvironment)
|
||||
protected abstract fun onDraw(env: TypeEnvironment, canvas: Canvas)
|
||||
protected fun drawBg(env: TypeEnvironment, canvas: Canvas) {
|
||||
if (env.backgroundColor != Color.TRANSPARENT) {
|
||||
canvas.drawRect(x, y, rightWithGap, y + measureHeight, env.bgPaint)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onMeasure(TypeEnvironment env);
|
||||
|
||||
protected abstract void onDraw(TypeEnvironment env, Canvas canvas);
|
||||
|
||||
protected void drawBg(TypeEnvironment env, Canvas canvas){
|
||||
if(env.getBackgroundColor() != Color.TRANSPARENT){
|
||||
canvas.drawRect(mX, mY, getRightWithGap(), mY + mMeasureHeight, env.getBgPaint());
|
||||
protected fun drawBorder(env: TypeEnvironment, canvas: Canvas) {
|
||||
val paint = env.borderPaint
|
||||
if (env.borderLeftWidth > 0) {
|
||||
paint.color = env.borderLeftColor
|
||||
canvas.drawRect(x, y, x + env.borderLeftWidth, y + measureHeight, paint)
|
||||
}
|
||||
if (env.borderTopWidth > 0) {
|
||||
paint.color = env.borderTopColor
|
||||
canvas.drawRect(x, y, rightWithGap, y + env.borderTopWidth, paint)
|
||||
}
|
||||
if (env.borderRightWidth > 0) {
|
||||
paint.color = env.borderRightColor
|
||||
canvas.drawRect(x + measureWidth - env.borderRightWidth, y,
|
||||
x + measureWidth, y + measureHeight, paint)
|
||||
}
|
||||
if (env.borderBottomWidth > 0) {
|
||||
paint.color = env.borderBottomColor
|
||||
canvas.drawRect(x, y + measureHeight - env.borderBottomWidth,
|
||||
rightWithGap, y + measureHeight, paint)
|
||||
}
|
||||
}
|
||||
|
||||
protected float getRightWithGap(){
|
||||
return mX + mMeasureWidth + mNextGapWidth + 0.5f;
|
||||
}
|
||||
|
||||
protected void drawBorder(TypeEnvironment env, Canvas canvas){
|
||||
Paint paint = env.getBorderPaint();
|
||||
|
||||
if(env.getBorderLeftWidth() > 0){
|
||||
paint.setColor(env.getBorderLeftColor());
|
||||
canvas.drawRect(mX, mY, mX + env.getBorderLeftWidth(), mY + mMeasureHeight, paint);
|
||||
}
|
||||
|
||||
if(env.getBorderTopWidth() > 0) {
|
||||
paint.setColor(env.getBorderTopColor());
|
||||
canvas.drawRect(mX, mY, getRightWithGap(), mY + env.getBorderTopWidth(), paint);
|
||||
}
|
||||
|
||||
if(env.getBorderRightWidth() > 0){
|
||||
paint.setColor(env.getBorderRightColor());
|
||||
canvas.drawRect(mX + mMeasureWidth - env.getBorderRightWidth(), mY,
|
||||
mX + mMeasureWidth, mY + mMeasureHeight, paint);
|
||||
}
|
||||
|
||||
if(env.getBorderBottomWidth() > 0){
|
||||
paint.setColor(env.getBorderBottomColor());
|
||||
canvas.drawRect(mX, mY + mMeasureHeight - env.getBorderBottomWidth(),
|
||||
getRightWithGap(), mY + mMeasureHeight, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,39 @@
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
/*
|
||||
* 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 android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
class EmojiElement(val drawable: Drawable, text: CharSequence, index: Int, start: Int) : Element(text, index, start) {
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment;
|
||||
|
||||
public class EmojiElement extends Element {
|
||||
|
||||
private final Drawable mDrawable;
|
||||
|
||||
public EmojiElement(
|
||||
@NonNull Drawable drawable,
|
||||
Character singleChar,
|
||||
@Nullable CharSequence text,
|
||||
int index, int originIndex) {
|
||||
super(singleChar, text, index, originIndex,
|
||||
text != null && text.length() > 2 && text.charAt(0) == '['
|
||||
? text.subSequence(1, text.length() - 1).toString() : null);
|
||||
mDrawable = drawable;
|
||||
override fun onMeasure(env: TypeEnvironment) {
|
||||
val paint = env.paint
|
||||
val size = (paint.fontMetrics.descent - paint.fontMetrics.ascent).toInt()
|
||||
drawable.setBounds(0, 0, size, size)
|
||||
setMeasureDimen(size.toFloat(), size.toFloat(), 0f)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(TypeEnvironment env) {
|
||||
Paint paint = env.getPaint();
|
||||
int size = (int) (paint.getFontMetrics().descent - paint.getFontMetrics().ascent);
|
||||
mDrawable.setBounds(0, 0, size, size);
|
||||
setMeasureDimen(size, size, 0);
|
||||
override fun onDraw(env: TypeEnvironment, canvas: Canvas) {
|
||||
drawBg(env, canvas)
|
||||
canvas.save()
|
||||
canvas.translate(x, y)
|
||||
drawable.draw(canvas)
|
||||
canvas.restore()
|
||||
drawBorder(env, canvas)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(TypeEnvironment env, Canvas canvas) {
|
||||
drawBg(env, canvas);
|
||||
canvas.save();
|
||||
canvas.translate(getX(), getY());
|
||||
mDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
drawBorder(env, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,37 +13,29 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.element
|
||||
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
import android.graphics.Canvas
|
||||
import com.qmuiteam.qmui.type.EnvironmentUpdater
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
|
||||
import android.graphics.Canvas;
|
||||
class IgnoreEffectElement(list: List<Element>) : Element("", -1, -1) {
|
||||
|
||||
import com.qmuiteam.qmui.type.EnvironmentUpdater;
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class IgnoreEffectElement extends Element {
|
||||
|
||||
public IgnoreEffectElement(final List<Element> list) {
|
||||
super(' ', null, -1, -1, "");
|
||||
addEnvironmentUpdater(new EnvironmentUpdater() {
|
||||
@Override
|
||||
public void update(TypeEnvironment env) {
|
||||
for (Element element : list) {
|
||||
element.move(env);
|
||||
init {
|
||||
addEnvironmentUpdater(object: EnvironmentUpdater {
|
||||
override fun update(env: TypeEnvironment) {
|
||||
for (element in list) {
|
||||
element.move(env)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(TypeEnvironment env) {
|
||||
setMeasureDimen(0, 0, 0);
|
||||
override fun onMeasure(env: TypeEnvironment) {
|
||||
setMeasureDimen(0f, 0f, 0f)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(TypeEnvironment env, Canvas canvas) {
|
||||
override fun onDraw(env: TypeEnvironment, canvas: Canvas) {}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,38 +13,16 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.element
|
||||
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
import android.graphics.Canvas
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
|
||||
import android.graphics.Canvas;
|
||||
class NextParagraphElement(text: CharSequence, index: Int, start: Int) : Element(text, index, start) {
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment;
|
||||
|
||||
public class NextParagraphElement extends Element {
|
||||
|
||||
public NextParagraphElement(int index, int originIndex) {
|
||||
this('\n', null, index, originIndex, "\n");
|
||||
override fun onMeasure(env: TypeEnvironment) {
|
||||
setMeasureDimen(0f, 0f, 0f)
|
||||
}
|
||||
|
||||
public NextParagraphElement(char singleChar, @Nullable String text, int index, int originIndex) {
|
||||
super(singleChar, null, index, originIndex);
|
||||
}
|
||||
|
||||
public NextParagraphElement(char singleChar, @Nullable CharSequence text, int index, int originIndex, @Nullable String description) {
|
||||
super(singleChar, text, index, originIndex, description);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onMeasure(TypeEnvironment env) {
|
||||
setMeasureDimen(0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(TypeEnvironment env, Canvas canvas) {
|
||||
|
||||
}
|
||||
}
|
||||
override fun onDraw(env: TypeEnvironment, canvas: Canvas) {}
|
||||
}
|
||||
@@ -13,42 +13,23 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.element
|
||||
|
||||
package com.qmuiteam.qmui.type.element;
|
||||
import android.graphics.Canvas
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
open class TextElement(text: CharSequence, index: Int, start: Int) : Element(text, index, start) {
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeEnvironment;
|
||||
|
||||
public class CharOrPhraseElement extends Element {
|
||||
|
||||
public CharOrPhraseElement(char singleChar, int index, int originIndex) {
|
||||
super(singleChar, null, index, originIndex);
|
||||
override fun onMeasure(env: TypeEnvironment) {
|
||||
val paint = env.paint
|
||||
setMeasureDimen(paint.measureText(text, 0, text.length),
|
||||
paint.fontMetrics.descent - paint.fontMetrics.ascent,
|
||||
-paint.fontMetrics.ascent)
|
||||
}
|
||||
|
||||
public CharOrPhraseElement(char singleChar, int index, int originIndex, @Nullable String description) {
|
||||
super(singleChar, null, index, originIndex, description);
|
||||
override fun onDraw(env: TypeEnvironment, canvas: Canvas) {
|
||||
drawBg(env, canvas)
|
||||
canvas.drawText(text, 0, text.length, x, y + baseLine, env.paint)
|
||||
drawBorder(env, canvas)
|
||||
}
|
||||
|
||||
public CharOrPhraseElement(String text, int index, int originIndex) {
|
||||
super('\u0000', text, index, originIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(TypeEnvironment env) {
|
||||
Paint paint = env.getPaint();
|
||||
setMeasureDimen(paint.measureText(toString()),
|
||||
paint.getFontMetrics().descent - paint.getFontMetrics().ascent,
|
||||
-paint.getFontMetrics().ascent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(TypeEnvironment env, Canvas canvas) {
|
||||
drawBg(env, canvas);
|
||||
canvas.drawText(toString(), getX(), getY() + getBaseLine(), env.getPaint());
|
||||
drawBorder(env, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,26 +13,13 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.parser
|
||||
|
||||
package com.qmuiteam.qmui.type.parser;
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public interface EmojiResourceProvider {
|
||||
|
||||
@Nullable
|
||||
Drawable queryForDrawable(CharSequence text);
|
||||
|
||||
@Nullable
|
||||
Drawable queryForDrawable(char c);
|
||||
|
||||
@Nullable
|
||||
Drawable queryForDrawable(int codePoint);
|
||||
|
||||
@Nullable
|
||||
Drawable queryForDrawable(int firstCodePoint, int secondCodePint);
|
||||
|
||||
|
||||
}
|
||||
interface EmojiResourceProvider {
|
||||
fun queryForDrawable(text: CharSequence): Drawable?
|
||||
fun queryForDrawable(c: Char): Drawable?
|
||||
fun queryForDrawable(codePoint: Int): Drawable?
|
||||
fun queryForDrawable(firstCodePoint: Int, secondCodePint: Int): Drawable?
|
||||
}
|
||||
@@ -13,116 +13,101 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.parser
|
||||
|
||||
package com.qmuiteam.qmui.type.parser;
|
||||
import com.qmuiteam.qmui.type.TypeModel
|
||||
import com.qmuiteam.qmui.type.element.*
|
||||
import java.util.*
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
class EmojiTextParser(private val emojiProvider: EmojiResourceProvider) : TextParser {
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeModel;
|
||||
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;
|
||||
import com.qmuiteam.qmui.type.element.NextParagraphElement;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class EmojiTextParser implements TextParser {
|
||||
|
||||
private final EmojiResourceProvider mEmojiProvider;
|
||||
|
||||
public EmojiTextParser(EmojiResourceProvider provider) {
|
||||
mEmojiProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeModel parse(CharSequence text) {
|
||||
int size = text.length();
|
||||
if (size == 0) {
|
||||
return null;
|
||||
override fun parse(text: CharSequence?): TypeModel? {
|
||||
if(text == null || text.isEmpty()){
|
||||
return null
|
||||
}
|
||||
HashMap<Integer, Element> map = new HashMap<>(text.length());
|
||||
Element first = null, last = null, tmp = null;
|
||||
int index = 0;
|
||||
for (int i = 0; i < size; i++) {
|
||||
char c = text.charAt(i);
|
||||
|
||||
val size = text.length
|
||||
val map = HashMap<Int, Element>(size)
|
||||
var first: Element? = null
|
||||
var last: Element? = null
|
||||
var tmp: Element? = null
|
||||
var index = 0
|
||||
var i = 0
|
||||
while (i < size) {
|
||||
val c = text[i]
|
||||
if (c == '\n') {
|
||||
tmp = new NextParagraphElement(c, null, index, i);
|
||||
tmp = NextParagraphElement(text.subSequence(i, i + 1), index, i)
|
||||
} else if (c == '\r') {
|
||||
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
|
||||
tmp = new NextParagraphElement('\u0000', "\r\n", index, i);
|
||||
i++;
|
||||
if (i + 1 < text.length && text[i + 1] == '\n') {
|
||||
tmp = NextParagraphElement(text.subSequence(i, i + 2), index, i)
|
||||
i++
|
||||
} else {
|
||||
tmp = new NextParagraphElement(c, null, index, i);
|
||||
tmp = NextParagraphElement(text.subSequence(i, i + 1), index, i)
|
||||
}
|
||||
} else if (c == '[') {
|
||||
int j = i + 1;
|
||||
boolean find = false;
|
||||
int end = Math.min(i + 30, size);
|
||||
var j = i + 1
|
||||
var find = false
|
||||
val end = Math.min(i + 30, size)
|
||||
while (j < end) {
|
||||
if (text.charAt(j) == ']') {
|
||||
CharSequence sub = text.subSequence(i, j + 1);
|
||||
Drawable emoji = mEmojiProvider.queryForDrawable(sub);
|
||||
if (text[j] == ']') {
|
||||
val sub = text.subSequence(i, j + 1)
|
||||
val emoji = emojiProvider.queryForDrawable(sub)
|
||||
if (emoji != null) {
|
||||
tmp = new EmojiElement(emoji, '\u0000', sub, index, i);
|
||||
i = j;
|
||||
find = true;
|
||||
break;
|
||||
tmp = EmojiElement(emoji, text.subSequence(i, j + 1), index, i)
|
||||
i = j
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
j++;
|
||||
j++
|
||||
}
|
||||
if (!find) {
|
||||
tmp = new CharOrPhraseElement(c, index, i);
|
||||
tmp = TextElement(text.subSequence(i, i + 1), index, i)
|
||||
}
|
||||
} else {
|
||||
boolean handled = false;
|
||||
Drawable emoji = mEmojiProvider.queryForDrawable(c);
|
||||
var handled = false
|
||||
var emoji = emojiProvider.queryForDrawable(c)
|
||||
if (emoji != null) {
|
||||
handled = true;
|
||||
tmp = new DrawableElement(emoji, c, null, index, i);
|
||||
handled = true
|
||||
tmp = DrawableElement(emoji, text.subSequence(i, i + 1), index, i)
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
int unicode = Character.codePointAt(text, i);
|
||||
int codeCount = Character.charCount(unicode);
|
||||
emoji = mEmojiProvider.queryForDrawable(unicode);
|
||||
val unicode = Character.codePointAt(text, i)
|
||||
val codeCount = Character.charCount(unicode)
|
||||
emoji = emojiProvider.queryForDrawable(unicode)
|
||||
if (emoji != null) {
|
||||
handled = true;
|
||||
tmp = new DrawableElement(emoji, c, text.subSequence(i, i + codeCount), index, i);
|
||||
i += codeCount - 1;
|
||||
handled = true
|
||||
tmp = DrawableElement(emoji, text.subSequence(i, i + codeCount), index, i)
|
||||
i += codeCount - 1
|
||||
}
|
||||
|
||||
int nextStart = i + codeCount;
|
||||
val nextStart = i + codeCount
|
||||
if (!handled && nextStart < size) {
|
||||
int nextUnicode = Character.codePointAt(text, nextStart);
|
||||
emoji = mEmojiProvider.queryForDrawable(unicode, nextUnicode);
|
||||
val nextUnicode = Character.codePointAt(text, nextStart)
|
||||
emoji = emojiProvider.queryForDrawable(unicode, nextUnicode)
|
||||
if (emoji != null) {
|
||||
handled = true;
|
||||
int nextCodeCount = Character.charCount(nextUnicode);
|
||||
tmp = new DrawableElement(emoji, c, text.subSequence(i, nextStart + nextCodeCount), index, i);
|
||||
i = nextStart + nextCodeCount - 1;
|
||||
handled = true
|
||||
val nextCodeCount = Character.charCount(nextUnicode)
|
||||
tmp = DrawableElement(emoji, text.subSequence(i, nextStart + nextCodeCount), index, i)
|
||||
i = nextStart + nextCodeCount - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
tmp = new CharOrPhraseElement(c, index, i);
|
||||
tmp = TextElement(text.subSequence(i, i + 1), index, i)
|
||||
}
|
||||
}
|
||||
|
||||
ParserHelper.handleWordPart(c, last, tmp);
|
||||
|
||||
index++;
|
||||
ParserHelper.handleWordPart(c, last, tmp!!)
|
||||
index++
|
||||
if (first == null) {
|
||||
first = tmp;
|
||||
last = tmp;
|
||||
first = tmp
|
||||
last = tmp
|
||||
} else {
|
||||
last.setNext(tmp);
|
||||
last = tmp;
|
||||
last!!.next = tmp
|
||||
last = tmp
|
||||
}
|
||||
map.put(tmp.getIndex(), tmp);
|
||||
map[tmp.index] = tmp
|
||||
i++
|
||||
}
|
||||
return new TypeModel(text, map, first, last, null);
|
||||
return TypeModel(text, map, first!!, last!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,31 +13,28 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.parser
|
||||
|
||||
package com.qmuiteam.qmui.type.parser;
|
||||
import com.qmuiteam.qmui.type.element.Element
|
||||
|
||||
import com.qmuiteam.qmui.type.element.Element;
|
||||
object ParserHelper {
|
||||
|
||||
public class ParserHelper {
|
||||
|
||||
public static boolean isEnglishLetterOrNumber(char c){
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
|
||||
fun isEnglishLetterOrNumber(c: Char): Boolean {
|
||||
return c in 'a'..'z' || c in 'A'..'Z' || c in '0'..'9'
|
||||
}
|
||||
|
||||
public static void handleWordPart(char c, Element prev, Element curr){
|
||||
if(ParserHelper.isEnglishLetterOrNumber(c)){
|
||||
if(prev == null ||
|
||||
prev.getWordPart() == Element.WORD_PART_WHOLE ||
|
||||
prev.getWordPart() == Element.WORD_PART_END){
|
||||
curr.setWordPart(Element.WORD_PART_START);
|
||||
}else{
|
||||
curr.setWordPart(Element.WORD_PART_MIDDLE);
|
||||
fun handleWordPart(c: Char, prev: Element?, curr: Element) {
|
||||
if (isEnglishLetterOrNumber(c)) {
|
||||
if (prev == null || prev.wordPart == Element.WORD_PART_WHOLE || prev.wordPart == Element.WORD_PART_END) {
|
||||
curr.wordPart = Element.WORD_PART_START
|
||||
} else {
|
||||
curr.wordPart = Element.WORD_PART_MIDDLE
|
||||
}
|
||||
}else{
|
||||
if(prev != null && prev.getWordPart() == Element.WORD_PART_MIDDLE){
|
||||
prev.setWordPart(Element.WORD_PART_END);
|
||||
} else {
|
||||
if (prev != null && prev.wordPart == Element.WORD_PART_MIDDLE) {
|
||||
prev.wordPart = Element.WORD_PART_END
|
||||
}
|
||||
curr.setWordPart(Element.WORD_PART_WHOLE);
|
||||
curr.wordPart = Element.WORD_PART_WHOLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,52 +13,53 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.parser
|
||||
|
||||
package com.qmuiteam.qmui.type.parser;
|
||||
import com.qmuiteam.qmui.type.TypeModel
|
||||
import com.qmuiteam.qmui.type.element.Element
|
||||
import com.qmuiteam.qmui.type.element.NextParagraphElement
|
||||
import com.qmuiteam.qmui.type.element.TextElement
|
||||
import com.qmuiteam.qmui.type.parser.ParserHelper.handleWordPart
|
||||
import java.util.*
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeModel;
|
||||
import com.qmuiteam.qmui.type.element.CharOrPhraseElement;
|
||||
import com.qmuiteam.qmui.type.element.Element;
|
||||
import com.qmuiteam.qmui.type.element.NextParagraphElement;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PlainTextParser implements TextParser {
|
||||
@Override
|
||||
public TypeModel parse(CharSequence text) {
|
||||
if (text.length() == 0) {
|
||||
return null;
|
||||
class PlainTextParser : TextParser {
|
||||
override fun parse(text: CharSequence?): TypeModel? {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
HashMap<Integer, Element> map = new HashMap<>(text.length());
|
||||
Element first = null, last = null, tmp;
|
||||
int index = 0;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
val size = text.length
|
||||
val map = HashMap<Int, Element>(size)
|
||||
var first: Element? = null
|
||||
var last: Element? = null
|
||||
var tmp: Element
|
||||
var index = 0
|
||||
var i = 0
|
||||
while (i < size) {
|
||||
val c = text[i]
|
||||
if (c == '\n') {
|
||||
tmp = new NextParagraphElement(c, null, index, i);
|
||||
tmp = NextParagraphElement(text.subSequence(i, i+1), index, i)
|
||||
} else if (c == '\r') {
|
||||
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
|
||||
tmp = new NextParagraphElement('\u0000', "\r\n", index, i);
|
||||
i++;
|
||||
if (i + 1 < text.length && text[i + 1] == '\n') {
|
||||
tmp = NextParagraphElement(text.subSequence(i, i+2), index, i)
|
||||
i++
|
||||
} else {
|
||||
tmp = new NextParagraphElement(c, null, index, i);
|
||||
tmp = NextParagraphElement(text.subSequence(i, i+1), index, i)
|
||||
}
|
||||
} else {
|
||||
tmp = new CharOrPhraseElement(c, index, i);
|
||||
tmp = TextElement(text.subSequence(i, i+1), index, i)
|
||||
}
|
||||
|
||||
ParserHelper.handleWordPart(c, last, tmp);
|
||||
|
||||
index++;
|
||||
handleWordPart(c, last, tmp)
|
||||
index++
|
||||
if (first == null) {
|
||||
first = tmp;
|
||||
last = tmp;
|
||||
first = tmp
|
||||
last = tmp
|
||||
} else {
|
||||
last.setNext(tmp);
|
||||
last = tmp;
|
||||
last!!.next = tmp
|
||||
last = tmp
|
||||
}
|
||||
map.put(tmp.getIndex(), tmp);
|
||||
map[tmp.index] = tmp
|
||||
i++
|
||||
}
|
||||
return new TypeModel(text, map, first, last, null);
|
||||
return TypeModel(text, map, first!!, last!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,10 @@
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.qmuiteam.qmui.type.parser
|
||||
|
||||
package com.qmuiteam.qmui.type.parser;
|
||||
import com.qmuiteam.qmui.type.TypeModel
|
||||
|
||||
import com.qmuiteam.qmui.type.TypeModel;
|
||||
|
||||
public interface TextParser {
|
||||
TypeModel parse(CharSequence text);
|
||||
}
|
||||
interface TextParser {
|
||||
fun parse(text: CharSequence?): TypeModel?
|
||||
}
|
||||
Reference in New Issue
Block a user