# Android EditText 技巧
###### tags: `Android` `EditText`
# 限制輸入字串長度 & 允許輸入的字元
## Reference
* [Working with the EditText](https://guides.codepath.com/android/Working-with-the-EditText)
## Content:
* limit the maximum number of characters user can input
```
android:maxLength="18"
```
* limit which characters user are allowed to input
```
android:digits="@string/backup_name_input_rule"
```
```
<string name="backup_name_input_rule">
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_
</string>
```
---
### 2019/12/24 發現一個很奇怪的現象
```
<string name="rule_text">
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`¬!"£$%^*()~=#{}[];':,./?/*-_+<>@&
</string>
```
上面這個,竟然無法輸入空白字! 但下面這個就可以,很神奇,只差在一個斷行。
```
<string name="rule_text">0123456789abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ`¬!"£$%^*()~=#{}[];':,./?/*-_+<>@&
</string>
```
----
# [Adding a prefix to an EditText](https://medium.com/@ali.muzaffar/adding-a-prefix-to-an-edittext-2a17a62c77e1)
### google: “android edittext prevent string from delete”
Adding an always visible prefix to an EditText is surprisingly hard!
Users can delete your prefix, type or paste text in the middle of it.
Handling this in a ``TextWatcher`` is a lot of code. However, there is an easier way!
Using a ``TextWatcher`` to add an always visible prefix is fairly complicated,
if someone clicks in the middle of your prefix,
you have to intercept the text and move it to the end or
more the cursor to the end immediately.
All of this can result in a LOT of code.
You need to disable copy/paste,
you need to handle onClick,
you need to handle text change etc.
An other approach may be to put a ``TextView`` in the background
and add ``padding`` to the EditText, however,
it’s tricky to align the background text and the ``EditText``.
The reason for that is that padding is in DIPs and text sizes are in SP.
If you do get things to align perfectly,
fragmentation of the ``EditText`` view, accessibility changes and
different devices densities may still cause the text to show up unaligned.
Look at compatibility issues here for more on ``EditText`` fragmentation.
The best way I can think of to address this issue
is to create a custom ``EditText`` which literally uses the same paint object
to draw the prefix text and and add padding according to the prefix text size.
Below is some quick and dirty code to do the same.
The below example prefixed the ``EditText`` with a country code
for input of phone numbers (for example).
#### Notes:
* __It doesn't support prefix text changing__ at the moment,
but I think looking at the code, it’s pretty obvious how you can support it.
* I also __use tag__ to pass in the prefix. This could be a bad idea,
so you may be better off adding a custom attribute.
* Also, I haven’t bothered to check to right to left displays
but that should be simple enough.
Finally, TalkBack won’t be able to read the prefix.
## The code
```
public class PrefixEditText extends AppCompatEditText {
float mOriginalLeftPadding = -1;
public PrefixEditText(Context context) {
super(context);
}
public PrefixEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PrefixEditText(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
calculatePrefix();
}
private void calculatePrefix() {
if (mOriginalLeftPadding == -1) {
String prefix = (String) getTag();
float[] widths = new float[prefix.length()];
getPaint().getTextWidths(prefix, widths);
float textWidth = 0;
for (float w : widths) {
textWidth += w;
}
mOriginalLeftPadding = getCompoundPaddingLeft();
setPadding((int) (textWidth + mOriginalLeftPadding),
getPaddingRight(), getPaddingTop(),
getPaddingBottom());
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
String prefix = (String) getTag();
canvas.drawText(prefix, mOriginalLeftPadding,
getLineBounds(0, null), getPaint());
}
}
```
Usage:
```
<com.alimuzaffar.customwidgets.PrefixEditText
fontPath="fonts/Lato-Light.ttf"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom"
android:textSize="24sp"
android:tag="+61 "
android:text="1234" />
```
## Another approach
使用 drawable left/ right
Another approach and in some respects a better approach
may be to create a custom drawable and
add it as a drawable right or drawable left.
The custom drawable will simply draw the text.
The reason I didn’t choose this approach is:
I would have to pass in the paint object or build one from the theme,
why bother when ``EditText`` already contains one.
Aligning the text drawn by my drawable with the ``EditText``
is not going to be easy and prone to getting misaligned.
To fix both the issues above,
the best approach would be to pass a reference to the ``EditText`` to our Drawable,
and this code is just becoming messy at that point IMHO.
UPDATE 7th March 2016:
I created a ``TextDrawable`` that
you can use to add a prefix to an ``EditText`` or
use emojis, Unicode characters or text as a ``Drawable``.
----
# [Put constant text inside EditText which should be non-editable - Android](https://stackoverflow.com/questions/14195207/put-constant-text-inside-edittext-which-should-be-non-editable-android)
```
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());
}
}
});
```
Option2:
```
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);
```
1.2.0 alpha1
https://stackoverflow.com/questions/43262272/textinputlayout-suffix-prefix
https://stackoverflow.com/questions/59101279/how-to-change-hint-padding-for-textinputlayout-when-using-the-new-prefixtext
---
# ``TextInputLayout`` with ``TextInputEditText``
## Reference:
* [Text fields @ Material.IO 2](https://m2.material.io/components/text-fields/android#outlined-text-field)
* [Android Material Text Fields](https://www.digitalocean.com/community/tutorials/android-material-text-fields)
* 2022/08/04
* [Replacing EditTexts with TextInputLayouts in Android](https://www.section.io/engineering-education/how-is-textinputlayout-different-from-edittext/)
* 2021/08/26
## Sample:
#### hint/ stroke color drawable:
[Changing the default TextInputLayout Border & Hint Color when focused & not focused #1492](https://github.com/material-components/material-components-android/issues/1492)
``question_text_input_stroke_selector.xml``:
```
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:color="@color/base_purple_001"
android:state_focused="true"/>
<item
android:color="@color/base_purple_001"
android:state_hovered="true"/>
<item
android:color="@color/base_pink_001"
android:state_enabled="false"/>
<item
android:color="@color/base_blue_006"/> <!-- unfocused -->
</selector>
```
``XML Layout:
```
<?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="wrap_content"
android:background="@color/base_yellow_002"
android:padding="10dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/textInput2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/base_grey_27"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="BBB"
android:hint="AAA"
/>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/base_red"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
/>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:boxBackgroundColor="@color/base_blue_009"
android:textColorHint="@drawable/question_text_input_stroke_selector"
app:hintTextAppearance="?android:attr/textAppearanceMedium"
android:hint="Hint"
app:boxStrokeColor="@drawable/question_text_input_stroke_selector"
app:boxStrokeWidth="2dp"
app:boxCornerRadiusTopStart="6dp"
app:boxCornerRadiusTopEnd="6dp"
app:boxCornerRadiusBottomStart="6dp"
app:boxCornerRadiusBottomEnd="6dp"
app:boxBackgroundMode="outline"
>
<!--app:boxBackgroundMode="filled|outline"-->
<!--app:prefixText="Foo"-->
<!--app:suffixText="Bar"-->
<!--app:helperTextEnabled="false"-->
<!--app:helperText="hello world"-->
<!--app:errorEnabled="false"-->
<!--style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"-->
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/textInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="@color/base_grey_27"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="WWTF"
/>
<!--app:backgroundTint="@android:color/holo_blue_dark"-->
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
```