# Android特效專輯(五)——自定義圓形頭像和仿MIUI卸載動畫—粒子爆炸
> 好的,各位親愛的朋友,今天講的特效還是比較炫的,首先,我們會講一個自定義圓形的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](http://download.csdn.net/detail/qq_26787115/9409139)
- 前言
- (一)——水波紋過渡特效(首頁)
- (二)——ViewPager渲染背景顏色漸變(引導頁)
- (三)——自定義不一樣的Toast
- (四)——APP主頁框架TabHost綁定ViewPager的替換者TabLayout
- (五)——自定義圓形頭像和仿MIUI卸載動畫—粒子爆炸
- (六)——仿QQ聊天撒花特效,無形裝逼,最為致命
- (七)——飛機升空特效,一鍵清理緩存,靈活運用動畫會有不一樣的感受
- (八)——實現心型起泡飛舞的特效,讓你的APP瞬間暖心
- (九)——仿微信雷達搜索好友特效,邏輯清晰實現簡單
- (十)——點擊水波紋效果實現,邏輯清晰實現簡單
- (十一)——仿水波紋流量球進度條控制器,實現高端大氣的主流特效
- (十二)——仿支付寶咻一咻功能實現波紋擴散特效,精細小巧的View