Search code examples
androidxamarinxamarin.formsandroid-edittextandroidx

How do I implement Android.Text.ISpannable on a Android.Widget.EditText inside of Xamarin.Forms?


How do I implement Android.Text.ISpannable on a Android.Widget.EditText inside of Xamarin.Forms?

I've looked into Previous Post but the native java implementation is very different compared to Xamarin. And I don't know how to code it correctly. I found the solution for iOS but haven't found one for Android.

The Goal: On tap of the text entry control: I want to do any of the following:

  • Nothing
  • move the cursor to start or end (move the cursor)
  • Select all text. If you tap the cell and start typing, you erase the contents and replace it with the new text.

Is this possible?

Also, the link for the repository in question. See "EntryCellRender" on android and iOS.

public class EntryCell :  : CellBaseView, Android.Text.ITextWatcher, Android.Widget.TextView.IOnFocusChangeListener, Android.Widget.TextView.IOnEditorActionListener
{
    AiEditText _EditText;
    ...
    void EntryCell_Focused( object sender, EventArgs e )
    {
        _EditText.RequestFocus();

        // https://stackoverflow.com/questions/20838227/set-cursor-position-in-android-edit-text/20838295
        switch ( _EntryCell.OnSelectAction )
        {
            case AiEntryCell.SelectAction.None:
                break;

            case AiEntryCell.SelectAction.Start:
                Selection.SetSelection(_EditText, 0); // Xamarin requires Android.Text.ISpannable implementation.

                break;

            case AiEntryCell.SelectAction.End:
                int position = _EditText.Length();
                Selection.SetSelection(_EditText, position); // Xamarin requires Android.Text.ISpannable implementation.

                break;

            case AiEntryCell.SelectAction.All:
                _EditText.SelectAll();

                break;

            default: throw new ArgumentOutOfRangeException();
        }

        ShowKeyboard(_EditText);
    }

    ...
}

The implementation of AiEditText.

[Android.Runtime.Preserve(AllMembers = true)]
internal class AiEditText : Android.Widget.EditText, Android.Text.ISpannable
{
    public System.Action ClearFocusAction { get; set; }

    public AiEditText( Context context ) : base(context) { }

    public override bool OnKeyPreIme( Keycode keyCode, KeyEvent e )
    {
        if ( keyCode != Keycode.Back ||
             e.Action != KeyEventActions.Up ) return base.OnKeyPreIme(keyCode, e);

        ClearFocus();
        ClearFocusAction?.Invoke();

        return base.OnKeyPreIme(keyCode, e);
    }

    public void RemoveSpan( Java.Lang.Object what )
    {
        throw new NotImplementedException();
    }
    public void SetSpan( Java.Lang.Object what,
                         int start,
                         int end,
                         [GeneratedEnum] Android.Text.SpanTypes flags )
    {
        switch ( flags )
        {
            case SpanTypes.Composing: break;

            case SpanTypes.ExclusiveExclusive: break;

            case SpanTypes.ExclusiveInclusive: break;

            case SpanTypes.InclusiveExclusive: break;

            case SpanTypes.InclusiveInclusive: break;

            case SpanTypes.Intermediate: break;

            case SpanTypes.Paragraph: break;

            case SpanTypes.Priority: break;

            case SpanTypes.PriorityShift: break;

            case SpanTypes.User: break;

            case SpanTypes.UserShift: break;

            default: throw new ArgumentOutOfRangeException(nameof(flags), flags, null);
        }
    }
    public int GetSpanEnd( Java.Lang.Object tag )
    {
        throw new NotImplementedException();
    }
    [return: GeneratedEnum]
    public SpanTypes GetSpanFlags( Java.Lang.Object tag )
    {
        throw new NotImplementedException();
    }
    public Java.Lang.Object[] GetSpans( int start, int end, Class type )
    {
        throw new NotImplementedException();
    }
    public int GetSpanStart( Java.Lang.Object tag )
    {
        throw new NotImplementedException();
    }
    public int NextSpanTransition( int start, int limit, Class type )
    {
        throw new NotImplementedException();
    }
    public char CharAt( int index ) => Text[index];
    public ICharSequence SubSequenceFormatted( int start, int end )
    {
        throw new NotImplementedException();
    }
    public IEnumerator<char> GetEnumerator() { return Text.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

Solution

  • How about using custom EntryRenderer to achieve that.

    Create a custom Entry(MyEntry):

    public class MyEntry : Entry
    {
    }
    

    Used in Xaml :

    <ContentPage ...
        xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer"
        ...>
        ...
        <local:MyEntry Text="In Shared Code" />
        ...
    </ContentPage>
    

    Then create a custom renderer(CustomEntryRenderer) in Android to use SpannableString.

    public class CustomEntryRenderer:EntryRenderer
    {
        public CustomEntryRenderer(Context context) : base(context)
        {
        }
    
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
    
            if (Control != null)
            {
                //Control.SetBackgroundColor(global::Android.Graphics.Color.LightGreen);
    
                SpannableString ss = new SpannableString("green call bold deleteline underline Italic BackgroundColorSpan");
    
                //color of font
                ss.SetSpan(new ForegroundColorSpan(Android.Graphics.Color.Green), 0, 5, SpanTypes.ExclusiveExclusive);
    
                //telephone- link
                ss.SetSpan(new URLSpan("tel:4155551212"), 6, 10, SpanTypes.ExclusiveExclusive);
    
                //bold
                ss.SetSpan(new StyleSpan(TypefaceStyle.Bold), 11, 15, SpanTypes.ExclusiveExclusive);
    
                //deleteline
                ss.SetSpan(new StrikethroughSpan(), 16, 26, SpanTypes.ExclusiveExclusive);
    
                //underline
                ss.SetSpan(new UnderlineSpan(), 27, 36, SpanTypes.ExclusiveExclusive);
                ss.SetSpan(new ForegroundColorSpan(Android.Graphics.Color.Red), 25, 34, SpanTypes.ExclusiveExclusive);
    
                //image
                //Drawable d = Resources.GetDrawable(Resource.Drawable.notification_template_icon_low_bg);
                //d.SetBounds(0, 0, d.IntrinsicWidth, d.IntrinsicHeight);
                //ImageSpan span = new ImageSpan(d,SpanAlign.Baseline);
                //ss.SetSpan(span, 35, 40, SpanTypes.ExclusiveExclusive);  //load image
    
                //Italic
                ss.SetSpan(new StyleSpan(TypefaceStyle.Italic), 37, 43, SpanTypes.ExclusiveExclusive);
                //BackgroundColorSpan
                ss.SetSpan(new BackgroundColorSpan(Android.Graphics.Color.Yellow), 44, 63, SpanTypes.ExclusiveExclusive);
    
    
                //text worked in EditText 
                Control.SetText(ss,TextView.BufferType.Editable);
                Control.MovementMethod = LinkMovementMethod.Instance;
            }
        }
    }
    

    The effect :

    enter image description here