Android送花动画
Posted freeCodeSunny
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android送花动画相关的知识,希望对你有一定的参考价值。
近期有一个需求,需要做一个送花的动画,初始点击一朵花就出现一个动画,再点一次又出现一次,就算是连击,点击多少次就出现多少次,但是感觉效果太丑,因此将连击合并在一起只更改数字。
效果
这里先做一个demo,效果就是demo运行中截取的,大致效果如下:
多次连击只需要更新数字,不在需要重新出现动画,只有当动画执行完毕后,才出现下一次动画。
实现
这里主要包括几个元素,一个描述文本,一个缩放的View,再加一个背景效果。描述文本设置一个图标。缩放文本有一个文字边框效果。
缩放View
首先来实现一个缩放的View,初始首先想到的是采用自定义View,自己draw一个文本,先draw外层文本,再draw内层文本。代码如下:
package com.demo.demo.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import com.demo.demo.R;
public class ScaleTextView extends View
private Paint paint;
private int outColor;
private int innerColor;
private boolean drawBorder;
private float scale;
private String text;
private int dimensionPixelSize;
public ScaleTextView(Context context)
super(context);
init(context, null);
public ScaleTextView(Context context, @Nullable AttributeSet attrs)
super(context, attrs);
init(context, attrs);
public ScaleTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init(context, attrs);
private void init(Context context, AttributeSet attrs)
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ScaleTextView);
outColor = array.getColor(R.styleable.ScaleTextView_o_color, getResources().getColor(R.color.colorPrimary));
innerColor = array.getColor(R.styleable.ScaleTextView_i_color, getResources().getColor(R.color.colorAccent));
drawBorder = array.getBoolean(R.styleable.ScaleTextView_is_border, false);
dimensionPixelSize = array.getDimensionPixelSize(R.styleable.ScaleTextView_text_size, getResources()
.getDimensionPixelSize(R.dimen.inSize));
array.recycle();
outPaint();
private void outPaint()
paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(dimensionPixelSize);
public float getScale()
return scale;
public void setScale(float scale)
this.scale = scale;
setScaleX(scale);
setScaleY(scale);
public void setText(String text)
this.text = text;
invalidate();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(dimensionPixelSize * 4, dimensionPixelSize);
@Override
protected void onDraw(Canvas canvas)
if (TextUtils.isEmpty(text))
return;
if (drawBorder)
paint.setColor(outColor);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText(text, 0, dimensionPixelSize, paint);
paint.setColor(innerColor);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText(text, 0, dimensionPixelSize, paint);
这里先不用管scale参数,这是为后面的属性动画设置的属性进行缩放动画。我们在onMeasure强制设置宽高,高就是文本的大小,宽就是最大文本的大小。
当有文本设置时,调用invalidate进行重绘,在onDraw中绘制文本,先绘制外层,再绘制内存,分别用不同的颜色。在构造的时候解析attr,attr如下:
<declare-styleable name="ScaleTextView">
<attr name="o_color" format="reference|color"></attr>
<attr name="i_color" format="reference|color"></attr>
<attr name="is_border" format="boolean"></attr>
<attr name="text_size" format="dimension"></attr>
</declare-styleable>
上面我们采用了自定义View来展示,需要我们传入文本大小,文本颜色,我在网上看到有人用TextView的方式实现了同样的效果,这里采用自定义TextView来实现同样的效果,代码如下:
package com.demo.demo.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.widget.TextView;
import com.demo.demo.R;
import java.lang.reflect.Field;
public class StrokeTextView extends android.support.v7.widget.AppCompatTextView
private TextPaint paint;
private int outColor;
private int innerColor;
private boolean drawBorder;
private float scale;
public StrokeTextView(Context context)
super(context);
init(context, null);
public StrokeTextView(Context context, @Nullable AttributeSet attrs)
super(context, attrs);
init(context, attrs);
public StrokeTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init(context, attrs);
private void init(Context context, AttributeSet attrs)
paint = this.getPaint();
paint.setAntiAlias(true);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StrokeTextView);
outColor = array.getColor(R.styleable.StrokeTextView_out_color, getResources().getColor(R.color.yellow));
innerColor = array.getColor(R.styleable.StrokeTextView_inner_color, getResources().getColor(R.color
.colorAccent));
drawBorder = array.getBoolean(R.styleable.StrokeTextView_draw_border, false);
array.recycle();
@Override
protected void onDraw(Canvas canvas)
if (drawBorder)
// 描外层
setTextColorUseReflection(outColor);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
super.onDraw(canvas);
// 描内层,恢复原先的画笔
setTextColorUseReflection(innerColor);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
super.onDraw(canvas);
/**
* 使用反射的方法进行字体颜色的设置
*
* @param color
*/
private void setTextColorUseReflection(int color)
Field textColorField;
try
textColorField = TextView.class.getDeclaredField("mCurTextColor");
textColorField.setAccessible(true);
textColorField.set(this, color);
textColorField.setAccessible(false);
catch (NoSuchFieldException e)
e.printStackTrace();
catch (IllegalArgumentException e)
e.printStackTrace();
catch (IllegalAccessException e)
e.printStackTrace();
paint.setColor(color);
public float getScale()
return scale;
public void setScale(float scale)
this.scale = scale;
setScaleX(scale);
setScaleY(scale);
attr定义方式与上面的类似,采用反射获取当前TextView的颜色,之后更改颜色,先绘制外层,再恢复颜色只,调用系统绘制方式。
组合View
这里整个组合效果我们也采用自定义View来实现,这样有一个好处就是代码比较隔离,所有的效果都在这里处理。
从上面的动画看,我们是送出花朵,但是同时我们收到花朵动画效果也是类似的,只是靠左,向上漂移,这里我们采用自定义attr来控制方向与文本描述符,attr如下:
<declare-styleable name="FlowerGiftView">
<attr name="left_to_right" format="boolean"></attr>
<attr name="gift_desc" format="reference|string"></attr>
</declare-styleable>
整个布局如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/gift_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:drawableLeft="@drawable/rtc_icon_flower1"
android:text="收到花"
android:textColor="@color/white"/>
<com.demo.opengl.widget.StrokeTextView
android:id="@+id/gift_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/gift_desc"
android:paddingLeft="6dp"
android:paddingRight="14dp"
android:text="x 1"
app:draw_border="true"
app:inner_color="@color/colorAccent"
app:out_color="@color/yellow"/>
</RelativeLayout>
相对布局中包含了两个view,一个作为描述View,一个作为缩放View,如果还有定制的效果。可以继续设置View,整个组合View的代码如下:
package com.demo.demo.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.demo.demo.R;
public class FlowerGiftView extends RelativeLayout
/**
* 是否从左到右
*/
private boolean left2Right;
/**
* gift desc
*/
private String desc;
/**
* 设置文案描述
*/
private TextView giftDesc;
/**
* 缩放动画
*/
private StrokeTextView giftCount;
public FlowerGiftView(Context context)
super(context);
init(context, null);
public FlowerGiftView(Context context, AttributeSet attrs)
super(context, attrs);
init(context, attrs);
public FlowerGiftView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init(context, attrs);
private void init(Context context, AttributeSet attrs)
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlowerGiftView);
left2Right = array.getBoolean(R.styleable.FlowerGiftView_left_to_right, false);
desc = array.getString(R.styleable.FlowerGiftView_gift_desc);
array.recycle();
View inflate = inflate(getContext(), R.layout.gift_view_layout, this);
findViews(inflate);
private void findViews(View view)
giftDesc = (TextView) view.findViewById(R.id.gift_desc);
giftDesc.setText(desc);
giftCount = (StrokeTextView) view.findViewById(R.id.gift_count);
这里我们就完成了组合View。之后我们需要设置动画。
动画效果
这里我们采用属性动画来实现,首先我们分析一下整个动画:
- 整个View从屏幕外水平移入
- 如果是连击则文本多次更改并缩放
- 最后动画向下或者向上飞出
这里我们先拆解了动画的几个步骤,我们分别来实现各个动画:
水平飞入
首先是整个动画的飞入,我们飞入的距离为整个View的宽度,因此我们需要获取View的宽高。因此我们首先获取View的宽高,我们可以在onSizeChanged的会调用中获取:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
transX = this.getMeasuredWidth();
transY = this.getMeasuredHeight();
这里获取了View的宽高,高是动画飞出时需要用到的参数。接着我们来实现飞入的动画:
@NonNull
private ObjectAnimator getTrans()
ObjectAnimator trans = ObjectAnimator.ofFloat(this, "translationX", left2Right ? -transX : transX, 0);
trans.setDuration(300);
return trans;
这里判断view放置的位置,如果再右边,需要从右向左飞入,如果再在左边,需要从左向右飞入,
缩放动画
文本缩放需要显示多次,因此我们需要一个重复的缩放动画,重复次数需要由外部传入:
private ObjectAnimator getScale(int count)
scale = ObjectAnimator.ofFloat(giftCount, "scale", 1.0f, 2.0f, 1.0f);
scale.setDuration(500);
scale.setRepeatCount(count - 1);
scale.setRepeatMode(ObjectAnimator.RESTART);
scale.addListener(new AnimatorListenerAdapter()
@Override
public void onAnimationRepeat(Animator animation)
super.onAnimationRepeat(animation);
giftCount.setText("x " + (++incTimes));
);
return scale;
这里首先设置了一个先放大再缩小的动画,之后设置重复次数,在回调中更改显示的文本。这里我们将scale作为一个全局变量,主要是因为需要更改重复次数。
这里需要注意的是,这里使用的属性是scale,这就是我们前面设置的属性。在scale里面我们调用了setScaleX与setScaleY,不然我们就需要实现两个动画才能达到缩放的效果。
飞出动画
最后一个是一个向上飞出的动画,移动的距离就是整个View的高度:
@NonNull
private ObjectAnimator getFlyFade()
PropertyValuesHolder flyUp = PropertyValuesHolder.ofFloat("translationY", 0, left2Right ? -transY : transY);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1, 0);
return ObjectAnimator.ofPropertyValuesHolder(this, flyUp, alpha).setDuration(500);
动画在飞出的过程中逐渐消失,主要采用更改透明度来实现。
组合
上面的动画我们需要序列执行,先执行飞入的动画,在执行缩放的动画,在执行飞出的动画,我们可以采用对每一动画加入监听,前一个执行完毕后执行下一个,也可以采用AnimatorSet,指定序列执行:
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.playSequentially(getTrans(), getScale(count), getFlyFade());
set.addListener(new AnimatorListenerAdapter()
@Override
public void onAnimationEnd(Animator animation)
super.onAnimationEnd(animation);
isAnimation = false;
FlowerGiftView.this.setVisibility(INVISIBLE);
animate().alpha(1).translationY(0).start();
@Override
public void onAnimationStart(Animator animation)
super.onAnimationStart(animation);
isAnimation = true;
FlowerGiftView.this.setVisibility(VISIBLE);
giftCount.setText("x 1");
incTimes = 1;
);
set.start();
这里主要在动画开始时候显示View,同时增长变量从1开始,在结束动画时候,重置动画,为下一次做好准备。
重复动画
上面的动画只是执行一次就结束,如果再执行过程中又有点击事件发生,我们怎么才能在动画上自动更改?这里主要是更改scale的缩放次数,代码如下:
/**
* 如果动画不存在,先创建再执行
* 如果正在运行,则直接更新执行次数
* 如果没执行, 则启动执行
* 当动画执行完成后,判断RepeatCount==incTimes,相等表示执行完成了。否则执行剩下的值
*
* @param count
*/
public void startAnim(int count)
if (set == null)
set = new AnimatorSet();
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.playSequentially(getTrans(), getScale(count), getFlyFade());
set.addListener(new AnimatorListenerAdapter()
@Override
public void onAnimationEnd(Animator animation)
super.onAnimationEnd(animation);
isAnimation = false;
FlowerGiftView.this.setVisibility(INVISIBLE);
animate().alpha(1).translationY(0).start();
int remainTimes = scale.getRepeatCount() - incTimes;
if (remainTimes > 0)
startAnim(remainTimes);
@Override
public void onAnimationStart(Animator animation)
super.onAnimationStart(animation);
isAnimation = true;
FlowerGiftView.this.setVisibility(VISIBLE);
giftCount.setText("x 1");
incTimes = 1;
);
set.start();
else
if (isAnimation)
scale.setRepeatCount(scale.getRepeatCount() + count);
else
scale.setRepeatCount(count - 1);
set.start();
如果动画不存在,表示还没有执行过先初始化,在执行,如果动画在执行过程中,则更改缩放次数,在整个动画的结束判断自增长与总数是否相同,如果不相同说明设置的时候,执行实际已过,需要执行剩下的次数,否则就重新设置缩放次数进行执行。
上面我们分步骤讲述了代码,下面是整个代码:
package com.demo.demo.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.demo.demo.R;
public class FlowerGiftView extends RelativeLayout
/**
* 动画执行状态
*/
private boolean isAnimation;
/**
* 是否从左到右
*/
private boolean left2Right;
/**
* gift desc
*/
private String desc;
/**
* 设置文案描述
*/
private TextView giftDesc;
/**
* 缩放动画
*/
private StrokeTextView giftCount;
/**
* 显示次数变动
*/
private int incTimes = 0;
/**
* x方向移动位置
*/
private int transX;
/**
* y方向移动位置
*/
private int transY;
private ObjectAnimator scale;
private AnimatorSet set;
public FlowerGiftView(Context context)
super(context);
init(context, null);
public FlowerGiftView(Context context, AttributeSet attrs)
super(context, attrs);
init(context, attrs);
public FlowerGiftView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
init(context, attrs);
private void init(Context context, AttributeSet attrs)
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlowerGiftView);
left2Right = array.getBoolean(R.styleable.FlowerGiftView_left_to_right, false);
desc = array.getString(R.styleable.FlowerGiftView_gift_desc);
array.recycle();
View inflate = inflate(getContext(), R.layout.gift_view_layout, this);
findViews(inflate);
private void findViews(View view)
giftDesc = (TextView) view.findViewById(R.id.gift_desc);
giftDesc.setText(desc);
giftCount = (StrokeTextView) view.findViewById(R.id.gift_count);
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh)
super.onSizeChanged(w, h, oldw, oldh);
transX = this.getMeasuredWidth();
transY = this.getMeasuredHeight();
/**
* 如果动画不存在,先创建再执行
* 如果正在运行,则直接更新执行次数
* 如果没执行, 则启动执行
* 当动画执行完成后,判断RepeatCount==incTimes,相等表示执行完成了。否则执行剩下的值
*
* @param count
*/
public void startAnim(int count)
if (set == null)
set = new AnimatorSet();
set.setInterpolator(new AccelerateDecelerateInterpolator());
set.playSequentially(getTrans(), getScale(count), getFlyFade());
set.addListener(new AnimatorListenerAdapter()
@Override
public void onAnimationEnd(Animator animation)
super.onAnimationEnd(animation);
isAnimation = false;
FlowerGiftView.this.setVisibility(INVISIBLE);
animate().alpha(1).translationY(0).start();
int remainTimes = scale.getRepeatCount() - incTimes;
if (remainTimes > 0)
startAnim(remainTimes);
@Override
public void onAnimationStart(Animator animation)
super.onAnimationStart(animation);
isAnimation = true;
FlowerGiftView.this.setVisibility(VISIBLE);
giftCount.setText("x 1");
incTimes = 1;
);
set.start();
else
if (isAnimation)
scale.setRepeatCount(scale.getRepeatCount() + count);
else
scale.setRepeatCount(count - 1);
set.start();
@NonNull
private ObjectAnimator getFlyFade()
PropertyValuesHolder flyUp = PropertyValuesHolder.ofFloat("translationY", 0, left2Right ? -transY : transY);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1, 0);
return ObjectAnimator.ofPropertyValuesHolder(this, flyUp, alpha).setDuration(500);
private ObjectAnimator getScale(int count)
scale = ObjectAnimator.ofFloat(giftCount, "scale", 1.0f, 2.0f, 1.0f);
scale.setDuration(500);
scale.setRepeatCount(count - 1);
scale.setRepeatMode(ObjectAnimator.RESTART);
scale.addListener(new AnimatorListenerAdapter()
@Override
public void onAnimationRepeat(Animator animation)
super.onAnimationRepeat(animation);
giftCount.setText("x " + (++incTimes));
);
return scale;
@NonNull
private ObjectAnimator getTrans()
ObjectAnimator trans = ObjectAnimator.ofFloat(this, "translationX", left2Right ? -transX : transX, 0);
trans.setDuration(300);
return trans;
使用
上面讲述了整个View的构建,但是还没有使用过,界面中怎么使用View:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
tools:context="com.demo.demo.activity.AnimActivity">
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start"/>
<Button
android:id="@+id/update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="update"/>
<com.demo.demo.widget.FlowerGiftView
android:id="@+id/right_gift_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginTop="100dp"
android:background="@drawable/rtc_flowers_right"
android:gravity="center"
android:paddingLeft="11dp"
android:visibility="invisible"
app:gift_desc="送出花"></com.demo.demo.widget.FlowerGiftView>
<com.demo.demo.widget.FlowerGiftView
android:id="@+id/left_gift_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:background="@drawable/rtc_flowers_left"
android:gravity="center"
android:paddingLeft="14dp"
android:visibility="invisible"
app:gift_desc="收到花"
app:left_to_right="true"></com.demo.demo.widget.FlowerGiftView>
</LinearLayout>
这里只是需要注意的是,初始我们设置的是invisible,因为我们需要获取到整个View的宽高,如果设置gone则获取不到宽高。
最终在界面中只需要调用startAnim就可以开始执行了。
Code
代码Git地址如下:
以上是关于Android送花动画的主要内容,如果未能解决你的问题,请参考以下文章