如何繪制一個(gè)MD風(fēng)格的輸入框

123.gif

提要

首先我們分析效果圖 達(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);
    }
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末再姑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子找御,更是在濱河造成了極大的恐慌元镀,老刑警劉巖绍填,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異栖疑,居然都是意外死亡讨永,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門遇革,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卿闹,“玉大人,你說(shuō)我怎么就攤上這事萝快《亡” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵揪漩,是天一觀的道長(zhǎng)旋恼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)奄容,這世上最難降的妖魔是什么冰更? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮昂勒,結(jié)果婚禮上蜀细,老公的妹妹穿的比我還像新娘。我一直安慰自己叁怪,他們只是感情好审葬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著奕谭,像睡著了一般涣觉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上血柳,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天官册,我揣著相機(jī)與錄音,去河邊找鬼难捌。 笑死膝宁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的根吁。 我是一名探鬼主播员淫,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼击敌!你這毒婦竟也來(lái)了介返?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎圣蝎,沒(méi)想到半個(gè)月后刃宵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徘公,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年牲证,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关面。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坦袍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缭裆,到底是詐尸還是另有隱情键闺,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布澈驼,位于F島的核電站,受9級(jí)特大地震影響筛武,放射性物質(zhì)發(fā)生泄漏缝其。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一徘六、第九天 我趴在偏房一處隱蔽的房頂上張望内边。 院中可真熱鬧,春花似錦待锈、人聲如沸漠其。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)和屎。三九已至,卻和暖如春春瞬,著一層夾襖步出監(jiān)牢的瞬間柴信,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工宽气, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留随常,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓萄涯,卻偏偏與公主長(zhǎng)得像绪氛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涝影,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容