Android特效专辑(五)——自定义圆形头像和仿MIUI卸载动画—粒子爆炸
2023-09-14 08:59:39 时间
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);
}
imagecontrol:border_inside_color="#bc0978" imagecontrol:border_outside_color="#ba3456" imagecontrol:border_thickness="1dp" /
public static final float DENSITY = Resources.getSystem().getDisplayMetrics().density; public static int dp2px(int dp) { return Math.round(dp * DENSITY);
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());
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();
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;
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));
在eclipse的安装文件夹下Features文件夹中删除以com.android开头的文件夹 同时也要删除plugin目录下面的有关com.android的jar包等相关内容。删除之前请做好备份! 最简单的,就是把已安装的整个ecl
imagecontrol:border_inside_color="#bc0978" imagecontrol:border_outside_color="#ba3456" imagecontrol:border_thickness="1dp" /
好的,让我们运行下吧
public static final float DENSITY = Resources.getSystem().getDisplayMetrics().density; public static int dp2px(int dp) { return Math.round(dp * DENSITY);
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());
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();
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;
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中我们什么也不用做,好的,让我们来运行一下
好的,本篇博客也到此结束了,喜欢的点个赞
在eclipse的安装文件夹下Features文件夹中删除以com.android开头的文件夹 同时也要删除plugin目录下面的有关com.android的jar包等相关内容。删除之前请做好备份! 最简单的,就是把已安装的整个ecl
相关文章
- 深入 Android 系统 - Android 的 JNI
- android flash路径动画,Flash制作沿着路径的动画
- Android开机动画bootanimation
- android+制作开机动画,Android 开机动画制作详解
- 74款android开机动画,修改Android系统开机动画
- android签名命令行,Android系统签名位置及命令
- android短信验证码代码,Android短信验证码自动填写实现代码
- android系统中toast是什么_Android个人资料简单布局
- android toast 自定义时间,Android 自定义 Toast 显示时间「建议收藏」
- android触摸屏事件,Android Touch事件分析
- 微信Android模块化架构重构实践
- Android TransitionDrawable:过渡动画Drawable详解手机开发
- [android] 手机卫士界面切换动画详解手机开发
- [android] 内容观察者详解手机开发
- Android 深入ViewPager补间动画,实现类京东商城首页广告Banner切换效果详解手机开发
- android Universal Image Loader for Android 说明文档 (1)详解手机开发
- Android系统自带样式(android:theme)
- Android开发之图形图像与动画(二)Animation实现图像的渐变/缩放/位移/旋转
- Android开机广播的使用及配置
- Android学习笔记之SharedPreference