Put constant text inside EditText which should be

2019-01-03 08:49发布

I want to have constant text inside editText like:

http://<here_user_can_write>

User should not be able to delete any chars from "http://", I searched about this and found this:

editText.setFilters(new InputFilter[] {
    new InputFilter() {
        public CharSequence filter(CharSequence src, int start,
            int end, Spanned dst, int dstart, int dend) {
            return src.length() < 1 ? dst.subSequence(dstart, dend) : "";
        }
    }
}); 

but I don't know whether it restricts user to not delete any chars from start to end limit. I also could not understand use of Spanned class.

One way would be a good choice if we can put a TextView inside EditText but I don't think it is possible in Android since both are Views, is it possible?

11条回答
走好不送
2楼-- · 2019-01-03 09:39

I know I'm reviving an old post but I want to share with the community that I have struggled with this topic these days and I found that placing a TextView over the EditText is not only perfectly doable (to respond to the second part of the question), much more in this case when the constant text is needed in the starting position, but preferable, too. Moreover the cursor won't even move before the "mutable" text at all, which is an elegant effect. I prefer this solution because it doesn't add workload and complexity to my app with listeners and whatsoever.

Here's a sample code of my solution:

<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginStart="3dp"
        android:text="http://" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="textUri"
        android:paddingStart="choose an appropriate padding" />
</RelativeLayout>

By enclosing the views in a RelativeLayout they will be overlapped. The trick here is playing with the android:paddingStart property of the EditText, to make the text start just right after the TextView, while android:layout_centerVertical="true" and android:layout_marginStart="3dp" properties of the TextView make sure that its text is correctly aligned with text inputted and with the start of the underlying line of the EditText (or at least this happens when using a Material themed one).

查看更多
We Are One
3楼-- · 2019-01-03 09:42

That's how you can actually do it with an InputFilter:

final String prefix = "http://"
editText.setText(prefix);

editText.setFilters(new InputFilter[] {
    new InputFilter() {
      @Override
      public CharSequence filter(final CharSequence source, final int start,
          final int end, final Spanned dest, final int dstart, final int dend) {
        final int newStart = Math.max(prefix.length(), dstart);
        final int newEnd = Math.max(prefix.length(), dend);
        if (newStart != dstart || newEnd != dend) {
          final SpannableStringBuilder builder = new SpannableStringBuilder(dest);
          builder.replace(newStart, newEnd, source);
          if (source instanceof Spanned) {
            TextUtils.copySpansFrom(
                (Spanned) source, 0, source.length(), null, builder, newStart);
          }
          Selection.setSelection(builder, newStart + source.length());
          return builder;
        } else {
          return null;
        }
      }
    }
});

If you also want the prefix to be not selectable you can add the following code.

final SpanWatcher watcher = new SpanWatcher() {
  @Override
  public void onSpanAdded(final Spannable text, final Object what,
      final int start, final int end) {
    // Nothing here.
  }

  @Override
  public void onSpanRemoved(final Spannable text, final Object what, 
      final int start, final int end) {
    // Nothing here.
  }

  @Override
  public void onSpanChanged(final Spannable text, final Object what, 
      final int ostart, final int oend, final int nstart, final int nend) {
    if (what == Selection.SELECTION_START) {
      if (nstart < prefix.length()) {
        final int end = Math.max(prefix.length(), Selection.getSelectionEnd(text));
        Selection.setSelection(text, prefix.length(), end);
      }
    } else if (what == Selection.SELECTION_END) {
      final int start = Math.max(prefix.length(), Selection.getSelectionEnd(text));
      final int end = Math.max(start, nstart);
      if (end != nstart) {
        Selection.setSelection(text, start, end);
      }
    }
  }
};

editText.getText().setSpan(watcher, 0, 0, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
查看更多
一纸荒年 Trace。
4楼-- · 2019-01-03 09:45

Here is a less efficient solution that should handle all cases for when characters OR words are deleted/inserted in OR around the prefix.

prefix = "http://"
extra = "ahhttp://"
differencePrefix(prefix, extra) = "aht"

Code:

public static String differencePrefix(String prefix, String extra) {
    if (extra.length() < prefix.length()) return "";
    StringBuilder sb = new StringBuilder();
    StringBuilder eb = new StringBuilder();
    int p = 0;
    for (int i = 0; i < extra.length(); i++) {
        if (i >= prefix.length()) {
            while(p < extra.length()) {
                eb.append(extra.charAt(p));
                p++;
            }
            break;
        }
        if (p >= extra.length()) break;
        char pchar = extra.charAt(p);
        char ichar = prefix.charAt(i);
        while(pchar != ichar) {
            //check if char was deleted
            int c = i + 1;
            if (c < prefix.length()) {
                char cchar = prefix.charAt(c);
                if (cchar == pchar) {
                    break;
                }
            }
            sb.append(pchar);
            p++;
            if (p >= extra.length()) break;
            pchar = extra.charAt(p);
        }
        p++;
    }

    return eb.toString() + sb.toString();
}

You can use it like this

editText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        String input = s.toString();
        if (!input.startsWith(prefix)) {
            String extra = differencePrefix(prefix, input);
            String newInput = prefix + extra;
            editText.setText(newInput);
            editText.setSelection(newInput.length());
        }
    }
});
查看更多
淡お忘
5楼-- · 2019-01-03 09:45

I just found the solution how to make prefix not-editable and how to save text if you try to remove prefix. That's very close to @Rajitha Siriwardena answer. All you missed is to save text before any changes applied. It will be restored in afterTextChanged(...).

Code:

final String prefix = "http://";
editText.setText(prefix);
Selection.setSelection(editText.getText(), editText.getText().length());

editText.addTextChangedListener(new TextWatcher() {
    String text;
    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        text = charSequence.toString();
    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    }

    @Override
    public void afterTextChanged(Editable editable) {
        if (!editable.toString().startsWith(prefix)) {
            editText.setText(text);
            Selection.setSelection(editText.getText(), editText.getText().length());
        }
    }
});
查看更多
狗以群分
6楼-- · 2019-01-03 09:47

Did u try this method?

final EditText edt = (EditText) findViewById(R.id.editText1);

edt.setText("http://");
Selection.setSelection(edt.getText(), edt.getText().length());


edt.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // TODO Auto-generated method stub

        }

        @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("http://")){
                edt.setText("http://");
                Selection.setSelection(edt.getText(), edt.getText().length());

            }

        }
    });
查看更多
登录 后发表回答