首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >自定义passwordToggleDrawable在TextInputLayout中太大

自定义passwordToggleDrawable在TextInputLayout中太大
EN

Stack Overflow用户
提问于 2019-03-12 16:34:36
回答 3查看 3.7K关注 0票数 1

我使用android.support.design.widget.TextInputLayout来输入密码,允许用户切换密码的可读性。xml如下:

代码语言:javascript
运行
复制
<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>

可绘制的选择器如How to customize android passwordToggleDrawable所描述

代码语言:javascript
运行
复制
<?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>

问题是定制绘图变得非常大。不大于编辑文本,相反,它似乎最大化了它的大小,同时仍然适合它(因此,它似乎被限制在元素的高度)。然而,如果我不设置passwordToggleDrawable属性,则切换按钮的可绘制大小与安卓正常的大小相同(我相信您以前在其他应用程序中见过该图标)。经过大量的搜索,我找到了一种调整自定义23+大小的方法,但我对它的实现方式并不满意(每个可绘图需要2个额外的xml文件),而且它只适用于API。

  • 我想知道是否有一种很好的方式来设置可绘制的大小,或者更好的是,让它针对默认可绘制的大小?

--我已经尝试过将EditText的填充设置为TextInputLayout的源,表示它从它获得了四个填充并应用到mPasswordToggleView (第1143行),但是它没有对图标进行任何更改,并且(正如预期的那样)也影响了EditText的填充。我试过把最小高度设为0。我还尝试了在EditText和TextInputEditText之间进行更改(现在似乎建议使用后者)。我已经尝试过将layout_height属性切换到wrap_content。我已经尝试使用xml的<scale>标记来缩放可绘制的对象,并设置了scale属性。我也尝试过使用<inset>标记。但这些方法都不起作用。

我发现(目前正在使用)调整实际工作的可绘制的大小的方法是使用<layer-list>标记,同时设置宽度和高度属性。然后,<selector> xml文件引用那些调整大小的可绘图,而不是png。但是我不喜欢这个解决方案,因为正如我提到的那样,它需要API 23,因此总共需要4个额外的xml文件。它还设置宽度和高度本身,而不是保持比率锁定。

代码语言:javascript
运行
复制
<?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;博士如何在TextInputLayout中设置自定义passwordToggleDrawable的大小?最好与默认的可绘图尺寸相同。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-03-13 16:08:14

我无法找到解决实际问题的任何方法,但我决定通过忽略问题的"in InputTextLayout“部分来解决这个问题,并实现了我自己的类版本。

大多数情况下,它只是InputTextLayout的一个副本(遗憾的是,该类不能很好地翻译子类,因为所有的东西都是私有的),但是对于大多数我不需要删除的东西,更重要的是,CheckableImageButton mPasswordToggleView变成了包含ViewViewGroup

ViewGroup是可点击的按钮,它处理setMinimumDimensions以使可点击区域保持在最小48 dp,就像通过design_text_input_password_icon.xml进行的原始操作一样。这也使得小的绘图不能拥抱屏幕的右侧,因为它们是在可点击区域的中心,给出了默认绘图所显示的边距。

View (或者更准确地说,是它的一个新子类,我称之为CheckableView)是实际可绘制(setBackground()),它取代了CheckableImageButton作为可绘图的容器,允许它基于state_checked选择器进行切换。

xml-属性passwordToggleSize允许设置维度,用于缩放绘图。我选择只有一个值,而不是宽度和高度,并且它的比率锁定的可绘制刻度使得它的最大维度与指定的维度相匹配。我将默认大小为24 in,这是为design_ic_visibility.xml中的默认绘图指定的。

PasswordToggleLayout.java:

代码语言:javascript
运行
复制
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中:

代码语言:javascript
运行
复制
<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>
票数 0
EN

Stack Overflow用户

发布于 2019-07-18 16:09:45

我知道这是个老问题,但我也面临着同样的问题,我相信我找到了一个简单的解决办法。

我正在使用TextInputLayout作为最新的材料库,我所做的唯一的事情就是从TextInputLayout中找到endIcon的参考,并改变它的最小尺寸。

代码语言:javascript
运行
复制
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上这样做的,但我相信它在某些活动类上会运行得很好。

干杯!

票数 2
EN

Stack Overflow用户

发布于 2020-04-10 11:48:20

对我来说同样的问题。问题来自于gradle material实现:

代码语言:javascript
运行
复制
implementation 'com.google.android.material:material:1.1.0'

降级到1.0.0版本解决了这个问题:

代码语言:javascript
运行
复制
implementation 'com.google.android.material:material:1.0.0'
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55126490

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档