返回介绍

八 - 实现心型起泡飞舞的特效,让你的 APP 瞬间暖心

发布于 2025-02-28 12:56:41 字数 9806 浏览 0 评论 0 收藏 0


马上也要放年假了,家里估计会没网,更完这篇的话,可能要到年后了,不过在此期间会把更新内容都保存在本地,这样有网就可以发表了,也是极好的,今天说的这个特效,原本是 Only 上的一个小彩蛋的,我们来看看图片

只要我点击了 Only 这个字,下面就开始上升起起泡了,这个实现起来其实就是一个欲盖弥彰的动画而已,准备好三张颜色不一样的心型图片咯,这样的话,我们就开始动手来写一写吧!
首先新建一个工程 - HeartFaom
准备工作就是准备图片咯

BezierEvaluator

单位转换以及计算轨迹
package com.lgl.heartfaom;

import android.animation.TypeEvaluator;
import android.graphics.PointF;

public class BezierEvaluator implements TypeEvaluator<PointF> {

  private PointF pointF1;
  private PointF pointF2;

  public BezierEvaluator(PointF pointF1, PointF pointF2) {
    this.pointF1 = pointF1;
    this.pointF2 = pointF2;
  }

  @Override
  public PointF evaluate(float time, PointF startValue, PointF endValue) {

    float timeLeft = 1.0f - time;
    PointF point = new PointF();// 结果

    point.x = timeLeft * timeLeft * timeLeft * (startValue.x) + 3
        * timeLeft * timeLeft * time * (pointF1.x) + 3 * timeLeft
        * time * time * (pointF2.x) + time * time * time * (endValue.x);

    point.y = timeLeft * timeLeft * timeLeft * (startValue.y) + 3
        * timeLeft * timeLeft * time * (pointF1.y) + 3 * timeLeft
        * time * time * (pointF2.y) + time * time * time * (endValue.y);
    return point;
  }
}

PeriscopeLayout

贝塞尔曲线的计算以及气泡的实现
package com.lgl.heartfaom;

import java.util.Random;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;

public class PeriscopeLayout extends RelativeLayout {

  private Interpolator line = new LinearInterpolator();// 线性
  private Interpolator acc = new AccelerateInterpolator();// 加速
  private Interpolator dce = new DecelerateInterpolator();// 减速
  private Interpolator accdec = new AccelerateDecelerateInterpolator();// 先加速后减速
  private Interpolator[] interpolators;

  private int mHeight;
  private int mWidth;
  private LayoutParams lp;
  private Drawable[] drawables;
  private Random random = new Random();

  private int dHeight;
  private int dWidth;

  public PeriscopeLayout(Context context) {
    super(context);
    init();
  }

  public PeriscopeLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public PeriscopeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public PeriscopeLayout(Context context, AttributeSet attrs,
      int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init();
  }

  private void init() {

    // 初始化显示的图片
    drawables = new Drawable[3];
    Drawable red = getResources().getDrawable(R.drawable.pl_red);
    Drawable yellow = getResources().getDrawable(R.drawable.pl_yellow);
    Drawable blue = getResources().getDrawable(R.drawable.pl_blue);

    drawables[0] = red;
    drawables[1] = yellow;
    drawables[2] = blue;
    // 获取图的宽高 用于后面的计算
    // 注意 我这里 3 张图片的大小都是一样的,所以我只取了一个
    dHeight = red.getIntrinsicHeight();
    dWidth = red.getIntrinsicWidth();

    // 底部 并且 水平居中
    lp = new LayoutParams(dWidth, dHeight);
    lp.addRule(CENTER_HORIZONTAL, TRUE);// 这里的 TRUE 要注意 不是 true
    lp.addRule(ALIGN_PARENT_BOTTOM, TRUE);

    // 初始化插补器
    interpolators = new Interpolator[4];
    interpolators[0] = line;
    interpolators[1] = acc;
    interpolators[2] = dce;
    interpolators[3] = accdec;

  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mWidth = getMeasuredWidth();
    mHeight = getMeasuredHeight();
  }

  public void addHeart() {

    ImageView imageView = new ImageView(getContext());
    // 随机选一个
    imageView.setImageDrawable(drawables[random.nextInt(3)]);
    imageView.setLayoutParams(lp);

    addView(imageView);

    Animator set = getAnimator(imageView);
    set.addListener(new AnimEndListener(imageView));
    set.start();

  }

  private Animator getAnimator(View target) {
    AnimatorSet set = getEnterAnimtor(target);

    ValueAnimator bezierValueAnimator = getBezierValueAnimator(target);

    AnimatorSet finalSet = new AnimatorSet();
    finalSet.playSequentially(set);
    finalSet.playSequentially(set, bezierValueAnimator);
    finalSet.setInterpolator(interpolators[random.nextInt(4)]);
    finalSet.setTarget(target);
    return finalSet;
  }

  private AnimatorSet getEnterAnimtor(final View target) {

    ObjectAnimator alpha = ObjectAnimator.ofFloat(target, View.ALPHA, 0.2f,
        1f);
    ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, View.SCALE_X,
        0.2f, 1f);
    ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, View.SCALE_Y,
        0.2f, 1f);
    AnimatorSet enter = new AnimatorSet();
    enter.setDuration(500);
    enter.setInterpolator(new LinearInterpolator());
    enter.playTogether(alpha, scaleX, scaleY);
    enter.setTarget(target);
    return enter;
  }

  private ValueAnimator getBezierValueAnimator(View target) {

    // 初始化一个贝塞尔计算器- - 传入
    BezierEvaluator evaluator = new BezierEvaluator(getPointF(2),
        getPointF(1));

    // 这里最好画个图 理解一下 传入了起点 和 终点
    ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF(
        (mWidth - dWidth) / 2, mHeight - dHeight),
        new PointF(random.nextInt(getWidth()), 0));
    animator.addUpdateListener(new BezierListenr(target));
    animator.setTarget(target);
    animator.setDuration(3000);
    return animator;
  }

  /**
   * 获取中间的两个 点
   *
   * @param scale
   */
  private PointF getPointF(int scale) {

    PointF pointF = new PointF();
    pointF.x = random.nextInt((mWidth - 100));// 减去 100 是为了控制 x 轴活动范围,看效果 随意~~
    // 再 Y 轴上 为了确保第二个点 在第一个点之上,我把 Y 分成了上下两半 这样动画效果好一些 也可以用其他方法
    pointF.y = random.nextInt((mHeight - 100)) / scale;
    return pointF;
  }

  private class BezierListenr implements ValueAnimator.AnimatorUpdateListener {

    private View target;

    public BezierListenr(View target) {
      this.target = target;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
      // 这里获取到贝塞尔曲线计算出来的的 x y 值 赋值给 view 这样就能让爱心随着曲线走啦
      PointF pointF = (PointF) animation.getAnimatedValue();
      target.setX(pointF.x);
      target.setY(pointF.y);
      // 这里顺便做一个 alpha 动画
      target.setAlpha(1 - animation.getAnimatedFraction());
    }
  }

  private class AnimEndListener extends AnimatorListenerAdapter {
    private View target;

    public AnimEndListener(View target) {
      this.target = target;
    }

    @Override
    public void onAnimationEnd(Animator animation) {
      super.onAnimationEnd(animation);
      // 因为不停的 add 导致子 view 数量只增不减,所以在 view 动画结束后 remove 掉
      removeView((target));
    }
  }
}

activity_main.xml

布局的实现
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="#000" >

  <Button
    android:id="@+id/btn_start"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="飞舞吧!" />

  <com.lgl.heartfaom.PeriscopeLayout
    android:id="@+id/periscope"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
  </com.lgl.heartfaom.PeriscopeLayout>

</RelativeLayout>

MainActivity

接着就是怎么去使用它了
package com.lgl.heartfaom;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

  private Button btn_start;
  // 心型气泡
  private PeriscopeLayout periscopeLayout;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // 初始化
    periscopeLayout = (PeriscopeLayout) findViewById(R.id.periscope);

    btn_start = (Button) findViewById(R.id.btn_start);
    btn_start.setOnClickListener(new OnClickListener() {

      @Override
      public void onClick(View v) {
        // 调用添加泡泡的方法
        periscopeLayout.addHeart();
      }
    });
  }
}

好,我们接下来就可以运行一下试试实际上的效果了

觉得不错的点个赞哦!

Demo 下载地址: http://download.csdn.net/detail/qq_26787115/9422603

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文