自定義EditText的需求:
最近工作中需要一個可以刪除所有字元的EditText,所以自己寫了個自定義view繼承Edittext,這個實現相對簡單,只用到了自定義view中的部分事件。
首先我們來看一下效果,是怎麼樣的:
從途中可以看到總共分為兩個部分,一個是標準的EditText,另一個是右邊的我們自定義的圖示,在未輸入字元之前,圖示是隱藏的,輸入字元後,圖示顯示,點選圖示即可刪除EditText中的所有文字,同時隱藏圖示。
繼承EditText
首先我們需要編寫一個類繼承自EditText:
public class ClearEditText extends android.support.v7.widget.AppCompatEditText{
public ClearEditText(Context context) {
this(context, null);
}
public ClearEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
public ClearEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
Log.d("順序", "init");
}
}複製程式碼
繼承EditText必須實現其中的構造方法,此處我們重寫了三個,事實上只要一個即可,不定義屬性時會預設設定為號為editTextStyle屬性集。
我們都知道自定義view時候通常會重寫onDraw和onMeasure方法,那麼這幾個方法到底是按怎樣的順序執行呢,我們可以在程式碼中新增測試程式碼來實驗一下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d("順序", "onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
Log.d("順序", "draw");
super.onDraw(canvas);
}複製程式碼
然後我們在一個fragment中載入這個view,輸出日誌
03-07 14:51:10.805 14606-14606/com.saka.customviewdemo D/順序: init
03-07 14:51:10.820 14606-14606/com.saka.customviewdemo D/順序: onMeasure
03-07 14:51:10.880 14606-14606/com.saka.customviewdemo D/順序: draw複製程式碼
可以看到執行的順序是按構造器—>onmeasure->onDraw來執行的。
設定圖示
最簡單的方法是讓ui切圖,切出不同的解析度,放在drawable中,直接呼叫。
此處我沒有UI,我也不擅長PS,所以我用xml做了一個簡單的刪除按鈕。
首先建立一個vector型別的drawableresource
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportHeight="16"
android:viewportWidth="16">
<path
android:pathData="M 0 8 L 16 8"
android:strokeColor="#2c2c2c"
android:strokeWidth="3" />
<path
android:pathData="M 8 0 L 8 16"
android:strokeColor="#2c2c2c"
android:strokeWidth="3" />
</vector>複製程式碼
這個圖示是正方形,邊長是16dp線條寬度3dp,然後做了一個十字型,大概是這個樣子
然後再自定義一個rotate型別的drawableresource,這個也就是我們要使用的圖示資源:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/delete"
android:fromDegrees="0"
android:toDegrees="225"
android:pivotX="50%"
android:pivotY="50%">
</rotate>複製程式碼
這個rotaterawable設定了旋轉角度是從0轉到225度,旋轉的中心位置在圖片X軸和Y軸的中心位置。
新增Drawable
我們實現自定義EditText的思路是佔用右邊的drawable位置,點選這個drawable即可觸發清除字元事件。
此處我們是在構造器中新增的Drawable,通過init()方法來載入右側圖示。那麼怎樣獲取這個位置呢?
首先我來看一下繼承關係
java.lang.Object
↳ android.view.View
↳ android.widget.TextView
↳ android.widget.EditText複製程式碼
在TextView中有這樣一個屬性:android:drawableRight
,這個屬性是設定控制元件右邊的圖示。這個屬性對應的java程式碼是setCompoundDrawablesWithIntrinsicBounds(int,int,int,int)
,這三個int值的順序對應的位置是左上右下,其中第三個位置就是drawableright屬性。此處應該注意,假如xml佈局中設定了drawableright屬性,同時java程式碼中設定了setCompoundDrawablesWithIntrinsicBounds(null,null,null,null),則java程式碼中的設定會覆蓋xml佈局中的設定。
既然能設定我們就有辦法獲取每一個圖示。通過檢視api我們找到一個方法,Drawable[] getCompoundDrawables ()
,注意這個方法返回的是一個drawable陣列,長度是4,對應的圖示位置是左上右下,即使你沒有設定任何drawable,這時的四個值都為null。
另一個方法Drawable[] getCompoundDrawablesRelative ()
返回的也是一個陣列,長度同樣是4,對應的圖示位置是start,top,end和bottom,注意和上面的方法區分。
此處為了簡單起見,我們直接在程式碼中設定右側圖示,覆蓋xml佈局中的設定,同時設定圖示不可見。
private RotateDrawable drawableRotate;
private void init() {
Log.d("順序", "init");
setIconVisible(false, getCompoundDrawables());
}
private void setIconVisible(boolean b, Drawable[] drawables) {
if (b) {
setCompoundDrawablesWithIntrinsicBounds(drawables[0], drawables[1], getResources().getDrawable(R.drawable.mydelete), drawables[3]);
drawableRotate = (RotateDrawable) getCompoundDrawables()[2];
} else {
setCompoundDrawablesWithIntrinsicBounds(drawables[0], drawables[1], null, drawables[3]);
}
}複製程式碼
這樣,我們的圖示就引入了EditText中,只是它現在是隱藏的,我們無法看到他。
設定圖示可見與不可見
我們的目標是在有文字時顯示圖示,沒有文字時隱藏圖示,這個時候我們最好的方法是實現TextWatcher方法,它一共有三個
public void beforeTextChanged(CharSequence s, int start,
int count, int after);
public void onTextChanged(CharSequence s, int start, int before, int count);
public void afterTextChanged(Editable s);複製程式碼
我們重點關注第二個方法,這個方法在更改s的時候會用這個回撥來通知你,在s中,從start位置開始的count個字元剛剛替換了before開始的的舊文字。
這裡我們就可以利用這幾個引數來計算此時的狀態:
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() == 0 && before > 0) {
//從有文字刪除到無文字的時候
startAnimatorSetResver();
return;
}
if (start == 0 && s.length() > 0) {
//從無文字到有文字
setIconVisible(true, getCompoundDrawables());
setAnimator();
startAnimatorSet();
}
}複製程式碼
註解中已經說明了兩個方法的作用,動畫函式稍後講。此時試試你就可以顯示和隱藏圖示了。
新增消失和出現的動畫
private ValueAnimator alphaAnimator = ValueAnimator.ofInt(0, 255);
private ValueAnimator rotateAnimator = ValueAnimator.ofInt(0, 10000);複製程式碼
此處我們設定了兩個動畫,一個是用來設定通明度變化,一個是用來設定旋轉角度的。drawable有兩個屬性可以用來設定,一個是setLevel(),這個level就是設定的旋轉角度,範圍是1-10000(假如你是用的是ScaleDrawable,這個level控制就是你的圖片的大小)。另一個就是setAlpha(),這個alpha就是透明度,範圍是0-255。
private void setAnimator() {
alphaAnimator.setDuration(1000);
alphaAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawableRotate.setAlpha((Integer) animation.getAnimatedValue());
}
});
rotateAnimator.setDuration(1000);
rotateAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
drawableRotate.setLevel((Integer) animation.getAnimatedValue());
}
});
}複製程式碼
然後我們定義一個同時啟動動畫的集合:
private void startAnimatorSet() {
AnimatorSet setVisible = new AnimatorSet();
setVisible.playTogether(alphaAnimator, rotateAnimator);
setVisible.start();
}複製程式碼
這樣,我們設定圖片顯示的動畫就完成了。當你輸入字元時,就可以看到圖示慢慢旋轉出現了。
同理可以設定圖示消失的動畫,不詳細寫出了,可以看demo(我的程式碼水平有點懶,沒有優化)。
設定點選事件
其實我們此處並不是真正的設定點選事件,而是通過判斷使用者的觸控行為來模擬點選事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (getCompoundDrawables()[2] != null) {
if (getWidth() - getTotalPaddingRight() < event.getX() &&
getWidth() - getPaddingRight() > event.getX()) {
this.setText("");
Log.d("點選了圖片", "圖片");
}
}
break;
}
return super.onTouchEvent(event);
}複製程式碼
我們並不是重寫onTouchEvent事件,我們只是在onTouchEvent中新增了一個在手指離開螢幕時是否正在圖片上的判斷,然後將內容設定為空,經過次操作以後,繼續原有的onTouchEvent流程。
判斷手指離開螢幕的位置的方法是這樣的,api中有這樣的方法:getWidth返回的是控制元件的寬度,getTotalPadingRight返回的是空間右邊的padding,包含了drawable,getPaddingRight返回的是view右邊的padding,要是包含滾動條,滾動條的寬度也在pading內。
至此我們的自定義EditText就完成了,可以使用。
這篇文章只是簡單的降解了一下自定義view中一些基本流程,要深入進去需要掌握的遠遠多於這些,下一節將繼續我們的學習。