mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 02:54:11 +08:00
feat(css): text-stroke support (#10399)
closes https://github.com/NativeScript/NativeScript/issues/3597 closes https://github.com/NativeScript/NativeScript/issues/3972
This commit is contained in:
Binary file not shown.
@ -10,4 +10,6 @@
|
||||
|
||||
-(void)nativeScriptSetFormattedTextDecorationAndTransform:(NSDictionary*)details letterSpacing:(CGFloat)letterSpacing lineHeight:(CGFloat)lineHeight;
|
||||
|
||||
-(void)nativeScriptSetFormattedTextStroke:(CGFloat)width color:(UIColor*)color;
|
||||
|
||||
@end
|
||||
|
@ -130,4 +130,19 @@
|
||||
((UILabel*)self).attributedText = attrText;
|
||||
}
|
||||
}
|
||||
|
||||
-(void)nativeScriptSetFormattedTextStroke:(CGFloat)width color:(UIColor*)color {
|
||||
if (width > 0) {
|
||||
NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] initWithAttributedString:((UILabel*)self).attributedText];
|
||||
[attrText addAttribute:NSStrokeWidthAttributeName value:[NSNumber numberWithFloat:width] range:(NSRange){
|
||||
0,
|
||||
attrText.length
|
||||
}];
|
||||
[attrText addAttribute:NSStrokeColorAttributeName value:color range:(NSRange){
|
||||
0,
|
||||
attrText.length
|
||||
}];
|
||||
((UILabel*)self).attributedText = attrText;
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
@ -68,7 +68,7 @@ export { CSSHelper } from './styling/css-selector';
|
||||
|
||||
export { Switch } from './switch';
|
||||
export { TabView, TabViewItem } from './tab-view';
|
||||
export { TextBase, getTransformedText, letterSpacingProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, whiteSpaceProperty, textOverflowProperty, lineHeightProperty } from './text-base';
|
||||
export { TextBase, getTransformedText, letterSpacingProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, textStrokeProperty, whiteSpaceProperty, textOverflowProperty, lineHeightProperty } from './text-base';
|
||||
export { FormattedString } from './text-base/formatted-string';
|
||||
export { Span } from './text-base/span';
|
||||
export { TextField } from './text-field';
|
||||
|
@ -7,12 +7,10 @@ import { CoreTypes } from '../../core-types';
|
||||
|
||||
export * from '../text-base';
|
||||
|
||||
let TextView: typeof android.widget.TextView;
|
||||
|
||||
@CSSType('Label')
|
||||
export class Label extends TextBase implements LabelDefinition {
|
||||
nativeViewProtected: android.widget.TextView;
|
||||
nativeTextViewProtected: android.widget.TextView;
|
||||
nativeViewProtected: org.nativescript.widgets.StyleableTextView;
|
||||
nativeTextViewProtected: org.nativescript.widgets.StyleableTextView;
|
||||
|
||||
get textWrap(): boolean {
|
||||
return this.style.whiteSpace === 'normal';
|
||||
@ -27,11 +25,7 @@ export class Label extends TextBase implements LabelDefinition {
|
||||
|
||||
@profile
|
||||
public createNativeView() {
|
||||
if (!TextView) {
|
||||
TextView = android.widget.TextView;
|
||||
}
|
||||
|
||||
return new TextView(this._context);
|
||||
return new org.nativescript.widgets.StyleableTextView(this._context);
|
||||
}
|
||||
|
||||
public initNativeView(): void {
|
||||
|
@ -26,15 +26,11 @@ const LENGTH_RE = /^-?[0-9]+[a-zA-Z%]*?$/;
|
||||
*/
|
||||
const isLength = (v) => v === '0' || LENGTH_RE.test(v);
|
||||
|
||||
/**
|
||||
* Parse a string into ShadowCSSValues
|
||||
* Supports any valid css box/text shadow combination.
|
||||
*
|
||||
* inspired by https://github.com/jxnblk/css-box-shadow/blob/master/index.js (MIT License)
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
export function parseCSSShadow(value: string): ShadowCSSValues {
|
||||
export function parseCSSShorthand(value: string): {
|
||||
values: Array<CoreTypes.LengthType>;
|
||||
color: string;
|
||||
inset: boolean;
|
||||
} {
|
||||
const parts = value.trim().split(PARTS_RE);
|
||||
const inset = parts.includes('inset');
|
||||
const first = parts[0];
|
||||
@ -44,15 +40,15 @@ export function parseCSSShadow(value: string): ShadowCSSValues {
|
||||
return null;
|
||||
}
|
||||
|
||||
let colorRaw = 'black';
|
||||
let color = 'black';
|
||||
if (first && !isLength(first) && first !== 'inset') {
|
||||
colorRaw = first;
|
||||
color = first;
|
||||
} else if (last && !isLength(last)) {
|
||||
colorRaw = last;
|
||||
color = last;
|
||||
}
|
||||
const nums = parts
|
||||
const values = parts
|
||||
.filter((n) => n !== 'inset')
|
||||
.filter((n) => n !== colorRaw)
|
||||
.filter((n) => n !== color)
|
||||
.map((val) => {
|
||||
try {
|
||||
return Length.parse(val);
|
||||
@ -60,51 +56,30 @@ export function parseCSSShadow(value: string): ShadowCSSValues {
|
||||
return CoreTypes.zeroLength;
|
||||
}
|
||||
});
|
||||
const [offsetX, offsetY, blurRadius, spreadRadius] = nums;
|
||||
|
||||
return {
|
||||
inset,
|
||||
color,
|
||||
values,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Parse a string into ShadowCSSValues
|
||||
* Supports any valid css box/text shadow combination.
|
||||
*
|
||||
* inspired by https://github.com/jxnblk/css-box-shadow/blob/master/index.js (MIT License)
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
export function parseCSSShadow(value: string): ShadowCSSValues {
|
||||
const data = parseCSSShorthand(value);
|
||||
const [offsetX, offsetY, blurRadius, spreadRadius] = data.values;
|
||||
|
||||
return {
|
||||
inset: data.inset,
|
||||
offsetX: offsetX,
|
||||
offsetY: offsetY,
|
||||
blurRadius: blurRadius,
|
||||
spreadRadius: spreadRadius,
|
||||
color: new Color(colorRaw),
|
||||
color: new Color(data.color),
|
||||
};
|
||||
}
|
||||
|
||||
// if (value.indexOf('rgb') > -1) {
|
||||
// arr = value.split(' ');
|
||||
// colorRaw = arr.pop();
|
||||
// } else {
|
||||
// arr = value.split(/[ ,]+/);
|
||||
// colorRaw = arr.pop();
|
||||
// }
|
||||
|
||||
// let offsetX: number;
|
||||
// let offsetY: number;
|
||||
// let blurRadius: number; // not currently in use
|
||||
// let spreadRadius: number; // maybe rename this to just radius
|
||||
// let color: Color = new Color(colorRaw);
|
||||
|
||||
// if (arr.length === 2) {
|
||||
// offsetX = parseFloat(arr[0]);
|
||||
// offsetY = parseFloat(arr[1]);
|
||||
// } else if (arr.length === 3) {
|
||||
// offsetX = parseFloat(arr[0]);
|
||||
// offsetY = parseFloat(arr[1]);
|
||||
// blurRadius = parseFloat(arr[2]);
|
||||
// } else if (arr.length === 4) {
|
||||
// offsetX = parseFloat(arr[0]);
|
||||
// offsetY = parseFloat(arr[1]);
|
||||
// blurRadius = parseFloat(arr[2]);
|
||||
// spreadRadius = parseFloat(arr[3]);
|
||||
// } else {
|
||||
// throw new Error('Expected 3, 4 or 5 parameters. Actual: ' + value);
|
||||
// }
|
||||
// return {
|
||||
// offsetX: offsetX,
|
||||
// offsetY: offsetY,
|
||||
// blurRadius: blurRadius,
|
||||
// spreadRadius: spreadRadius,
|
||||
// color: color,
|
||||
// };
|
||||
|
30
packages/core/ui/styling/css-stroke.spec.ts
Normal file
30
packages/core/ui/styling/css-stroke.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { parseCSSStroke } from './css-stroke';
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { Length } from './style-properties';
|
||||
import { Color } from '../../color';
|
||||
|
||||
describe('css-text-stroke', () => {
|
||||
it('empty', () => {
|
||||
const stroke = parseCSSStroke('');
|
||||
expect(stroke.width).toBe(CoreTypes.zeroLength);
|
||||
expect(stroke.color).toEqual(new Color('black'));
|
||||
});
|
||||
|
||||
it('1px navy', () => {
|
||||
const stroke = parseCSSStroke('1px navy');
|
||||
expect(stroke.width).toEqual(Length.parse('1px'));
|
||||
expect(stroke.color).toEqual(new Color('navy'));
|
||||
});
|
||||
|
||||
it('5 green', () => {
|
||||
const stroke = parseCSSStroke('5 green');
|
||||
expect(stroke.width).toEqual(Length.parse('5'));
|
||||
expect(stroke.color).toEqual(new Color('green'));
|
||||
});
|
||||
|
||||
it('2px #999', () => {
|
||||
const stroke = parseCSSStroke('2px #999');
|
||||
expect(stroke.width).toEqual(Length.parse('2px'));
|
||||
expect(stroke.color).toEqual(new Color('#999'));
|
||||
});
|
||||
});
|
23
packages/core/ui/styling/css-stroke.ts
Normal file
23
packages/core/ui/styling/css-stroke.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { Color } from '../../color';
|
||||
import { parseCSSShorthand } from './css-shadow';
|
||||
|
||||
export interface StrokeCSSValues {
|
||||
width: CoreTypes.LengthType;
|
||||
color: Color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string into StrokeCSSValues
|
||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke
|
||||
* @param value
|
||||
*/
|
||||
export function parseCSSStroke(value: string): StrokeCSSValues {
|
||||
const data = parseCSSShorthand(value);
|
||||
const [width] = data.values;
|
||||
|
||||
return {
|
||||
width,
|
||||
color: new Color(data.color),
|
||||
};
|
||||
}
|
@ -11,6 +11,7 @@ import { Trace } from '../../../trace';
|
||||
import { CoreTypes } from '../../../core-types';
|
||||
import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState } from '../../../accessibility/accessibility-types';
|
||||
import { ShadowCSSValues } from '../css-shadow';
|
||||
import { StrokeCSSValues } from '../css-stroke';
|
||||
|
||||
export interface CommonLayoutParams {
|
||||
width: number;
|
||||
@ -171,6 +172,7 @@ export class Style extends Observable implements StyleDefinition {
|
||||
public textDecoration: CoreTypes.TextDecorationType;
|
||||
public textTransform: CoreTypes.TextTransformType;
|
||||
public textShadow: ShadowCSSValues;
|
||||
public textStroke: StrokeCSSValues;
|
||||
public whiteSpace: CoreTypes.WhiteSpaceType;
|
||||
public textOverflow: CoreTypes.TextOverflowType;
|
||||
|
||||
|
@ -5,9 +5,10 @@ import { ShadowCSSValues } from '../styling/css-shadow';
|
||||
// Requires
|
||||
import { Font } from '../styling/font';
|
||||
import { backgroundColorProperty } from '../styling/style-properties';
|
||||
import { TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textProperty, textTransformProperty, textShadowProperty, letterSpacingProperty, whiteSpaceProperty, lineHeightProperty, isBold, resetSymbol } from './text-base-common';
|
||||
import { TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, whiteSpaceProperty, lineHeightProperty, isBold, resetSymbol } from './text-base-common';
|
||||
import { Color } from '../../color';
|
||||
import { colorProperty, fontSizeProperty, fontInternalProperty, paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../styling/style-properties';
|
||||
import { StrokeCSSValues } from '../styling/css-stroke';
|
||||
import { FormattedString } from './formatted-string';
|
||||
import { Span } from './span';
|
||||
import { CoreTypes } from '../../core-types';
|
||||
@ -15,7 +16,6 @@ import { layout } from '../../utils';
|
||||
import { SDK_VERSION } from '../../utils/constants';
|
||||
import { isString, isNullOrUndefined } from '../../utils/types';
|
||||
import { accessibilityIdentifierProperty } from '../../accessibility/accessibility-properties';
|
||||
import * as Utils from '../../utils';
|
||||
import { testIDProperty } from '../../ui/core/view';
|
||||
|
||||
export * from './text-base-common';
|
||||
@ -169,9 +169,9 @@ function initializeBaselineAdjustedSpan(): void {
|
||||
}
|
||||
|
||||
export class TextBase extends TextBaseCommon {
|
||||
nativeViewProtected: android.widget.TextView;
|
||||
nativeViewProtected: org.nativescript.widgets.StyleableTextView;
|
||||
// @ts-ignore
|
||||
nativeTextViewProtected: android.widget.TextView;
|
||||
nativeTextViewProtected: org.nativescript.widgets.StyleableTextView;
|
||||
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
||||
private _paintFlags: number;
|
||||
private _minHeight: number;
|
||||
@ -237,6 +237,9 @@ export class TextBase extends TextBaseCommon {
|
||||
|
||||
this._setNativeText(reset);
|
||||
}
|
||||
[textStrokeProperty.setNative](value: StrokeCSSValues) {
|
||||
this._setNativeText();
|
||||
}
|
||||
createFormattedTextNative(value: FormattedString) {
|
||||
return createSpannableStringBuilder(value, this.style.fontSize);
|
||||
}
|
||||
@ -386,7 +389,7 @@ export class TextBase extends TextBaseCommon {
|
||||
}
|
||||
}
|
||||
|
||||
[textDecorationProperty.getDefault](value: number) {
|
||||
[textDecorationProperty.getDefault]() {
|
||||
return (this._paintFlags = this.nativeTextViewProtected.getPaintFlags());
|
||||
}
|
||||
|
||||
@ -410,7 +413,7 @@ export class TextBase extends TextBaseCommon {
|
||||
}
|
||||
}
|
||||
|
||||
[textShadowProperty.getDefault](value: number) {
|
||||
[textShadowProperty.getDefault]() {
|
||||
return {
|
||||
radius: this.nativeTextViewProtected.getShadowRadius(),
|
||||
offsetX: this.nativeTextViewProtected.getShadowDx(),
|
||||
@ -498,6 +501,13 @@ export class TextBase extends TextBaseCommon {
|
||||
transformedText = getTransformedText(stringValue, this.textTransform);
|
||||
}
|
||||
|
||||
if (this.style?.textStroke) {
|
||||
this.nativeViewProtected.setTextStroke(Length.toDevicePixels(this.style.textStroke.width), this.style.textStroke.color.android, this.style.color.android);
|
||||
} else if (this.nativeViewProtected.setTextStroke) {
|
||||
// reset
|
||||
this.nativeViewProtected.setTextStroke(0, 0, 0);
|
||||
}
|
||||
|
||||
this.nativeTextViewProtected.setText(<any>transformedText);
|
||||
}
|
||||
|
||||
|
2
packages/core/ui/text-base/index.d.ts
vendored
2
packages/core/ui/text-base/index.d.ts
vendored
@ -5,6 +5,7 @@ import { Length } from '../styling/style-properties';
|
||||
import { Property, CssProperty, InheritedCssProperty } from '../core/properties';
|
||||
import { CoreTypes } from '../../core-types';
|
||||
import { ShadowCSSValues } from '../styling/css-shadow';
|
||||
import { StrokeCSSValues } from '../styling/css-stroke';
|
||||
|
||||
export class TextBase extends View implements AddChildFromBuilder {
|
||||
/**
|
||||
@ -138,6 +139,7 @@ export const textAlignmentProperty: InheritedCssProperty<Style, CoreTypes.TextAl
|
||||
export const textDecorationProperty: CssProperty<Style, CoreTypes.TextDecorationType>;
|
||||
export const textTransformProperty: CssProperty<Style, CoreTypes.TextTransformType>;
|
||||
export const textShadowProperty: CssProperty<Style, ShadowCSSValues>;
|
||||
export const textStrokeProperty: CssProperty<Style, StrokeCSSValues>;
|
||||
export const whiteSpaceProperty: CssProperty<Style, CoreTypes.WhiteSpaceType>;
|
||||
export const textOverflowProperty: CssProperty<Style, CoreTypes.TextOverflowType>;
|
||||
export const letterSpacingProperty: CssProperty<Style, number>;
|
||||
|
@ -5,11 +5,12 @@ import { ShadowCSSValues } from '../styling/css-shadow';
|
||||
// Requires
|
||||
import { Font } from '../styling/font';
|
||||
import { iosAccessibilityAdjustsFontSizeProperty, iosAccessibilityMaxFontScaleProperty, iosAccessibilityMinFontScaleProperty } from '../../accessibility/accessibility-properties';
|
||||
import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, letterSpacingProperty, lineHeightProperty, maxLinesProperty, resetSymbol } from './text-base-common';
|
||||
import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, lineHeightProperty, maxLinesProperty, resetSymbol } from './text-base-common';
|
||||
import { Color } from '../../color';
|
||||
import { FormattedString } from './formatted-string';
|
||||
import { Span } from './span';
|
||||
import { colorProperty, fontInternalProperty, fontScaleInternalProperty, Length } from '../styling/style-properties';
|
||||
import { StrokeCSSValues } from '../styling/css-stroke';
|
||||
import { isString, isNullOrUndefined } from '../../utils/types';
|
||||
import { iOSNativeHelper, layout } from '../../utils';
|
||||
import { Trace } from '../../trace';
|
||||
@ -247,6 +248,10 @@ export class TextBase extends TextBaseCommon {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[textStrokeProperty.setNative](value: StrokeCSSValues) {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[letterSpacingProperty.setNative](value: number) {
|
||||
this._setNativeText();
|
||||
}
|
||||
@ -306,16 +311,19 @@ export class TextBase extends TextBaseCommon {
|
||||
const letterSpacing = this.style.letterSpacing ? this.style.letterSpacing : 0;
|
||||
const lineHeight = this.style.lineHeight ? this.style.lineHeight : 0;
|
||||
if (this.formattedText) {
|
||||
(<any>this.nativeTextViewProtected).nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(this.getFormattedStringDetails(this.formattedText), letterSpacing, lineHeight);
|
||||
this.nativeTextViewProtected.nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(this.getFormattedStringDetails(this.formattedText) as any, letterSpacing, lineHeight);
|
||||
} else {
|
||||
// console.log('setTextDecorationAndTransform...')
|
||||
const text = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform);
|
||||
(<any>this.nativeTextViewProtected).nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight);
|
||||
this.nativeTextViewProtected.nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight);
|
||||
|
||||
if (!this.style?.color && majorVersion >= 13 && UIColor.labelColor) {
|
||||
this._setColor(UIColor.labelColor);
|
||||
}
|
||||
}
|
||||
if (this.style?.textStroke) {
|
||||
this.nativeTextViewProtected.nativeScriptSetFormattedTextStrokeColor(Length.toDevicePixels(this.style.textStroke.width, 0), this.style.textStroke.color.ios);
|
||||
}
|
||||
}
|
||||
|
||||
createFormattedTextNative(value: FormattedString) {
|
||||
|
@ -14,6 +14,7 @@ import { CoreTypes } from '../../core-types';
|
||||
import { TextBase as TextBaseDefinition } from '.';
|
||||
import { Color } from '../../color';
|
||||
import { ShadowCSSValues, parseCSSShadow } from '../styling/css-shadow';
|
||||
import { StrokeCSSValues, parseCSSStroke } from '../styling/css-stroke';
|
||||
|
||||
const CHILD_SPAN = 'Span';
|
||||
const CHILD_FORMATTED_TEXT = 'formattedText';
|
||||
@ -288,6 +289,16 @@ export const textShadowProperty = new CssProperty<Style, string | ShadowCSSValue
|
||||
});
|
||||
textShadowProperty.register(Style);
|
||||
|
||||
export const textStrokeProperty = new CssProperty<Style, string | StrokeCSSValues>({
|
||||
name: 'textStroke',
|
||||
cssName: 'text-stroke',
|
||||
affectsLayout: global.isIOS,
|
||||
valueConverter: (value) => {
|
||||
return parseCSSStroke(value);
|
||||
},
|
||||
});
|
||||
textStrokeProperty.register(Style);
|
||||
|
||||
const whiteSpaceConverter = makeParser<CoreTypes.WhiteSpaceType>(makeValidator<CoreTypes.WhiteSpaceType>('initial', 'normal', 'nowrap'));
|
||||
export const whiteSpaceProperty = new CssProperty<Style, CoreTypes.WhiteSpaceType>({
|
||||
name: 'whiteSpace',
|
||||
|
@ -403,6 +403,13 @@
|
||||
setImageLoadedListener(listener: image.Worker.OnImageLoadedListener): void;
|
||||
}
|
||||
|
||||
export class StyleableTextView extends android.widget.TextView {
|
||||
public static class: java.lang.Class<org.nativescript.widgets.StyleableTextView>;
|
||||
public onDraw(param0: globalAndroid.graphics.Canvas): void;
|
||||
public setTextStroke(param0: number, param1: number, param2: number): void;
|
||||
public constructor(param0: globalAndroid.content.Context);
|
||||
}
|
||||
|
||||
export enum TabIconRenderingMode {
|
||||
original,
|
||||
template
|
||||
|
@ -25831,8 +25831,11 @@ declare class UIView extends UIResponder implements CALayerDelegate, NSCoding, U
|
||||
layoutSubviews(): void;
|
||||
|
||||
nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(details: NSDictionary<any, any>, letterSpacing: number, lineHeight: number): void;
|
||||
|
||||
|
||||
nativeScriptSetFormattedTextStrokeColor(width: number, color: UIColor): void;
|
||||
|
||||
nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text: string, textDecoration: string, letterSpacing: number, lineHeight: number): void;
|
||||
|
||||
|
||||
needsUpdateConstraints(): boolean;
|
||||
|
||||
|
@ -0,0 +1,50 @@
|
||||
package org.nativescript.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.TextPaint;
|
||||
|
||||
/**
|
||||
* @author NathanWalker
|
||||
*/
|
||||
public class StyleableTextView extends androidx.appcompat.widget.AppCompatTextView {
|
||||
int mTextStrokeWidth = 0;
|
||||
int mTextStrokeColor = 0;
|
||||
int mTextColor = 0;
|
||||
|
||||
public StyleableTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mTextStrokeWidth > 0) {
|
||||
_applyStroke(canvas);
|
||||
} else {
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextStroke(int width, int color, int textColor) {
|
||||
mTextStrokeWidth = width;
|
||||
mTextStrokeColor = color;
|
||||
mTextColor = textColor;
|
||||
}
|
||||
|
||||
private void _applyStroke(Canvas canvas) {
|
||||
// set paint to fill mode
|
||||
TextPaint p = this.getPaint();
|
||||
p.setStyle(TextPaint.Style.FILL);
|
||||
// draw the fill part of text
|
||||
super.onDraw(canvas);
|
||||
// stroke color and width
|
||||
p.setStyle(TextPaint.Style.STROKE);
|
||||
p.setStrokeWidth(mTextStrokeWidth);
|
||||
this.setTextColor(mTextStrokeColor);
|
||||
// draw stroke
|
||||
super.onDraw(canvas);
|
||||
// draw original text color fill back (fallback to white)
|
||||
p.setStyle(TextPaint.Style.FILL);
|
||||
this.setTextColor(mTextColor != 0 ? mTextColor : 0xffffffff);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user