登录页面,手机号输入框:
- 手机号3-4-4格式化输入
- 带删除图标的EditText
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.appcompat.widget.AppCompatEditText;
import com.blankj.utilcode.util.SizeUtils;
/**
* 手机号3-4-4格式化输入的EditView
* https://www.lmlphp.com/user/56/article/item/3686/
* <pre>
* 作者:Caowj
* 日期:2023/5/8 0008_15:52
* </pre>
*/
public class PhoneEditView extends AppCompatEditText implements TextWatcher, View.OnFocusChangeListener {
private final int COUNT_PHONE = 11;//手机位数
private final char SEPARATOR = ' ';//分隔符,一般为-或半角空格
private final int NUMBER_FRONT = 3;//前面有几位数
private final int NUMBER_MIDDLE = 4;//中间有几位数
private StringBuilder mBuilder = new StringBuilder();
private int mChangeCount;
private Drawable mClearDrawable;//清除按钮图片
private boolean hasFocus;
private TextWatcher mTextWatcher;
private OnFocusChangeListener mOnFocusChangeListener;
public PhoneEditView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PhoneEditView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
requestFocus();
// setInputType(InputType.TYPE_CLASS_NUMBER);
setInputType(InputType.TYPE_CLASS_PHONE);
//限制最大输入长度,需要考虑分隔符所占的位置
setFilters(new InputFilter[]{new InputFilter.LengthFilter(COUNT_PHONE + 2)});
hasFocus = hasFocus();
//获取清除图标,这个图标是通过布局文件里面的drawableEnd或者drawableRight来设置的
//getCompoundDrawables:Returns drawables for the left, top, right, and bottom borders.
//getCompoundDrawablesRelative:Returns drawables for the start, top, end, and bottom borders.
mClearDrawable = getCompoundDrawables()[2];
if (mClearDrawable == null) {
mClearDrawable = getCompoundDrawablesRelative()[2];
}
if (mClearDrawable != null) {
//设置图标的位置以及大小,getIntrinsicWidth()获取显示出来的大小而不是原图片的大小
// mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
mClearDrawable.setBounds(0, 0, SizeUtils.dp2px(26), SizeUtils.dp2px(26));
}
//默认设置隐藏图标
setClearIconVisible(false);
//设置焦点改变的监听
setOnFocusChangeListener(this);
//设置输入框里面内容发生改变的监听
addTextChangedListener(this);
}
@Override
public void onTextChanged(CharSequence s, int start, int count, int after) {
mChangeCount = after - count;
setClearIconVisible(hasFocus && s.length() > 0);
if (mTextWatcher != null) {
mTextWatcher.onTextChanged(s, start, count, after);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (mTextWatcher != null) {
mTextWatcher.beforeTextChanged(s, start, count, after);
}
}
@Override
public void afterTextChanged(Editable s) {
//只考虑当输入数字时的情况,若按删除键就直接调用系统的
if (mChangeCount > 0 && !TextUtils.equals(s, mBuilder.toString())) {
mBuilder.delete(0, mBuilder.length());
//因为在setText时getSelectionStart会重置为0,所以要在setText前记录上一个光标的位置
int preCursorIndex = getSelectionStart();
int length = s.length();
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c != SEPARATOR) {
mBuilder.append(c);
}
if ((isSeparationPosition(mBuilder.length()))) {
mBuilder.append(SEPARATOR);
}
}
//如果正好处于分隔符处,需要将光标向后移动分隔符位数
int adjust = isSeparationPosition(preCursorIndex) ? 1 : 0;
setText(mBuilder.toString());
//设置光标位置
setSelection(preCursorIndex + adjust);
}
if (mTextWatcher != null) {
mTextWatcher.afterTextChanged(s);
}
Log.i(PhoneEditView.class.getSimpleName(), "getPhoneCode:" + getPhoneCode());
}
/**
* 是否分隔位置
*/
private boolean isSeparationPosition(int index) {
final int secondSeparatorPosition = NUMBER_FRONT + NUMBER_MIDDLE + 1;
return index == NUMBER_FRONT || index == secondSeparatorPosition;
}
/**
* @return 手机号码
*/
public String getPhoneCode() {
Editable text = getText();
return text == null ? null : text.toString().replace(String.valueOf(SEPARATOR), "");
}
/**
* 用记住我们按下的位置来模拟点击事件
* <p>
* 当我们按下的位置在 EditText的宽度 - 文本右边到图标左边缘的距离 - 图标左边缘至控件右边缘的距离
* <p>
* 到 EditText的右边缘 之间就算点击了图标,竖直方向就以 EditText高度为边界
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP && mClearDrawable != null) {
//getTotalPaddingRight()图标左边缘至控件右边缘的距离
//getCompoundDrawablePadding()表示从文本右边到图标左边缘的距离
int left = getWidth() - getTotalPaddingRight() - getCompoundDrawablePadding();
boolean touchable = event.getX() > left && event.getX() < getWidth();
if (touchable) {
this.setText("");
}
}
return super.onTouchEvent(event);
}
/**
* 当ClearEditText焦点发生变化的时候:
* <p>
* 有焦点并且输入的文本内容不为空时则显示清除按钮
*/
@Override
public void onFocusChange(View v, boolean hasFocus) {
this.hasFocus = hasFocus;
setClearIconVisible(hasFocus && !TextUtils.isEmpty(getText()));
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(v, hasFocus);
}
}
/**
* 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
*
* @param visible
*/
protected void setClearIconVisible(boolean visible) {
if (mClearDrawable == null) return;
Drawable right = visible ? mClearDrawable : null;
setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
}
/**
* 1、是自己本身的话则调用父类的实现,
* <p>
* 2、是外部设置的就自己处理回调回去
*/
@Override
public void setOnFocusChangeListener(OnFocusChangeListener l) {
if (l instanceof PhoneEditView) {
super.setOnFocusChangeListener(l);
} else {
mOnFocusChangeListener = l;
}
}
@Override
public void addTextChangedListener(TextWatcher watcher) {
if (watcher instanceof PhoneEditView) {
super.addTextChangedListener(watcher);
} else {
mTextWatcher = watcher;
}
}
}
使用:
<com.test.widgets.PhoneEditView
android:id="@+id/edit_phone"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_94"
android:background="@android:color/transparent"
android:drawableEnd="@mipmap/ic_delete"
android:hint="@string/edit_enter_phone_hint"
android:text=""
android:textColorHint="@color/gray_999"
android:textSize="@dimen/sp_28" />