Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Extracted Text for a custom Android View

Background

A custom editor view in Android is able to receive text from a system keyboard though an InputConnection. I have been able to make such a view successfully. However, when the device is in landscape mode, the system will sometimes choose to show a extracted text view. When users type in this mode the extracted text view should be updated with the same text that is in the custom view.

I have not been able to implement the extracted text view functionality. (Here are some things I've tried.)

I also have not been able to find any clear documentation or full examples of how to do it. (Here are some of the better things I've read: one, two, three, four).

MCVE

I've created the most basic custom editor that I can. The following gif shows the functionality. It can receive text from the keyboard but it does not update the extracted text view in landscape orientation. Thus you can't see the updated text unless you dismiss the keyboard.

enter image description here

MyCustomView.java

public class MyCustomView extends View {

    SpannableStringBuilder mText;
    Paint mPaint;

    public MyCustomView(Context context) {
        this(context, null, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setFocusableInTouchMode(true);
        mText = new SpannableStringBuilder();
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setTextSize(60);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText(mText, 0, mText.length(), 50, 100, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (imm == null) return false;
            imm.showSoftInput(this, InputMethodManager.SHOW_FORCED);
        }
        return true;
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
        return new MyInputConnection(this, true);
    }
}

MyInputConnection.java

public class MyInputConnection extends BaseInputConnection {

    private MyCustomView customView;

    MyInputConnection(View targetView, boolean fullEditor) {
        super(targetView, fullEditor);
        customView = (MyCustomView) targetView;
    }

    @Override
    public Editable getEditable() {
        return customView.mText;
    }

    @Override
    public boolean commitText(CharSequence text, int newCursorPosition) {
        boolean returnValue = super.commitText(text, newCursorPosition);
        customView.invalidate();
        return returnValue;
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <net.example.extractedtext.MyCustomView
        android:id="@+id/myCustomView"
        android:background="@android:color/holo_blue_bright"
        android:layout_margin="50dp"
        android:layout_width="300dp"
        android:layout_height="150dp"
        android:layout_centerHorizontal="true"
        />

</RelativeLayout>

Summary

I am looking for a canonical answer that describes and gives an example of how to implement extracted text updates for a custom editor view.

If I figure it out myself, I will add my own answer. Until then the best I have been able to do it just disable extracted text altogether. This is not ideal.

like image 508
Suragch Avatar asked Jun 14 '18 04:06

Suragch


1 Answers

You can use inputMethodManager.updateExtractedText(view, token, extractedText) for that.

First parameter of this method is easy. You can pass instance of your CustomView there. Last one also. Just create ExtractedText and set its fields like this.

ExtractedText extractedText = new ExtractedText();
extractedText.text = "sample text";

More difficult is to pass correct token. To get to know right value of this parameter you can override methodgetExtractedText(ExtractedTextRequest request, int flags) inside your MyInputConnection class (token is stored in request object).

@Override
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
        currentToken = request.token;
        return new ExtractedText();
}

I return empty ExtractedText object from this method to make view active (by default text looks like hint).

You can find my solution here https://github.com/ljarka/ExtractedText

Extracted text preview

like image 127
lukjar Avatar answered Nov 07 '22 06:11

lukjar