自定义TextInputLayout

Posted 钰娘娘ynn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义TextInputLayout相关的知识,希望对你有一定的参考价值。


说实话,虽然TextInputLayout效果看起来不错,但用起来还是挺麻烦。
- 槽点1:提示字颜色和错误字颜色设定麻烦,这里提示字要用主题的colorAccent,太不灵活了,错误字颜色还直接设置不了!
- 槽点2:你这东西能不能智能一点?错误格式改为正确的时候为啥不能自动识别?

所以无奈自定义MyEditTextInput :

public class MyEditTextInput extends TextInputLayout
public MyEditTextInput(Context context)
super(context);


public MyEditTextInput(Context context, AttributeSet attrs)
super(context, attrs);


public MyEditTextInput(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);


public interface OnFitErrorListener
boolean isError();
String getOtherErrorTxt();


private OnFitErrorListener errorListener;
public void setOnFitErrorListener(OnFitErrorListener mListener)
errorListener = mListener;


@Override
protected void onFinishInflate()
super.onFinishInflate();
if(getEditText() == null)
return;
getEditText().addTextChangedListener(new TextWatcher()
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)



@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
//考虑到一些值可以为空,不强制检查



@Override
public void afterTextChanged(Editable s)
setErrorText();

);


private void setErrorText()
if(getEditText().getText().toString().isEmpty() && getEmptyText().length()>0)
setError(getEmptyText());
else if(errorListener!=null && errorListener.isError())
setError(errorListener.getOtherErrorTxt());
innerSetErrorTextColor(errColor);
else
setError(null);



private CharSequence getEmptyText()
return emptyTxt;


/**
* 设置信息为空时的提示
*/
private CharSequence emptyTxt="";
public void setEmptyText(CharSequence text)
emptyTxt = text;


/**
* 设置错误时的提示颜色
* @param
private int errColor = 0xffff0000;
private void innerSetErrorTextColor(int color)
this.errColor = color;
try
Field fErrorView = TextInputLayout.class.getDeclaredField("mErrorView");
fErrorView.setAccessible(true);
TextView mErrorView = (TextView) fErrorView.get(this);

Method m = TextView.class.getMethod("setTextColor",int.class);
m.invoke(mErrorView,color);
catch (Exception e)
e.printStackTrace();



public void setErrorTextColor(int color)
CharSequence beforeError = getError();
setError("abc");
innerSetErrorTextColor(color);
setError(beforeError);



/**
* 设置正确时的提示颜色
* @param
public void setRightTextColor(int color)
try
Field fView = TextInputLayout.class.getDeclaredField("mCollapsingTextHelper");
fView.setAccessible(true);
int[] colors = new int[]color;
Field fCurTextColor = TextInputLayout.class.getDeclaredField("mFocusedTextColor");
fCurTextColor.setAccessible(true);
Field mColors = ColorStateList.class.getDeclaredField("mColors");
mColors.setAccessible(true);
mColors.set(fCurTextColor.get(this),colors);
catch

用法:

editNo.setErrorTextColor(0xff880000);//错误颜色
editNo.setRightTextColor(0xff99ee55);//提示颜色
editNo.setEmptyText("用户名不能为空!");
editNo.setOnFitErrorListener(new MyEditTextInput.OnFitErrorListener()
@Override
public boolean isError()
return editNo.getEditText().getText().toString().length()<8;


@Override
public String getOtherErrorTxt()
return "用户名不能少于8位!";

);

这里说一下为什么没有把空值放入listener:因为大多数情形都需要进行空检查,而其他检查在客户端较少,有一些可能用不到。比如备注对输入的要求较少,那就可以不使用listener,从而更合理有效的使用代码。

错误提示

按照官方的说法,错误提示是在xml内添加:

app:errorTextAppearance="@color/colorPrimary"

但我试过了,这种方式除了让提示字变黑以外,设置任何颜色都是无效的= =

这里提一下:错误提示的反射写法开始的时候是参考别人的: ​​
但是,这个并不能起到效果,可能和手机厂商有关,于是换了一个直接设置errorview的颜色

以下是源码的关键代码:

public void setErrorEnabled(boolean enabled) 
if (mErrorEnabled != enabled)
if (mErrorView != null)
ViewCompat.animate(mErrorView).cancel();


if (enabled)
mErrorView = new TextView(getContext());
boolean useDefaultColor = false;
try
TextViewCompat.setTextAppearance(mErrorView, mErrorTextAppearance);

if (Build.VERSION.SDK_INT >= 23
&& mErrorView.getTextColors().getDefaultColor() == Color.MAGENTA)

useDefaultColor = true;

catch (Exception e)
useDefaultColor = true;

if (useDefaultColor) TextViewCompat.setTextAppearance(mErrorView,
android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
mErrorView.setTextColor(ContextCompat.getColor(
getContext(), R.color.design_textinput_error_color_light));

mErrorView.setVisibility(INVISIBLE);
ViewCompat.setAccessibilityLiveRegion(mErrorView, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
addIndicator(mErrorView, 0);
else
mErrorShown = false;
updateEditTextBackground();
removeIndicator(mErrorView);
mErrorView = null;

mErrorEnabled = enabled;

在提取以下,变成这样:

if (enabled) 
mErrorView = new TextView(getContext());
boolean useDefaultColor = false;
try
TextViewCompat.setTextAppearance(mErrorView, mErrorTextAppearance);

if (Build.VERSION.SDK_INT >= 23
&& mErrorView.getTextColors().getDefaultColor() == Color.MAGENTA)

useDefaultColor = true;

catch (Exception e)
useDefaultColor = true;

if (useDefaultColor) TextViewCompat.setTextAppearance(mErrorView,
android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
mErrorView.setTextColor(ContextCompat.getColor(
getContext(), R.color.design_textinput_error_color_light));

可以看到,这段代码很坑,是的,确实有这样一句:TextViewCompat.setTextAppearance(mErrorView, mErrorTextAppearance);用了设置的样式,然后呢?useDefaultColor不论任何版本都被设置为true,然后居然用的是固定的颜色!R.color.design_textinput_error_color_light,坑!想用反射改个颜色都做不到,没办法每次改变文字的时候都重新设置颜色了。然后就这样,innerSetErrorTextColor()应该在MyEditTextInput中addTextChangedListener的afterTextChanged()中调用,外部则在需要调用时调用。

/**
* 设置错误时的提示颜色
* @param
private int errColor = 0xffff0000;
private void innerSetErrorTextColor(int color)
this.errColor = color;
try
Field fErrorView = TextInputLayout.class.getDeclaredField("mErrorView");
fErrorView.setAccessible(true);
TextView mErrorView = (TextView) fErrorView.get(this);

Method m = TextView.class.getMethod("setTextColor",int.class);
m.invoke(mErrorView,color);
catch

按道理说应该行了吧?然而并不行。。。还有一个bug!第一次出现error的时候下划线是红色,错误字是设置的颜色。于是外部设置的时候用:

public void setErrorTextColor(int color) 
CharSequence beforeError = getError();
setError("abc");
innerSetErrorTextColor(color);
setError(beforeError);

普通提示是完全自己写的。方式是直接在源码里面用调试,检查提示文字的相关变量,发现了mCollapsingTextHelper变量设置时的mFocusedTextColor直接影响这一颜色,所以进行修改。具体在:TextInputLayout的updateLabelState(boolean)方法里面。
用了两种,各有缺点:
方式1:直接改变mFocusedTextColor的值。
优点:不同EditText可设置不相同。
缺点:正确时无法保证EditText下划线的颜色,该下滑线颜色会设置成colorAccent值,而上面的提示文字成为设置值

public void setRightTextColor(int color) 
try
int[][] states = new int[1][];
states[0] = new int[1];
int[] colors = new int[]color;

Class clazz = Class.forName("android.content.res.ColorStateList");
Constructor c = clazz.getConstructor(int[][].class, int[].class);
ColorStateList mColorState = (ColorStateList) c.newInstance(states,colors);


Field fCurTextColor = TextInputLayout.class.getDeclaredField("mFocusedTextColor");
fCurTextColor.setAccessible(true);
ColorStateList beforeColorState = (ColorStateList) fCurTextColor.get(this);
Field mColors = ColorStateList.class.getDeclaredField("mColors");
mColors.setAccessible(true);
mColors.set(mColorState,colors);
fCurTextColor.set(this,mColorState);
catch

方式2:重新设置color字段
优点:可在正确时同时设置上方的文字和EditText的下划线颜色
缺点:不能单独设置,更改会更改所有EditText下滑线颜色

int[] colors = new int[]color;
Class clazz = Class.forName("android.content.res.ColorStateList");
Constructor c = clazz.getConstructor(int[][].class, int[].class);
Field fCurTextColor = TextInputLayout.class.getDeclaredField("mFocusedTextColor");
fCurTextColor.setAccessible(true);
ColorStateList beforeColorState = (ColorStateList) fCurTextColor.get(this);
Field mColors = ColorStateList.class.getDeclaredField("mColors");
mColors.setAccessible(true);
mColors.set(beforeColorState,colors);

其实我比较疑惑,感觉这两种方式没什么不同,然而结果就是不一样

TextInputLayout的致命缺陷与Android L以后部分控件感想

不能更改EditText下滑线样式。换了样式之后整个效果就会变的奇怪。高封装性考虑不全面的结果就是难以扩展。这是最近的android新api都比较难用的根本性原因。

看着效果不错,但实际上需要改装的时候就会发现具有高阻力,低扩展性,最要命的是api有些东西还看不到。最终只能从自己写一个,放弃改装和采用第三方框架中选择一个。近几期的android很多功能趋向越来越复杂。比如侧滑抽屉,比如recyclerview的ItemDecoration,比如折叠菜单,比如悬浮按钮,横幅菜单。但是更好的效果?没有。

难以扩展的例子:
​​​侧滑菜单​​里面要求使用类似某版QQ一样存在主屏放大缩小功能。甚至以前用slidingmenu的阴影都没法加。

​折叠菜单​​,实际上用起来并不算顺畅,如果你采用了底部viewpager+view的方式view里面再使用scrollview就会发现卡顿了。

​悬浮按钮​​。某项目突然需要使用类似悬浮按钮的功能,需要放射方式,要有文字说明,然后很高兴的用了FloatingActionButton,结果发现折叠以后字什么的完全无法加进去,放射方式更是想都别想,无奈自定义了。

​横幅菜单​​,这个干脆小米就不能用。

这里面唯一一个风评还不错的,应该是recyclerview了,同时兼容了listview和gridview,让一些情况下,两者的转换更加方便。不过里面的ItemDecoration除了需要特殊功能,如加标签,通讯录,悬浮标题,其他情况还是不喜欢用,原因是感觉不爽:原来用listview完全不用写java代码,怎么recyclerview反而要写这么麻烦了?

总结

自定义才是王道,官方的新控件慎用,这是多次走弯路的教训。


以上是关于自定义TextInputLayout的主要内容,如果未能解决你的问题,请参考以下文章

Android:TextInputLayout - 自定义提示、底线和错误消息的颜色

登录界面AutoUtils 屏幕适配自定义Edittext(显示密码可见和一键清空)和 TextInputLayout的使用。

iOS 自定义智能应用横幅

自定义视图焦点区域

自定义控件 - 滚动横幅

更改 TextInputLayout 轮廓颜色