自定义实现向量图标动画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
效果如下:
1.3.然后在上面的文件中加入向右伸缩的路径
1.4.在加上向左伸缩的路径
二、合并属性动画
图标基本画完了,下面加个属性动画,让它有伸缩效果。
新建个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:472482006@qq.com * 向量动画输入框 */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混淆代码proguard,内存溢出
- android App全局SD卡路径统一管理
- Android动态加载补充 加载SD卡中的SO库
- Android实现可播放GIF动画的ImageView
- Android(安卓)APK DEX分包总结
- Android笔记:AlbumSaver图片视频保存工具类
- Android增加一个物理按键检测步骤
- Android(安卓)AppWidget(桌面小部件-音乐播放动画)
- Android小项目之五 splash动画效果