@nativescript-community/ui-label 1.2.2 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,41 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.2.6](https://github.com/nativescript-community/ui-label/compare/v1.2.5...v1.2.6) (2022-02-09)
7
+
8
+ **Note:** Version bump only for package @nativescript-community/ui-label
9
+
10
+
11
+
12
+
13
+
14
+ ## [1.2.5](https://github.com/nativescript-community/ui-label/compare/v1.2.4...v1.2.5) (2022-02-09)
15
+
16
+ **Note:** Version bump only for package @nativescript-community/ui-label
17
+
18
+
19
+
20
+
21
+
22
+ ## [1.2.4](https://github.com/nativescript-community/ui-label/compare/v1.2.3...v1.2.4) (2022-01-14)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * uglify fix ([f04189f](https://github.com/nativescript-community/ui-label/commit/f04189f9838615d4eb037a6423fd245538860358))
28
+
29
+
30
+
31
+
32
+
33
+ ## [1.2.3](https://github.com/nativescript-community/ui-label/compare/v1.2.2...v1.2.3) (2022-01-13)
34
+
35
+ **Note:** Version bump only for package @nativescript-community/ui-label
36
+
37
+
38
+
39
+
40
+
6
41
  ## [1.2.2](https://github.com/nativescript-community/ui-label/compare/v1.2.1...v1.2.2) (2022-01-08)
7
42
 
8
43
  **Note:** Version bump only for package @nativescript-community/ui-label
package/label-common.js CHANGED
@@ -9,15 +9,6 @@ import { layout } from '@nativescript/core/utils/utils';
9
9
  // }
10
10
  // init text to ensure font overrides are called
11
11
  init();
12
- // const CHILD_SPAN = 'Span';
13
- // const CHILD_FORMATTED_TEXT = 'formattedText';
14
- // const CHILD_FORMATTED_STRING = 'FormattedString';
15
- // FormattedString.prototype.addPropertyChangeHandler = function (span: Span) {
16
- // span.on(Observable.propertyChangeEvent, this.onPropertyChange, this);
17
- // };
18
- // FormattedString.prototype.removePropertyChangeHandler = function (span: Span) {
19
- // span.off(Observable.propertyChangeEvent, this.onPropertyChange, this);
20
- // };
21
12
  export const needFormattedStringComputation = function (target, propertyKey, descriptor) {
22
13
  const originalMethod = descriptor.value;
23
14
  descriptor.value = function (...args) {
@@ -42,7 +42,7 @@ declare abstract class LabelBase extends View implements LabelViewDefinition {
42
42
  linkUnderline: boolean;
43
43
  html: string;
44
44
  selectable: boolean;
45
- _isSingleLine: boolean;
45
+ mIsSingleLine: boolean;
46
46
  text: string;
47
47
  formattedText: FormattedString;
48
48
  get nativeTextViewProtected(): any;
@@ -76,10 +76,13 @@ declare abstract class LabelBase extends View implements LabelViewDefinition {
76
76
  }
77
77
  export declare class Label extends LabelBase {
78
78
  nativeViewProtected: com.nativescript.label.EllipsizingTextView;
79
- handleFontSize: boolean;
79
+ mHandleFontSize: boolean;
80
+ private mAutoFontSize;
80
81
  private _defaultMovementMethod;
81
82
  get nativeTextViewProtected(): com.nativescript.label.EllipsizingTextView;
82
83
  createNativeView(): com.nativescript.label.EllipsizingTextView;
84
+ private enableAutoSize;
85
+ private disableAutoSize;
83
86
  createFormattedTextNative(value: any): any;
84
87
  createHTMLString(): globalAndroid.text.SpannableStringBuilder;
85
88
  _tappable: boolean;
package/label.android.js CHANGED
@@ -12,9 +12,7 @@ export { createNativeAttributedString, enableIOSDTCoreText } from '@nativescript
12
12
  export * from './label-common';
13
13
  const sdkVersion = lazy(() => parseInt(Device.sdkVersion, 10));
14
14
  let TextView;
15
- const CHILD_SPAN = 'Span';
16
15
  const CHILD_FORMATTED_TEXT = 'formattedText';
17
- const CHILD_FORMATTED_STRING = 'FormattedString';
18
16
  const resetSymbol = Symbol('textPropertyDefault');
19
17
  var SuspendType;
20
18
  (function (SuspendType) {
@@ -159,7 +157,7 @@ let LabelBase = class LabelBase extends View {
159
157
  }
160
158
  }
161
159
  _addChildFromBuilder(name, value) {
162
- if (name === CHILD_SPAN) {
160
+ if (name === Span.name) {
163
161
  if (!this.formattedText) {
164
162
  let formattedText;
165
163
  if (overrideSpanAndFormattedStringEnabled) {
@@ -176,7 +174,7 @@ let LabelBase = class LabelBase extends View {
176
174
  this.formattedText.spans.push(value);
177
175
  }
178
176
  }
179
- else if (name === CHILD_FORMATTED_TEXT || name === CHILD_FORMATTED_STRING) {
177
+ else if (name === CHILD_FORMATTED_TEXT || name === FormattedString.name) {
180
178
  this.formattedText = value;
181
179
  value.parent = this;
182
180
  }
@@ -269,7 +267,8 @@ LabelBase = __decorate([
269
267
  export class Label extends LabelBase {
270
268
  constructor() {
271
269
  super(...arguments);
272
- this.handleFontSize = true;
270
+ this.mHandleFontSize = true;
271
+ this.mAutoFontSize = false;
273
272
  this._tappable = false;
274
273
  }
275
274
  get nativeTextViewProtected() {
@@ -382,12 +381,20 @@ export class Label extends LabelBase {
382
381
  }
383
382
  }
384
383
  [fontSizeProperty.setNative](value) {
384
+ // setTextSize is ignored if autoFontSize is enabled
385
+ // so we need to disable autoFontSize just to set textSize
386
+ if (this.mAutoFontSize) {
387
+ this.disableAutoSize();
388
+ }
385
389
  if (typeof value === 'number') {
386
390
  this.nativeTextViewProtected.setTextSize(value);
387
391
  }
388
392
  else {
389
393
  this.nativeTextViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
390
394
  }
395
+ if (this.mAutoFontSize) {
396
+ this.enableAutoSize();
397
+ }
391
398
  }
392
399
  [lineHeightProperty.setNative](value) {
393
400
  if (sdkVersion() >= 28) {
@@ -452,12 +459,19 @@ export class Label extends LabelBase {
452
459
  [paddingLeftProperty.setNative](value) {
453
460
  org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0));
454
461
  }
462
+ enableAutoSize() {
463
+ androidx.core.widget.TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this.nativeView, this.minFontSize || 10, this.maxFontSize || 200, this.autoFontSizeStep || 1, android.util.TypedValue.COMPLEX_UNIT_DIP);
464
+ }
465
+ disableAutoSize() {
466
+ androidx.core.widget.TextViewCompat.setAutoSizeTextTypeWithDefaults(this.nativeView, androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
467
+ }
455
468
  [autoFontSizeProperty.setNative](value) {
469
+ this.mAutoFontSize = value;
456
470
  if (value) {
457
- androidx.core.widget.TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this.nativeView, this.minFontSize || 10, this.maxFontSize || 200, this.autoFontSizeStep || 1, android.util.TypedValue.COMPLEX_UNIT_DIP);
471
+ this.enableAutoSize();
458
472
  }
459
473
  else {
460
- androidx.core.widget.TextViewCompat.setAutoSizeTextTypeWithDefaults(this.nativeView, androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
474
+ this.disableAutoSize();
461
475
  }
462
476
  }
463
477
  [selectableProperty.setNative](value) {
package/label.ios.d.ts CHANGED
@@ -39,5 +39,12 @@ export declare class Label extends LabelBase {
39
39
  setFormattedTextDecorationAndTransform(): void;
40
40
  fontSizeRatio: number;
41
41
  _lastAutoSizeKey: string;
42
- textViewDidChange(textView: UITextView, width?: any, height?: any, force?: boolean): void;
42
+ updateAutoFontSize({ textView, width, height, force, onlyMeasure }: {
43
+ textView: UITextView;
44
+ width?: any;
45
+ height?: any;
46
+ force?: boolean;
47
+ onlyMeasure?: boolean;
48
+ }): any;
49
+ textViewDidChange(textView: UITextView): void;
43
50
  }
package/label.ios.js CHANGED
@@ -79,7 +79,7 @@ var LabelUITextViewDelegateImpl = /** @class */ (function (_super) {
79
79
  LabelUITextViewDelegateImpl.prototype.textViewDidChange = function (textView) {
80
80
  var owner = this._owner.get();
81
81
  if (owner) {
82
- owner.textViewDidChange(textView, undefined, undefined, true);
82
+ owner.textViewDidChange(textView);
83
83
  }
84
84
  };
85
85
  LabelUITextViewDelegateImpl.ObjCProtocols = [UITextViewDelegate];
@@ -252,14 +252,23 @@ export class Label extends LabelBase {
252
252
  const widthMode = layout.getMeasureSpecMode(widthMeasureSpec);
253
253
  const height = layout.getMeasureSpecSize(heightMeasureSpec);
254
254
  const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);
255
+ let resetFont;
255
256
  if (this.autoFontSize) {
256
257
  const finiteWidth = widthMode === layout.EXACTLY;
257
258
  const finiteHeight = heightMode === layout.EXACTLY;
258
259
  if (!finiteWidth || !finiteHeight) {
259
- this.textViewDidChange(nativeView, layout.toDeviceIndependentPixels(width), layout.toDeviceIndependentPixels(height));
260
+ resetFont = this.updateAutoFontSize({
261
+ textView: nativeView,
262
+ width: layout.toDeviceIndependentPixels(width),
263
+ height: layout.toDeviceIndependentPixels(height),
264
+ onlyMeasure: true
265
+ });
260
266
  }
261
267
  }
262
268
  const desiredSize = layout.measureNativeView(nativeView, width, widthMode, height, heightMode);
269
+ if (resetFont) {
270
+ nativeView.font = resetFont;
271
+ }
263
272
  const labelWidth = widthMode === layout.AT_MOST ? Math.min(desiredSize.width, width) : desiredSize.width;
264
273
  // const labelHeight = heightMode === layout.AT_MOST ? Math.min(desiredSize.height, height) : desiredSize.height;
265
274
  const measureWidth = Math.max(labelWidth, this.effectiveMinWidth);
@@ -272,7 +281,7 @@ export class Label extends LabelBase {
272
281
  _onSizeChanged() {
273
282
  super._onSizeChanged();
274
283
  if (this.autoFontSize) {
275
- this.textViewDidChange(this.nativeTextViewProtected);
284
+ this.updateAutoFontSize({ textView: this.nativeTextViewProtected });
276
285
  }
277
286
  }
278
287
  // _htmlTappable = false;
@@ -298,7 +307,7 @@ export class Label extends LabelBase {
298
307
  fontSize,
299
308
  familyName,
300
309
  fontWeight,
301
- color: this.color,
310
+ // color: this.color,
302
311
  letterSpacing: this.letterSpacing,
303
312
  lineHeight: this.lineHeight,
304
313
  textAlignment: this.nativeTextViewProtected.textAlignment
@@ -337,7 +346,12 @@ export class Label extends LabelBase {
337
346
  }
338
347
  else {
339
348
  if (this.formattedText || this.html) {
340
- this._setNativeText();
349
+ if (this.html) {
350
+ this.updateHTMLString();
351
+ }
352
+ else {
353
+ super._setNativeText();
354
+ }
341
355
  }
342
356
  else {
343
357
  this.nativeTextViewProtected.textColor = color;
@@ -445,6 +459,10 @@ export class Label extends LabelBase {
445
459
  else {
446
460
  super._setNativeText();
447
461
  }
462
+ if (this.color) {
463
+ const color = this.color instanceof Color ? this.color.ios : this.color;
464
+ this._setColor(color);
465
+ }
448
466
  this.updateTextContainerInset();
449
467
  this._requestLayoutOnTextChanged();
450
468
  }
@@ -634,29 +652,32 @@ export class Label extends LabelBase {
634
652
  }
635
653
  }
636
654
  }
637
- textViewDidChange(textView, width, height, force = false) {
655
+ updateAutoFontSize({ textView, width, height, force = false, onlyMeasure = false }) {
656
+ let currentFont;
638
657
  if (textView && this.autoFontSize) {
639
658
  if ((!textView.attributedText && !textView.text) ||
640
659
  (width === undefined && height === undefined && CGSizeEqualToSize(textView.bounds.size, CGSizeZero))) {
641
- return;
660
+ return currentFont;
642
661
  }
643
662
  const textViewSize = textView.frame.size;
644
663
  const fixedWidth = Math.floor(width !== undefined ? width : textViewSize.width);
645
664
  const fixedHeight = Math.floor(height !== undefined ? height : textViewSize.height);
646
665
  if (fixedWidth === 0 || fixedHeight === 0) {
647
- return;
666
+ return currentFont;
648
667
  }
649
668
  const autoSizeKey = fixedWidth + '_' + fixedHeight;
650
- if (!force && autoSizeKey === this._lastAutoSizeKey) {
651
- return;
669
+ const fontSize = this.style.fontSize || 17;
670
+ let expectFont = (this.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(fontSize));
671
+ //if we are not on the "default" font size we need to measure again or we could break
672
+ //the layout behavior like for flexbox where there are multiple measure passes
673
+ if (!force && autoSizeKey === this._lastAutoSizeKey && expectFont.pointSize === textView.font.pointSize) {
674
+ return null;
652
675
  }
676
+ currentFont = textView.font;
653
677
  this._lastAutoSizeKey = autoSizeKey;
654
678
  const nbLines = textView.textContainer.maximumNumberOfLines;
655
679
  // we need to reset verticalTextAlignment or computation will be wrong
656
680
  this.updateTextContainerInset(false);
657
- const fontSize = this.style.fontSize || 17;
658
- let expectFont = (this.style.fontInternal || Font.default).getUIFont(UIFont.systemFontOfSize(fontSize));
659
- //first reset the font size
660
681
  let expectSize;
661
682
  const stepSize = this.autoFontSizeStep || 1;
662
683
  const updateFontSize = (font) => {
@@ -667,6 +688,7 @@ export class Label extends LabelBase {
667
688
  textView.font = font;
668
689
  }
669
690
  };
691
+ //first reset the font size
670
692
  updateFontSize(expectFont);
671
693
  const size = () => {
672
694
  if (nbLines === 1) {
@@ -713,14 +735,20 @@ export class Label extends LabelBase {
713
735
  }
714
736
  }
715
737
  }
716
- this.fontSizeRatio = expectFont.pointSize / fontSize;
738
+ if (!onlyMeasure) {
739
+ this.fontSizeRatio = expectFont.pointSize / fontSize;
740
+ }
717
741
  this.updateTextContainerInset();
718
742
  }
743
+ return currentFont;
744
+ }
745
+ textViewDidChange(textView) {
746
+ this.updateAutoFontSize({ textView, force: true });
719
747
  }
720
748
  [autoFontSizeProperty.setNative](value) {
721
749
  if (value) {
722
750
  if (this.isLayoutValid && (this.text || this.html || this.formattedText)) {
723
- this.textViewDidChange(this.nativeTextViewProtected, undefined, undefined, true);
751
+ this.updateAutoFontSize({ textView: this.nativeTextViewProtected, force: true });
724
752
  }
725
753
  }
726
754
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nativescript-community/ui-label",
3
- "version": "1.2.2",
3
+ "version": "1.2.6",
4
4
  "description": "Alternative to the built-in NativeScript Label but with better performance and additional features such as HTML rendering and more.",
5
5
  "main": "./label",
6
6
  "sideEffects": false,
@@ -32,7 +32,7 @@
32
32
  "license": "Apache-2.0",
33
33
  "readmeFilename": "README.md",
34
34
  "dependencies": {
35
- "@nativescript-community/text": "^1.4.29"
35
+ "@nativescript-community/text": "^1.4.36"
36
36
  },
37
- "gitHead": "ca0bb9ddbdc909b7bcb799357231cf82e2a9ee52"
37
+ "gitHead": "d460f69b4dbc638edd9691912df3c52211d65c12"
38
38
  }
@@ -1,497 +0,0 @@
1
- package com.nativescript.label;
2
-
3
- import android.content.Context;
4
- import android.content.res.TypedArray;
5
- import android.graphics.Canvas;
6
- import android.graphics.Color;
7
- import android.text.Layout;
8
- import android.text.Layout.Alignment;
9
- import android.text.Spannable;
10
- import android.text.SpannableString;
11
- import android.text.SpannableStringBuilder;
12
- import android.text.Spanned;
13
- import android.text.StaticLayout;
14
- import android.text.TextUtils;
15
- import android.text.TextUtils.TruncateAt;
16
- import android.text.style.ForegroundColorSpan;
17
- import android.util.AttributeSet;
18
- import android.util.Log;
19
- import android.util.TypedValue;
20
- import android.view.View;
21
-
22
- import java.util.ArrayList;
23
- import java.util.List;
24
- import java.util.regex.Pattern;
25
-
26
- import androidx.annotation.NonNull;
27
- import androidx.appcompat.widget.AppCompatTextView;
28
-
29
- /**
30
- * A {@link android.widget.TextView} that ellipsizes more intelligently.
31
- * This class supports ellipsizing multiline text through setting {@code android:ellipsize}
32
- * and {@code android:maxLines}.
33
- * <p/>
34
- * Note: {@link android.text.TextUtils.TruncateAt#MARQUEE} ellipsizing type is not supported.
35
- * <p>
36
- * 学习戚继光,一边儿读别人的源码,一边儿做笔记
37
- */
38
- public class EllipsizingTextView extends AppCompatTextView {
39
- public static final int ELLIPSIZE_ALPHA = 0x88;
40
- private SpannableString ELLIPSIS = new SpannableString("\u2026");
41
-
42
- private static final Pattern DEFAULT_END_PUNCTUATION
43
- = Pattern.compile("[\\.!?,;:\u2026]*$", Pattern.DOTALL);
44
-
45
- private final List<EllipsizeListener> mEllipsizeListeners = new ArrayList<>();
46
-
47
- private EllipsizeStrategy mEllipsizeStrategy;
48
-
49
- private boolean isEllipsized;
50
-
51
- //In order to be the reset method only needs to be called once
52
- private boolean isStale;
53
-
54
- //The entry used to determine the call setText () is RESETTEXT or otherwhere call, this practice is cool.
55
- private boolean programmaticChange;
56
-
57
- //text
58
- private CharSequence mFullText;
59
- private int mMaxLines;
60
-
61
- //The line spacing, this point is considered, great.
62
- private float mLineSpacingMult = 1.0f;
63
- private float mLineAddVertPad = 0.0f;
64
-
65
-
66
- private boolean wordWrap = true;
67
- private float minTextSize = 20;
68
- private float maxTextSize = 30;
69
-
70
- /**
71
- * The end punctuation which will be removed when appending {@link #ELLIPSIS}.
72
- */
73
- private Pattern mEndPunctPattern;
74
-
75
- public EllipsizingTextView(Context context) {
76
- this(context, null);
77
- }
78
-
79
-
80
- public EllipsizingTextView(Context context, AttributeSet attrs) {
81
- this(context, attrs, android.R.attr.textViewStyle);
82
- }
83
-
84
-
85
- public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
86
- super(context, attrs, defStyle);
87
- TypedArray a = context.obtainStyledAttributes(attrs,
88
- new int[]{android.R.attr.maxLines, android.R.attr.ellipsize}, defStyle, 0);
89
- setMaxLines(a.getInt(0, Integer.MAX_VALUE));
90
- a.recycle();
91
- setEndPunctuationPattern(DEFAULT_END_PUNCTUATION);
92
- maxTextSize = this.getTextSize();
93
- if (maxTextSize < 35) {
94
- maxTextSize = 30;
95
- }
96
- super.setSingleLine(false);
97
- }
98
-
99
- public void setEndPunctuationPattern(Pattern pattern) {
100
- mEndPunctPattern = pattern;
101
- }
102
-
103
- @SuppressWarnings("unused")
104
- public void addEllipsizeListener(@NonNull EllipsizeListener listener) {
105
- mEllipsizeListeners.add(listener);
106
- }
107
-
108
- @SuppressWarnings("unused")
109
- public void removeEllipsizeListener(@NonNull EllipsizeListener listener) {
110
- mEllipsizeListeners.remove(listener);
111
- }
112
-
113
- @SuppressWarnings("unused")
114
- public boolean isEllipsized() {
115
- return isEllipsized;
116
- }
117
-
118
- /**
119
- * @return The maximum number of lines displayed in this {@link android.widget.TextView}.
120
- */
121
- public int getMaxLines() {
122
- return mMaxLines;
123
- }
124
-
125
- @Override
126
- public void setMaxLines(int maxLines) {
127
- super.setMaxLines(maxLines);
128
- // super.setMaxLines((maxLines == 0) ? Integer.MAX_VALUE : maxLines);
129
- // if (maxLines == Integer.MAX_VALUE) {
130
- // maxLines = 0;
131
- // }
132
- // // super.setMaxLines((maxLines == 0) ? Integer.MAX_VALUE : maxLines);
133
- mMaxLines = maxLines;
134
- isStale = true;
135
- }
136
-
137
- public float getMinTextSize() {
138
- return minTextSize;
139
- }
140
-
141
- public void setMinTextSize(float minTextSize) {
142
- this.minTextSize = minTextSize;
143
- }
144
-
145
- public float getMaxTextSize() {
146
- return maxTextSize;
147
- }
148
-
149
- public void setMaxTextSize(float minTextSize) {
150
- this.maxTextSize = minTextSize;
151
- }
152
-
153
- /**
154
- * Determines if the last fully visible line is being ellipsized.
155
- *
156
- * @return {@code true} if the last fully visible line is being ellipsized;
157
- * otherwise, returns {@code false}.
158
- */
159
- public boolean ellipsizingLastFullyVisibleLine() {
160
- return mMaxLines == Integer.MAX_VALUE;
161
- }
162
-
163
- @Override
164
- public void setLineSpacing(float add, float mult) {
165
- mLineAddVertPad = add;
166
- mLineSpacingMult = mult;
167
- super.setLineSpacing(add, mult);
168
- }
169
-
170
- @Override
171
- public void setText(CharSequence text, BufferType type) {
172
- if (!programmaticChange) {//Distribution source
173
- mFullText = text instanceof Spanned ? (Spanned) text : text;
174
- isStale = true;
175
- }
176
- super.setText(text, type);
177
- }
178
-
179
- @Override
180
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
181
- int w = View.MeasureSpec.getSize(widthMeasureSpec);
182
- // int wm = MeasureSpec.getMode(widthMeasureSpec);
183
- int h = View.MeasureSpec.getSize(heightMeasureSpec);
184
- int hm = View.MeasureSpec.getMode(heightMeasureSpec);
185
- if (hm == 0)
186
- h = 100000;
187
- if ((hm == View.MeasureSpec.AT_MOST || hm == View.MeasureSpec.UNSPECIFIED)
188
- && (mFullText == null || mFullText.length() == 0)) {
189
- heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.EXACTLY);
190
- }
191
-
192
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
193
- }
194
- @Override
195
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
196
- super.onSizeChanged(w, h, oldw, oldh);
197
- if (ellipsizingLastFullyVisibleLine()) {//Rigorous, learning
198
- isStale = true;
199
- }
200
- }
201
-
202
- @Override
203
- public void setPadding(int left, int top, int right, int bottom) {
204
- super.setPadding(left, top, right, bottom);
205
- if (ellipsizingLastFullyVisibleLine()) {//Rigorous, learning
206
- isStale = true;
207
- }
208
- }
209
-
210
- @Override
211
- protected void onDraw(@NonNull Canvas canvas) {
212
- if (isStale) {
213
- resetText();
214
- }
215
- super.onDraw(canvas);
216
- }
217
-
218
- /**
219
- * Sets the ellipsized text if appropriate.
220
- */
221
- private void resetText() {
222
- int maxLines = getMaxLines();
223
- CharSequence workingText = mFullText;
224
- boolean ellipsized = false;
225
-
226
- if (maxLines != -1) {
227
- if (mEllipsizeStrategy == null) setEllipsize(null);
228
- workingText = mEllipsizeStrategy.processText(mFullText);
229
- ellipsized = !mEllipsizeStrategy.isInLayout(mFullText);
230
- }
231
-
232
- if (!workingText.equals(getText())) {//Avoid useless operations
233
- programmaticChange = true;
234
- try {
235
- setText(workingText);
236
- } finally {
237
- programmaticChange = false;
238
- }
239
- }
240
-
241
- isStale = false;//Switch status
242
-
243
- //Notification listener
244
- if (ellipsized != isEllipsized) {
245
- isEllipsized = ellipsized;
246
- for (EllipsizeListener listener : mEllipsizeListeners) {
247
- listener.ellipsizeStateChanged(ellipsized);
248
- }
249
- }
250
- }
251
-
252
- /**
253
- * Causes words in the text that are longer than the view is wide to be ellipsized
254
- * instead of broken in the middle. Use {@code null} to turn off ellipsizing.
255
- * <p/>
256
- * Note: Method does nothing for {@link android.text.TextUtils.TruncateAt#MARQUEE}
257
- * ellipsizing type.
258
- *
259
- * @param where part of text to ellipsize
260
- */
261
- @Override
262
- public void setEllipsize(TruncateAt where) {
263
- if (where == null) {
264
- mEllipsizeStrategy = new EllipsizeNoneStrategy();
265
- return;
266
- }
267
-
268
- switch (where) {
269
- case END:
270
- mEllipsizeStrategy = new EllipsizeEndStrategy();
271
- break;
272
- case START:
273
- mEllipsizeStrategy = new EllipsizeStartStrategy();
274
- break;
275
- case MIDDLE:
276
- mEllipsizeStrategy = new EllipsizeMiddleStrategy();
277
- break;
278
- case MARQUEE:
279
- default:
280
- mEllipsizeStrategy = new EllipsizeNoneStrategy();
281
- break;
282
- }
283
- }
284
-
285
- /**
286
- * A listener that notifies when the ellipsize state has changed.
287
- */
288
- public interface EllipsizeListener {
289
- void ellipsizeStateChanged(boolean ellipsized);
290
- }
291
-
292
- /**
293
- * A base class for an ellipsize strategy.
294
- */
295
- private abstract class EllipsizeStrategy {
296
- /**
297
- * Returns ellipsized text if the text does not fit inside of the layout;
298
- * otherwise, returns the full text.
299
- *
300
- * @param text text to process
301
- * @return Ellipsized text if the text does not fit inside of the layout;
302
- * otherwise, returns the full text.
303
- */
304
- public CharSequence processText(CharSequence text) {
305
- return !isInLayout(text) ? createEllipsizedText(text) : text;
306
- }
307
-
308
- /**
309
- * Determines if the text fits inside of the layout.
310
- *
311
- * @param text text to fit
312
- * @return {@code true} if the text fits inside of the layout;
313
- * otherwise, returns {@code false}.
314
- */
315
- public boolean isInLayout(CharSequence text) {
316
- Layout layout = createWorkingLayout(text);
317
- return layout.getLineCount() <= getLinesCount();
318
- }
319
-
320
- /**
321
- * Creates a working layout with the given text.
322
- *
323
- * @param workingText text to create layout with
324
- * @return {@link android.text.Layout} with the given text.
325
- */
326
- protected Layout createWorkingLayout(CharSequence workingText) {
327
- return new StaticLayout(workingText, getPaint(),
328
- getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(),
329
- Alignment.ALIGN_NORMAL, mLineSpacingMult,
330
- mLineAddVertPad, false /* includepad */);
331
- }
332
-
333
- /**
334
- * Get how many lines of text we are allowed to display.
335
- */
336
- protected int getLinesCount() {
337
- if (ellipsizingLastFullyVisibleLine()) {
338
- int fullyVisibleLinesCount = getFullyVisibleLinesCount();
339
- return fullyVisibleLinesCount == -1 ? 1 : fullyVisibleLinesCount;
340
- } else {
341
- return mMaxLines;
342
- }
343
- }
344
-
345
- /**
346
- * Get how many lines of text we can display so their full height is visible.
347
- */
348
- protected int getFullyVisibleLinesCount() {
349
- Layout layout = createWorkingLayout("");
350
- int height = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
351
- int lineHeight = layout.getLineBottom(0);
352
- return height / lineHeight;
353
- }
354
-
355
- /**
356
- * Creates ellipsized text from the given text.
357
- *
358
- * @param fullText text to ellipsize
359
- * @return Ellipsized text
360
- */
361
- protected abstract CharSequence createEllipsizedText(CharSequence fullText);
362
- }
363
-
364
- /**
365
- * An {@link EllipsizingTextView.EllipsizeStrategy} that
366
- * does not ellipsize text.
367
- */
368
- private class EllipsizeNoneStrategy extends EllipsizeStrategy {
369
- @Override
370
- protected CharSequence createEllipsizedText(CharSequence fullText) {
371
- return fullText;
372
- }
373
- }
374
-
375
- /**
376
- * An {@link EllipsizingTextView.EllipsizeStrategy} that
377
- * ellipsizes text at the end.
378
- */
379
- private class EllipsizeEndStrategy extends EllipsizeStrategy {
380
- @Override
381
- protected CharSequence createEllipsizedText(CharSequence fullText) {
382
- Layout layout = createWorkingLayout(fullText);
383
- int lineCount = layout.getLineCount();
384
- int lastLineIndex = Math.min(mMaxLines - 1, lineCount -1);
385
- int cutOffIndex = layout.getLineEnd(lastLineIndex);
386
- int textLength = fullText.length();
387
- int cutOffLength = textLength - cutOffIndex;
388
- if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
389
-
390
- //True to display text
391
- CharSequence workingText = TextUtils.substring(fullText, 0, textLength - cutOffLength).trim();
392
-
393
-
394
- final int lastLineStart = layout.getLineStart(lastLineIndex);
395
- final CharSequence remainder = TextUtils.ellipsize(fullText.subSequence(lastLineStart,
396
- fullText.length()), getPaint(), getWidth(), TextUtils.TruncateAt.END);
397
-
398
- //seems to be handling corner case when there is still too many chars
399
- while (!isInLayout(TextUtils.concat(stripEndPunctuation(workingText), ELLIPSIS))) {
400
- workingText = workingText.subSequence(0, workingText.length() - 1);
401
-
402
- }
403
-
404
- //first we copy the text
405
- workingText = TextUtils.concat(stripEndPunctuation(workingText), ELLIPSIS);
406
- SpannableStringBuilder dest = new SpannableStringBuilder(workingText);
407
-
408
- //then we apply spans if necessary
409
- if (fullText instanceof Spanned) {
410
- TextUtils.copySpansFrom((Spanned) fullText, 0, workingText.length(), null, dest, 0);
411
- }
412
- return dest;
413
- }
414
-
415
- /**
416
- * Strips the end punctuation from a given text according to {@link #mEndPunctPattern}.
417
- *
418
- * @param workingText text to strip end punctuation from
419
- * @return Text without end punctuation.
420
- */
421
- public String stripEndPunctuation(CharSequence workingText) {
422
- return mEndPunctPattern.matcher(workingText).replaceFirst("");
423
- }
424
- }
425
-
426
- /**
427
- * An {@link EllipsizingTextView.EllipsizeStrategy} that
428
- * ellipsizes text at the start.
429
- */
430
- private class EllipsizeStartStrategy extends EllipsizeStrategy {
431
- @Override
432
- protected CharSequence createEllipsizedText(CharSequence fullText) {
433
- Layout layout = createWorkingLayout(fullText);
434
- int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
435
- int textLength = fullText.length();
436
- int cutOffLength = textLength - cutOffIndex;
437
- if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
438
- CharSequence workingText = TextUtils.substring(fullText, cutOffLength, textLength).trim();
439
-
440
- while (!isInLayout(TextUtils.concat(ELLIPSIS, workingText))) {
441
- int firstSpace = TextUtils.indexOf(workingText, ' ');
442
- if (firstSpace == -1) {
443
- break;
444
- }
445
- workingText = TextUtils.substring(workingText, firstSpace, workingText.length()).trim();
446
- }
447
-
448
- workingText = TextUtils.concat(ELLIPSIS, workingText);
449
- SpannableStringBuilder dest = new SpannableStringBuilder(workingText);
450
-
451
- if (fullText instanceof Spanned) {
452
- TextUtils.copySpansFrom((Spanned) fullText, textLength - workingText.length(),
453
- textLength, null, dest, 0);
454
- }
455
- return dest;
456
- }
457
- }
458
-
459
- /**
460
- * An {@link EllipsizingTextView.EllipsizeStrategy} that
461
- * ellipsizes text in the middle.
462
- */
463
- private class EllipsizeMiddleStrategy extends EllipsizeStrategy {
464
- @Override
465
- protected CharSequence createEllipsizedText(CharSequence fullText) {
466
- Layout layout = createWorkingLayout(fullText);
467
- int cutOffIndex = layout.getLineEnd(mMaxLines - 1);
468
- int textLength = fullText.length();
469
- int cutOffLength = textLength - cutOffIndex;
470
- if (cutOffLength < ELLIPSIS.length()) cutOffLength = ELLIPSIS.length();
471
- cutOffLength += cutOffIndex % 2; // Make it even.
472
- String firstPart = TextUtils.substring(
473
- fullText, 0, textLength / 2 - cutOffLength / 2).trim();
474
- String secondPart = TextUtils.substring(
475
- fullText, textLength / 2 + cutOffLength / 2, textLength).trim();
476
-
477
- while (!isInLayout(TextUtils.concat(firstPart, ELLIPSIS, secondPart))) {
478
- int lastSpaceFirstPart = firstPart.lastIndexOf(' ');
479
- int firstSpaceSecondPart = secondPart.indexOf(' ');
480
- if (lastSpaceFirstPart == -1 || firstSpaceSecondPart == -1) break;
481
- firstPart = firstPart.substring(0, lastSpaceFirstPart).trim();
482
- secondPart = secondPart.substring(firstSpaceSecondPart, secondPart.length()).trim();
483
- }
484
-
485
- SpannableStringBuilder firstDest = new SpannableStringBuilder(firstPart);
486
- SpannableStringBuilder secondDest = new SpannableStringBuilder(secondPart);
487
-
488
- if (fullText instanceof Spanned) {
489
- TextUtils.copySpansFrom((Spanned) fullText, 0, firstPart.length(),
490
- null, firstDest, 0);
491
- TextUtils.copySpansFrom((Spanned) fullText, textLength - secondPart.length(),
492
- textLength, null, secondDest, 0);
493
- }
494
- return TextUtils.concat(firstDest, ELLIPSIS, secondDest);
495
- }
496
- }
497
- }