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

最后更新于:2022-04-01 09:36:17

# Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸 > 好的,各位亲爱的朋友,今天讲的特效还是比较炫的,首先,我们会讲一个自定义圆形的imageView,接着,我们会来实现粒子爆炸的特效,按照国际惯例,无图无真相的没这个效果也是模仿大神的,现在应用在了我的《Only》上 ### 截图 ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c174d678.jpg "") ~~~ 好的,我们新建一个工程——AnimView,我们要用到的图片 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c1792fd5.jpg "") ### 一.自定义圆形头像—— ~~~ 直接开写了,要实现的东西都在注释上了 ~~~ ### 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" /> ~~~ ~~~ 好的,让我们运行下吧 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c17af7f9.jpg "") ### 二.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中我们什么也不用做,好的,让我们来运行一下 ~~~ ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-24_56cd2c17de8ca.jpg "") ~~~ 好的,本篇博客也到此结束了,喜欢的点个赞 ~~~ ### Demo下载:[http://download.csdn.net/detail/qq_26787115/9409139](http://download.csdn.net/detail/qq_26787115/9409139)
';