Android 自定义编辑文本不在特定位置编辑项目,但始终从最后一个项目开始编辑
我正在制作一个自定义的edittext来输入手机号码。这是密码Android 自定义编辑文本不在特定位置编辑项目,但始终从最后一个项目开始编辑,android,android-edittext,Android,Android Edittext,我正在制作一个自定义的edittext来输入手机号码。这是密码 public class PinEntryEditText extends android.support.v7.widget.AppCompatEditText { private static final String XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"; protected String mMask = nu
public class PinEntryEditText extends android.support.v7.widget.AppCompatEditText {
private static final String XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android";
protected String mMask = null;
protected StringBuilder mMaskChars = null;
protected String mSingleCharHint = null;
protected int mAnimatedType = 0;
protected float mSpace = 24; //24 dp by default, space between the lines
protected float mCharSize;
protected float mNumChars = 4;
protected float mTextBottomPadding = 8; //8dp by default, height of the text from our lines
protected int mMaxLength = 4;
protected RectF[] mLineCoords;
protected float[] mCharBottom;
protected Paint mCharPaint;
protected Paint mLastCharPaint;
protected Paint mSingleCharPaint;
protected Drawable mPinBackground;
protected Rect mTextHeight = new Rect();
protected boolean mIsDigitSquare = false;
protected View.OnClickListener mClickListener;
protected OnPinEnteredListener mOnPinEnteredListener = null;
protected float mLineStroke = 1; //1dp by default
protected float mLineStrokeSelected = 2; //2dp by default
protected Paint mLinesPaint;
protected boolean mAnimate = false;
protected boolean mHasError = false;
protected ColorStateList mOriginalTextColors;
protected int[][] mStates = new int[][]{
new int[]{android.R.attr.state_selected}, // selected
new int[]{android.R.attr.state_active}, // error
new int[]{android.R.attr.state_focused}, // focused
new int[]{-android.R.attr.state_focused}, // unfocused
};
protected int[] mColors = new int[]{
Color.GREEN,
Color.RED,
Color.BLACK,
Color.GRAY
};
protected ColorStateList mColorStates = new ColorStateList(mStates, mColors);
public PinEntryEditText(Context context) {
super(context);
}
public PinEntryEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public PinEntryEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// this(context, attrs, android.R.attr.editTextStyle);
init(context, attrs);
}
public void setMaxLength(final int maxLength) {
mMaxLength = maxLength;
mNumChars = maxLength;
setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
setText(null);
invalidate();
}
private void init(Context context, AttributeSet attrs) {
float multi = context.getResources().getDisplayMetrics().density;
mLineStroke = multi * mLineStroke;
mLineStrokeSelected = multi * mLineStrokeSelected;
mSpace = multi * mSpace; //convert to pixels for our density
mTextBottomPadding = multi * mTextBottomPadding; //convert to pixels for our density
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PinEntryEditText, 0, 0);
try {
TypedValue outValue = new TypedValue();
ta.getValue(R.styleable.PinEntryEditText_pinAnimationType, outValue);
mAnimatedType = outValue.data;
mMask = ta.getString(R.styleable.PinEntryEditText_pinCharacterMask);
mSingleCharHint = ta.getString(R.styleable.PinEntryEditText_pinRepeatedHint);
mLineStroke = ta.getDimension(R.styleable.PinEntryEditText_pinLineStroke, mLineStroke);
mLineStrokeSelected = ta.getDimension(R.styleable.PinEntryEditText_pinLineStrokeSelected, mLineStrokeSelected);
mSpace = ta.getDimension(R.styleable.PinEntryEditText_pinCharacterSpacing, mSpace);
mTextBottomPadding = ta.getDimension(R.styleable.PinEntryEditText_pinTextBottomPadding, mTextBottomPadding);
mIsDigitSquare = ta.getBoolean(R.styleable.PinEntryEditText_pinBackgroundIsSquare, mIsDigitSquare);
mPinBackground = ta.getDrawable(R.styleable.PinEntryEditText_pinBackgroundDrawable);
ColorStateList colors = ta.getColorStateList(R.styleable.PinEntryEditText_pinLineColors);
if (colors != null) {
mColorStates = colors;
}
} finally {
ta.recycle();
}
mCharPaint = new Paint(getPaint());
mLastCharPaint = new Paint(getPaint());
mSingleCharPaint = new Paint(getPaint());
mLinesPaint = new Paint(getPaint());
mLinesPaint.setStrokeWidth(mLineStroke);
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorControlActivated,
outValue, true);
int colorSelected = outValue.data;
mColors[0] = colorSelected;
int colorFocused = isInEditMode() ? Color.GRAY : ContextCompat.getColor(context, R.color.pin_normal);
mColors[1] = colorFocused;
int colorUnfocused = isInEditMode() ? Color.GRAY : ContextCompat.getColor(context, R.color.pin_normal);
mColors[2] = colorUnfocused;
setBackgroundResource(0);
mMaxLength = attrs.getAttributeIntValue(XML_NAMESPACE_ANDROID, "maxLength", 4);
mNumChars = mMaxLength;
//Disable copy paste
super.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
public void onDestroyActionMode(ActionMode mode) {
}
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
});
// When tapped, move cursor to end of text.
super.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSelection(getText().length());
if (mClickListener != null) {
mClickListener.onClick(v);
}
}
});
super.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
setSelection(getText().length());
return true;
}
});
//If input type is password and no mask is set, use a default mask
if ((getInputType() & InputType.TYPE_TEXT_VARIATION_PASSWORD) == InputType.TYPE_TEXT_VARIATION_PASSWORD && TextUtils.isEmpty(mMask)) {
mMask = "\u25CF";
} else if ((getInputType() & InputType.TYPE_NUMBER_VARIATION_PASSWORD) == InputType.TYPE_NUMBER_VARIATION_PASSWORD && TextUtils.isEmpty(mMask)) {
mMask = "\u25CF";
}
if (!TextUtils.isEmpty(mMask)) {
mMaskChars = getMaskChars();
}
//Height of the characters, used if there is a background drawable
getPaint().getTextBounds("|", 0, 1, mTextHeight);
mAnimate = mAnimatedType > -1;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mOriginalTextColors = getTextColors();
if (mOriginalTextColors != null) {
mLastCharPaint.setColor(mOriginalTextColors.getDefaultColor());
mCharPaint.setColor(mOriginalTextColors.getDefaultColor());
mSingleCharPaint.setColor(getCurrentHintTextColor());
}
int availableWidth = getWidth() - ViewCompat.getPaddingEnd(this) - ViewCompat.getPaddingStart(this);
if (mSpace < 0) {
mCharSize = (availableWidth / (mNumChars * 2 - 1));
} else {
mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars;
}
mLineCoords = new RectF[(int) mNumChars];
mCharBottom = new float[(int) mNumChars];
int startX;
int bottom = getHeight() - getPaddingBottom();
int rtlFlag;
final boolean isLayoutRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;
if (isLayoutRtl) {
rtlFlag = -1;
startX = (int) (getWidth() - ViewCompat.getPaddingStart(this) - mCharSize);
} else {
rtlFlag = 1;
startX = ViewCompat.getPaddingStart(this);
}
for (int i = 0; i < mNumChars; i++) {
mLineCoords[i] = new RectF(startX, bottom, startX + mCharSize, bottom);
if (mPinBackground != null) {
if (mIsDigitSquare) {
mLineCoords[i].top = getPaddingTop();
mLineCoords[i].right = startX + mLineCoords[i].height();
} else {
mLineCoords[i].top -= mTextHeight.height() + mTextBottomPadding * 2;
}
}
if (mSpace < 0) {
startX += rtlFlag * mCharSize * 2;
} else {
startX += rtlFlag * (mCharSize + mSpace);
}
mCharBottom[i] = mLineCoords[i].bottom - mTextBottomPadding;
}
}
@Override
public void setOnClickListener(View.OnClickListener l) {
mClickListener = l;
}
@Override
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
throw new RuntimeException("setCustomSelectionActionModeCallback() not supported.");
}
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
CharSequence text = getFullText();
int textLength = text.length();
float[] textWidths = new float[textLength];
getPaint().getTextWidths(text, 0, textLength, textWidths);
float hintWidth = 0;
if (mSingleCharHint != null) {
float[] hintWidths = new float[mSingleCharHint.length()];
getPaint().getTextWidths(mSingleCharHint, hintWidths);
for (float i : hintWidths) {
hintWidth += i;
}
}
for (int i = 0; i < mNumChars; i++) {
//If a background for the pin characters is specified, it should be behind the characters.
if (mPinBackground != null) {
updateDrawableState(i < textLength, i == textLength);
mPinBackground.setBounds((int) mLineCoords[i].left, (int) mLineCoords[i].top, (int) mLineCoords[i].right, (int) mLineCoords[i].bottom);
mPinBackground.draw(canvas);
}
float middle = mLineCoords[i].left + mCharSize / 2;
if (textLength > i) {
if (!mAnimate || i != textLength - 1) {
canvas.drawText(text, i, i + 1, middle - textWidths[i] / 2, mCharBottom[i], mCharPaint);
} else {
canvas.drawText(text, i, i + 1, middle - textWidths[i] / 2, mCharBottom[i], mLastCharPaint);
}
} else if (mSingleCharHint != null) {
canvas.drawText(mSingleCharHint, middle - hintWidth / 2, mCharBottom[i], mSingleCharPaint);
}
//The lines should be in front of the text (because that's how I want it).
if (mPinBackground == null) {
updateColorForLines(i <= textLength);
canvas.drawLine(mLineCoords[i].left, mLineCoords[i].top, mLineCoords[i].right, mLineCoords[i].bottom, mLinesPaint);
}
}
}
private CharSequence getFullText() {
if (mMask == null) {
return getText();
} else {
return getMaskChars();
}
}
private StringBuilder getMaskChars() {
if (mMaskChars == null) {
mMaskChars = new StringBuilder();
}
int textLength = getText().length();
while (mMaskChars.length() != textLength) {
if (mMaskChars.length() < textLength) {
mMaskChars.append(mMask);
} else {
mMaskChars.deleteCharAt(mMaskChars.length() - 1);
}
}
return mMaskChars;
}
private int getColorForState(int... states) {
return mColorStates.getColorForState(states, Color.GRAY);
}
/**
* @param hasTextOrIsNext Is the color for a character that has been typed or is
* the next character to be typed?
*/
protected void updateColorForLines(boolean hasTextOrIsNext) {
if (mHasError) {
mLinesPaint.setColor(getColorForState(android.R.attr.state_active));
} else if (isFocused()) {
mLinesPaint.setStrokeWidth(mLineStrokeSelected);
mLinesPaint.setColor(getColorForState(android.R.attr.state_focused));
if (hasTextOrIsNext) {
mLinesPaint.setColor(getColorForState(android.R.attr.state_selected));
}
} else {
mLinesPaint.setStrokeWidth(mLineStroke);
mLinesPaint.setColor(getColorForState(-android.R.attr.state_focused));
}
}
protected void updateDrawableState(boolean hasText, boolean isNext) {
if (mHasError) {
mPinBackground.setState(new int[]{android.R.attr.state_active});
} else if (isFocused()) {
mPinBackground.setState(new int[]{android.R.attr.state_focused});
if (isNext) {
mPinBackground.setState(new int[]{android.R.attr.state_focused, android.R.attr.state_selected});
} else if (hasText) {
mPinBackground.setState(new int[]{android.R.attr.state_focused, android.R.attr.state_checked});
}
} else {
mPinBackground.setState(new int[]{-android.R.attr.state_focused});
}
}
public void setError(boolean hasError) {
mHasError = hasError;
}
public boolean isError() {
return mHasError;
}
/**
* Request focus on this PinEntryEditText
*/
public void focus() {
requestFocus();
// Show keyboard
InputMethodManager inputMethodManager = (InputMethodManager) getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(this, 0);
}
@Override
protected void onTextChanged(CharSequence text, final int start, int lengthBefore, final int lengthAfter) {
setError(false);
if (mLineCoords == null || !mAnimate) {
if (mOnPinEnteredListener != null && text.length() == mMaxLength) {
mOnPinEnteredListener.onPinEntered(text);
}
return;
}
if (mAnimatedType == -1) {
invalidate();
return;
}
if (lengthAfter > lengthBefore) {
if (mAnimatedType == 0) {
animatePopIn();
} else {
animateBottomUp(text, start);
}
}
}
private void animatePopIn() {
ValueAnimator va = ValueAnimator.ofFloat(1, getPaint().getTextSize());
va.setDuration(200);
va.setInterpolator(new OvershootInterpolator());
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mLastCharPaint.setTextSize((Float) animation.getAnimatedValue());
PinEntryEditText.this.invalidate();
}
});
if (getText().length() == mMaxLength && mOnPinEnteredListener != null) {
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mOnPinEnteredListener.onPinEntered(getText());
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
va.start();
}
private void animateBottomUp(CharSequence text, final int start) {
mCharBottom[start] = mLineCoords[start].bottom - mTextBottomPadding;
ValueAnimator animUp = ValueAnimator.ofFloat(mCharBottom[start] + getPaint().getTextSize(), mCharBottom[start]);
animUp.setDuration(300);
animUp.setInterpolator(new OvershootInterpolator());
animUp.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float value = (Float) animation.getAnimatedValue();
mCharBottom[start] = value;
PinEntryEditText.this.invalidate();
}
});
mLastCharPaint.setAlpha(255);
ValueAnimator animAlpha = ValueAnimator.ofInt(0, 255);
animAlpha.setDuration(300);
animAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
mLastCharPaint.setAlpha(value);
}
});
AnimatorSet set = new AnimatorSet();
if (text.length() == mMaxLength && mOnPinEnteredListener != null) {
set.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mOnPinEnteredListener.onPinEntered(getText());
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
set.playTogether(animUp, animAlpha);
set.start();
}
public void setAnimateText(boolean animate) {
mAnimate = animate;
}
public void setOnPinEnteredListener(OnPinEnteredListener l) {
mOnPinEnteredListener = l;
}
public interface OnPinEnteredListener {
void onPinEntered(CharSequence str);
}
}
公共类PinEntryEditText扩展了android.support.v7.widget.appcompatiedittext{
私有静态最终字符串XML_NAMESPACE_ANDROID=”http://schemas.android.com/apk/res/android";
受保护字符串mMask=null;
受保护的StringBuilder mMaskChars=null;
受保护的字符串mSingleCharHint=null;
受保护的int mAnimatedType=0;
受保护的浮点mSpace=24;//默认情况下为24 dp,行之间的空间
受保护的浮动麦哈斯化;
受保护浮点数mNumChars=4;
受保护的float mTextBottomPadding=8;//8dp默认情况下,文本距行的高度
受保护的整数mm x长度=4;
受保护的RectF[]mLineCoords;
受保护的漂浮物[]mCharBottom;
保护漆;
受保护涂料;
受保护的油漆和油漆;
受保护的可牵引MPIN背景;
受保护的Rect mTextHeight=new Rect();
受保护的布尔误码方=假;
受保护的View.OnClickListener McClickListener;
受保护的OnPineInteredListener MonPineInteredListener=null;
受保护的浮点mLineStroke=1;//默认为1dp
受保护的浮点mLineStrokeSelected=2;//默认为2dp
受保护的油漆或油漆;
受保护的布尔型人体模型=false;
受保护布尔值mHasError=false;
受保护的ColorStateList mOriginalTextColors;
受保护的int[]mStates=新int[]{
新建int[]{android.R.attr.state_selected},//selected
new int[]{android.R.attr.state_active},//错误
新int[]{android.R.attr.state_focused},//focused
新int[]{-android.R.attr.state_-focused},//未聚焦
};
受保护的int[]mColors=新int[]{
颜色,绿色,
颜色,红色,
颜色,黑色,
颜色:灰色
};
受保护的ColorStateList mColorStates=新的ColorStateList(mStates,mColors);
公共PinEntryEditText(上下文){
超级(上下文);
}
公共PinEntryEditText(上下文、属性集属性){
超级(上下文,attrs);
init(上下文,attrs);
}
公共PinEntryEditText(上下文上下文、属性集属性、int defStyleAttr){
super(上下文、attrs、defStyleAttr);
//这(context、attrs、android.R.attr.editTextStyle);
init(上下文,attrs);
}
公共void setMaxLength(最终整数maxLength){
Mmax长度=最大长度;
mNumChars=最大长度;
setFilters(new-InputFilter[]{new-InputFilter.LengthFilter(maxLength)});
setText(空);
使无效();
}
私有void init(上下文上下文、属性集属性){
float multi=context.getResources().getDisplayMetrics().density;
mLineStroke=多*mLineStroke;
mLineStrokeSelected=多*mLineStrokeSelected;
mSpace=multi*mSpace;//转换为密度的像素
mTextBottomPadding=multi*mTextBottomPadding;//将密度转换为像素
TypedArray ta=context.ActainStyledAttributes(attrs,R.styleable.PinEntryEditText,0,0);
试一试{
TypedValue outValue=新的TypedValue();
ta.getValue(R.styleable.PinEntryEditText\u pinAnimationType,outValue);
mAnimatedType=outValue.data;
mMask=ta.getString(R.styleable.PinEntryEditText\u pinCharacterMask);
mSingleCharHint=ta.getString(R.styleable.PinEntryEditText\u pinRepeatedHint);
mLineStroke=ta.getDimension(R.styleable.PinEntryEditText_pinLineStroke,mLineStroke);
mLineStrokeSelected=ta.getDimension(R.styleable.PinEntryEditText_pinLineStrokeSelected,mLineStrokeSelected);
mSpace=ta.getDimension(R.styleable.PinEntryEditText\u pinCharacterSpacing,mSpace);
mTextBottomPadding=ta.getDimension(R.styleable.PinEntryEditText\u pinTextBottomPadding,mTextBottomPadding);
mIsDigitSquare=ta.getBoolean(R.styleable.PinEntryEditText\u pinbackgroundisquare,mIsDigitSquare);
mPinBackground=ta.getDrawable(R.styleable.PinEntryEditText\u pinBackgroundDrawable);
ColorStateList colors=ta.getColorStateList(R.styleable.PinEntryEditText\u pinLineColors);
如果(颜色!=null){
mColorStates=颜色;
}
}最后{
ta.recycle();
}
mCharPaint=新油漆(getPaint());
mLastCharPaint=新油漆(getPaint());
mSingleCharPaint=新绘制(getPaint());
mLinesPaint=新油漆(getPaint());
MLINESTAINT.设定行程宽度(MLINESTRAKE);
TypedValue outValue=新的TypedValue();
context.getTheme().resolveAttribute(R.attr.colorControlActivated,
超值,正确);
int colorSelected=outValue.data;
mColors[0]=选定的颜色;
int colorFocused=isInEditMode()?Color.GRAY:ContextCompat.getColor(context,R.Color.pin_normal);
mColors[1]=彩色聚焦;
int colorUnfocused=isInEditMode()?Color.GRAY:ContextCompat.getColor(context,R.Color.pin_normal);
mColors[2]=颜色未聚焦;
挫折资源(0);
mMaxLength=attrs.getAttributeIntValue(XML_NAMESPACE_ANDROID,“maxLength”,4);
mNumChars=mm x长度;
//禁用复制粘贴
super.setCustomSelectionActionModeCallback(新的ActionMode.Callback(){
公共布尔onPrepareActionMode(操作模式,菜单){
返回false;
}
公共void onDestroyActionMode(ActionMode模式){
}
公共布尔onCreateActionMode(ActionMode模式,菜单){
返回false;
}
公共布尔值onActionItemClicked(ActionMode模式,菜单项){
返回false;
}
});
//点击时,移动光标
<com.cartoon.customLayout.PinEntryEditText
android:id="@+id/et_activity_mobile_pinEntryEditText"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:background="@null"
android:cursorVisible="true"
android:digits="1234567890"
android:focusable="true"
android:imeOptions="actionDone"
android:inputType="number|phone"
android:maxLength="10"
android:textColor="@android:color/white"
android:textIsSelectable="true"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.40"
app:pinAnimationType="fromBottom"
app:pinBackgroundDrawable="@drawable/bg_pin"
app:pinCharacterSpacing="4dp" />
// When tapped, move cursor to end of text.
super.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//setSelection(getText().length());
if (mClickListener != null) {
mClickListener.onClick(v);
}
}
});
// When tapped, move cursor to end of text.
super.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setSelection(getText().length());
if (mClickListener != null) {
mClickListener.onClick(v);
}
}
});