多行编辑文本,其中部分不可编辑,例如填空

2023-11-23

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

例子:

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

上面的“_____”可以是任意长度,并且最后应该感觉像一个段落。上面给出的其余文本不可更改。就像填空一样。


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

  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 = "_____";  

}

活动_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. 控制中的动作模式需要根据需求进行一些工作。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多行编辑文本,其中部分不可编辑,例如填空 的相关文章

  • 将正确的上下文传递给 greendao 的 OpenHelper 构造函数

    如果我理解正确的话 在使用数据库时 我必须执行以下操作 DaoMaster OpenHelper helper new DaoMaster OpenHelper this test db null Override public void
  • 毕加索动画加载图像

    我有以下代码在毕加索中加载图像 使用可绘制的占位符在图像下载时显示 不过 我想要的是一个动画旋转进度条样式的旋转器 它可以在图像加载时不断地旋转 就像我在大多数专业应用程序中看到的那样 毕加索似乎不支持这一点 只支持静态图像可绘制 有没有办
  • FileNotFoundException:/存储/模拟/0/Android

    我尝试这个文件写入器 读取器代码段进行测试 File file new File Environment getExternalStorageDirectory LM lm lisdat 01 txt FileOutputStream ou
  • 使用 RecyclerView 适配器在运行时更改布局屏幕

    我有两个布局文件 如下所示 如果列表中存在数据 则我显示此布局 当列表为空时 我会显示此布局 现在我想在运行时更改布局 当用户从列表中删除最后一项时 我想将布局更改为第二张图片中显示的 空购物车布局 In getItemCount Recy
  • Firestore - RecycleView - 图像持有者

    我不知道如何编写图像的支架 我已经设置了 2 个文本 但我不知道图像的支架应该是什么样子 你能帮我告诉我图像的文字应该是什么样子才能正确显示吗 holder artistImage setImageResource model getArt
  • Youtube 退出全屏模式 TextView 可见性问题

    我正在全屏模式下播放视频 当我单击后退按钮时 我可以退出全屏模式 但无法显示我在全屏情况下隐藏的 TextView 要在全屏模式下隐藏 textView 我使用以下代码 Override public void onInitializati
  • Renderscript 示例构建错误

    所以我想尝试使用 RenderScript 的示例 并在 Eclipse 中导入了 HelloWorld 但它给了我这样的错误 2011 10 25 13 10 48 HelloWorld home mileoresko workspace
  • Android:应用内计费V3超时返回哪个响应码?

    出现网络超时情况时 Google Play 应用内结算服务 ice er V3 将返回哪些响应状态代码 它的所有功能都是统一的吗 我将在这里描述我的发现 我通过拔掉主机插头 在安装了全功能 GP GP Store V3 10 10 GP S
  • finish() 完成活动但它仍然在后台

    我有一个关于 android studio 中活动的 finish 方法的问题 我有这个简单的代码 public class MainActivity extends AppCompatActivity Override protected
  • 如何查看 Android 上的 Wi-Fi 是否已连接?

    我什至不希望我的用户尝试下载某些内容 除非他们连接了 Wi Fi 然而 我似乎只能判断是否启用了 Wi Fi 但他们仍然可以有 3G 连接 android net wifi WifiManager m WifiManager getSyst
  • Android Studio - 值必须 ≥ 0

    我在 Android Studio 中收到与光标有关的错误 我的代码中有以下行 String data cursor getString cursor getColumnIndex columnIndex columnIndex 被传递到该
  • 为什么Android应用程序在发布到市场后尺寸会增加?

    我最近在 Android 市场上发布了我的应用程序 显示应用程序大小为 5 4MB 而实际 apk 大小为 2 8MB 为什么显示多出2MB 我应该如何限制我的应用程序大小 请帮我 您的应用程序大小会增加 因为您使用了复制保护选项ON在发布
  • Android 从命令行停止模拟器

    这个问题与如何通过命令行关闭Android模拟器 https stackoverflow com questions 5912403 how to shut down android emulator via cmd 但是 在尝试第一个答案
  • Android Studio 将音乐文件读取为文本文件,如何恢复它?

    gameAlert mp3是我的声音文件 运行应用程序时 它询问我该文件不与任何文件类型关联 请定义关联 我选择TextFile错误地 现在我的音乐文件被读取为文本文件 我如何将其转换回music file protected void o
  • 在为 Android 实现 Google 登录时,任务“:app:transformClassesWithDexForDebug”执行失败

    我正在尝试为 Android 实现 Google 登录 并且我正在按照以下说明进行操作 https developers google com identity sign in android start integrating https
  • Android AutoCompleteTextView 带芯片

    我不确定我是否使用了正确的词语来描述此 UI 功能 但我已附上我希望在我的应用程序中实现的目标的快照 它由 Go SMS 使用 用户在编辑文本中键入联系人 在用户从完成下拉列表中选择联系人后 该联系人将被插入到编辑文本中 如附图所示 编辑文
  • Android:解析 XML 数据的最佳解析器 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我正在开发一个应用程序 其中我第一次要解析来自远程服务器的 xml 文件中的数据 但我无法选择哪个解析器是有效的或最适合解析的 因为我知道主要有
  • 在没有 Wifi 的情况下获取 Android 设备的 MAC 地址

    如何获取没有 Wifi 接口的 Android 设备 例如 Android 模拟器 的网络接口的 MAC 地址 通过WifiManager返回获取的WifiInfonull EDIT 更清楚地说 我必须与本地网络上的现有网络协议 不是我设计
  • Android ScrollView fillViewport 不工作

    我有一个简单的布局 名称位于顶部 按钮位于屏幕底部 或者超出该按钮 以防我添加更多项目 所以我使用带有 LinearLayout 的 ScrollView 如下所示
  • 在 Android 中使用 iText 将图像添加到特定位置

    我想使用 Android 中的 iText 将图像添加到 PDF 文件中的特定位置 这是一个可填写的表单 我添加了作为图像占位符的文本框 我想要做的就是像这样获取该文本框和图像 public class FormFill public st

随机推荐