提要
首先我們分析效果圖 達(dá)成的效果 輸入框有數(shù)值的時(shí)候,繪制hint的文字在頂部容诬,當(dāng)輸入框沒(méi)有數(shù)值的時(shí)候?qū)⒗L制的文字去掉娩梨,并且過(guò)程中插入一個(gè)向下/向上的一個(gè)移動(dòng)動(dòng)畫(huà)。
準(zhǔn)備工作
首先定義需要的量
public static final float TEXT_SIZE = Utils.dpTopx(12);//繪制的hint的文字大小
public static final float TEXT_MARGIN = Utils.dpTopx(8);//繪制的hint文本的縮進(jìn)距離
public static final int TEXT_VERTICAL_OFFSET = (int) Utils.dpTopx(22);//繪制的hint文字水平偏移
public static final int TEXT_HORIZONTAL_OFFSET = (int) Utils.dpTopx(5);//繪制的hint文字垂直方向偏移
public static final int TEXT_ANIMATION_OFFSET = (int) Utils.dpTopx(16);//動(dòng)畫(huà)的移動(dòng)幅度
boolean floatingLableShow;//當(dāng)前狀態(tài):是否顯示上部的hint文字
Rect boundPading;//用于獲取pading
float floatingLableFraction = 0;//動(dòng)畫(huà)執(zhí)行的進(jìn)度
private boolean useFloatingLabel = true;//是否使用MD效果
ObjectAnimator animator;//動(dòng)畫(huà)對(duì)象
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);//繪制文字的畫(huà)筆
初始化
{
paint.setTextSize(TEXT_SIZE);
}
public MaterialEditText(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialEditText);
useFloatingLabel = typedArray.getBoolean(R.styleable.MaterialEditText_useFloatingLabel, true);//獲取attr中聲明的值览徒,如果不需要在xml中設(shè)置屬性狈定,可以跳過(guò)此步驟
typedArray.recycle(); // 回收
}
Hint文字的繪制
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setAlpha((int) (0xff * floatingLableFraction));//根據(jù)動(dòng)畫(huà)執(zhí)行的進(jìn)度設(shè)置畫(huà)筆的透明度
float extraOffset = TEXT_ANIMATION_OFFSET * (1 - floatingLableFraction);//根據(jù)動(dòng)畫(huà)的執(zhí)行進(jìn)度獲取水平方向上的偏移量
if (useFloatingLabel) {
canvas.drawText(
(String) getHint(), TEXT_HORIZONTAL_OFFSET, TEXT_VERTICAL_OFFSET + extraOffset, paint);//繪制文字,繪制時(shí)候根據(jù)動(dòng)畫(huà)進(jìn)度可以變化水平位置
}
}
動(dòng)畫(huà)
private ObjectAnimator getAninator() {
if (animator == null) {
animator = ObjectAnimator.ofFloat(MaterialEditText.this, "floatingLableFraction", 0, 1);
}
return animator;
}
布局重新測(cè)量
private void onUseFloatLableChanged() {
if (useFloatingLabel) {
setPadding(
getPaddingLeft(),
(int) (boundPading.top + TEXT_SIZE + TEXT_MARGIN),//顯示的時(shí)候习蓬,將布局向上排
getPaddingRight(),
getPaddingBottom());
} else {
setPadding(getPaddingLeft(), (boundPading.top), getPaddingRight(), getPaddingBottom());//不使用時(shí)候則將padding恢復(fù)原始狀態(tài)
}
}
public void setUseFloatingLabel(boolean useFloatingLabel) {
if (this.useFloatingLabel != useFloatingLabel) {
this.useFloatingLabel = useFloatingLabel;//當(dāng)狀態(tài)改變時(shí)候
onUseFloatLableChanged();//應(yīng)用變化
}
requestLayout(); // 重新測(cè)量
}
設(shè)置監(jiān)聽(tīng)器
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) {
if (useFloatingLabel) {
if (floatingLableShow && TextUtils.isEmpty(s)) {
floatingLableShow = !floatingLableShow;
getAninator().reverse();//逆向執(zhí)行動(dòng)畫(huà)
} else if (!floatingLableShow && !TextUtils.isEmpty(s)) {
floatingLableShow = !floatingLableShow;
getAninator().start();
}
}
}
@Override
public void afterTextChanged(Editable s) {}
});
Tips
為什么獲取paddingTop使用
boundPading.top
而不會(huì)使用getPaddingTop
?
因?yàn)榇薞iew中纽什,Padding并不是一個(gè)定值,每次setPadding
的時(shí)候躲叼,都會(huì)進(jìn)行改變,所以要將Padding恢復(fù)的時(shí)候芦缰,要涉及到一個(gè)累減的一個(gè)問(wèn)題,而從background中獲取的bounds是一個(gè)定值枫慷,所以可以有效避免復(fù)雜的計(jì)算让蕾。getAninator().start();
和getAninator().reverse();
啥玩意還能這樣子操作?流礁?涕俗?
使用getAninator()
可以避免重復(fù)構(gòu)造動(dòng)畫(huà)對(duì)象避免性能損失,start表示的是動(dòng)畫(huà)的正方向執(zhí)行神帅,reverse表示倒敘執(zhí)行
完整代碼
public class MaterialEditText extends AppCompatEditText {
private static final String TAG = "MaterialEditText";
public static final float TEXT_SIZE = Utils.dpTopx(12);
public static final float TEXT_MARGIN = Utils.dpTopx(8);
public static final int TEXT_VERTICAL_OFFSET = (int) Utils.dpTopx(22);
public static final int TEXT_HORIZONTAL_OFFSET = (int) Utils.dpTopx(5);
public static final int TEXT_ANIMATION_OFFSET = (int) Utils.dpTopx(16);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
boolean floatingLableShow;
ObjectAnimator animator;
Rect boundPading;
float floatingLableFraction = 0;
private boolean useFloatingLabel = true;
{
paint.setTextSize(TEXT_SIZE);
boundPading = getBackground().getBounds();
setPadding(
getPaddingLeft(),
(int) (boundPading.top + TEXT_SIZE + TEXT_MARGIN),
getPaddingRight(),
getPaddingBottom());
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) {
if (useFloatingLabel) {
if (floatingLableShow && TextUtils.isEmpty(s)) {
floatingLableShow = !floatingLableShow;
getAninator().reverse();
} else if (!floatingLableShow && !TextUtils.isEmpty(s)) {
floatingLableShow = !floatingLableShow;
getAninator().start();
}
}
}
@Override
public void afterTextChanged(Editable s) {}
});
}
public MaterialEditText(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialEditText);
useFloatingLabel = typedArray.getBoolean(R.styleable.MaterialEditText_useFloatingLabel, true);
typedArray.recycle(); // 回收
}
public float getFloatingLableFraction() {
return floatingLableFraction;
}
public void setFloatingLableFraction(float floatingLableFraction) {
this.floatingLableFraction = floatingLableFraction;
invalidate();
}
private void onUseFloatLableChanged() {
if (useFloatingLabel) {
setPadding(
getPaddingLeft(),
(int) (boundPading.top + TEXT_SIZE + TEXT_MARGIN),
getPaddingRight(),
getPaddingBottom());
} else {
setPadding(getPaddingLeft(), (boundPading.top), getPaddingRight(), getPaddingBottom());
}
}
public void setUseFloatingLabel(boolean useFloatingLabel) {
if (this.useFloatingLabel != useFloatingLabel) {
this.useFloatingLabel = useFloatingLabel;
onUseFloatLableChanged();
}
requestLayout(); // 重新測(cè)量
}
private ObjectAnimator getAninator() {
if (animator == null) {
animator = ObjectAnimator.ofFloat(MaterialEditText.this, "floatingLableFraction", 0, 1);
}
return animator;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setAlpha((int) (0xff * floatingLableFraction));
float extraOffset = TEXT_ANIMATION_OFFSET * (1 - floatingLableFraction);
if (useFloatingLabel) {
canvas.drawText(
(String) getHint(), TEXT_HORIZONTAL_OFFSET, TEXT_VERTICAL_OFFSET + extraOffset, paint);
}
}
}