自定义实现向量图标动画VectorDrawable

前言

从5.0(API等级21)开始,android开始支持矢量图了。利用矢量动画可以实现一些很酷炫的效果。
前阵子有个需求要实现一个酷炫输入框,利用矢量动画完美解决。

思路:画个路径,然后是加个分开和合并动画
向量动画结合TextInputLayout封装成一个输入框组件
Android 官网提示利用 AnimatedVectorDrawableCompat类兼容 Android 3.0(API 级别 11)及更高版本的

效果如下:
自定义实现向量图标动画VectorDrawable_第1张图片

一、画个正常圆角输入框背景路径,及合并分开路径

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>

效果如下
自定义实现向量图标动画VectorDrawable_第2张图片

1.2在drawable下建个xml文件,画个圆角带缺口的输入框背景图路径,这里起名login_input_vector_anim_drawable.xml





    

效果如下:
自定义实现向量图标动画VectorDrawable_第3张图片

1.3.然后在上面的文件中加入向右伸缩的路径



1.4.在加上向左伸缩的路径



二、合并属性动画

图标基本画完了,下面加个属性动画,让它有伸缩效果。
新建个xml命名login_input_merge_anim.xml


<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


<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


<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


<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


<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() {
                            @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() {
                            @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

你可能感兴趣的:(android,java)