自定義實現向量圖示動畫VectorDrawable
前言
從5.0(API等級21)開始,android
開始支援向量圖了。利用向量動畫可以實現一些很酷炫的效果。
前陣子有個需求要實現一個酷炫輸入框,利用向量動畫完美解決。
思路:畫個路徑,然後是加個分開和合並動畫
向量動畫結合TextInputLayout
封裝成一個輸入框元件
Android 官網提示利用AnimatedVectorDrawableCompat
類相容 Android 3.0(API 級別 11)及更高版本的
效果如下:
一、畫個正常圓角輸入框背景路徑,及合併分開路徑
1.1畫個正常圓角輸入框背景路徑
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="90dp"
android:height="12dp"
android:viewportWidth="90.0"
android:viewportHeight="14.0">
<path
android:strokeColor="@color/login_input_normal"
android:pathData="
M60,1
h23,0
q6,0 6,6
q0,6 -6,6
h-76
q-6,0 -6,-6
q0,-6,6,-6
h54,0
" />
</vector>
效果如下
1.2在drawable
下建個xml檔案,畫個圓角帶缺口的輸入框背景圖路徑,這裡起名login_input_vector_anim_drawable.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="90dp" android:height="12dp" android:viewportWidth="90.0" android:viewportHeight="14.0"> <!--畫個圓角帶缺口的輸入框背景--> <path android:strokeColor="@color/login_input_normal" android:pathData=" M65,1 h18,0 q6,0 6,6 q0,6 -6,6 h-76 q-6,0 -6,-6 q0,-6,6,-6 h18,0 " /> </vector>
效果如下:
1.3.然後在上面的檔案中加入向右伸縮的路徑
<!--向右動畫到底部-->
<path
android:name="right"
android:strokeColor="@color/login_input_normal"
android:pathData="
M45,1
h20,0
" />
1.4.在加上向左伸縮的路徑
<!--向左動畫到底部-->
<path
android:name="left"
android:strokeColor="@color/login_input_normal"
android:pathData="
M45,1
h-20,0
" />
二、合併屬性動畫
圖示基本畫完了,下面加個屬性動畫,讓它有伸縮效果。
新建個xml命名login_input_merge_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="500"
android:propertyName="trimPathStart"
android:valueFrom="1"
android:valueTo="0" />
</set>
三、分開屬性動畫
在畫個反過來的,讓路徑反過來伸縮
新建個xml命名login_input_merge_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
android:duration="500"
android:propertyName="trimPathStart"
android:valueFrom="0"
android:valueTo="1" />
</set>
四、合併的向量動畫
(基於向量圖示和屬性動畫)login_input_vector_merge_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/login_input_vector_anim_drawable">
<target
android:name="left"
android:animation="@anim/login_input_merge_anim" />
<target
android:name="right"
android:animation="@anim/login_input_merge_anim" />
</animated-vector>
五、分開向量動畫
(基於向量圖示和屬性動畫)login_input_vector_split_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/login_input_vector_anim_drawable">
<target
android:name="left"
android:animation="@anim/login_input_split_anim" />
<target
android:name="right"
android:animation="@anim/login_input_split_anim" />
</animated-vector>
六、自定義輸入框元件,封裝向量動畫使用
思路:封裝一個自定義輸入框元件,結合TextInputLayout和上面的向量動畫達到,失去焦點,執行合併動畫,提示下滑到中間並放大。獲取焦點,沒有輸入內容,執行分開動畫,提示上滑變小。
6.1 下面就可以程式碼中使用了,佈局檔案view_anim_edit_text.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.design.widget.TextInputLayout
android:id="@+id/et_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint=""
app:hintTextAppearance="@style/HintTextAppearance"
app:hintAnimationEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="36dip"
android:background="@null"
android:gravity="center"
android:text=""
android:textSize="15sp"
android:paddingBottom="10dip"
android:imeOptions="actionDone"
android:inputType="textCapCharacters|textPhonetic"
android:maxLength="100" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
6.2自定義元件,其中使用了Rxjava2.0
,要首先在專案中引用外掛
compile 'com.android.support:appcompat-v7:25.4.0'
compile 'com.android.support:design:25.4.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
6.3下面是本列子使用的相關程式碼庫
package aimissu.com.animationinputbox;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatDelegate;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import java.util.concurrent.TimeUnit;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Consumer;
/**
* author:dz-hexiang on 2017/10/30.
* email:[email protected]
* 向量動畫輸入框
*/
public class AnimEditText extends LinearLayout {
private TextInputEditText mEditText;
private TextInputLayout mEditTextContainer;
private AnimatedVectorDrawableCompat mSplitAnim;
private AnimatedVectorDrawableCompat mMergeAnim;
private VectorDrawableCompat noAnimBg;
private String mHit;
private float mHitSize;
private int mHitColor;
private String mText;
private float mTextSize;
private int mTextColor;
private boolean mIsPwd;
private int mMaxLength;
private boolean mIsNumber;
public AnimEditText(Context context) {
super(context);
initView(context,null,-1);
}
public AnimEditText(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context, attrs,-1);
}
public AnimEditText(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context, attrs,defStyleAttr);
}
@SuppressLint("NewApi")
public AnimEditText(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context, attrs,defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
public void initView(Context context, AttributeSet attrs, int defStyleRes)
{
LayoutInflater.from(context).inflate(R.layout.view_anim_edit_text, this);
mEditText = (TextInputEditText) findViewById(R.id.et);
mEditTextContainer = (TextInputLayout) findViewById(R.id.et_container);
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.animedittext_style);
if(typedArray != null){
//這裡要注意,String型別是沒有預設值的,所以必須定義好,不然又是空指標大法
mHit = typedArray.getString(R.styleable.animedittext_style_hit);
mHitColor = typedArray.getColor(R.styleable.animedittext_style_hitColor, ContextCompat.getColor(context,R.color.login_input_text_color));
mHitSize = typedArray.getDimension(R.styleable.animedittext_style_hitSize, 13);
mText = typedArray.getString(R.styleable.animedittext_style_text);
mTextColor = typedArray.getColor(R.styleable.animedittext_style_textColor, ContextCompat.getColor(context,R.color.login_input_text_color));
mTextSize = typedArray.getDimensionPixelSize(R.styleable.animedittext_style_textSize, 13);
mIsPwd = typedArray.getBoolean(R.styleable.animedittext_style_isPwd, false);
mIsNumber = typedArray.getBoolean(R.styleable.animedittext_style_isNumber, false);
mMaxLength = typedArray.getInt(R.styleable.animedittext_style_maxLength,0);
}
if(!TextUtils.isEmpty(mText))
mEditText.setText(mText);
else
mEditText.setText("");
mEditText.setTextColor(mTextColor);
if(mIsPwd)
mEditText.setInputType(InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD);
if(!TextUtils.isEmpty(mHit))
mEditTextContainer.setHint(mHit);
else
mEditTextContainer.setHint("");
mEditText.setHintTextColor(mHitColor);
mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX,mTextSize);
if(mIsNumber)
{
mEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
}
if(mMaxLength >0)
mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mMaxLength)});
// mSplitAnim = (AnimatedVectorDrawable) ContextCompat.getDrawable(context,R.drawable.login_input_vector_split_anim);
// mMergeAnim = (AnimatedVectorDrawable) ContextCompat.getDrawable(context,R.drawable.login_input_vector_merge_anim);
mSplitAnim= AnimatedVectorDrawableCompat.create(context,R.drawable.login_input_vector_split_anim);
mMergeAnim= AnimatedVectorDrawableCompat.create(context,R.drawable.login_input_vector_merge_anim);
noAnimBg= VectorDrawableCompat.create(context.getResources(), R.drawable.login_input_no_anim_vector_drawable,null);
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
mEditTextContainer.setBackgroundDrawable(noAnimBg);
} else {
mEditTextContainer.setBackground(noAnimBg);
}
mEditText.setOnFocusChangeListener(new AOnFocusChangeListener(){
@Override
public void onFocusChange(View v, boolean hasFocus) {
super.onFocusChange(v, hasFocus);
}
});
mEditText.addTextChangedListener(new ATextWatcher());
}
public boolean mIsSplit=false;
public abstract class AOnFocusChangeListener implements OnFocusChangeListener {
@SuppressLint("NewApi")
@Override
public void onFocusChange(View v, boolean hasFocus) {
setHitNotice();
if (hasFocus) {
if(!TextUtils.isEmpty(mEditText.getText().toString()))
return;
/**
* 只有當為空值的時候才提示hit,和分開動畫
*/
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
mEditTextContainer.setBackgroundDrawable(mSplitAnim);
} else {
mEditTextContainer.setBackground(mSplitAnim);
}
Drawable drawable = mEditTextContainer.getBackground();
if (drawable instanceof Animatable){
((Animatable) drawable).start();
mIsSplit=true;
}
}
else{
if(!mIsSplit)
return;
/**
* 只有當分開的拾柴可以觸發合併動畫
*/
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
mEditTextContainer.setBackgroundDrawable(mMergeAnim);
} else {
mEditTextContainer.setBackground(mMergeAnim);
}
Drawable drawable = mEditTextContainer.getBackground();
if (drawable instanceof Animatable){
((Animatable) drawable).start();
mIsSplit=false;
}
}
}
}
/**
* 設定hit提示
* @return
* 返回true 設定了hit ,表示沒有資料
*
* 返回false 沒有hit提示,表示有資料
*/
private boolean setHitNotice()
{
String str= mEditText.getText().toString();
if(!TextUtils.isEmpty(str))
{
mEditTextContainer.setHint("");
return false;
}
else
{
mEditTextContainer.setHint(mHit);
return true;
}
}
public class ATextWatcher implements 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) {
/**
*沒有資料 並且合併 ,應該進行分開動畫給出提示
* 為了增加體驗延遲設定hit
*/
if(TextUtils.isEmpty(mEditText.getText().toString())&&!mIsSplit)
{
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
mEditTextContainer.setBackgroundDrawable(mSplitAnim);
} else {
mEditTextContainer.setBackground(mSplitAnim);
}
Drawable drawable = mEditTextContainer.getBackground();
if (drawable instanceof Animatable){
((Animatable) drawable).start();
mIsSplit=true;
}
Flowable.timer(350, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
mEditTextContainer.setHint(mHit);
}
});
}
/**
* 如果有數,但是分開著,應該進行合併動畫,並且清楚hit
*/
if(!TextUtils.isEmpty(mEditText.getText().toString())&&mIsSplit)
{
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
mEditTextContainer.setBackgroundDrawable(mMergeAnim);
} else {
mEditTextContainer.setBackground(mMergeAnim);
}
Drawable drawable = mEditTextContainer.getBackground();
if (drawable instanceof Animatable){
((Animatable) drawable).start();
mIsSplit=false;
}
Flowable.timer(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(@NonNull Long aLong) throws Exception {
mEditTextContainer.setHint("");
}
});
}
}
}
public void setOnFocusChangeListener(AOnFocusChangeListener aOnFocusChangeListener)
{
if(aOnFocusChangeListener!=null)
mEditText.setOnFocusChangeListener(aOnFocusChangeListener);
}
public void addTextChangedListener(ATextWatcher aTextWatcher)
{
if(aTextWatcher!=null)
mEditText.addTextChangedListener(aTextWatcher);
}
public String getText()
{
return mEditText.getText().toString();
}
public void setText(String str)
{
mEditText.setText(str);
}
public void setmHit(String mHit)
{
this.mHit = mHit;
mEditTextContainer.setHint(mHit);
}
}
七、專案例子的地址:
https://github.com/dz-hexiang/AnimEditText.git