返回介绍

五 - 自定义圆形头像和仿 MIUI 卸载动画 - 粒子爆炸

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

好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的 imageView,接着,我们会来实现粒子爆炸的特效,按照国际惯例,无图无真相的没这个效果也是模仿大神的,现在应用在了我的《Only》上

截图

好的,我们新建一个工程 - AnimView,我们要用到的图片

一.自定义圆形头像 -

直接开写了,要实现的东西都在注释上了

1.编写自定义属性 attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

  <declare-styleable name="roundedimageview">

    <attr name="border_thickness" format="dimension" />

    <attr name="border_inside_color" format="color" />

    <attr name="border_outside_color" format="color"></attr>

  </declare-styleable>
</resources>

2.自定义 View

紧接着我们就可以编写这个类了
package com.lgl.animview;

/**
 * 圆形头像
 * Created by LGL on 2016/1/12.
 */

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
 * 圆形 ImageView,可设置最多两个宽度不同且颜色不同的圆形边框。 设置颜色在 xml 布局文件中由自定义属性配置参数指定
 */

public class RoundImageView extends ImageView {

  private int mBorderThickness = 0;

  private Context mContext;

  private int defaultColor = 0xFFFFFFFF;

  // 如果只有其中一个有值,则只画一个圆形边框

  private int mBorderOutsideColor = 0;

  private int mBorderInsideColor = 0;

  // 控件默认长、宽

  private int defaultWidth = 0;

  private int defaultHeight = 0;

  public RoundImageView(Context context) {

    super(context);

    mContext = context;

  }

  public RoundImageView(Context context, AttributeSet attrs) {

    super(context, attrs);

    mContext = context;

    setCustomAttributes(attrs);

  }

  public RoundImageView(Context context, AttributeSet attrs, int defStyle) {

    super(context, attrs, defStyle);

    mContext = context;

    setCustomAttributes(attrs);

  }

  private void setCustomAttributes(AttributeSet attrs) {

    // 获取自定义的属性
    TypedArray a = mContext.obtainStyledAttributes(attrs,
        R.styleable.roundedimageview);

    mBorderThickness = a.getDimensionPixelSize(
        R.styleable.roundedimageview_border_thickness, 0);

    mBorderOutsideColor = a
        .getColor(R.styleable.roundedimageview_border_outside_color,
            defaultColor);

    mBorderInsideColor = a.getColor(
        R.styleable.roundedimageview_border_inside_color, defaultColor);

  }

  @Override
  protected void onDraw(Canvas canvas) {

    Drawable drawable = getDrawable();

    if (drawable == null) {

      return;

    }

    if (getWidth() == 0 || getHeight() == 0) {

      return;

    }

    this.measure(0, 0);

    if (drawable.getClass() == NinePatchDrawable.class)

      return;

    Bitmap b = ((BitmapDrawable) drawable).getBitmap();

    Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);

    if (defaultWidth == 0) {

      defaultWidth = getWidth();

    }

    if (defaultHeight == 0) {

      defaultHeight = getHeight();

    }

    int radius = 0;

    if (mBorderInsideColor != defaultColor
        && mBorderOutsideColor != defaultColor) {// 定义画两个边框,分别为外圆边框和内圆边框

      radius = (defaultWidth < defaultHeight ? defaultWidth
          : defaultHeight) / 2 - 2 * mBorderThickness;

      // 画内圆

      drawCircleBorder(canvas, radius + mBorderThickness / 2,
          mBorderInsideColor);

      // 画外圆

      drawCircleBorder(canvas, radius + mBorderThickness
          + mBorderThickness / 2, mBorderOutsideColor);

    } else if (mBorderInsideColor != defaultColor
        && mBorderOutsideColor == defaultColor) {// 定义画一个边框

      radius = (defaultWidth < defaultHeight ? defaultWidth
          : defaultHeight) / 2 - mBorderThickness;

      drawCircleBorder(canvas, radius + mBorderThickness / 2,
          mBorderInsideColor);

    } else if (mBorderInsideColor == defaultColor
        && mBorderOutsideColor != defaultColor) {// 定义画一个边框

      radius = (defaultWidth < defaultHeight ? defaultWidth
          : defaultHeight) / 2 - mBorderThickness;

      drawCircleBorder(canvas, radius + mBorderThickness / 2,
          mBorderOutsideColor);

    } else {// 没有边框

      radius = (defaultWidth < defaultHeight ? defaultWidth
          : defaultHeight) / 2;

    }

    Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius);

    canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight
        / 2 - radius, null);

  }

  /**
   * 获取裁剪后的圆形图片
   */

  public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) {

    Bitmap scaledSrcBmp;

    int diameter = radius * 2;

    // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片

    int bmpWidth = bmp.getWidth();

    int bmpHeight = bmp.getHeight();

    int squareWidth = 0, squareHeight = 0;

    int x = 0, y = 0;

    Bitmap squareBitmap;

    if (bmpHeight > bmpWidth) {// 高大于宽

      squareWidth = squareHeight = bmpWidth;

      x = 0;

      y = (bmpHeight - bmpWidth) / 2;

      // 截取正方形图片

      squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth,
          squareHeight);

    } else if (bmpHeight < bmpWidth) {// 宽大于高

      squareWidth = squareHeight = bmpHeight;

      x = (bmpWidth - bmpHeight) / 2;

      y = 0;

      squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth,
          squareHeight);

    } else {

      squareBitmap = bmp;

    }

    if (squareBitmap.getWidth() != diameter
        || squareBitmap.getHeight() != diameter) {

      scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter,
          diameter, true);

    } else {

      scaledSrcBmp = squareBitmap;

    }

    Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(),

    scaledSrcBmp.getHeight(),

    Bitmap.Config.ARGB_8888);

    Canvas canvas = new Canvas(output);

    Paint paint = new Paint();

    Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),
        scaledSrcBmp.getHeight());

    paint.setAntiAlias(true);

    paint.setFilterBitmap(true);

    paint.setDither(true);

    canvas.drawARGB(0, 0, 0, 0);

    canvas.drawCircle(scaledSrcBmp.getWidth() / 2,

    scaledSrcBmp.getHeight() / 2,

    scaledSrcBmp.getWidth() / 2,

    paint);

    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

    canvas.drawBitmap(scaledSrcBmp, rect, rect, paint);

    bmp = null;

    squareBitmap = null;

    scaledSrcBmp = null;

    return output;

  }

  /**
   * 边缘画圆
   */

  private void drawCircleBorder(Canvas canvas, int radius, int color) {

    Paint paint = new Paint();

    /* 去锯齿 */

    paint.setAntiAlias(true);

    paint.setFilterBitmap(true);

    paint.setDither(true);

    paint.setColor(color);

    /* 设置 paint 的 style 为 STROKE:空心 */

    paint.setStyle(Paint.Style.STROKE);

    /* 设置 paint 的外框宽度 */

    paint.setStrokeWidth(mBorderThickness);

    canvas.drawCircle(defaultWidth / 2, defaultHeight / 2, radius, paint);

  }

}

3.引用

引用起来就比较简单了,我们首先来引入他的命名空间
 xmlns:imagecontrol="http://schemas.android.com/apk/res-auto"
然后我们直接写 xml
  <com.lgl.animview.RoundImageView
    android:id="@+id/iv_round"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/photo"
    imagecontrol:border_inside_color="#bc0978"
    imagecontrol:border_outside_color="#ba3456"
    imagecontrol:border_thickness="1dp" />
好的,让我们运行下吧

二.MUI 卸载动画 - 粒子爆炸

关于这个粒子特效,在开篇的时候已经展示了效果,那么我们接下来,要怎么做尼?

1.ParticleUtils

用于粒子动画的单位转换
package com.lgl.animview;

import android.content.res.Resources;

/**
 * 粒子动画
 */
public class ParticleUtils {
  /**
   * 密度
   */
  public static final float DENSITY = Resources.getSystem().getDisplayMetrics().density;

  public static int dp2px(int dp) {
    return Math.round(dp * DENSITY);
  }
}

2.Particle

用于爆破效果的分子计算
package com.lgl.animview;

import java.util.Random;

import android.graphics.Point;
import android.graphics.Rect;

/**
 * Created by lgl on 16/01/14. 爆破粒子
 */
public class Particle {

  public static final int PART_WH = 8; // 默认小球宽高

  // 原本的值(不可变)
  // float originCX;
  // float originCY;
  // float originRadius;

  // 实际的值(可变)
  float cx; // center x of circle
  float cy; // center y of circle
  float radius;

  int color;
  float alpha;

  static Random random = new Random();

  Rect mBound;

  public static Particle generateParticle(int color, Rect bound, Point point) {
    int row = point.y; // 行是高
    int column = point.x; // 列是宽

    Particle particle = new Particle();
    particle.mBound = bound;
    particle.color = color;
    particle.alpha = 1f;

    particle.radius = PART_WH;
    particle.cx = bound.left + PART_WH * column;
    particle.cy = bound.top + PART_WH * row;

    return particle;
  }

  public void advance(float factor) {
    cx = cx + factor * random.nextInt(mBound.width())
        * (random.nextFloat() - 0.5f);
    cy = cy + factor * random.nextInt(mBound.height() / 2);

    radius = radius - factor * random.nextInt(2);

    alpha = (1f - factor) * (1 + random.nextFloat());
  }
}

3.ExplosionAnimator

属性动画,用于动画展示
package com.lgl.animview;

import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.View;

/**
 * Created by lgl on 16/01/14.
 */
public class ExplosionAnimator extends ValueAnimator {
  public static final int DEFAULT_DURATION = 1500;
  private Particle[][] mParticles;
  private Paint mPaint;
  private View mContainer;

  public ExplosionAnimator(View view, Bitmap bitmap, Rect bound) {

    mPaint = new Paint();
    mContainer = view;

    setFloatValues(0.0f, 1.0f);
    setDuration(DEFAULT_DURATION);

    mParticles = generateParticles(bitmap, bound);
  }

  private Particle[][] generateParticles(Bitmap bitmap, Rect bound) {
    int w = bound.width();
    int h = bound.height();

    int partW_Count = w / Particle.PART_WH; // 横向个数
    int partH_Count = h / Particle.PART_WH; // 竖向个数

    int bitmap_part_w = bitmap.getWidth() / partW_Count;
    int bitmap_part_h = bitmap.getHeight() / partH_Count;

    Particle[][] particles = new Particle[partH_Count][partW_Count];
    Point point = null;
    for (int row = 0; row < partH_Count; row++) { // 行
      for (int column = 0; column < partW_Count; column++) { // 列
        // 取得当前粒子所在位置的颜色
        int color = bitmap.getPixel(column * bitmap_part_w, row
            * bitmap_part_h);

        point = new Point(column, row); // x 是列,y 是行

        particles[row][column] = Particle.generateParticle(color,
            bound, point);
      }
    }

    return particles;
  }

  public void draw(Canvas canvas) {
    if (!isStarted()) { // 动画结束时停止
      return;
    }
    for (Particle[] particle : mParticles) {
      for (Particle p : particle) {
        p.advance((Float) getAnimatedValue());
        mPaint.setColor(p.color);
        // mPaint.setAlpha((int) (255 * p.alpha)); //只是这样设置,透明色会显示为黑色
        mPaint.setAlpha((int) (Color.alpha(p.color) * p.alpha)); // 这样透明颜色就不是黑色了
        canvas.drawCircle(p.cx, p.cy, p.radius, mPaint);
      }
    }

    mContainer.invalidate();
  }

  @Override
  public void start() {
    super.start();
    mContainer.invalidate();
  }
}

4.ExplosionField

开始执行这个实例的动画了
package com.lgl.animview;

import java.util.ArrayList;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;

/**
 * Created by lgl on 16/01/14.
 */
public class ExplosionField extends View {
  private static final String TAG = "ExplosionField";
  private static final Canvas mCanvas = new Canvas();
  private ArrayList<ExplosionAnimator> explosionAnimators;
  private OnClickListener onClickListener;

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

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

  private void init() {
    explosionAnimators = new ArrayList<ExplosionAnimator>();

    attach2Activity((Activity) getContext());
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    for (ExplosionAnimator animator : explosionAnimators) {
      animator.draw(canvas);
    }
  }

  /**
   * 爆破
   *
   * @param view
   *      使得该 view 爆破
   */
  public void explode(final View view) {
    Rect rect = new Rect();
    view.getGlobalVisibleRect(rect); // 得到 view 相对于整个屏幕的坐标
    rect.offset(0, -ParticleUtils.dp2px(25)); // 去掉状态栏高度

    final ExplosionAnimator animator = new ExplosionAnimator(this,
        createBitmapFromView(view), rect);
    explosionAnimators.add(animator);

    animator.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationStart(Animator animation) {
        view.animate().alpha(0f).setDuration(150).start();
      }

      @Override
      public void onAnimationEnd(Animator animation) {
        view.animate().alpha(1f).setDuration(150).start();

        // 动画结束时从动画集中移除
        explosionAnimators.remove(animation);
        animation = null;
      }
    });
    animator.start();
  }

  private Bitmap createBitmapFromView(View view) {
    /*
     * 为什么屏蔽以下代码段? 如果 ImageView 直接得到位图,那么当它设置背景(backgroud) 时,不会读取到背景颜色
     */
    // if (view instanceof ImageView) {
    // Drawable drawable = ((ImageView)view).getDrawable();
    // if (drawable != null && drawable instanceof BitmapDrawable) {
    // return ((BitmapDrawable) drawable).getBitmap();
    // }
    // }

    // view.clearFocus(); //不同焦点状态显示的可能不同 - (azz:不同就不同有什么关系?)

    Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
        Bitmap.Config.ARGB_8888);

    if (bitmap != null) {
      synchronized (mCanvas) {
        mCanvas.setBitmap(bitmap);
        view.draw(mCanvas);
        mCanvas.setBitmap(null); // 清除引用
      }
    }
    return bitmap;
  }

  /**
   * 给 Activity 加上全屏覆盖的 ExplosionField
   */
  private void attach2Activity(Activity activity) {
    ViewGroup rootView = (ViewGroup) activity
        .findViewById(Window.ID_ANDROID_CONTENT);

    ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT);
    rootView.addView(this, lp);
  }

  /**
   * 希望谁有破碎效果,就给谁加 Listener
   *
   * @param view
   *      可以是 ViewGroup
   */
  public void addListener(View view) {
    if (view instanceof ViewGroup) {
      ViewGroup viewGroup = (ViewGroup) view;
      int count = viewGroup.getChildCount();
      for (int i = 0; i < count; i++) {
        addListener(viewGroup.getChildAt(i));
      }
    } else {
      view.setClickable(true);
      view.setOnClickListener(getOnClickListener());
    }
  }

  private OnClickListener getOnClickListener() {
    if (null == onClickListener) {

      onClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
          ExplosionField.this.explode(v);

          // view.setOnClickListener(null); // 用过一次就不需要了
        }
      };
    }

    return onClickListener;
  }
}

5.MainActivity

好的,一切准备好了之后我们就可以使用了
package com.lgl.animview;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

  // 实例化粒子动画
  private ExplosionField explosionField;

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

    explosionField = new ExplosionField(this);
    // 绑定哪个控件哪个控件就有效果,如果需要整个 layout,只要绑定根布局的 id 即可
    explosionField.addListener(findViewById(R.id.iv_round));
  }
}
在 xml 中我们什么也不用做,好的,让我们来运行一下
好的,本篇博客也到此结束了,喜欢的点个赞

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

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

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

发布评论

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