feat(android): clickable span

Initial support for clickable span on Android
This commit is contained in:
Eduardo Speroni
2019-03-26 18:44:10 -03:00
parent b436ecde36
commit dbb5684153
3 changed files with 107 additions and 1 deletions

View File

@@ -50,6 +50,16 @@ export class Span extends ViewBase {
* Gets or sets the text for the span.
*/
public text: string;
/**
* String value used when hooking to linkClicked event.
*/
public static linkClickedEvent: string;
/**
* Gets if the span is clickable or not.
*/
public readonly clickable: boolean;
//@private
/**

View File

@@ -2,10 +2,12 @@
import { Span as SpanDefinition } from "./span";
import { ViewBase } from "../ui/core/view";
import { FontStyle, FontWeight, } from "../ui/styling/font";
import { TextDecoration } from "../ui/text-base";
import { TextDecoration, EventData } from "../ui/text-base";
export class Span extends ViewBase implements SpanDefinition {
static linkClickEvent = "linkClick";
private _text: string;
private _clickable: boolean = false;
get fontFamily(): string {
return this.style.fontFamily;
@@ -68,7 +70,29 @@ export class Span extends ViewBase implements SpanDefinition {
}
}
get clickable(): boolean {
return this._clickable;
}
addEventListener(arg: string, callback: (data: EventData) => void, thisArg?: any) {
console.log(arg);
super.addEventListener(arg, callback, thisArg);
this._setClickable(this.hasListeners(Span.linkClickEvent));
}
removeEventListener(arg: string, callback?: any, thisArg?: any) {
super.removeEventListener(arg, callback, thisArg);
this._setClickable(this.hasListeners(Span.linkClickEvent));
}
_setTextInternal(value: string): void {
this._text = value;
}
private _setClickable(value: boolean): void {
if (this._clickable !== value) {
this._clickable = value;
this.notifyPropertyChange("clickable", value);
}
}
}

View File

@@ -48,6 +48,41 @@ function initializeTextTransformation(): void {
TextTransformation = TextTransformationImpl;
}
interface ClickableSpan {
new (owner: Span): android.text.style.ClickableSpan;
}
let ClickableSpan: ClickableSpan;
function initializeClickableSpan(): void {
if (ClickableSpan) {
return;
}
class ClickableSpanImpl extends android.text.style.ClickableSpan {
owner: WeakRef<Span>;
constructor(owner: Span) {
super();
this.owner = new WeakRef(owner);
return global.__native(this);
}
onClick(view: android.view.View): void {
const owner = this.owner.get();
if (owner) {
owner.notify({ eventName: Span.linkClickEvent, object: owner });
}
view.clearFocus();
view.invalidate();
}
updateDrawState(tp: android.text.TextPaint): void {
// don't style as link
}
}
ClickableSpan = ClickableSpanImpl;
}
export class TextBase extends TextBaseCommon {
nativeViewProtected: android.widget.TextView;
nativeTextViewProtected: android.widget.TextView;
@@ -57,12 +92,15 @@ export class TextBase extends TextBaseCommon {
private _maxHeight: number;
private _minLines: number;
private _maxLines: number;
private _clickable: boolean = false;
private _defaultMovementMethod: android.text.method.MovementMethod;
public initNativeView(): void {
super.initNativeView();
initializeTextTransformation();
const nativeView = this.nativeTextViewProtected;
this._defaultTransformationMethod = nativeView.getTransformationMethod();
this._defaultMovementMethod = this.nativeView.getMovementMethod();
this._minHeight = nativeView.getMinHeight();
this._maxHeight = nativeView.getMaxHeight();
this._minLines = nativeView.getMinLines();
@@ -108,6 +146,7 @@ export class TextBase extends TextBaseCommon {
if (!reset && this.formattedText) {
return;
}
this._setClickableState(false);
this._setNativeText(reset);
}
@@ -128,6 +167,7 @@ export class TextBase extends TextBaseCommon {
const spannableStringBuilder = createSpannableStringBuilder(value);
nativeView.setText(<any>spannableStringBuilder);
this._setClickableState(isStringClickable(value));
textProperty.nativeValueChange(this, (value === null || value === undefined) ? "" : value.toString());
@@ -310,6 +350,19 @@ export class TextBase extends TextBaseCommon {
this.nativeTextViewProtected.setText(<any>transformedText);
}
_setClickableState(clickable: boolean) {
if (this._clickable !== clickable) {
this._clickable = clickable;
if (this._clickable) {
this.nativeViewProtected.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
this.nativeViewProtected.setHighlightColor(null);
}
else {
this.nativeViewProtected.setMovementMethod(this._defaultMovementMethod);
}
}
}
}
function getCapitalizedString(str: string): string {
@@ -341,6 +394,19 @@ export function getTransformedText(text: string, textTransform: TextTransform):
}
}
function isStringClickable(formattedString: FormattedString) {
if (!formattedString) {
return false;
}
for (let i = 0, length = formattedString.spans.length; i < length; i++) {
const span = formattedString.spans.getItem(i);
if (span.clickable) {
return true;
}
}
return false;
}
function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder {
if (!formattedString) {
return null;
@@ -439,6 +505,12 @@ function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span,
}
}
const clickable = span.clickable;
if (clickable) {
initializeClickableSpan();
ssb.setSpan(new ClickableSpan(span), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
// TODO: Implement letterSpacing for Span here.
// const letterSpacing = formattedString.parent.style.letterSpacing;
// if (letterSpacing > 0) {