从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
<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