Skip to content

Commit 2b22e82

Browse files
committed
feat(canvas): ellipsize support for StaticLayout
1 parent fadf4bb commit 2b22e82

File tree

4 files changed

+140
-22
lines changed

4 files changed

+140
-22
lines changed
Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,113 @@
11
package com.akylas.canvas;
22

33
import android.os.Build;
4-
import android.graphics.Typeface;
4+
import android.graphics.Canvas;
55
import android.graphics.Paint;
6-
import android.text.TextPaint;
6+
import android.graphics.Typeface;
77
import android.text.Layout;
8+
import android.text.SpannableString;
9+
import android.text.SpannableStringBuilder;
10+
import android.text.TextPaint;
11+
import android.text.TextUtils;
812
import java.lang.CharSequence;
913

1014
public class StaticLayout {
1115

16+
public static android.text.StaticLayout.Builder createStaticLayoutBuilder(CharSequence source, TextPaint paint, int width,
17+
Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
18+
android.text.StaticLayout.Builder builder = android.text.StaticLayout.Builder
19+
.obtain(source, 0, source.length(), paint, width)
20+
.setBreakStrategy(android.text.Layout.BREAK_STRATEGY_SIMPLE)
21+
.setAlignment(align)
22+
.setLineSpacing(spacingadd, spacingmult)
23+
.setIncludePad(includepad)
24+
.setEllipsizedWidth(ellipsizedWidth)
25+
.setEllipsize(ellipsize);
26+
if (Build.VERSION.SDK_INT >= 26) {
27+
builder = builder.setJustificationMode(android.text.Layout.JUSTIFICATION_MODE_NONE);
28+
}
29+
return builder;
30+
}
31+
1232
public static android.text.StaticLayout createStaticLayout(CharSequence source, TextPaint paint, int width,
13-
Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad) {
14-
15-
if (Build.VERSION.SDK_INT >= 24) {
16-
android.text.StaticLayout.Builder builder = android.text.StaticLayout.Builder
17-
.obtain(source, 0, source.length(), paint, width)
18-
.setBreakStrategy(android.text.Layout.BREAK_STRATEGY_SIMPLE).setAlignment(align)
19-
.setLineSpacing(spacingadd, spacingmult).setIncludePad(includepad);
20-
if (Build.VERSION.SDK_INT >= 26) {
21-
builder = builder.setJustificationMode(android.text.Layout.JUSTIFICATION_MODE_NONE);
22-
}
23-
return builder.build();
33+
Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
34+
35+
if (Build.VERSION.SDK_INT >= 23) {
36+
return createStaticLayoutBuilder(source, paint, width, align, spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth).build();
2437
} else {
25-
return new android.text.StaticLayout(source, paint, width, align, spacingmult, spacingadd, includepad);
38+
return new android.text.StaticLayout(source, 0, source.length(), paint, width, align, spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
2639
}
2740
}
2841

2942
public static android.text.StaticLayout createStaticLayout(CharSequence source, Paint paint, int width,
30-
Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad) {
31-
return createStaticLayout(source, new TextPaint(paint), width, align, spacingmult, spacingadd, includepad);
43+
Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
44+
return createStaticLayout(source, new TextPaint(paint), width, align, spacingmult, spacingadd, includepad,ellipsize, ellipsizedWidth);
45+
}
46+
47+
public static void draw(android.text.StaticLayout staticLayout, Canvas canvas, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxHeight) {
48+
if (maxHeight != -1 && ellipsize != null) {
49+
50+
// Calculate the number of lines that fit within the available height
51+
int lineCount = staticLayout.getLineCount();
52+
int maxLines = lineCount;
53+
54+
int i = 0;
55+
float lineHeight = staticLayout.getLineBottom(i) - staticLayout.getLineTop(i);
56+
float totalHeight = lineHeight * lineCount;
57+
58+
while (totalHeight > maxHeight && maxLines > 0) {
59+
maxLines--;
60+
i++;
61+
lineHeight = staticLayout.getLineBottom(i) - staticLayout.getLineTop(i);
62+
totalHeight = lineHeight * maxLines;
63+
}
64+
// Check if truncation is needed
65+
if (maxLines < lineCount) {
66+
if (Build.VERSION.SDK_INT >= 23 && ellipsize == TextUtils.TruncateAt.END) {
67+
android.text.StaticLayout.Builder builder = createStaticLayoutBuilder(staticLayout.getText(),
68+
staticLayout.getPaint(),
69+
staticLayout.getWidth(),
70+
staticLayout.getAlignment(),
71+
staticLayout.getSpacingMultiplier(),
72+
staticLayout.getSpacingAdd(),
73+
includepad,
74+
ellipsize,
75+
ellipsizedWidth);
76+
builder.setMaxLines(maxLines);
77+
staticLayout = builder.build();
78+
} else {
79+
int truncationIndex = staticLayout.getLineEnd(maxLines - 1);
80+
81+
// Truncate the text by replacing the characters after the truncation index with ellipsis
82+
SpannableStringBuilder truncatedText = null;
83+
if (ellipsize == TextUtils.TruncateAt.END || ellipsize == TextUtils.TruncateAt.MARQUEE) {
84+
truncatedText = new SpannableStringBuilder(staticLayout.getText().subSequence(0, truncationIndex));
85+
truncatedText.append("…");
86+
87+
} else if (ellipsize == TextUtils.TruncateAt.START) {
88+
truncatedText = new SpannableStringBuilder("…");
89+
truncatedText.append(staticLayout.getText().subSequence(0, truncationIndex));
90+
91+
} else {
92+
int split = truncationIndex/2;
93+
truncatedText = new SpannableStringBuilder(staticLayout.getText().subSequence(0, split));
94+
truncatedText.append("…");
95+
truncatedText.append(staticLayout.getText().subSequence(split, truncationIndex));
96+
}
97+
98+
// Re-create the StaticLayout with the truncated text
99+
staticLayout = createStaticLayout(new SpannableString(truncatedText), staticLayout.getPaint(),
100+
staticLayout.getWidth(),
101+
staticLayout.getAlignment(),
102+
staticLayout.getSpacingMultiplier(),
103+
staticLayout.getSpacingAdd(),
104+
includepad,
105+
ellipsize,
106+
ellipsizedWidth);
107+
108+
}
109+
}
110+
}
111+
staticLayout.draw(canvas);
32112
}
33113
}

src/ui-canvas/index.android.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ function drawViewOnCanvas(canvas: android.graphics.Canvas, view: View, rect?: an
9696
class ProxyClass<T> {
9797
mNative: T;
9898
static augmentedMethods = [];
99+
static nonNativeMethods = [];
99100
getNative() {
100101
return this.mNative;
101102
}
@@ -106,7 +107,7 @@ class ProxyClass<T> {
106107
handleCustomMethods(target: this, native: T, methodName: string, args: any[]): any {}
107108
get(target: this, name, receiver) {
108109
const native = target.getNative();
109-
if (native && (target.constructor['augmentedMethods'].indexOf(name) >= 0 || native[name])) {
110+
if (native && target.constructor['nonNativeMethods'].indexOf(name) === -1 && (target.constructor['augmentedMethods'].indexOf(name) >= 0 || native[name])) {
110111
return function (...args) {
111112
const methodName = name;
112113
for (let index = 0; index < args.length; index++) {
@@ -417,17 +418,47 @@ export class BitmapShader extends ProxyClass<android.graphics.BitmapShader> {
417418
}
418419
}
419420

421+
function lineBreakToEllipsize(value) {
422+
if (typeof value === 'string') {
423+
switch (value) {
424+
case 'end':
425+
return android.text.TextUtils.TruncateAt.END;
426+
case 'start':
427+
return android.text.TextUtils.TruncateAt.START;
428+
case 'marquee':
429+
return android.text.TextUtils.TruncateAt.MARQUEE;
430+
case 'middle':
431+
return android.text.TextUtils.TruncateAt.MIDDLE;
432+
default:
433+
return null;
434+
}
435+
}
436+
return value;
437+
}
438+
420439
export class StaticLayout extends ProxyClass<android.text.StaticLayout> {
421-
constructor(text: any, paint: android.graphics.Paint, width: number, align = LayoutAlignment.ALIGN_NORMAL, spacingmult = 1, spacingadd = 0, includepad = true) {
440+
ellipsize: android.text.TextUtils.TruncateAt;
441+
static nonNativeMethods = ['draw'];
442+
constructor(
443+
text: any,
444+
paint: android.graphics.Paint,
445+
width: number,
446+
align = LayoutAlignment.ALIGN_NORMAL,
447+
spacingmult = 1,
448+
spacingadd = 0,
449+
private includepad = true,
450+
ellipsize = null,
451+
private ellipsizedWidth = width
452+
) {
422453
super();
423454
paint = (paint as any).getNative ? (paint as any).getNative() : paint;
424455

425456
if (typeof text === 'boolean' || typeof text === 'number') {
426457
// in case it is a number or a boolean
427458
text = text + '';
428459
}
429-
this.mNative = com.akylas.canvas.StaticLayout.createStaticLayout(text, paint, width, align, spacingmult, spacingadd, includepad);
430-
460+
this.ellipsize = lineBreakToEllipsize(ellipsize);
461+
this.mNative = com.akylas.canvas.StaticLayout.createStaticLayout(text, paint, width, align, spacingmult, spacingadd, includepad, this.ellipsize, ellipsizedWidth);
431462
return this;
432463
}
433464

@@ -446,6 +477,10 @@ export class StaticLayout extends ProxyClass<android.text.StaticLayout> {
446477
//@ts-ignore
447478
return android.text.StaticLayout.getDesiredWidth(...args);
448479
}
480+
481+
draw(canvas: Canvas, maxHeight = -1) {
482+
com.akylas.canvas.StaticLayout.draw(this.getNative(), canvas.getNative(), this.includepad, this.ellipsize, this.ellipsizedWidth, maxHeight);
483+
}
449484
}
450485
let Cap, Direction, DrawFilter, FillType, Join, Matrix, Op, PathEffect, Rect, RectF, Style, TileMode, FontMetrics, Align, LayoutAlignment;
451486
let PorterDuffMode, PorterDuffXfermode;

src/ui-canvas/index.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class Paint {
9494
// export class StaticLayout {
9595
// }
9696
export class StaticLayout extends android.text.StaticLayout {
97-
constructor(text: any, paint: Paint, width: number, align, spacingmult, spacingadd, includepad);
97+
constructor(text: any, paint: Paint, width: number, align?, spacingmult?, spacingadd?, includepad?, ellipsize?, ellipsizedWidth?);
9898
public draw(canvas: any, path?: any, paint?: any, param3?: number): void;
9999

100100
static getDesiredWidth(text: any, paint: any);
@@ -341,4 +341,3 @@ declare class CanvasView extends View {
341341

342342
export function createImage(options: { width: number; height: number; scale?: number; config?: any }): ImageSource;
343343
export function releaseImage(image: ImageSource);
344-

src/ui-canvas/index.ios.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,10 @@ export class StaticLayout {
22752275
paragraphStyle.alignment = NSTextAlignment.Right;
22762276
break;
22772277
}
2278+
2279+
if (this.lineBreak) {
2280+
paragraphStyle.lineBreakMode = lineBreakToLineBreakMode(this.lineBreak);
2281+
}
22782282
const fullRange = { location: 0, length: nsAttributedString.length };
22792283
nsAttributedString.addAttributeValueRange(NSParagraphStyleAttributeName, paragraphStyle, fullRange);
22802284
this.toDraw = nsAttributedString;

0 commit comments

Comments
 (0)