Android动画实例 (二)

效果图

一个直播点赞的效果

  • 实现思路
    循环添加一个自定义的ImageView,每个ImageView随机设置不同颜色的Bitmap,并且有一个放大的动画,比较简单。然后ImageView的移动轨迹使用贝塞尔曲线来完成。最后一个缩小到动画,动画结束移除控件。
public HeartView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    Bitmap bm = BitmapUtil.createHeart(context);
    setImageBitmap(bm);
}

在三个参数的构造方法里设置Bitmap,Btimap通过工具类BitmapUtil来创建,下面是这个工具类

public class BitmapUtil {

    private static final Paint sPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    private static final Canvas sCanvas = new Canvas();
    private static Random mRandom = new Random();

    public static Bitmap createHeart(Context context) {
        Bitmap heart = BitmapFactory.decodeResource(context.getResources(), R.drawable.heart);
        Bitmap heartBorder = BitmapFactory.decodeResource(context.getResources(), R.drawable.heart_border);
        Bitmap bm = Bitmap.createBitmap(heartBorder.getWidth(), heartBorder.getHeight(), Bitmap.Config.ARGB_8888);
        if (bm == null) {
            return null;
        }
        Canvas canvas = sCanvas;
        canvas.setBitmap(bm);
        Paint p = sPaint;
        // 画边框
        canvas.drawBitmap(heartBorder, 0, 0, p);
        // 随机生成爱心颜色
        int color = Color.rgb(mRandom.nextInt(255), mRandom.nextInt(255), mRandom.nextInt(255));
        // 设置ColorFilter
        p.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
        float dx = (heartBorder.getWidth() - heart.getWidth()) / 2f;
        float dy = (heartBorder.getHeight() - heart.getHeight()) / 2f;
        // 因为边框图片比爱心图片大,爱心会在边框的中间
        canvas.drawBitmap(heart, dx, dy, p);
        p.setColorFilter(null);
        canvas.setBitmap(null);
        return bm;
    }

}

爱心爱心边框 这是工具类中设计的两个图片,这个工具类是当初做这个东西的时候在网上找的,链接忘记了,上面有都有注释

@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    int width = getDrawable().getIntrinsicWidth();
    int height = getDrawable().getIntrinsicHeight();
    // 控制自己在父控件中的位置
    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
    params.leftMargin = (ScreenUtils.getScreenWidth(getContext()) - width) / 2;
    // 这个值可以适当的改一下
    params.topMargin = ScreenUtils.getScreenHeight(getContext()) - height * 3;
    setLayoutParams(params);
    zoom();
}

当控件绑定到Window的时候会调用这个方法,在这个方法中初始化控件的坐标,控制控件的初始位置在底部居中。然后执行一个放大的动画。

/**
 * 放大动画
 */
private void zoom() {
    AnimatorSet set = new AnimatorSet();
    ObjectAnimator scaleXAnim = scaleX(0.0f, 1.0f);
    ObjectAnimator scaleYAnim = scaleY(0.0f, 1.0f);
    set.play(scaleXAnim).with(scaleYAnim);
    set.start();
    set.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            // 动画结束之后,
            bezier();
        }
    });
}

放大动画比较简单,在动画结束后调用bezier方法,也是实现的重点

private void bezier() {
    final int width = getDrawable().getIntrinsicWidth();
    final int height = getDrawable().getIntrinsicHeight();
    final int screentWidth = ScreenUtils.getScreenWidth(getContext());
    final int screenHeight = ScreenUtils.getScreenHeight(getContext());
    final Random random = new Random();
    // 设置贝塞尔曲线的起始坐标和控制坐标
    // 开始坐标与onAttachedToWindow中的初始坐标一致
    // 结束坐标
    float startX = (screentWidth- width) / 2;
    float startY = screenHeight - height * 3f;
    float stopX = random.nextInt(screentWidth);
    float stopY = 0;
    float controlX = random.nextInt(screentWidth);
    float controlY = ScreenUtils.getScreenHeight(getContext()) / 2;
    Path path = new Path();
    path.moveTo(startX, startY);
    path.quadTo(controlX, controlY, stopX, stopY);
    BezierEvaluator evaluator = new BezierEvaluator(new PointF(controlX, controlY));
    final PointF start = new PointF(startX, startY);
    final PointF stop = new PointF(stopX, stopY);
    ValueAnimator animator = ValueAnimator.ofObject(evaluator, start, stop);
    animator.setDuration(3000);
    animator.addUpdateListener(this);
    animator.addListener(new AnimatorListenerAdapter(){
        @Override
        public void onAnimationEnd(Animator animation) {
             shrink();
        }
    });
    animator.start();
}

使用二阶贝塞尔曲线,首先设置起点坐标与初始化坐标一致,然后设置终点坐标,想法是移动到顶部,所以stopY一直为0,stopX设置为random.nextInt(screentWidth)不超过宽度的一个随机大小,因为不想落下同一个位置,控制点也是一个意思,大致在窗口的中间位置。然后通过属性动画获取到这条曲线上的坐标,这里使用一个带有TypeEvaluator参数的方法来构造一个属性动画。那么TypeEvaluator的作用到底是什么呢?简单来说,就是告诉动画系统如何从初始值过度到结束值。最后这句来自大神郭霖的博客 下面是这个自定义的TypeEvaluator的内容:

public class BezierEvaluator implements TypeEvaluator<PointF> {

    private PointF mControlPointF;

    public BezierEvaluator(PointF controlPointF) {
        mControlPointF = controlPointF;
    }

    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        return BezierUtil.CalculateBezierPointForQuadratic(fraction, startValue, mControlPointF, endValue);
    }

}

BezierUtil来自大神徐宜生的慕课网课程 然后在动画过程中实时更新控件的位置,实现控件随着曲线轨迹移动:

@Override
public void onAnimationUpdate(ValueAnimator animation) {
    // 不断获取曲线上的点,更新控件坐标,实现控件随着曲线移动
    final PointF p = (PointF) animation.getAnimatedValue();
    ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
    params.leftMargin = (int) p.x;
    params.topMargin = (int) p.y;
    setLayoutParams(params);
}

最后在曲线动画结束的时候缩小动画,缩小动画结束移除控件,与放大动画一样比较简单

/**
 * 缩小动画
 */
private void shrink() {
    AnimatorSet set = new AnimatorSet();
    ObjectAnimator scaleXAnim = scaleX(1.0f, 0.0f);
    ObjectAnimator scaleYAnim = scaleY(1.0f, 0.0f);
    set.play(scaleXAnim).with(scaleYAnim);
    set.start();
    set.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            // 动画结束,移除控件
            if(getParent() instanceof ViewGroup) {
                ViewGroup parent = (ViewGroup) getParent();
                parent.removeView(HeartView.this);
            }
        }
    });
}

最后使用:

final ViewGroup content = (ViewGroup) findViewById(android.R.id.content);
final RelativeLayout layout = (RelativeLayout) content.getChildAt(0);

final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        HeartView view = new HeartView(MainActivity.this);
        layout.addView(view);
        handler.postDelayed(this, 500);
    }
}, 1000);

实现之前束手无策,实现之后感觉没啥可写的源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值