轉(zhuǎn)發(fā)請注明原創(chuàng):http://blog.csdn.net/snailbaby_soko/article/details/53893047
Google 在 2015 的 IO 大會上,給我們帶來了更加詳細的 Material Design 設(shè)計規(guī)范,同時,也給我們帶來了全新的 Android Design Support Library,在這個 support 庫里面,Google 給我們提供了更加規(guī)范的MD 設(shè)計風格的控件,這不得不說是一個良心之作。相信很多小伙子已經(jīng)體驗過其中的一些控件了,但 FRANCE 的一位設(shè)計師 Florent Champigny 卻發(fā)現(xiàn)了更有趣的實現(xiàn)MaterialTextField 。看圖
比 Google 推薦的 TextInputLayout 動感吧,作為走在進階道路上的苦逼程序員,不能說拿來能用就行,必須知其然知其所以然,于是 fork 下來一步一步解剖代碼。
我打算站在作者的位置上思考,要實現(xiàn)這個 MaterialTextField 具體會怎么去考慮。
需求分析:
既然 Google 推薦了 TextInputLayout,那首先看看它是繼承了什么來拓展,里面還有什么可以讓我們定義的。分析了一番代碼后,盡管能拓展的地方有很多,如輸入錯誤能設(shè)置錯誤提示,框框變顏色等等,但它繼承了 LinearLayout ,也就是說布局上限制了能拓展的方向,想在它的身上實現(xiàn)這個動感的輸入框效果,可能會很棘手。
在分析代碼的時候,有意思地發(fā)現(xiàn) TextInputLayout 展開的時候用到了 ValueAnimatorCompat 來做動畫,那就簡單了。只要我弄個容器放 label、editText、icon 這些小控件,再讓他們用動畫展開和收縮就行了吧。思路上沒問題之后,就該考慮容器要怎么樣定義才方便使用了。
場景使用:
我們是針對開發(fā)者的角度來定義吧,那么怎樣才能減輕開發(fā)者的工作來快速實現(xiàn)這個效果。在業(yè)務(wù)上,開發(fā)者除了要處理 editText 的內(nèi)容是否滿足某些條件外,其它貌似也不需要他們?nèi)ヌ幚怼D敲次覀兙涂梢园堰@些 label、icon 當作成 MaterialTextField 的一個屬性,可以直接在 xml 的控件上添加的屬性。
下面是見證奇跡的時候
把夢幻變成現(xiàn)實:
第一步自定義屬性:
mtf_cardCollapsedHeight 為未展開狀態(tài)下 label 下面白色那線條的高度;
mtf_hasFocus 是否獲得焦點;
mtf_openKeyboardOnFocus 展示是是否需要打開軟鍵盤;
mtf_animationDuration 展開和收縮時動畫執(zhí)行的時間;
這些非必須屬性并非在第一時間就想到,而是在逐步編碼過程中想到,所以說不要第一時間把事情考慮的太復雜才是最重要的。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MaterialTextField">
<attr name="mtf_cardCollapsedHeight" format="dimension"/>
<attr name="mtf_labelColor" format="color"/>
<attr name="mtf_image" format="reference"/>
<attr name="mtf_hasFocus" format="boolean"/>
<attr name="mtf_animationDuration" format="integer"/>
<attr name="mtf_openKeyboardOnFocus" format="boolean"/>
</declare-styleable>
</resources>```
第二步處理自定義的屬性:
```java
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaterialTextField);
ANIMATION_DURATION = styledAttrs.getInteger(R.styleable.MaterialTextField_mtf_animationDuration, 400);
.
.
.```
諸如此類獲取自定義的屬性就不一一貼代碼了,大家可以 fork 項目下來慢慢看。
再下來就重寫 onFinishInflate() 來獲得子控件來初始化一些參數(shù)和設(shè)置了(具體看看項目源碼),相信有些同學不太了解這個函數(shù)在什么時候調(diào)用,官方對這個周期方法的解釋為:
**onFinishInflate()**: 當View中所有的子控件 均被映射成xml后觸發(fā)。
其實我們一般使用 View 的流程是在 onCreate 中使用 setContentView 來設(shè)置要顯示 Layout 文件或直接創(chuàng)建一個 View,在當設(shè)置了 ContentView 之后系統(tǒng)會對這個 View 進行解析,然后回調(diào)當前視圖 View 中的onFinishInflate 方法。只有解析了這個 View 我們才能在這個View容器中獲取到擁有Id的組件,同樣因為系統(tǒng)解析完 View 之后才會調(diào)用 onFinishInflate 方法,所以我們自定義組件時可以 onFinishInflate 方法中獲取指定子View的引用。
第三步處理邏輯:展開的動畫、收縮的動畫,其實都用了v4包的 ViewCompat 來制作動畫。主要是過程中對那些子控件做一些位移、漸變的效果,再有就是對獲得焦點是否彈出軟鍵盤(作者的項目對軟鍵盤彈出收回的邏輯處理不太妥當,體驗上不太友好)。
```java
public void expand() {//展開的動畫處理
if (!expanded) {
ViewCompat.animate(editText).alpha(1f).setDuration(ANIMATION_DURATION);
ViewCompat.animate(card).scaleY(1f).setDuration(ANIMATION_DURATION);
ViewCompat.animate(label).alpha(0.4f).scaleX(0.7f).scaleY(0.7f).
translationY(-labelTopMargin).setDuration(ANIMATION_DURATION);
ViewCompat.animate(image).alpha(1f).scaleX(1f).scaleY(1f) .setDuration(ANIMATION_DURATION);
if (editText != null) {
editText.requestFocus();
}
if (OPEN_KEYBOARD_ON_FOCUS) {
((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE))
.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT); } expanded = true;
}
}```
```java
public void reduce() {//收縮的動畫處理
if (expanded) {
final int heightInitial = getContext().getResources().getDimensionPixelOffset(R.dimen.mtf_cardHeight_final);
ViewCompat.animate(label).alpha(1).scaleX(1).scaleY(1).
translationY(0).setDuration(ANIMATION_DURATION);
ViewCompat.animate(image).alpha(0).scaleX(0.4f).scaleY(0.4f) .setDuration(ANIMATION_DURATION);
ViewCompat.animate(editText).alpha(1f).setUpdateListener(
new ViewPropertyAnimatorUpdateListener() {
@Override
public void onAnimationUpdate(View view) {
float value = ViewCompat.getAlpha(view); //percentage
card.getLayoutParams().height =
(int) (value * (heightInitial - cardCollapsedHeight) + cardCollapsedHeight); card.requestLayout(); } }
) .setDuration(ANIMATION_DURATION);
ViewCompat.animate(card).scaleY(reducedScale).setDuration(ANIMATION_DURATION);
if (OPEN_KEYBOARD_ON_FOCUS) {
((InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).
toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}
editText.clearFocus(); expanded = false;
}
}```
上舞臺:其實軟鍵盤的彈出收縮邏輯換成,體驗會比較好。但是還是會出現(xiàn),當收縮狀態(tài)軟鍵盤還是不消失的小bug,如果再加上一些標記來記錄軟鍵盤的狀態(tài)來控制是否該彈出還是隱藏會好一點。
```java
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);```
如果有什么說的不好,可以提出來。來互相傷害吧...