小米平板卸载软件的时候,会有一个粉碎的效果,看起来很拉风,GitHub上有一个开源控件可以实现这个效果,我们一起来看看。先来看看效果图:

看起来不错吧,那我们今天就来详细说说ExplosionField的使用和它内部的实现原理吧。

1.获得一个ExplosionField

ExplosionField是GitHub上的一个开源项目,我们直接在GitHub上下载就可以了。

下载地址https://github.com/tyrantgit/ExplosionField

下载好之后我们直接将tyrantgit文件夹拷贝到我们的项目中就可以使用了,该文件夹的位置在:ExplosionField-master\ExplosionField-master\explosionfield\src\main\java中。

注意:使用ExplosionField要求我们的jre版本在1.7以上。

2.ExplosionField的使用

布局文件很简单,就是四个ImageView:

<LinearLayout 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"
android:orientation="vertical" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:orientation="horizontal" > <ImageView
android:id="@+id/iv1"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_weight="1"
android:onClick="onClick"
android:src="@drawable/p1" /> <ImageView
android:id="@+id/iv2"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_weight="1"
android:onClick="onClick"
android:src="@drawable/p2" />
</LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal" > <ImageView
android:id="@+id/iv3"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_weight="1"
android:onClick="onClick"
android:src="@drawable/p3" /> <ImageView
android:id="@+id/iv4"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_weight="1"
android:onClick="onClick"
android:src="@drawable/p4" />
</LinearLayout> </LinearLayout>

在MainActivity中,我们拿到一个ExplosionField的实例,然后通过执行它的explode方法就可以实现View的粉碎效果了。

public class MainActivity extends Activity {

	private ExplosionField explosionField;
private ImageView iv1, iv2, iv3, iv4; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv1 = (ImageView) this.findViewById(R.id.iv1);
iv2 = (ImageView) this.findViewById(R.id.iv2);
iv3 = (ImageView) this.findViewById(R.id.iv3);
iv4 = (ImageView) this.findViewById(R.id.iv4);
explosionField = ExplosionField.attach2Window(this);
} public void onClick(View v) {
switch (v.getId()) {
case R.id.iv1:
explosionField.explode(iv1);
iv1.setVisibility(View.INVISIBLE);
break;
case R.id.iv2:
explosionField.explode(iv2);
iv2.setVisibility(View.INVISIBLE);
break;
case R.id.iv3:
explosionField.explode(iv3);
iv3.setVisibility(View.INVISIBLE);
break;
case R.id.iv4:
explosionField.explode(iv4);
iv4.setVisibility(View.INVISIBLE);
break;
}
}
}

使用起来是如此的方便,这里我要特别说一句,为什么粉碎完成之后还要将控件隐藏,这是因为View粉碎之后,我们就看不到了,但是实际上它还在页面上,如果这个View本身有点击事件,那么这个时候你点击它粉碎前的位置,点击事件还是能够响应,将控件隐藏之后就可以避免这个问题了。说了这么多,我们来看看这个效果究竟是怎么实现的。

3.源码解读

通过前面两步,我们已经知道,跟这个效果有关的就是三个类,那么我们这里就从explosionField = ExplosionField.attach2Window(this);这行代码所执行的attach2Window(this);方法看起:

	public static ExplosionField attach2Window(Activity activity) {
ViewGroup rootView = (ViewGroup) activity
.findViewById(Window.ID_ANDROID_CONTENT);
ExplosionField explosionField = new ExplosionField(activity);
rootView.addView(explosionField, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
return explosionField;
}

首先我们要知道,ExplosionField继承自View,它本身就是一个View,那么在attach2Window方法中,我们先拿到一个rootView,这个rootView其实就是我们setContentView(R.layout.activity_main);所设置的View,然后我们new一个ExplosionField并把它添加到rootView当中,并把它的宽高都设置为MATCH_PARENT,这样就可以保证View爆炸产生的碎片可以被绘制在整个Activity区域。

好了,拿到一个ExplosionField对象之后,我们就可以粉碎一个View了,粉碎View执行的方法是explode,这个方法需要我们把要粉碎的View作为一个参数传入进去,我们看看这个方法:

	public void explode(final View view) {
Rect r = new Rect();
view.getGlobalVisibleRect(r);
int[] location = new int[2];
getLocationOnScreen(location);
r.offset(-location[0], -location[1]);
r.inset(-mExpandInset[0], -mExpandInset[1]);
int startDelay = 100;
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { Random random = new Random(); @Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setTranslationX((random.nextFloat() - 0.5f)
* view.getWidth() * 0.05f);
view.setTranslationY((random.nextFloat() - 0.5f)
* view.getHeight() * 0.05f); }
});
animator.start();
view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f)
.scaleY(0f).alpha(0f).start();
explode(Utils.createBitmapFromView(view), r, startDelay,
ExplosionAnimator.DEFAULT_DURATION);
}

这里的代码我们可以分为4部分来理解:

第一部分:2-7行

这一段代码我们主要是用来获取要粉碎View的一个可视区域,拿到这个Rect之后,我们再拿到ExplosionField在当前页面中的坐标,然后根据这个坐标对Rect进行平移,平移之后就可以让粉碎的效果在ExplosionField中显示,最后我们还要将Rect的区域扩大一下,默认情况下,横向扩大64dp,纵向扩大64dp,mExpandInset的初始化在ExplosionField的构造方法中调用,最终拿到的Rect我们会在第四部分使用。

第二部分:8-23行

这一段代码主要是是完成粉碎前的振动效果,一个View要被粉碎了,吓得它先打个哆嗦。这里的实现还是比较简单的,先是生成一个随机数,根据这个随机数算出View在X轴和Y轴平移的距离,反复的平移就形成了振动效果。

第三部分:24-25行

这一段代码是将View隐藏,隐藏的方式就是让scaleX和scaleY、alpha最终都变为0f。

第四部分:26-27行

这一段代码是粉碎View的核心代码,我们下面分析。
好了,这一段代码算是一个准备工作,下面我们看看上面第四部分提到的这个explode的重载方法,这里一共需要四个参数,第一个参数是一个Bitmap,这个Bitmap根据我们传入的View创建,第二个参数就是我们上面第一部分提到的那个Rect,第三个参数是一个启动延迟时间,第四个参数是动画默认的执行时间,四个参数中,后面两个都是固定值,第二个参数我们上文已经说过,这里我们主要看看第一个参数的获得:

    public static Bitmap createBitmapFromView(View view) {
if (view instanceof ImageView) {
Drawable drawable = ((ImageView) view).getDrawable();
if (drawable != null && drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
}
view.clearFocus();
Bitmap bitmap = createBitmapSafely(view.getWidth(),
view.getHeight(), Bitmap.Config.ARGB_8888, 1);
if (bitmap != null) {
synchronized (sCanvas) {
Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
view.draw(canvas);
canvas.setBitmap(null);
}
}
return bitmap;
} public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
try {
return Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
e.printStackTrace();
if (retryCount > 0) {
System.gc();
return createBitmapSafely(width, height, config, retryCount - 1);
}
return null;
}
}

根据我们传入的View,我们得到一个Bitmap,如果这个View本身就是一个ImageView,那么我们会比较容易获得一个Bitmap,如果这个View不是ImageView,那么我们根据这个View的大小创建一个空的Bitmap,在创建的过程中,如果发生了OOM,我们会尝试执行一次System.gc,然后重新创建,如果还创建失败,那么就会返回一个null。拿到Bitmap之后,我们把这个Bitmap设置为一个Canvas的底布,然后把View绘制在Canvas上,最后再把Canvas的底布设置为null,同时将Bitmap返回。

经过上面的步骤,我们就可以拿到一个根据我们要粉碎的View创建的bitmap,然后我们就来看看explode的重载方法:

	public void explode(Bitmap bitmap, Rect bound, long startDelay,
long duration) {
final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap,
bound);
explosion.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mExplosions.remove(animation);
}
});
explosion.setStartDelay(startDelay);
explosion.setDuration(duration);
mExplosions.add(explosion);
explosion.start();
}

到了这里,我们先来看看ExplosionAnimator这个类,这个类继承自ValueAnimator,我们来看看ExplosionField的构造方法:

	public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {
mPaint = new Paint();
mBound = new Rect(bound);
int partLen = 15;
mParticles = new Particle[partLen * partLen];
Random random = new Random(System.currentTimeMillis());
int w = bitmap.getWidth() / (partLen + 2);
int h = bitmap.getHeight() / (partLen + 2);
for (int i = 0; i < partLen; i++) {
for (int j = 0; j < partLen; j++) {
mParticles[(i * partLen) + j] = generateParticle(
bitmap.getPixel((j + 1) * w, (i + 1) * h), random);
}
}
mContainer = container;
setFloatValues(0f, END_VALUE);
setInterpolator(DEFAULT_INTERPOLATOR);
setDuration(DEFAULT_DURATION);
}

在这里我们看到了View被粉碎的方式,一个View一共被分为15×15个Particle,通过generateParticle方法可以获得这一个一个的Particle,每一个Particle的颜色就是bitmap上对应的颜色,这些Particle存在一个mParticles数组中,container就是我们传进来的ExplosionField,它赋给了mContainer。我们注意到,ExplosionField重写了start方法:

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

在start方法中调用了ExplosionField的draw方法,那我们看看ExplosionField方法中的draw方法:

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

好啊,这里竟然又调用了ExplosionAnimator的draw方法,那我们再看看ExplosionAnimator中的draw方法:

	public boolean draw(Canvas canvas) {
if (!isStarted()) {
return false;
}
for (Particle particle : mParticles) {
particle.advance((float) getAnimatedValue());
if (particle.alpha > 0f) {
mPaint.setColor(particle.color);
mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
canvas.drawCircle(particle.cx, particle.cy, particle.radius,
mPaint);
}
}
mContainer.invalidate();
return true;
}

在ExplosionAnimator的draw方法中,我们看到,在for循环中会将我们的15×15个Parcicle都画出来,然后又回去调用ExplosionField中的draw方法,如此循环往复互相之间不断调用,直到Particle的alpha属性为0时停止调用。

看到这里,我们对ExplosionField有了一个基本的了解,这个时候我们再回过头看ExplosionField中的explode方法,我们注意到有一个List集合专门用来存放ExplosionAnimator,这是由于我们的一个页面上可能同时有多个View被粉碎,每一个被粉碎的View对应一个单独的ExplosionAnimator,当该动画执行完了之后,就把它从List集合中移除。

关于粉碎View的事就说这么多。

Demo下载https://github.com/lenve/ExplosionFieldTest

详解ExplosionField的使用,实现View的粉碎效果的更多相关文章

  1. 详解iOS开发之自定义View

    iOS开发之自定义View是本文要将介绍的内容,iOS SDK中的View是UIView,我们可以很方便的自定义一个View.创建一个 Window-based Application程序,在其中添加 ...

  2. Android开发:文本控件详解——TextView(二)文字跑马灯效果实现

    一.需要使用的属性: 1.android:ellipsize 作用:若文字过长,控制该控件如何显示. 对于同样的文字“Android开发:文本控件详解——TextView(二)文字跑马灯效果实现”,不 ...

  3. 详解实现Android中实现View滑动的几种方式

    注: 本文提到的所有三种滑动方式的完整demo:ScrollDemo 1. 关于View我们需要知道的 (1)什么是View? Android中的View类是所有UI控件的基类(Base class) ...

  4. Django 详解&lt;二&gt; 之url和view

    Django URL(路由系统) RL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表:你就是以这种方式告诉Django,对 ...

  5. Unity 坐标 转换 详解 World世界坐标 Screen屏幕坐标 View视口坐标 GUI坐标 NGUI坐标 localPosition相对父级坐标

    在制作游戏中我们经常会遇到这样一个需求: 在人物模型的上面显示 名字.称号 一类的文字或者图片 如下图 人物模型属于是Camera1   UI Title信息属于NGUI Camera2 如下图 这时 ...

  6. SwipeListView 详解 实现微信,QQ等滑动删除效果

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/28508769 今天看别人项目,看到别人使用了SwipeListView,Goog ...

  7. Android listview 侧滑 SwipeListView 详解 实现微信,QQ等滑动删除效果

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/28508769 今天看别人项目,看到别人使用了SwipeListView,Goog ...

  8. 自定义控件详解(四):Paint 画笔路径效果

    Paint  画笔 ,即用来绘制图形的"笔" 前面我们知道了Paint的一些基本用法: paint.setAntiAlias(true);//抗锯齿功能 paint.setColo ...

  9. 局域网内远程连接OPC配置方法详解

    局域网内远程连接OPC配置方法详解 https://wenku.baidu.com/view/20fb8ea6d1d233d4b14e852458fb770bf78a3bcc.html   OPC服务 ...

随机推荐

  1. Java—网络技术

    1  TCP Sockets基础 Sockets是一个编程抽象概念,它是网络上与另一个应用程序通信连接的句柄.Sockets编程将用户代码与TCP/IP协议堆栈的底层实现隔离开,允许用户灵活地实现自己 ...

  2. information_schema系列十二

    1: INNODB_SYS_VIRTUAL 表存储的是INNODB表的虚拟列的信息,当然这个还是比较简单的,我们直接通过SHOW CREATE TABLE 或者DESC TABLE就能看得到. Col ...

  3. Spring REST实践之HATEOAS

    HATEOAS HATEOAS(The Hypermedia As The Engine Of Application Statue)是REST架构的主要约束."hepermedia&quo ...

  4. JavaScript小结

    语法小结 /** * Created by M.C on 2017/5/26. */ /*弹框*/ //var message = "Hello world"; //alert(m ...

  5. sqlserver 多行转一行

    sql 例子: SELECT STUFF((SELECT ',' + CONVERT(VARCHAR, b.SCsinfoSourceId) FROM PZDataCsinfo b WHERE b.D ...

  6. Linux下简单的缓冲区溢出

    缓冲区溢出是什么? 科班出身,或者学过汇编的应该知道,当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被“撑爆”,从而覆盖了相邻内存区域的数据 成功修改内存数据,可造成进程劫持 ...

  7. 用交叉验证改善模型的预测表现-着重k重交叉验证

    机器学习技术在应用之前使用“训练+检验”的模式(通常被称作”交叉验证“). 预测模型为何无法保持稳定? 让我们通过以下几幅图来理解这个问题: 此处我们试图找到尺寸(size)和价格(price)的关系 ...

  8. 从swap说引用

    C++的引用类型是个很奇妙的存在,比如下面这个例子: #include<iostream> using namespace std; void swap(int& a, int&a ...

  9. DHCP 服务测试

    DHCP三个端口: 服务端:UDP 67 客户端:UDP 68 DHCPv6 客户端:UDP 546,这是需要特别开启的 DHCP failover 服务,用来做双机热备的. 实验一.DHCP服务器基 ...

  10. 工作常用shell集合

    <1>日志回滚案例======>[root@localhost test]# cat hbase.sh hbase_rotate_log ()       {    log=$1;  ...