多行edittext,其中零件不可编辑,例如填写空白 [英] multiline edittext where parts are not editable, like fill in the blanks

查看:96
本文介绍了多行edittext,其中零件不可编辑,例如填写空白的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

示例:

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

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

Above "_____" could be of any length and it should feel like a paragraph in the end. Rest of the text given above is not changeable. Just like fill in the blanks.

推荐答案

在我看来,空白填充小部件应该执行以下操作:

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

  1. 仅允许更改文本的某些已识别部分.其余文本被锁定.
  2. 不允许光标移动到锁定的文本中.
  3. EditText一样在一行之间流动.
  4. 通过空格的可变放置来泛化.
  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.

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

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

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>

一些注意事项:

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

这篇关于多行edittext,其中零件不可编辑,例如填写空白的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆