1. 程式人生 > >自定義實現向量圖示動畫VectorDrawable

自定義實現向量圖示動畫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