返回介绍

六 - 仿 QQ 聊天撒花特效,无形装逼,最为致命

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

我的关于特效的专辑已经在 CSDN 上申请了一个专栏 - http://blog.csdn.net/column/details/liuguilin.html
日后我所写的特效专辑也会以一添加在这个专栏上,今天写的这个特效,是关于聊天的,你肯定遇到过,就是你跟人家聊天的时候,比如发送应(么么哒),然后屏幕上全部就是表情了,今天我们就是做这个,撒花的特效,国际惯例,上图

截图

实现这样的效果,你要知道贝塞尔曲线,何谓贝塞尔曲线?其实就是曲线,嘿嘿,关于曲线的概念大家可以去

Android 绘图机制(二) - 自定义 View 绘制形,圆形,三角形,扇形,椭圆,曲线,文字和图片的坐标讲解
中看下,我们这里就直接写了

1.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" >

  //撒花的区域
  <RelativeLayout
    android:id="@+id/rlt_animation_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
  </RelativeLayout>

  <Button
    android:id="@+id/btn_start"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="23dp"
    android:text="开始撒花" />

</RelativeLayout>

2.Fllower

传参类
package com.lgl.test;

import android.graphics.Bitmap;
import android.graphics.Path;

import java.io.Serializable;

public class Fllower implements Serializable {

  private static final long serialVersionUID = 1L;
  private Bitmap image;
  private float x;
  private float y;
  private Path path;
  private float value;

  public Bitmap getResId() {
    return image;
  }

  public void setResId(Bitmap img) {
    this.image = img;
  }

  public float getX() {
    return x;
  }

  public void setX(float x) {
    this.x = x;
  }

  public float getY() {
    return y;
  }

  public void setY(float y) {
    this.y = y;
  }

  public Path getPath() {
    return path;
  }

  public void setPath(Path path) {
    this.path = path;
  }

  public float getValue() {
    return value;
  }

  public void setValue(float value) {
    this.value = value;
  }

  @Override
  public String toString() {
    return "Fllower [ x=" + x + ", y=" + y + ", path=" + path + ", value="
        + value + "]";
  }

}

3.FllowerAnimation

动画类
package com.lgl.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;

/**
 * 撒花 用到的知识点: 1、android 属性动画 2、Path 路径绘制 3、贝塞尔曲线
 */
public class FllowerAnimation extends View implements AnimatorUpdateListener {

  /**
   * 动画改变的属性值
   */
  private float phase1 = 0f;
  private float phase2 = 0f;
  private float phase3 = 0f;

  /**
   * 小球集合
   */
  private List<Fllower> fllowers1 = new ArrayList<Fllower>();
  private List<Fllower> fllowers2 = new ArrayList<Fllower>();
  private List<Fllower> fllowers3 = new ArrayList<Fllower>();

  /**
   * 动画播放的时间
   */
  private int time = 4000;
  /**
   * 动画间隔
   */
  private int delay = 400;

  int[] ylocations = { -100, -50, -25, 0 };

  /**
   * 资源 ID
   */
  // private int resId = R.drawable.fllower_love;
  public FllowerAnimation(Context context) {
    super(context);
    init(context);
    // this.resId = resId;
  }

  @SuppressWarnings("deprecation")
  private void init(Context context) {
    WindowManager wm = (WindowManager) context
        .getSystemService(Context.WINDOW_SERVICE);
    width = wm.getDefaultDisplay().getWidth();
    height = (int) (wm.getDefaultDisplay().getHeight() * 3 / 2f);

    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    // mPaint.setStrokeWidth(2);
    // mPaint.setColor(Color.BLUE);
    // mPaint.setStyle(Style.STROKE);

    pathMeasure = new PathMeasure();

    builderFollower(fllowerCount, fllowers1);
    builderFollower(fllowerCount, fllowers2);
    builderFollower(fllowerCount, fllowers3);

  }

  /**
   * 宽度
   */
  private int width = 0;
  /**
   * 高度
   */
  private int height = 0;

  /**
   * 曲线高度个数分割
   */
  private int quadCount = 10;
  /**
   * 曲度
   */
  private float intensity = 0.2f;

  /**
   * 第一批个数
   */
  private int fllowerCount = 4;

  /**
   * 创建花
   */
  private void builderFollower(int count, List<Fllower> fllowers) {

    int max = (int) (width * 3 / 4f);
    int min = (int) (width / 4f);
    Random random = new Random();
    for (int i = 0; i < count; i++) {
      int s = random.nextInt(max) % (max - min + 1) + min;
      Path path = new Path();
      CPoint CPoint = new CPoint(s, ylocations[random.nextInt(3)]);
      List<CPoint> points = builderPath(CPoint);
      drawFllowerPath(path, points);
      Fllower fllower = new Fllower();
      fllower.setPath(path);
      Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
          R.drawable.lift_flower);
      fllower.setResId(bitmap);
      fllowers.add(fllower);
    }

  }

  /**
   * 画曲线
   *
   * @param path
   * @param points
   */
  private void drawFllowerPath(Path path, List<CPoint> points) {
    if (points.size() > 1) {
      for (int j = 0; j < points.size(); j++) {

        CPoint point = points.get(j);

        if (j == 0) {
          CPoint next = points.get(j + 1);
          point.dx = ((next.x - point.x) * intensity);
          point.dy = ((next.y - point.y) * intensity);
        } else if (j == points.size() - 1) {
          CPoint prev = points.get(j - 1);
          point.dx = ((point.x - prev.x) * intensity);
          point.dy = ((point.y - prev.y) * intensity);
        } else {
          CPoint next = points.get(j + 1);
          CPoint prev = points.get(j - 1);
          point.dx = ((next.x - prev.x) * intensity);
          point.dy = ((next.y - prev.y) * intensity);
        }

        // create the cubic-spline path
        if (j == 0) {
          path.moveTo(point.x, point.y);
        } else {
          CPoint prev = points.get(j - 1);
          path.cubicTo(prev.x + prev.dx, (prev.y + prev.dy), point.x
              - point.dx, (point.y - point.dy), point.x, point.y);
        }
      }
    }
  }

  /**
   * 曲线摇摆的幅度
   */
  private int range = (int) TypedValue
      .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources()
          .getDisplayMetrics());

  /**
   * 画路径
   *
   * @param point
   * @return
   */
  private List<CPoint> builderPath(CPoint point) {
    List<CPoint> points = new ArrayList<CPoint>();
    Random random = new Random();
    for (int i = 0; i < quadCount; i++) {
      if (i == 0) {
        points.add(point);
      } else {
        CPoint tmp = new CPoint(0, 0);
        if (random.nextInt(100) % 2 == 0) {
          tmp.x = point.x + random.nextInt(range);
        } else {
          tmp.x = point.x - random.nextInt(range);
        }
        tmp.y = (int) (height / (float) quadCount * i);
        points.add(tmp);
      }
    }
    return points;
  }

  /**
   * 画笔
   */
  private Paint mPaint;

  /**
   * 测量路径的坐标位置
   */
  private PathMeasure pathMeasure = null;

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    drawFllower(canvas, fllowers1);
    drawFllower(canvas, fllowers2);
    drawFllower(canvas, fllowers3);

  }

  /**
   * 高度往上偏移量,把开始点移出屏幕顶部
   */
  private float dy = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
      40, getResources().getDisplayMetrics());

  /**
   * @param canvas
   * @param fllowers
   */
  private void drawFllower(Canvas canvas, List<Fllower> fllowers) {
    for (Fllower fllower : fllowers) {
      float[] pos = new float[2];
      // canvas.drawPath(fllower.getPath(),mPaint);
      pathMeasure.setPath(fllower.getPath(), false);
      pathMeasure.getPosTan(height * fllower.getValue(), pos, null);
      // canvas.drawCircle(pos[0], pos[1], 10, mPaint);
      canvas.drawBitmap(fllower.getResId(), pos[0], pos[1] - dy, null);
    }
  }

  ObjectAnimator mAnimator1;
  ObjectAnimator mAnimator2;
  ObjectAnimator mAnimator3;

  public void startAnimation() {
    if (mAnimator1 != null && mAnimator1.isRunning()) {
      mAnimator1.cancel();
    }
    mAnimator1 = ObjectAnimator.ofFloat(this, "phase1", 0f, 1f);
    mAnimator1.setDuration(time);
    mAnimator1.addUpdateListener(this);

    mAnimator1.start();
    mAnimator1.setInterpolator(new AccelerateInterpolator(1f));

    if (mAnimator2 != null && mAnimator2.isRunning()) {
      mAnimator2.cancel();
    }
    mAnimator2 = ObjectAnimator.ofFloat(this, "phase2", 0f, 1f);
    mAnimator2.setDuration(time);
    mAnimator2.addUpdateListener(this);
    mAnimator2.start();
    mAnimator2.setInterpolator(new AccelerateInterpolator(1f));
    mAnimator2.setStartDelay(delay);

    if (mAnimator3 != null && mAnimator3.isRunning()) {
      mAnimator3.cancel();
    }
    mAnimator3 = ObjectAnimator.ofFloat(this, "phase3", 0f, 1f);
    mAnimator3.setDuration(time);
    mAnimator3.addUpdateListener(this);
    mAnimator3.start();
    mAnimator3.setInterpolator(new AccelerateInterpolator(1f));
    mAnimator3.setStartDelay(delay * 2);
  }

  /**
   * 跟新小球的位置
   *
   * @param value
   * @param fllowers
   */
  private void updateValue(float value, List<Fllower> fllowers) {
    for (Fllower fllower : fllowers) {
      fllower.setValue(value);
    }
  }

  /**
   * 动画改变回调
   */
  @Override
  public void onAnimationUpdate(ValueAnimator arg0) {

    updateValue(getPhase1(), fllowers1);
    updateValue(getPhase2(), fllowers2);
    updateValue(getPhase3(), fllowers3);
    Log.i(tag, getPhase1() + "");
    invalidate();
  }

  public float getPhase1() {
    return phase1;
  }

  public void setPhase1(float phase1) {
    this.phase1 = phase1;
  }

  public float getPhase2() {
    return phase2;
  }

  public void setPhase2(float phase2) {
    this.phase2 = phase2;
  }

  public float getPhase3() {
    return phase3;
  }

  public void setPhase3(float phase3) {
    this.phase3 = phase3;
  }

  private String tag = this.getClass().getSimpleName();

  private class CPoint {

    public float x = 0f;
    public float y = 0f;

    /**
     * x-axis distance
     */
    public float dx = 0f;

    /**
     * y-axis distance
     */
    public float dy = 0f;

    public CPoint(float x, float y) {
      this.x = x;
      this.y = y;
    }
  }

}

4. MainActivity

接着就看我们使用
package com.lgl.test;

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

public class MainActivity extends Activity {

  private Button btn_start;
  // 撒花特效
  private RelativeLayout rlt_animation_layout;
  private FllowerAnimation fllowerAnimation;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 撒花初始化
    rlt_animation_layout = (RelativeLayout) findViewById(R.id.rlt_animation_layout);
    rlt_animation_layout.setVisibility(View.VISIBLE);
    fllowerAnimation = new FllowerAnimation(this);
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
        RelativeLayout.LayoutParams.MATCH_PARENT,
        RelativeLayout.LayoutParams.MATCH_PARENT);
    fllowerAnimation.setLayoutParams(params);
    rlt_animation_layout.addView(fllowerAnimation);

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

      @Override
      public void onClick(View v) {
        // 开始撒花
        fllowerAnimation.startAnimation();
      }
    });
  }
}
好,我们现在来看看效果
好的,你也赶快去试一下吧!

Demo 下载: http://download.csdn.net/detail/qq_26787115/9410578

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

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

发布评论

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