multiline edittext where parts are not editable, like fill in the blanks

后端 未结 3 569
旧巷少年郎
旧巷少年郎 2020-12-31 23:07

I need to have a view which contains textview and edittext.

Example:

Yay! you made it to ______ We should hang out! feel ____ to follow me. 


        
相关标签:
3条回答
  • 2020-12-31 23:13

    From my perspective, a fill-in-the-blank widget should do the following:

    1. Allow only certain identified portions of the text to be changed. The rest of the text is locked.
    2. Not allow cursor movement into the locked text.
    3. Flow from line to line like EditText.
    4. Be generalized with variable placement of blanks.

    Here is an implementation of such a widget based upon EditText. Editable spans are set up using a span (BlanksSpan) extended from StyleSpan. A blank span is identified by five underscores ("_____") in the text. Cursor movement is controlled in OnSelectionChanged() and various EditText callbacks. Changes to the text is monitor by a TextWatcher and adjustments to the displayed text are made there.

    Here is the video of the widget in use:

    FillInBlanksEditText.java

    public class FillInBlanksEditText extends android.support.v7.widget.AppCompatEditText  
        implements View.OnFocusChangeListener, TextWatcher {  
        private int mLastSelStart;  
        private int mLastSelEnd;  
        private BlanksSpan mSpans[];  
        private Editable mUndoChange;  
        private BlanksSpan mWatcherSpan;  
    
        public FillInBlanksEditText(Context context) {  
            super(context);  
            init();  
        }  
    
        public FillInBlanksEditText(Context context, AttributeSet attrs) {  
            super(context, attrs);  
            init();  
        }  
    
        public FillInBlanksEditText(Context context, AttributeSet attrs, int defStyleAttr) {  
            super(context, attrs, defStyleAttr);  
            init();  
        }  
    
        private void init() {  
            mSpans = setSpans();  
            setOnFocusChangeListener(this);  
        }  
    
        @Override  
      public void onRestoreInstanceState(Parcelable state) {  
            mSpans = null;  
            super.onRestoreInstanceState(state);  
            Editable e = getEditableText();  
            mSpans = e.getSpans(0, e.length(), BlanksSpan.class);  
        }  
    
        @Override  
      public void onFocusChange(View v, boolean hasFocus) {  
            if (hasFocus) {  
                addTextChangedListener(this);  
                if (findInSpan(getSelectionStart(), getSelectionEnd()) != null) {  
                    mLastSelStart = getSelectionStart();  
                    mLastSelEnd = getSelectionEnd();  
                } else if (findInSpan(mLastSelStart, mLastSelEnd) == null) {  
                    setSelection(getEditableText().getSpanStart(mSpans[0]));  
                }  
            } else {  
                removeTextChangedListener(this);  
            }  
        }  
    
        @Override  
      protected void onSelectionChanged(int selStart, int selEnd) {  
            if (!isFocused() || mSpans == null ||  
                (getSelectionStart() == mLastSelStart && getSelectionEnd() == mLastSelEnd)) {  
                return;  
            }  
    
            // The selection must be completely within a Blankspan.  
      final BlanksSpan span = findInSpan(selStart, selEnd);  
            if (span == null) {  
                // Current selection is not within a Blankspan. Restore selection to prior location.  
      moveCursor(mLastSelStart);  
            } else if (selStart > getEditableText().getSpanStart(span) + span.getDataLength()) {  
                // Acceptable location for selection (within a Blankspan).  
     // Make sure that the cursor is at the end of the entered data.  mLastSelStart = getEditableText().getSpanStart(span) + span.getDataLength();  
                mLastSelEnd = mLastSelStart;  
                moveCursor(mLastSelStart);  
    
            } else {  
                // Just capture the placement.  
      mLastSelStart = selStart;  
                mLastSelEnd = selEnd;  
            }  
            super.onSelectionChanged(mLastSelStart, mLastSelEnd);  
        }  
    
        // Safely move the cursor without directly invoking setSelection from onSelectionChange.  
      private void moveCursor(final int selStart) {  
            post(new Runnable() {  
                @Override  
      public void run() {  
                    setSelection(selStart);  
                }  
            });  
            // Stop cursor form jumping on move.  
      getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {  
                @Override  
      public boolean onPreDraw() {  
                    getViewTreeObserver().removeOnPreDrawListener(this);  
                    return false;  
                }  
            });  
        }  
    
        @Nullable  
      private BlanksSpan findInSpan(int selStart, int selEnd) {  
            for (BlanksSpan span : mSpans) {  
                if (selStart >= getEditableText().getSpanStart(span) &&  
                    selEnd <= getEditableText().getSpanEnd(span)) {  
                    return span;  
                }  
            }  
            return null;  
        }  
    
        // Set up a Blankspan to cover each occurrence of BLANKS_TOKEN.  
      private BlanksSpan[] setSpans() {  
            Editable e = getEditableText();  
            String s = e.toString();  
            int offset = 0;  
            int blanksOffset;  
    
            while ((blanksOffset = s.substring(offset).indexOf(BLANKS_TOKEN)) != -1) {  
                offset += blanksOffset;  
                e.setSpan(new BlanksSpan(Typeface.BOLD), offset, offset + BLANKS_TOKEN.length(),  
                          Spanned.SPAN_INCLUSIVE_INCLUSIVE);  
                offset += BLANKS_TOKEN.length();  
            }  
            return e.getSpans(0, e.length(), BlanksSpan.class);  
        }  
    
        // Check change to make sure that it is acceptable to us.  
      @Override  
      public void beforeTextChanged(CharSequence s, int start, int count, int after) {  
            mWatcherSpan = findInSpan(start, start + count);  
            if (mWatcherSpan == null) {  
                // Change outside of a Blankspan. Just put things back the way they were.  
     // Do this in afterTextChaanged.  mUndoChange = Editable.Factory.getInstance().newEditable(s);  
            } else {  
                // Change is OK. Track data length.  
      mWatcherSpan.adjustDataLength(count, after);  
            }  
        }  
    
        @Override  
      public void onTextChanged(CharSequence s, int start, int before, int count) {  
            // Do nothing...  
      }  
    
        @Override  
      public void afterTextChanged(Editable s) {  
            if (mUndoChange == null) {  
                // The change is legal. Modify the contents of the span to the format we want.  
      CharSequence newContents = mWatcherSpan.getFormattedContent(s);  
                if (newContents != null) {  
                    removeTextChangedListener(this);  
                    int selection = getSelectionStart();  
                    s.replace(s.getSpanStart(mWatcherSpan), s.getSpanEnd(mWatcherSpan), newContents);  
                    setSelection(selection);  
                    addTextChangedListener(this);  
                }  
            } else {  
                // Illegal change - put things back the way they were.  
      removeTextChangedListener(this);  
                setText(mUndoChange);  
                mUndoChange = null;  
                addTextChangedListener(this);  
            }  
        }  
    
        @SuppressWarnings("WeakerAccess")  
        public static class BlanksSpan extends StyleSpan {  
            private int mDataLength;  
    
            public BlanksSpan(int style) {  
                super(style);  
            }  
    
            @SuppressWarnings("unused")  
            public BlanksSpan(@NonNull Parcel src) {  
                super(src);  
            }  
    
            public void adjustDataLength(int count, int after) {  
                mDataLength += after - count;  
            }  
    
            @Nullable  
      public CharSequence getFormattedContent(Editable e) {  
                if (mDataLength == 0) {  
                    return BLANKS_TOKEN;  
                }  
                int spanStart = e.getSpanStart(this);  
                return (e.getSpanEnd(this) - spanStart > mDataLength)  
                    ? e.subSequence(spanStart, spanStart + mDataLength)  
                    : null;  
            }  
    
            public int getDataLength() {  
                return mDataLength;  
            }  
    
        }  
    
        @SuppressWarnings({"FieldCanBeLocal", "unused"})  
        private static final String TAG = "FillInBlanksEditText";  
        private static final String BLANKS_TOKEN = "_____";  
    
    }
    

    activity_main.java
    A sample layout.

    <android.support.constraint.ConstraintLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.example.fillintheblanks.FillInBlanksEditText
            android:id="@+id/editText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="@android:color/transparent"
            android:inputType="textMultiLine"
            android:padding="16dp"
            android:text="Yay! You made it to _____. We should hang out! Feel _____ to follow me."
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <com.example.fillintheblanks.FillInBlanksEditText
            android:id="@+id/editText2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="@android:color/transparent"
            android:inputType="textMultiLine"
            android:padding="16dp"
            android:text="_____ says that it is time to _____. Are you _____?"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/editText" />
    
    </android.support.constraint.ConstraintLayout>
    

    A few things to note:

    1. In extracted mode, cursor placement jumps around if a touch is made outside of a BlanksSpan. Things still work but misbehave a little.
    2. The length of the blanks fields is fixed, but it can be made variable in length with some additional work.
    3. The action mode in the control needs some work based upon requirements.
    0 讨论(0)
  • 2020-12-31 23:33

    multiline edittext where parts are not editable, like fill in the blanks

    You can use a TextWatcher() for this requirement

    Try this he is the little work around for this

    MainActivity

    public class MainActivity extends AppCompatActivity {
    
    
        EditText myEditText;
        String startText = "I'm The First Part";
        String lastText = "I'm The Last Part";
    
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            final SpannableStringBuilder firstStringBuilder = new SpannableStringBuilder(startText);
            final SpannableStringBuilder lastStringBuilder = new SpannableStringBuilder(lastText);
    
            StyleSpan firstStyleSpan = new StyleSpan(android.graphics.Typeface.BOLD);
    
            firstStringBuilder.setSpan(firstStyleSpan, 0, firstStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); // make first 4 characters Bold
            lastStringBuilder.setSpan(firstStyleSpan, 0, lastStringBuilder.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); // make first 4 characters Bold
    
            myEditText = findViewById(R.id.myEditText);
    
    
            spannableStringBuilder.append(firstStringBuilder);
            spannableStringBuilder.append("   ");
            spannableStringBuilder.append(lastStringBuilder);
    
            myEditText.setText(spannableStringBuilder);
            Selection.setSelection(myEditText.getText(), startText.length() + 1);
    
            myEditText.addTextChangedListener(new TextWatcher() {
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
    
    
                }
    
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count,
                                              int after) {
                    // TODO Auto-generated method stub
    
                }
    
                @Override
                public void afterTextChanged(Editable s) {
    
                    if (!s.toString().startsWith(firstStringBuilder.toString())
                            || !s.toString().contains(lastText)) {
                        Log.e("StringBuilder_TAG", spannableStringBuilder.toString());
    
                        myEditText.setText(spannableStringBuilder);
                        Selection.setSelection(myEditText.getText(), myEditText.getText().length() - lastStringBuilder.length() - 1);
                    } else {
    
                        spannableStringBuilder.clear();
                        spannableStringBuilder.append(s.toString());
                        Log.e("My_TAG", spannableStringBuilder.toString());
    
                    }
    
                }
            });
    
        }
    
    
    }
    

    layout.activity_main

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
    
        <EditText
            android:id="@+id/myEditText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            android:padding="5dp" />
    
    
    </LinearLayout>
    

    Here is the output video of above code https://www.youtube.com/watch?v=pfhUzLiFD6U

    using above code you able to make not editble first and last parts of editext

    Note

    You can also use a TextDrawable here are some links for that

    • How to put text in a drawable?
    • https://github.com/amulyakhare/TextDrawable
    • Set unchangeable some part of editText android

    You can also create a custom EditText for this

    • Adding a prefix to an EditText

    You can also use InputFilter

    0 讨论(0)
  • 2020-12-31 23:38

    Solution one

    Try using flexbox-layout - https://github.com/google/flexbox-layout.

    Solution Two

    Use textWatcher

    Solution Three

    • Use html, css and javascript to design a simple webpage.
    • Use webview to load the html file.
    0 讨论(0)
提交回复
热议问题