CustomEditText 的奇怪行为
Strange behavior of CustomEditText
我创建了一个自定义 EditText
以适应我的应用程序的设计。 EditText
应该有一个提示,当它被聚焦时,提示应该变色并向右移动。当它不再聚焦时,提示要么回到左侧,要么,如果文本被写入 EditText
,它会停留在右侧,只是将颜色更改回默认的提示颜色.
这是代码,它运行得非常完美:
(进一步解释 LayoutWrapContentUpdater here)
public class CustomEditText extends ConstraintLayout {
private EditText textField;
private TextView hint;
private float hintSize;
private float hintSizeFocused;
private int hintColorFocused;
private int hintColor;
public CustomEditText(Context context) {
this(context, null);
}
public CustomEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.custom_edit_text, this, true);
setClickable(false);
textField = findViewById(R.id.custom_edit_text_text_field);
textField.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus) {
focusHint();
} else {
unfocusHint();
}
}
});
hint = findViewById(R.id.custom_edit_text_hint);
hint.setClickable(false);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomEditText, defStyleAttr, 0);
hintSize = a.getDimension(R.styleable.CustomEditText_hintSize, dpToPx(DEF_HINT_SIZE));
hint.setTextSize(hintSize);
hintSizeFocused = a.getDimension(R.styleable.CustomEditText_hintSizeFocused, dpToPx(DEF_HINT_SIZE_FOCUSED));
if(a.hasValue(R.styleable.CustomEditText_hint)) {
hint.setText(a.getText(R.styleable.CustomEditText_hint));
}
hintColorFocused = a.getColor(R.styleable.CustomEditText_hintColorFocused, getResources().getColor(R.color.colorPrimary));
hintColor = a.getColor(R.styleable.CustomEditText_hintColor, getResources().getColor(R.color.def_hint_color));
if (a.hasValue(R.styleable.CustomEditText_customBackground)) {
Drawable background = a.getDrawable(R.styleable.CustomEditText_customBackground);
textField.setBackground(background);
}
if(a.getBoolean(R.styleable.CustomEditText_isPassword, false)) {
textField.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
textField.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
textField.setTextSize(a.getDimension(R.styleable.CustomEditText_textSize, dpToPx(DEF_TEXT_SIZE)));
a.recycle();
}
private void focusHint() {
hint.setTextSize(hintSizeFocused);
LayoutWrapContentUpdater.wrapContentAgain(this);
hint.setTextColor(hintColorFocused);
if(textField.getText().toString().isEmpty())
moveHintToRight();
if(textField.getPaddingRight() == 0) {
textField.setPadding(textField.getPaddingLeft(), textField.getPaddingTop(), hint.getWidth() + dpToPx(HINT_MARGIN_SIDE), textField.getPaddingBottom());
}
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(textField, InputMethodManager.SHOW_IMPLICIT);
}
private void unfocusHint() {
hint.setTextColor(hintColor);
if(textField.getText().toString().isEmpty()) {
moveHintToLeft();
hint.setTextSize(hintSize);
LayoutWrapContentUpdater.wrapContentAgain(this);
}
}
private void moveHintToRight() {
int horizontalDistance = textField.getWidth() - hint.getWidth() - dpToPx(HINT_MARGIN_SIDE * 2);
TranslateAnimation anim = new TranslateAnimation(0, horizontalDistance, 0, 0);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
if(textField.hasFocus()) {
ConstraintLayout l = findViewById(R.id.custom_edit_text_constraint_layout);
ConstraintSet set = new ConstraintSet();
set.clone(l);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.LEFT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.RIGHT, R.id.custom_edit_text_constraint_layout, ConstraintSet.RIGHT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(l);
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
hint.startAnimation(anim);
}
private void moveHintToLeft() {
ConstraintSet set = new ConstraintSet();
ConstraintLayout l = findViewById(R.id.custom_edit_text_constraint_layout);
set.clone(l);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.RIGHT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.LEFT, R.id.custom_edit_text_constraint_layout, ConstraintSet.LEFT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(l);
}
}
属性:
<resources>
<declare-styleable name="CustomEditText">
<attr name="hintSize" format="dimension"/>
<attr name="hintSizeFocused" format="dimension"/>
<attr name="hint" format="string"/>
<attr name="hintColorFocused" format="color"/>
<attr name="hintColor" format="color"/>
<attr name="customBackground" format="integer"/>
<attr name="isPassword" format="boolean"/>
<attr name="textSize" format="dimension"/>
</declare-styleable>
</resources>
XML中的示例:
<com.workoutlog.workoutlog.views.CustomEditText
android:layout_width="match_parent"
android:layout_height="@dimen/textfield_height"
app:hint="@string/hint"
app:hintSize="@dimen/hint_size"
/>
如前所述,这行得通。但是有一件奇怪的事情。我在片段中使用这些 CustomEditText
s。现在,当我在一个布局中有更多它们时,在最后一个 CustomEditText
中输入一些文本,转到另一个片段并再次 return 到该片段(按下后退按钮或调用 supportFragmentManager.popBackStack()
),当我打开新片段时,该片段中的每个 CustomEditText
都写了最后一个 CustomEditText
的文本,并且提示不再正常工作(文本被写在提示上并且它们不'向右移动)。当我在另一个 CustomEditText
中键入一些文本而在最后一个中没有文本时,这不会发生。我无法解释为什么会这样。
CustomEditText
s 位于膨胀成片段的布局内。
只有在布局中的最后一个 CustomEditText
中输入一些文本时才会发生。
如果您需要更多代码,我可以编辑它。
编辑
Wirling 的回答有效,谢谢!但是有一件事仍然不起作用:我无法将提示向右移动。当状态恢复时,提示再次位于左侧,并具有默认的 textSize 和 textColor。我将此代码放入 onRestoreInstanceState
:
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).restoreHierarchyState(ss.childrenStates);
}
if(!textField.getText().toString().isEmpty()) {
hint.setTextSize(hintSizeFocused);
LayoutWrapContentUpdater.wrapContentAgain(this);
hint.setTextColor(hintColorFocused);
ConstraintSet set = new ConstraintSet();
set.clone(layout);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.LEFT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.RIGHT, R.id.custom_edit_text_constraint_layout, ConstraintSet.RIGHT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(layout);
}
}
颜色和大小都变了,但我不能把它放在右边。
编辑2:
每次将 childrenState
传递给方法时,我也会收到警告。例如:
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeSparseArray(childrenStates);
}
这里我收到以下警告:
Unchecked assignment: 'android.util.SparseArray' to 'android.util.SparseArray<java.lang.Object>'
这是因为视图 ID 重复。看看http://trickyandroid.com/saving-android-view-state-correctly/
并将此代码添加到您的 class:
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.childrenStates = new SparseArray();
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).saveHierarchyState(ss.childrenStates);
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).restoreHierarchyState(ss.childrenStates);
}
}
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
dispatchThawSelfOnly(container);
}
static class SavedState extends BaseSavedState {
SparseArray childrenStates;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in, ClassLoader classLoader) {
super(in);
childrenStates = in.readSparseArray(classLoader);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeSparseArray(childrenStates);
}
public static final ClassLoaderCreator<SavedState> CREATOR
= new ClassLoaderCreator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel source, ClassLoader loader) {
return new SavedState(source, loader);
}
@Override
public SavedState createFromParcel(Parcel source) {
return createFromParcel(source, null);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
我创建了一个自定义 EditText
以适应我的应用程序的设计。 EditText
应该有一个提示,当它被聚焦时,提示应该变色并向右移动。当它不再聚焦时,提示要么回到左侧,要么,如果文本被写入 EditText
,它会停留在右侧,只是将颜色更改回默认的提示颜色.
这是代码,它运行得非常完美: (进一步解释 LayoutWrapContentUpdater here)
public class CustomEditText extends ConstraintLayout {
private EditText textField;
private TextView hint;
private float hintSize;
private float hintSizeFocused;
private int hintColorFocused;
private int hintColor;
public CustomEditText(Context context) {
this(context, null);
}
public CustomEditText(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutInflater.inflate(R.layout.custom_edit_text, this, true);
setClickable(false);
textField = findViewById(R.id.custom_edit_text_text_field);
textField.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus) {
focusHint();
} else {
unfocusHint();
}
}
});
hint = findViewById(R.id.custom_edit_text_hint);
hint.setClickable(false);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomEditText, defStyleAttr, 0);
hintSize = a.getDimension(R.styleable.CustomEditText_hintSize, dpToPx(DEF_HINT_SIZE));
hint.setTextSize(hintSize);
hintSizeFocused = a.getDimension(R.styleable.CustomEditText_hintSizeFocused, dpToPx(DEF_HINT_SIZE_FOCUSED));
if(a.hasValue(R.styleable.CustomEditText_hint)) {
hint.setText(a.getText(R.styleable.CustomEditText_hint));
}
hintColorFocused = a.getColor(R.styleable.CustomEditText_hintColorFocused, getResources().getColor(R.color.colorPrimary));
hintColor = a.getColor(R.styleable.CustomEditText_hintColor, getResources().getColor(R.color.def_hint_color));
if (a.hasValue(R.styleable.CustomEditText_customBackground)) {
Drawable background = a.getDrawable(R.styleable.CustomEditText_customBackground);
textField.setBackground(background);
}
if(a.getBoolean(R.styleable.CustomEditText_isPassword, false)) {
textField.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
textField.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
textField.setTextSize(a.getDimension(R.styleable.CustomEditText_textSize, dpToPx(DEF_TEXT_SIZE)));
a.recycle();
}
private void focusHint() {
hint.setTextSize(hintSizeFocused);
LayoutWrapContentUpdater.wrapContentAgain(this);
hint.setTextColor(hintColorFocused);
if(textField.getText().toString().isEmpty())
moveHintToRight();
if(textField.getPaddingRight() == 0) {
textField.setPadding(textField.getPaddingLeft(), textField.getPaddingTop(), hint.getWidth() + dpToPx(HINT_MARGIN_SIDE), textField.getPaddingBottom());
}
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(textField, InputMethodManager.SHOW_IMPLICIT);
}
private void unfocusHint() {
hint.setTextColor(hintColor);
if(textField.getText().toString().isEmpty()) {
moveHintToLeft();
hint.setTextSize(hintSize);
LayoutWrapContentUpdater.wrapContentAgain(this);
}
}
private void moveHintToRight() {
int horizontalDistance = textField.getWidth() - hint.getWidth() - dpToPx(HINT_MARGIN_SIDE * 2);
TranslateAnimation anim = new TranslateAnimation(0, horizontalDistance, 0, 0);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
if(textField.hasFocus()) {
ConstraintLayout l = findViewById(R.id.custom_edit_text_constraint_layout);
ConstraintSet set = new ConstraintSet();
set.clone(l);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.LEFT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.RIGHT, R.id.custom_edit_text_constraint_layout, ConstraintSet.RIGHT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(l);
}
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
hint.startAnimation(anim);
}
private void moveHintToLeft() {
ConstraintSet set = new ConstraintSet();
ConstraintLayout l = findViewById(R.id.custom_edit_text_constraint_layout);
set.clone(l);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.RIGHT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.LEFT, R.id.custom_edit_text_constraint_layout, ConstraintSet.LEFT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(l);
}
}
属性:
<resources>
<declare-styleable name="CustomEditText">
<attr name="hintSize" format="dimension"/>
<attr name="hintSizeFocused" format="dimension"/>
<attr name="hint" format="string"/>
<attr name="hintColorFocused" format="color"/>
<attr name="hintColor" format="color"/>
<attr name="customBackground" format="integer"/>
<attr name="isPassword" format="boolean"/>
<attr name="textSize" format="dimension"/>
</declare-styleable>
</resources>
XML中的示例:
<com.workoutlog.workoutlog.views.CustomEditText
android:layout_width="match_parent"
android:layout_height="@dimen/textfield_height"
app:hint="@string/hint"
app:hintSize="@dimen/hint_size"
/>
如前所述,这行得通。但是有一件奇怪的事情。我在片段中使用这些 CustomEditText
s。现在,当我在一个布局中有更多它们时,在最后一个 CustomEditText
中输入一些文本,转到另一个片段并再次 return 到该片段(按下后退按钮或调用 supportFragmentManager.popBackStack()
),当我打开新片段时,该片段中的每个 CustomEditText
都写了最后一个 CustomEditText
的文本,并且提示不再正常工作(文本被写在提示上并且它们不'向右移动)。当我在另一个 CustomEditText
中键入一些文本而在最后一个中没有文本时,这不会发生。我无法解释为什么会这样。
CustomEditText
s 位于膨胀成片段的布局内。
只有在布局中的最后一个 CustomEditText
中输入一些文本时才会发生。
如果您需要更多代码,我可以编辑它。
编辑
Wirling 的回答有效,谢谢!但是有一件事仍然不起作用:我无法将提示向右移动。当状态恢复时,提示再次位于左侧,并具有默认的 textSize 和 textColor。我将此代码放入 onRestoreInstanceState
:
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).restoreHierarchyState(ss.childrenStates);
}
if(!textField.getText().toString().isEmpty()) {
hint.setTextSize(hintSizeFocused);
LayoutWrapContentUpdater.wrapContentAgain(this);
hint.setTextColor(hintColorFocused);
ConstraintSet set = new ConstraintSet();
set.clone(layout);
set.clear(R.id.custom_edit_text_hint, ConstraintSet.LEFT);
set.connect(R.id.custom_edit_text_hint, ConstraintSet.RIGHT, R.id.custom_edit_text_constraint_layout, ConstraintSet.RIGHT, dpToPx(HINT_MARGIN_SIDE));
set.applyTo(layout);
}
}
颜色和大小都变了,但我不能把它放在右边。
编辑2:
每次将 childrenState
传递给方法时,我也会收到警告。例如:
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeSparseArray(childrenStates);
}
这里我收到以下警告:
Unchecked assignment: 'android.util.SparseArray' to 'android.util.SparseArray<java.lang.Object>'
这是因为视图 ID 重复。看看http://trickyandroid.com/saving-android-view-state-correctly/
并将此代码添加到您的 class:
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.childrenStates = new SparseArray();
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).saveHierarchyState(ss.childrenStates);
}
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).restoreHierarchyState(ss.childrenStates);
}
}
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
dispatchThawSelfOnly(container);
}
static class SavedState extends BaseSavedState {
SparseArray childrenStates;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in, ClassLoader classLoader) {
super(in);
childrenStates = in.readSparseArray(classLoader);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeSparseArray(childrenStates);
}
public static final ClassLoaderCreator<SavedState> CREATOR
= new ClassLoaderCreator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel source, ClassLoader loader) {
return new SavedState(source, loader);
}
@Override
public SavedState createFromParcel(Parcel source) {
return createFromParcel(source, null);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}