多行edittext,其中部分不可编辑,例如填入空白

时间:2018-10-13 08:46:03

标签: android android-edittext textview android-custom-view

我需要一个包含textview和edittext的视图。

示例:

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

“ _____”上方可以是任意长度,最后应该像一段。上面给出的其余文本是不可更改的。就像填补空白一样。

3 个答案:

答案 0 :(得分:3)

从我的角度来看,填写空白的小部件应执行以下操作:

  1. 仅允许更改文本的某些已识别部分。其余文本被锁定。
  2. 不允许光标移动到锁定的文本中。
  3. EditText那样一行一行地流动。
  4. 通过空格的可变放置来概括。

这是基于EditText的此类小部件的实现。使用从BlanksSpan扩展的跨度(StyleSpan)设置可编辑跨度。文本中的五个下划线(______)标识了空白范围。光标移动在OnSelectionChanged()和各种EditText回调中进行控制。文本的更改由TextWatcher监视,并在那里显示的文本进行了调整。

以下是正在使用的小部件的视频:

enter image description here

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
一个示例布局。

<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>

一些注意事项:

  1. 在提取模式下,如果在BlanksSpan之外进行触摸,则光标位置会跳来跳去。事情仍然有效,但行为异常。
  2. 空白字段的长度是固定的,但可以通过做一些额外的工作使其长度可变。
  3. 控件中的动作模式需要根据要求进行一些工作。

答案 1 :(得分:2)

  

多行edittext,其中的部分不可编辑,例如填入空白

您可以使用 TextWatcher() 满足此要求

尝试这个,他是解决这个问题的小手笔

  

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>
  

这是上面代码https://www.youtube.com/watch?v=pfhUzLiFD6U

的输出视频

使用上述代码,您无法编辑editext的第一部分和最后一部分

注意

您还可以使用 TextDrawable  这里有一些链接

您也可以为此创建自定义EditText

您也可以使用InputFilter

答案 2 :(得分:0)

解决方案之一

尝试使用flexbox-layout-https://github.com/google/flexbox-layout

第二种解决方案

使用textWatcher

解决方案三

  • 使用html,css和javascript设计一个简单的网页。
  • 使用webview加载html文件。