自定义 passwordToggleDrawable 在 TextInputLayout 中太大
Custom passwordToggleDrawable is too large in TextInputLayout
我已经使用 android.support.design.widget.TextInputLayout 进行了密码输入,允许用户切换密码的可读性。 xml如下:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hintEnabled="false"
app:passwordToggleDrawable="@drawable/password_toggle_selector"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
可绘制选择器如
所述
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/password_toggle_show"
android:state_checked="true"/>
<item android:drawable="@drawable/password_toggle_hide"/>
</selector>
问题 是自定义可绘制对象变得非常大。不大于 edittext,但它似乎最大化了它的大小,同时仍然适合它(因此,它似乎受元素高度的限制)。但是,如果我未设置 passwordToggleDrawable 属性,则切换的可绘制对象的大小与 android 的正常大小相同(我相信您之前已经在其他应用程序中看到过该图标)。经过大量搜索,我找到了一种调整自定义大小的方法,但我对它的完成方式不满意(每个可绘制对象需要 2 extra xml 文件)并且它只能工作API 23 岁以上。
- 我想知道是否有设置可绘制对象大小的好方法,或者更好的方法是使其以默认可绘制对象的大小为目标?
我试过将 EditText 的填充设置为 TextInputLayout 的源说它从中获取四个填充并应用于 mPasswordToggleView(第 1143 行),但是它没有对图标进行任何更改,并且(如预期的那样)也影响了 EditText 的填充。我试过将 minheight 设置为 0。我也试过在 EditText 和 TextInputEditText 之间切换(现在似乎推荐使用后者)。我尝试将 layout_height 属性切换为 wrap_content。我已经尝试使用 xml 的 <scale>
标签和缩放属性集来缩放可绘制对象。我对 <inset>
标签进行了类似的尝试。 但是 none 这些方法有效。
我发现(并且目前正在使用)调整实际工作的可绘制对象大小的方法是使用xml标签<layer-list>
,同时设置宽度和高度属性。然后 <selector>
xml 文件引用那些调整大小的可绘制对象而不是 png 的可绘制对象。但我不喜欢这个解决方案,因为正如我提到的那样,它需要 API 23,因此导致总共有 4 个额外的 xml 文件。它还自行设置宽度和高度,而不是保持比例锁定。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/password_toggle_hide"
android:width="22dp"
android:height="15dp"/>
</layer-list>
TL;DR
如何在 TextInputLayout 中设置自定义 passwordToggleDrawable 的大小?最好与默认可绘制对象的大小相同。
我遇到同样的问题。为了避免这种情况,我使用了 png 并将它们设置为基于 dpi,如 drawable-hdpi、drawable-mdpi 等。还根据收音机使它们可绘制。希望这个技巧也对你有用。
我无法找到我实际提出的问题的任何解决方案,但我决定通过忽略问题的 "in InputTextLayout" 部分来解决问题,并且实现了我自己的 class.
版本
大部分它只是 InputTextLayout 的副本(遗憾的是 class 不能很好地转换为 subclassing,因为一切都是私有的)但是我不需要删除大部分内容,还有更多重要的是,将 CheckableImageButton mPasswordToggleView
更改为包含 View
.
的 ViewGroup
ViewGroup
是可点击的按钮,并处理 setMinimumDimensions 以将可点击区域保持在最小 48 dp,就像原来通过 design_text_input_password_icon.xml
所做的那样。这也使得小型可绘制对象不会紧贴屏幕右侧,因为它们位于可点击区域的中心,从而提供默认可绘制对象看起来具有的边距。
View
(或者更准确地说,我称之为 CheckableView
的新子 class)是实际的可绘制对象 (setBackground()
),替换了 CheckableImageButton
作为 drawable 的容器,可以根据 state_checked
选择器进行切换。
xml-属性 passwordToggleSize
允许设置尺寸,用于缩放drawable。我选择只有一个值而不是宽度和高度,并且锁定比例的可绘制缩放比例使其最大尺寸与指定的尺寸相匹配。我将默认大小设置为 24dp,这是为 design_ic_visibility.xml
.
中的默认可绘制对象指定的
PasswordToggleLayout.java:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.mylifediary.android.client.R;
public class PasswordToggleLayout extends LinearLayout {
// Default values from InputTextLayout's drawable and inflated layout
final int BUTTON_MIN_SIZE = 48; // The button is 48 dp at minimum.
final int DEFAULT_DRAWABLE_SIZE = 24; // The default drawable is 24 dp.
int mButtonMinSize;
final FrameLayout mInputFrame;
EditText mEditText;
private boolean mPasswordToggleEnabled;
private Drawable mPasswordToggleDrawable;
private CharSequence mPasswordToggleContentDesc;
ViewGroup mPasswordToggleViewGroup;
CheckableView mPasswordToggleView;
private boolean mPasswordToggledVisible;
private int mPasswordToggleSize;
private Drawable mPasswordToggleDummyDrawable;
private Drawable mOriginalEditTextEndDrawable;
private ColorStateList mPasswordToggleTintList;
private boolean mHasPasswordToggleTintList;
public PasswordToggleLayout(Context context) {
this(context, null);
}
public PasswordToggleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasswordToggleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
mButtonMinSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, BUTTON_MIN_SIZE,
getResources().getDisplayMetrics());
mInputFrame = new FrameLayout(context);
mInputFrame.setAddStatesFromChildren(true);
addView(mInputFrame);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PasswordToggleLayout, defStyleAttr,
R.style.Widget_Design_TextInputLayout);
mPasswordToggleEnabled = a.getBoolean(
R.styleable.PasswordToggleLayout_passwordToggleEnabled, false);
mPasswordToggleDrawable = a.getDrawable(
R.styleable.PasswordToggleLayout_passwordToggleDrawable);
mPasswordToggleContentDesc = a.getText(
R.styleable.PasswordToggleLayout_passwordToggleContentDescription);
if (a.hasValue(R.styleable.PasswordToggleLayout_passwordToggleTint)) {
mHasPasswordToggleTintList = true;
mPasswordToggleTintList = a.getColorStateList(
R.styleable.PasswordToggleLayout_passwordToggleTint);
}
mPasswordToggleSize = a.getDimensionPixelSize(
R.styleable.PasswordToggleLayout_passwordToggleSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_DRAWABLE_SIZE, getResources().getDisplayMetrics()));
a.recycle();
applyPasswordToggleTint();
}
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
throw new IllegalArgumentException(
"We already have an EditText, can only have one");
}
mEditText = editText;
final boolean hasPasswordTransformation = hasPasswordTransformation();
updatePasswordToggleView();
}
private void updatePasswordToggleView() {
if (mEditText == null) {
// If there is no EditText, there is nothing to update
return;
}
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
// Keep ratio
double w = mPasswordToggleDrawable.getIntrinsicWidth();
double h = mPasswordToggleDrawable.getIntrinsicHeight();
double scale = mPasswordToggleSize / Math.max(w,h);
int scaled_width = (int) (w * scale);
int scaled_height = (int) (h * scale);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END | Gravity.RIGHT);
FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(
scaled_width, scaled_height, Gravity.CENTER);
mPasswordToggleViewGroup = new FrameLayout(this.getContext());
mPasswordToggleViewGroup.setMinimumWidth(mButtonMinSize);
mPasswordToggleViewGroup.setMinimumHeight(mButtonMinSize);
mPasswordToggleViewGroup.setLayoutParams(lp);
mInputFrame.addView(mPasswordToggleViewGroup);
mPasswordToggleViewGroup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
mPasswordToggleView = new CheckableView(this.getContext());
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mPasswordToggleView.setLayoutParams(lp2);
mPasswordToggleViewGroup.addView(mPasswordToggleView);
}
if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
// We should make sure that the EditText has the same min-height
// as the password toggle view. This ensure focus works properly,
// and there is no visual jump if the password toggle is enabled/disabled.
mEditText.setMinimumHeight(
ViewCompat.getMinimumHeight(mPasswordToggleViewGroup));
}
mPasswordToggleViewGroup.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
// Need to add a dummy drawable as the end compound drawable so that
// the text is indented and doesn't display below the toggle view.
if (mPasswordToggleDummyDrawable == null) {
mPasswordToggleDummyDrawable = new ColorDrawable();
}
// Important to use mPasswordToggleViewGroup, as mPasswordToggleView
// wouldn't replicate the margin of the default-drawable.
mPasswordToggleDummyDrawable.setBounds(
0, 0, mPasswordToggleViewGroup.getMeasuredWidth(), 1);
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
// Store the user defined end compound drawable so that we can restore it later
if (compounds[2] != mPasswordToggleDummyDrawable) {
mOriginalEditTextEndDrawable = compounds[2];
}
TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0],
compounds[1], mPasswordToggleDummyDrawable, compounds[3]);
// Copy over the EditText's padding so that we match
mPasswordToggleViewGroup.setPadding(mEditText.getPaddingLeft(),
mEditText.getPaddingTop(), mEditText.getPaddingRight(),
mEditText.getPaddingBottom());
} else {
if (mPasswordToggleViewGroup != null
&& mPasswordToggleViewGroup.getVisibility() == VISIBLE) {
mPasswordToggleViewGroup.setVisibility(View.GONE);
}
if (mPasswordToggleDummyDrawable != null) {
// Make sure that we remove the dummy end compound drawable if
// it exists, and then clear it
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
if (compounds[2] == mPasswordToggleDummyDrawable) {
TextViewCompat.setCompoundDrawablesRelative(mEditText,
compounds[0], compounds[1],
mOriginalEditTextEndDrawable, compounds[3]);
mPasswordToggleDummyDrawable = null;
}
}
}
}
private void applyPasswordToggleTint() {
if (mPasswordToggleDrawable != null && mHasPasswordToggleTintList) {
mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
if (mPasswordToggleView != null
&& mPasswordToggleView.getBackground() != mPasswordToggleDrawable) {
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
}
}
}
private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
if (mPasswordToggleEnabled) {
// Store the current cursor position
final int selection = mEditText.getSelectionEnd();
if (hasPasswordTransformation()) {
mEditText.setTransformationMethod(null);
mPasswordToggledVisible = true;
} else {
mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
mPasswordToggledVisible = false;
}
mPasswordToggleView.setChecked(mPasswordToggledVisible);
if (shouldSkipAnimations) {
mPasswordToggleView.jumpDrawablesToCurrentState();
}
// And restore the cursor position
mEditText.setSelection(selection);
}
}
private boolean hasPasswordTransformation() {
return mEditText != null
&& mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
}
private boolean shouldShowPasswordIcon() {
return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
}
@Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
// Make sure that the EditText is vertically at the bottom,
// so that it sits on the EditText's underline
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
flp.gravity = Gravity.CENTER_VERTICAL
| (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
mInputFrame.addView(child, flp);
// Now use the EditText's LayoutParams as our own and update them
// to make enough space for the label
mInputFrame.setLayoutParams(params);
setEditText((EditText) child);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updatePasswordToggleView();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isPasswordToggledVisible = mPasswordToggledVisible;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isPasswordToggledVisible) {
passwordVisibilityToggleRequested(true);
}
requestLayout();
}
static class SavedState extends AbsSavedState {
boolean isPasswordToggledVisible;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
isPasswordToggledVisible = (source.readInt() == 1);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isPasswordToggledVisible ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public static class CheckableView extends View {
private final int[] DRAWABLE_STATE_CHECKED =
new int[]{android.R.attr.state_checked};
private boolean mChecked;
public CheckableView(Context context) {
super(context);
}
public CheckableView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CheckableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
if (mChecked) {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace
+ DRAWABLE_STATE_CHECKED.length), DRAWABLE_STATE_CHECKED);
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
}
然后在 attrs.xml:
<declare-styleable name="PasswordToggleLayout">
<attr name="passwordToggleEnabled" format="boolean"/>
<attr name="passwordToggleDrawable" format="reference"/>
<attr name="passwordToggleContentDescription" format="string"/>
<attr name="passwordToggleTint" format="color"/>
<attr name="passwordToggleSize" format="dimension"/>
</declare-styleable>
我知道这是一个老问题,但我遇到了同样的问题,我相信我找到了一个简单的解决方案。
我正在为最新的 material 库使用 TextInputLayout,我唯一做的就是从 TextInputLayout 中找到 endIcon 的引用并更改它的最小尺寸。
val dimension = //here you get the dimension you want to
val endIconImageView = yourTextInputLayout.findViewById<ImageView>(R.id.text_input_end_icon)
endIconImageView.minimumHeight = dimension
endIconImageView.minimumWidth = dimension
yourTextInputLayout.requestLayout()
需要注意的重要事项:
我在 OnFinishedInflated
上通过自定义 TextInputLayout 执行了此操作,但我相信它会在某些 activity class.
上正常工作
干杯!
我也遇到同样的问题。问题来自 gradle material API 实现:
implementation 'com.google.android.material:material:1.1.0'
降级到版本 1.0.0 修复了问题:
implementation 'com.google.android.material:material:1.0.0'
我已经使用 android.support.design.widget.TextInputLayout 进行了密码输入,允许用户切换密码的可读性。 xml如下:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hintEnabled="false"
app:passwordToggleDrawable="@drawable/password_toggle_selector"
app:passwordToggleEnabled="true" >
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Password"
android:inputType="textPassword"/>
</android.support.design.widget.TextInputLayout>
可绘制选择器如
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/password_toggle_show"
android:state_checked="true"/>
<item android:drawable="@drawable/password_toggle_hide"/>
</selector>
问题 是自定义可绘制对象变得非常大。不大于 edittext,但它似乎最大化了它的大小,同时仍然适合它(因此,它似乎受元素高度的限制)。但是,如果我未设置 passwordToggleDrawable 属性,则切换的可绘制对象的大小与 android 的正常大小相同(我相信您之前已经在其他应用程序中看到过该图标)。经过大量搜索,我找到了一种调整自定义大小的方法,但我对它的完成方式不满意(每个可绘制对象需要 2 extra xml 文件)并且它只能工作API 23 岁以上。
- 我想知道是否有设置可绘制对象大小的好方法,或者更好的方法是使其以默认可绘制对象的大小为目标?
我试过将 EditText 的填充设置为 TextInputLayout 的源说它从中获取四个填充并应用于 mPasswordToggleView(第 1143 行),但是它没有对图标进行任何更改,并且(如预期的那样)也影响了 EditText 的填充。我试过将 minheight 设置为 0。我也试过在 EditText 和 TextInputEditText 之间切换(现在似乎推荐使用后者)。我尝试将 layout_height 属性切换为 wrap_content。我已经尝试使用 xml 的 <scale>
标签和缩放属性集来缩放可绘制对象。我对 <inset>
标签进行了类似的尝试。 但是 none 这些方法有效。
我发现(并且目前正在使用)调整实际工作的可绘制对象大小的方法是使用xml标签<layer-list>
,同时设置宽度和高度属性。然后 <selector>
xml 文件引用那些调整大小的可绘制对象而不是 png 的可绘制对象。但我不喜欢这个解决方案,因为正如我提到的那样,它需要 API 23,因此导致总共有 4 个额外的 xml 文件。它还自行设置宽度和高度,而不是保持比例锁定。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/password_toggle_hide"
android:width="22dp"
android:height="15dp"/>
</layer-list>
TL;DR 如何在 TextInputLayout 中设置自定义 passwordToggleDrawable 的大小?最好与默认可绘制对象的大小相同。
我遇到同样的问题。为了避免这种情况,我使用了 png 并将它们设置为基于 dpi,如 drawable-hdpi、drawable-mdpi 等。还根据收音机使它们可绘制。希望这个技巧也对你有用。
我无法找到我实际提出的问题的任何解决方案,但我决定通过忽略问题的 "in InputTextLayout" 部分来解决问题,并且实现了我自己的 class.
版本大部分它只是 InputTextLayout 的副本(遗憾的是 class 不能很好地转换为 subclassing,因为一切都是私有的)但是我不需要删除大部分内容,还有更多重要的是,将 CheckableImageButton mPasswordToggleView
更改为包含 View
.
ViewGroup
ViewGroup
是可点击的按钮,并处理 setMinimumDimensions 以将可点击区域保持在最小 48 dp,就像原来通过 design_text_input_password_icon.xml
所做的那样。这也使得小型可绘制对象不会紧贴屏幕右侧,因为它们位于可点击区域的中心,从而提供默认可绘制对象看起来具有的边距。
View
(或者更准确地说,我称之为 CheckableView
的新子 class)是实际的可绘制对象 (setBackground()
),替换了 CheckableImageButton
作为 drawable 的容器,可以根据 state_checked
选择器进行切换。
xml-属性 passwordToggleSize
允许设置尺寸,用于缩放drawable。我选择只有一个值而不是宽度和高度,并且锁定比例的可绘制缩放比例使其最大尺寸与指定的尺寸相匹配。我将默认大小设置为 24dp,这是为 design_ic_visibility.xml
.
PasswordToggleLayout.java:
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.TextViewCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.mylifediary.android.client.R;
public class PasswordToggleLayout extends LinearLayout {
// Default values from InputTextLayout's drawable and inflated layout
final int BUTTON_MIN_SIZE = 48; // The button is 48 dp at minimum.
final int DEFAULT_DRAWABLE_SIZE = 24; // The default drawable is 24 dp.
int mButtonMinSize;
final FrameLayout mInputFrame;
EditText mEditText;
private boolean mPasswordToggleEnabled;
private Drawable mPasswordToggleDrawable;
private CharSequence mPasswordToggleContentDesc;
ViewGroup mPasswordToggleViewGroup;
CheckableView mPasswordToggleView;
private boolean mPasswordToggledVisible;
private int mPasswordToggleSize;
private Drawable mPasswordToggleDummyDrawable;
private Drawable mOriginalEditTextEndDrawable;
private ColorStateList mPasswordToggleTintList;
private boolean mHasPasswordToggleTintList;
public PasswordToggleLayout(Context context) {
this(context, null);
}
public PasswordToggleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasswordToggleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
setWillNotDraw(false);
setAddStatesFromChildren(true);
mButtonMinSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, BUTTON_MIN_SIZE,
getResources().getDisplayMetrics());
mInputFrame = new FrameLayout(context);
mInputFrame.setAddStatesFromChildren(true);
addView(mInputFrame);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.PasswordToggleLayout, defStyleAttr,
R.style.Widget_Design_TextInputLayout);
mPasswordToggleEnabled = a.getBoolean(
R.styleable.PasswordToggleLayout_passwordToggleEnabled, false);
mPasswordToggleDrawable = a.getDrawable(
R.styleable.PasswordToggleLayout_passwordToggleDrawable);
mPasswordToggleContentDesc = a.getText(
R.styleable.PasswordToggleLayout_passwordToggleContentDescription);
if (a.hasValue(R.styleable.PasswordToggleLayout_passwordToggleTint)) {
mHasPasswordToggleTintList = true;
mPasswordToggleTintList = a.getColorStateList(
R.styleable.PasswordToggleLayout_passwordToggleTint);
}
mPasswordToggleSize = a.getDimensionPixelSize(
R.styleable.PasswordToggleLayout_passwordToggleSize,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
DEFAULT_DRAWABLE_SIZE, getResources().getDisplayMetrics()));
a.recycle();
applyPasswordToggleTint();
}
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
throw new IllegalArgumentException(
"We already have an EditText, can only have one");
}
mEditText = editText;
final boolean hasPasswordTransformation = hasPasswordTransformation();
updatePasswordToggleView();
}
private void updatePasswordToggleView() {
if (mEditText == null) {
// If there is no EditText, there is nothing to update
return;
}
if (shouldShowPasswordIcon()) {
if (mPasswordToggleView == null) {
// Keep ratio
double w = mPasswordToggleDrawable.getIntrinsicWidth();
double h = mPasswordToggleDrawable.getIntrinsicHeight();
double scale = mPasswordToggleSize / Math.max(w,h);
int scaled_width = (int) (w * scale);
int scaled_height = (int) (h * scale);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER_VERTICAL | Gravity.END | Gravity.RIGHT);
FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(
scaled_width, scaled_height, Gravity.CENTER);
mPasswordToggleViewGroup = new FrameLayout(this.getContext());
mPasswordToggleViewGroup.setMinimumWidth(mButtonMinSize);
mPasswordToggleViewGroup.setMinimumHeight(mButtonMinSize);
mPasswordToggleViewGroup.setLayoutParams(lp);
mInputFrame.addView(mPasswordToggleViewGroup);
mPasswordToggleViewGroup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
passwordVisibilityToggleRequested(false);
}
});
mPasswordToggleView = new CheckableView(this.getContext());
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
mPasswordToggleView.setContentDescription(mPasswordToggleContentDesc);
mPasswordToggleView.setLayoutParams(lp2);
mPasswordToggleViewGroup.addView(mPasswordToggleView);
}
if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
// We should make sure that the EditText has the same min-height
// as the password toggle view. This ensure focus works properly,
// and there is no visual jump if the password toggle is enabled/disabled.
mEditText.setMinimumHeight(
ViewCompat.getMinimumHeight(mPasswordToggleViewGroup));
}
mPasswordToggleViewGroup.setVisibility(VISIBLE);
mPasswordToggleView.setChecked(mPasswordToggledVisible);
// Need to add a dummy drawable as the end compound drawable so that
// the text is indented and doesn't display below the toggle view.
if (mPasswordToggleDummyDrawable == null) {
mPasswordToggleDummyDrawable = new ColorDrawable();
}
// Important to use mPasswordToggleViewGroup, as mPasswordToggleView
// wouldn't replicate the margin of the default-drawable.
mPasswordToggleDummyDrawable.setBounds(
0, 0, mPasswordToggleViewGroup.getMeasuredWidth(), 1);
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
// Store the user defined end compound drawable so that we can restore it later
if (compounds[2] != mPasswordToggleDummyDrawable) {
mOriginalEditTextEndDrawable = compounds[2];
}
TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0],
compounds[1], mPasswordToggleDummyDrawable, compounds[3]);
// Copy over the EditText's padding so that we match
mPasswordToggleViewGroup.setPadding(mEditText.getPaddingLeft(),
mEditText.getPaddingTop(), mEditText.getPaddingRight(),
mEditText.getPaddingBottom());
} else {
if (mPasswordToggleViewGroup != null
&& mPasswordToggleViewGroup.getVisibility() == VISIBLE) {
mPasswordToggleViewGroup.setVisibility(View.GONE);
}
if (mPasswordToggleDummyDrawable != null) {
// Make sure that we remove the dummy end compound drawable if
// it exists, and then clear it
final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText);
if (compounds[2] == mPasswordToggleDummyDrawable) {
TextViewCompat.setCompoundDrawablesRelative(mEditText,
compounds[0], compounds[1],
mOriginalEditTextEndDrawable, compounds[3]);
mPasswordToggleDummyDrawable = null;
}
}
}
}
private void applyPasswordToggleTint() {
if (mPasswordToggleDrawable != null && mHasPasswordToggleTintList) {
mPasswordToggleDrawable = DrawableCompat.wrap(mPasswordToggleDrawable).mutate();
DrawableCompat.setTintList(mPasswordToggleDrawable, mPasswordToggleTintList);
if (mPasswordToggleView != null
&& mPasswordToggleView.getBackground() != mPasswordToggleDrawable) {
mPasswordToggleView.setBackground(mPasswordToggleDrawable);
}
}
}
private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
if (mPasswordToggleEnabled) {
// Store the current cursor position
final int selection = mEditText.getSelectionEnd();
if (hasPasswordTransformation()) {
mEditText.setTransformationMethod(null);
mPasswordToggledVisible = true;
} else {
mEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
mPasswordToggledVisible = false;
}
mPasswordToggleView.setChecked(mPasswordToggledVisible);
if (shouldSkipAnimations) {
mPasswordToggleView.jumpDrawablesToCurrentState();
}
// And restore the cursor position
mEditText.setSelection(selection);
}
}
private boolean hasPasswordTransformation() {
return mEditText != null
&& mEditText.getTransformationMethod() instanceof PasswordTransformationMethod;
}
private boolean shouldShowPasswordIcon() {
return mPasswordToggleEnabled && (hasPasswordTransformation() || mPasswordToggledVisible);
}
@Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
// Make sure that the EditText is vertically at the bottom,
// so that it sits on the EditText's underline
FrameLayout.LayoutParams flp = new FrameLayout.LayoutParams(params);
flp.gravity = Gravity.CENTER_VERTICAL
| (flp.gravity & ~Gravity.VERTICAL_GRAVITY_MASK);
mInputFrame.addView(child, flp);
// Now use the EditText's LayoutParams as our own and update them
// to make enough space for the label
mInputFrame.setLayoutParams(params);
setEditText((EditText) child);
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
updatePasswordToggleView();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.isPasswordToggledVisible = mPasswordToggledVisible;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
if (ss.isPasswordToggledVisible) {
passwordVisibilityToggleRequested(true);
}
requestLayout();
}
static class SavedState extends AbsSavedState {
boolean isPasswordToggledVisible;
SavedState(Parcelable superState) {
super(superState);
}
SavedState(Parcel source, ClassLoader loader) {
super(source, loader);
isPasswordToggledVisible = (source.readInt() == 1);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(isPasswordToggledVisible ? 1 : 0);
}
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in, ClassLoader loader) {
return new SavedState(in, loader);
}
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in, null);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public static class CheckableView extends View {
private final int[] DRAWABLE_STATE_CHECKED =
new int[]{android.R.attr.state_checked};
private boolean mChecked;
public CheckableView(Context context) {
super(context);
}
public CheckableView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CheckableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
if (mChecked) {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace
+ DRAWABLE_STATE_CHECKED.length), DRAWABLE_STATE_CHECKED);
} else {
return super.onCreateDrawableState(extraSpace);
}
}
}
}
然后在 attrs.xml:
<declare-styleable name="PasswordToggleLayout">
<attr name="passwordToggleEnabled" format="boolean"/>
<attr name="passwordToggleDrawable" format="reference"/>
<attr name="passwordToggleContentDescription" format="string"/>
<attr name="passwordToggleTint" format="color"/>
<attr name="passwordToggleSize" format="dimension"/>
</declare-styleable>
我知道这是一个老问题,但我遇到了同样的问题,我相信我找到了一个简单的解决方案。
我正在为最新的 material 库使用 TextInputLayout,我唯一做的就是从 TextInputLayout 中找到 endIcon 的引用并更改它的最小尺寸。
val dimension = //here you get the dimension you want to
val endIconImageView = yourTextInputLayout.findViewById<ImageView>(R.id.text_input_end_icon)
endIconImageView.minimumHeight = dimension
endIconImageView.minimumWidth = dimension
yourTextInputLayout.requestLayout()
需要注意的重要事项:
我在 OnFinishedInflated
上通过自定义 TextInputLayout 执行了此操作,但我相信它会在某些 activity class.
干杯!
我也遇到同样的问题。问题来自 gradle material API 实现:
implementation 'com.google.android.material:material:1.1.0'
降级到版本 1.0.0 修复了问题:
implementation 'com.google.android.material:material:1.0.0'