自定义粒子破碎效果图:
本文内容仿至阿曌博客,并在此基础上稍作修改,纯粹学习目的,更多详情请查看原文
个人觉得粒子破碎的核心就是怎样把一张图片分解成粒子,并让其呈现破碎四散的效果,下面说说实现过程:
- 我们需要拿到图片对象,因为我们是要让整个view都呈现破碎的效果,那么就直接把view复制成一张图片
/**
* 把当前view复制成一张图片,并绘制出来
* @param view
* @return
*/
private Bitmap CopyView(View view) {
Bitmap bitmap=Bitmap.createBitmap(view.getMeasuredWidth(),view.getMeasuredHeight(),Config.ARGB_8888);
canvas.setBitmap(bitmap);
view.draw(canvas);
return bitmap;
}
2 . 拿到图片后,把图片裁剪成若干个粒子,每个粒子都保存相应的信息(中心点,半径,颜色,透明度),并把粒子保存到一个二维数组里
/**
* 把当前复制过来的view图片裁剪成颗粒
* @param copyView
* @param rect 包裹view的矩形
*/
private Granule[][] DivisionView(Bitmap copyView, Rect rect) {
int color;
Point point;
//当前view宽高可以裁剪成多少粒子
int countW=rect.width()/Granule.GranuleWH;
int countH=rect.height()/Granule.GranuleWH;
int bitmap_part_w = copyView.getWidth() / countW;
int bitmap_part_h = copyView.getHeight() / countH;
//二位数组包含该view图片所有的粒子信息
Granule[][]granules=new Granule[countH][countW];//先行后列
for(int row=0;row<countH;row++){//行
for(int column=0;column<countW;column++){//列
//获取每个粒子的颜色
color=copyView.getPixel(column*bitmap_part_w,row*bitmap_part_h);//竖
//记录每个点
point=new Point(column,row);
//记录粒子信息,并返回一个粒子对象
granules[row][column]=Granule.SetGranuleAttribute(color, rect, point);
}
}
return granules;
}
3 . 上面我们已经把图片裁剪成了粒子,呈现破碎效果就只需要不断的更改每个粒子的信息(中心点,半径,颜色,透明度)就可以了,修改粒子信息需要一个动态的值,最合适的就是通过动画来实现,通过继承属性 ValueAnimator,设置setFloatValues(0f,1f);表示从0平滑过渡到1,然后我们通过getAnimatedValue()拿到实时的过渡值,通过这个值来修改信息,即可实现破碎的效果;
public void ondraw(Canvas canvas){
//判断动画是否开始
if(!isStarted()){
return;
}
for(Granule[]granule:granules){
for(Granule g:granule){
//修改粒子信息
g.ChangeGranuleAttribute((float) getAnimatedValue());
//把粒子修改成圆形绘制出来
paint.setColor(g.color);
// paint.setAlpha((int) (255 * g.alpha)); //不会出现透明度的渐变效果
paint.setAlpha((int) (Color.alpha(g.color) * g.alpha));
canvas.drawCircle(g.centerX,g.centerY, g.radius,paint);
}
}
view.invalidate();
}
/**
* 修改粒子属性
* @param factor
*/
public void ChangeGranuleAttribute(float factor){
//中心点在原view矩形内随机
centerX=centerX+factor*random.nextInt(rect.width())* (random.nextFloat() - 0.5f);//nextFloat() 0-1内随机
centerY=centerY+factor*random.nextInt(rect.height())* (random.nextFloat() - 0.5f);
//半径慢慢变小
radius=radius-factor*random.nextInt(2);
//透明度随之变低
alpha = (1f - factor) * (1 + random.nextFloat());
}
最关键的部分上面已经介绍完了,下面说说我在实现效果过程中觉得有必要记录的知识点:
继承ViewGroup 的view通常情况下不会调用onDraw方法,原因, 解决办法:
1,在构造函数里面,给其设置一个颜色,如#00000000。
if(getBackground()==null){
setBackgroundColor(Color.WHITE);
}2,在构造函数里面,调用setWillNotDraw(false),去掉其WILL_NOT_DRAW flag。
属性动画ValueAnimator的使用
源代码:
GranuleExplodeView:
import java.util.ArrayList;
import java.util.List;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
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.graphics.Bitmap.Config;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.LinearLayout;
import android.view.View.OnClickListener;;
public class GranuleExplodeView extends LinearLayout implements OnClickListener, OnItemClickListener{
private Canvas canvas;
/** 包含了粒子信息的粒子集合*/
private Granule [][]granules;
/** 动画集合*/
private List<GranuleAnimation> animations;
public GranuleExplodeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public GranuleExplodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GranuleExplodeView(Context context) {
super(context);
init();
}
private void init() {
// if(getBackground()==null){
// setBackgroundColor(Color.WHITE);
// }
setWillNotDraw(false);
setOrientation(VERTICAL);
canvas=new Canvas();
animations=new ArrayList<GranuleAnimation>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
MeasureChild(this);
}
private void MeasureChild(ViewGroup viewGroup) {
int childcount=viewGroup.getChildCount();
for(int i=0;i<childcount;i++){
View view=viewGroup.getChildAt(i);
if(view instanceof ViewGroup){
if(view instanceof AbsListView){
((AbsListView) view).setOnItemClickListener(this);
}else {
MeasureChild(((ViewGroup) view));
}
}else {
//给每个子view都添加监听
view.setClickable(true);
view.setOnClickListener(this);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for(GranuleAnimation animation:animations){
animation.ondraw(canvas);
}
}
@Override
public void onClick(View v) {
CopyDivisionView(v);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
CopyDivisionView(view);
}
/**
* 把view复制并分割成若干颗粒
* @param view
*/
private void CopyDivisionView(final View view) {
Rect rect=new Rect();
//http://www.cnblogs.com/ai-developers/p/4413585.html
//getGlobalVisibleRect方法的作用是获取视图在屏幕坐标中的可视区域
//getLocalVisibleRect的作用是获取视图本身可见的坐标区域,坐标以自己的左上角为原点(0,0)
view.getGlobalVisibleRect(rect);
//把view图片裁剪,记录每个粒子信息 并保存在一个二维数组里
granules=DivisionView(CopyView(view),rect);
//开启一个动画
GranuleAnimation granuleAnimation=new GranuleAnimation(this, granules);
animations.add(granuleAnimation);
granuleAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
//让原有的view隐藏起来
view.animate().alpha(0f).setDuration(5).start();
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束的时候可以去刷新,这里就直接还原了
view.animate().alpha(1f).setDuration(150).start();
//移除动画
animations.remove(animation);
animation=null;
}
});
granuleAnimation.start();
}
/**
* 把当前复制过来的view图片裁剪成颗粒
* @param copyView
* @param rect 包裹view的矩形
*/
private Granule[][] DivisionView(Bitmap copyView, Rect rect) {
int color;
Point point;
//当前view宽高可以裁剪成多少粒子
int countW=rect.width()/Granule.GranuleWH;
int countH=rect.height()/Granule.GranuleWH;
int bitmap_part_w = copyView.getWidth() / countW;
int bitmap_part_h = copyView.getHeight() / countH;
//二位数组包含该view图片所有的粒子信息
Granule[][]granules=new Granule[countH][countW];//先行后列
for(int row=0;row<countH;row++){//行
for(int column=0;column<countW;column++){//列
//获取每个粒子的颜色
color=copyView.getPixel(column*bitmap_part_w,row*bitmap_part_h);//竖
//记录每个点
point=new Point(column,row);
//记录粒子信息,并返回一个粒子对象
granules[row][column]=Granule.SetGranuleAttribute(color, rect, point);
}
}
return granules;
}
/**
* 把当前view复制成一张图片,并绘制出来
* @param view
* @return
*/
private Bitmap CopyView(View view) {
Bitmap bitmap=Bitmap.createBitmap(view.getMeasuredWidth(),view.getMeasuredHeight(),Config.ARGB_8888);
canvas.setBitmap(bitmap);
view.draw(canvas);
return bitmap;
}
class GranuleAnimation extends ValueAnimator{
private View view;
private Granule[][]granules;
private Paint paint;
public GranuleAnimation(View view,Granule[][]granules) {
paint=new Paint();
this.view=view;
this.granules=granules;
setFloatValues(0f,1f);
setDuration(1500);
}
@Override
public void start() {
super.start();
//会触发ondraw
view.invalidate();
}
public void ondraw(Canvas canvas){
//判断动画是否开始
if(!isStarted()){
return;
}
for(Granule[]granule:granules){
for(Granule g:granule){
//修改粒子信息
g.ChangeGranuleAttribute((float) getAnimatedValue());
//把粒子修改成圆形绘制出来
paint.setColor(g.color);
// paint.setAlpha((int) (255 * g.alpha)); //不会出现透明度的渐变效果
paint.setAlpha((int) (Color.alpha(g.color) * g.alpha));
canvas.drawCircle(g.centerX,g.centerY, g.radius,paint);
}
}
view.invalidate();
}
}
}
Granule:
import java.util.Random;
import android.graphics.Point;
import android.graphics.Rect;
/**
* 粒子颗粒(圆形)
*/
public class Granule {
/** 默认粒子的宽高*/
static final int GranuleWH=8;
/** 每粒颗粒的中心点X*/
float centerX;
/** 每粒颗粒的中心点Y*/
float centerY;
/** 每粒颗粒的透明度,默认为1f,会随时间变为0f , 0为完全透明,255为全显示*/
float alpha=1f;
/** 每粒颗粒的圆半径*/
float radius;
/** 每粒颗粒的包裹矩形*/
Rect rect;
/** 每粒颗粒的颜色*/
int color;
/** 每粒颗粒扩散的随机值*/
Random random=new Random();
/**
* 设置每个粒子的属性并返回该粒子对象
* @param color
* @param rect
* @param point
*/
public static Granule SetGranuleAttribute(int color,Rect rect,Point point){
Granule granule=new Granule();
granule.centerX=rect.left+GranuleWH*point.x;
granule.centerY=rect.top+GranuleWH*point.y;
granule.alpha=1f;
granule.color=color;
granule.radius=GranuleWH;
granule.rect=rect;
return granule;
}
/**
* 修改粒子属性
* @param factor
*/
public void ChangeGranuleAttribute(float factor){
//中心点在原view矩形内随机
centerX=centerX+factor*random.nextInt(rect.width())* (random.nextFloat() - 0.5f);//nextFloat() 0-1内随机
centerY=centerY+factor*random.nextInt(rect.height())* (random.nextFloat() - 0.5f);
//半径慢慢变小
radius=radius-factor*random.nextInt(2);
//透明度随之变低
alpha = (1f - factor) * (1 + random.nextFloat());
}
}
GranuleExplodeActivity:
import java.util.ArrayList;
import java.util.HashMap;
import com.example.mytoolutils.R;
import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
import android.widget.SimpleAdapter;
public class GranuleExplodeActivity extends Activity {
//生成动态数组,并且转入数据
private ArrayList<HashMap<String, Object>> lstImageItem = new ArrayList<HashMap<String, Object>>();
private SimpleAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_granule_explode);
SetData();
GridView gridView=(GridView) findViewById(R.id.gridview);
adapter=new SimpleAdapter(this,lstImageItem, R.layout.activity_gridview_item,
new String[]{"ItemImage","ItemText"},
new int[] {R.id.imagviewgridview,R.id.textviewgridview});
gridView.setAdapter(adapter);
}
private void SetData() {
for(int i=0;i<20;i++)
{
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("ItemImage", R.drawable.yueyunp);//添加图像资源的ID
map.put("ItemText", "NO."+String.valueOf(i));//按序号做ItemText
lstImageItem.add(map);
}
}
}
R.layout.activity_granule_explode:
<com.example.mytoolutils.explode.GranuleExplodeView 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"
tools:context="com.example.mytoolutils.explode.GranuleExplodeActivity" >
<GridView
android:id="@+id/gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:horizontalSpacing="10dp"
android:listSelector="@color/transparent"
android:numColumns="4"
android:padding="10dp"
android:stretchMode="columnWidth"
android:verticalSpacing="10dp" >
</GridView>
</com.example.mytoolutils.explode.GranuleExplodeView>