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 = "_____";
}
活动_主要. 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>
4条答案
按热度按时间drkbr07n1#
从我的Angular 来看,一个填空小部件应该完成以下任务:
1.只允许更改文本的特定标识部分。文本的其余部分被锁定。
1.不允许光标移动到锁定文本中。
1.像
EditText
一样从一条线流到另一条线。1.用可变的空格位置概括。
下面是基于
EditText
的此类小部件的实现。(BlanksSpan
)扩展自StyleSpan
。空白范围由五个下划线标识("_____")。光标移动在OnSelectionChanged()
和各种EditText
回调中控制。通过TextWatcher
监控文本的变更,并在此处对显示的文本进行调整。下面是正在使用的小部件的视频:
示例布局。
需要注意的几点:
1.在提取模式下,如果在
BlanksSpan
之外进行触摸,光标位置会跳来跳去。事情仍然可以工作,但有点不正常。1.空白字段的长度是固定的,但可以通过一些额外的工作使其长度可变。
1.控件中的动作模式需要根据需要做一些工作。
laik7k3q2#
多行编辑部分不可编辑的文本,如填空
您可以使用**
TextWatcher()
**满足此要求试试这个,他是这个周围的小工作
主要活动
布局.活动_主
下面是上述代码https://www.youtube.com/watch?v=pfhUzLiFD6U的输出视频
使用上面的代码你可以使
editext
的开头和结尾部分不可编辑您也可以使用**
TextDrawable
**,这里有一些链接您还可以为此创建自定义
EditText
您也可以使用
InputFilter
r6l8ljro3#
解决方案一
尝试使用flexbox-layout -https://github.com/google/flexbox-layout。
解决方案二
使用文本监视器
解决方案三
nbewdwxp4#
按照@Cheticamp的答案,当你在setText()方法中设置一个SpannableString时,它就起作用了。
此外,您还应覆盖setText()方法并将Spans设置为mSpans:
如果你不设置一个SpannableString,Editable接口将得到一个简单的没有Spans的String。所以如果你调用如下方法: