前言
从5.0(API等级21)开始,android
开始支持矢量图了。利用矢量动画可以实现一些很酷炫的效果。
前阵子有个需求要实现一个酷炫输入框,利用矢量动画完美解决。
思路:画个路径,然后是加个分开和合并动画
向量动画结合TextInputLayout
封装成一个输入框组件
Android 官网提示利用AnimatedVectorDrawableCompat
类兼容 Android 3.0(API 级别 11)及更高版本的
一、画个正常圆角输入框背景路径,及合并分开路径
1.1画个正常圆角输入框背景路径1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<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
1
2
3
4
5
6
7
8
9<?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.xml1
2
3
4
5
6
7
8
9<?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
1 | <?xml version="1.0" encoding="utf-8"?> |
五、分开向量动画
(基于向量图标和属性动画)login_input_vector_split_anim.xml
1
2
3
4
5
6
7
8
9
10
11
12<?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
1 | <?xml version="1.0" encoding="utf-8"?> |
6.2自定义组件,其中使用了Rxjava2.0
,要首先在项目中引用插件1
2
3
4compile '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下面是本列子使用的相关代码库1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366package 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<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);
}
}
七、项目例子的地址:
1 | https://github.com/dz-hexiang/AnimEditText.git |