├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── drawable-xxhdpi │ │ │ ├── p1.webp │ │ │ ├── p2.webp │ │ │ ├── p3.webp │ │ │ ├── p4.webp │ │ │ ├── p5.webp │ │ │ └── p6.webp │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── values │ │ │ ├── styles.xml │ │ │ ├── dimens.xml │ │ │ └── strings.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ ├── menu │ │ │ └── menu_main.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── tyrantgit │ │ └── sample │ │ └── MainActivity.java ├── build.gradle ├── proguard-rules.pro └── app.iml ├── explosionfield ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── tyrantgit │ │ └── explosionfield │ │ ├── Utils.java │ │ ├── ExplosionAnimator.java │ │ ├── ExplosionField.java │ │ └── PieceAnimator.java ├── build.gradle ├── proguard-rules.pro └── explosionfield.iml ├── settings.gradle ├── demo.apk ├── haha.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── local.properties ├── gradle.properties ├── trunk.iml ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /explosionfield/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':explosionfield' 2 | -------------------------------------------------------------------------------- /demo.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/demo.apk -------------------------------------------------------------------------------- /haha.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/haha.gif -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/p1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/drawable-xxhdpi/p1.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/p2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/drawable-xxhdpi/p2.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/p3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/drawable-xxhdpi/p3.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/p4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/drawable-xxhdpi/p4.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/p5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/drawable-xxhdpi/p5.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/p6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/drawable-xxhdpi/p6.webp -------------------------------------------------------------------------------- /explosionfield/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Confetti 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqwang/Confetti/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /explosionfield/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 23 17:18:18 CST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ConfettiSample 3 | Reset 4 | Explosion mode 5 | Confetti mode 6 | Confetti gravity 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | #Fri Dec 11 17:03:59 CST 2015 11 | sdk.dir=/Users/wanghuaiqing/Library/Android/sdk 12 | -------------------------------------------------------------------------------- /explosionfield/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | } 24 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "23.0.2" 6 | 7 | defaultConfig { 8 | applicationId "tyrantgit.sample" 9 | minSdkVersion 14 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile project(":explosionfield") 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\develop\AndroidSdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /explosionfield/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\develop\AndroidSdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 12 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /trunk.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Confetti 2 | An implementation of transition effect in Keynote. See ![demo.apk](demo.apk) 3 | 4 | ![](https://img.shields.io/hexpm/l/plug.svg) 5 | ![](https://img.shields.io/badge/Platform-Android-brightgreen.svg) 6 | ![](https://img.shields.io/badge/Android-CustomView-blue.svg) 7 | 8 | ![Confetti.gif](haha.gif) 9 | 10 | Notice: most code base on ExplosionFiled. 11 | 12 | ## Getting started 13 | 14 | ```java 15 | ExplosionField explosionField = ...; 16 | explosionField.explode(view); 17 | ``` 18 | 19 | --- 20 | ## License 21 | 22 | This library is licensed under the [Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 23 | 24 | Copyright (C) 2015 [hqwang](https://github.com/hqwang/Confetti) 25 | 26 | Licensed under the Apache License, Version 2.0 (the "License"); 27 | you may not use this file except in compliance with the License. 28 | You may obtain a copy of the License at 29 | 30 | http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | Unless required by applicable law or agreed to in writing, software 33 | distributed under the License is distributed on an "AS IS" BASIS, 34 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | See the License for the specific language governing permissions and 36 | limitations under the License. -------------------------------------------------------------------------------- /app/src/main/java/tyrantgit/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package tyrantgit.sample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.view.View; 8 | 9 | import tyrantgit.explosionfield.ExplosionField; 10 | 11 | 12 | public class MainActivity extends Activity { 13 | 14 | private ExplosionField mExplosionField; 15 | private int mode = ExplosionField.MODE_CONFETTI; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | mExplosionField = ExplosionField.attach2Window(this); 22 | mExplosionField.addListener(findViewById(R.id.root)); 23 | } 24 | 25 | @Override 26 | public boolean onCreateOptionsMenu(Menu menu) { 27 | getMenuInflater().inflate(R.menu.menu_main, menu); 28 | return true; 29 | } 30 | 31 | @Override 32 | public boolean onOptionsItemSelected(MenuItem item) { 33 | boolean consumed = false; 34 | int flag = 0; 35 | 36 | switch (item.getItemId()) { 37 | case R.id.action_reset: 38 | consumed = true; 39 | break; 40 | case R.id.action_mode_explosion: 41 | consumed = true; 42 | mode = ExplosionField.MODE_EXPLOSION; 43 | break; 44 | case R.id.action_mode_confetti: 45 | consumed = true; 46 | mode = ExplosionField.MODE_CONFETTI; 47 | break; 48 | case R.id.action_mode_confetti_gravity: 49 | consumed = true; 50 | mode = ExplosionField.MODE_CONFETTI; 51 | flag = ExplosionField.FLAG_SUPPORT_GRAVITY; 52 | break; 53 | } 54 | 55 | if (consumed) { 56 | View root = findViewById(R.id.root); 57 | mExplosionField.reset(root); 58 | mExplosionField.addListener(root); 59 | mExplosionField.setMode(mode); 60 | if (flag > 0) { 61 | mExplosionField.setFlag(flag); 62 | } 63 | mExplosionField.clear(); 64 | return true; 65 | } 66 | return super.onOptionsItemSelected(item); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 20 | 21 | 27 | 28 | 29 | 35 | 36 | 41 | 42 | 43 | 44 | 49 | 50 | 56 | 57 | 62 | 63 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /explosionfield/src/main/java/tyrantgit/explosionfield/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package tyrantgit.explosionfield; 17 | 18 | 19 | import android.content.res.Resources; 20 | import android.graphics.Bitmap; 21 | import android.graphics.Canvas; 22 | import android.graphics.drawable.BitmapDrawable; 23 | import android.graphics.drawable.Drawable; 24 | import android.os.Environment; 25 | import android.text.TextUtils; 26 | import android.view.View; 27 | import android.widget.ImageView; 28 | 29 | import java.io.BufferedOutputStream; 30 | import java.io.File; 31 | import java.io.FileNotFoundException; 32 | import java.io.FileOutputStream; 33 | import java.io.IOException; 34 | 35 | public class Utils { 36 | 37 | private Utils() { 38 | } 39 | 40 | private static final float DENSITY = Resources.getSystem().getDisplayMetrics().density; 41 | private static final Canvas sCanvas = new Canvas(); 42 | 43 | public static int dp2Px(int dp) { 44 | return Math.round(dp * DENSITY); 45 | } 46 | 47 | public static Bitmap createBitmapFromView(View view) { 48 | if (view instanceof ImageView) { 49 | Drawable drawable = ((ImageView) view).getDrawable(); 50 | if (drawable != null && drawable instanceof BitmapDrawable) { 51 | return ((BitmapDrawable) drawable).getBitmap(); 52 | } 53 | } 54 | view.clearFocus(); 55 | Bitmap bitmap = createBitmapSafely(view.getWidth(), 56 | view.getHeight(), Bitmap.Config.ARGB_8888, 1); 57 | if (bitmap != null) { 58 | synchronized (sCanvas) { 59 | Canvas canvas = sCanvas; 60 | canvas.setBitmap(bitmap); 61 | view.draw(canvas); 62 | canvas.setBitmap(null); 63 | } 64 | } 65 | 66 | return bitmap; 67 | } 68 | 69 | public static String save(Bitmap bitmap, String name) { 70 | if (bitmap == null || TextUtils.isEmpty(name)) { 71 | return null; 72 | } 73 | 74 | File file = new File(Environment.getExternalStorageDirectory().getPath() + "/" + name); 75 | try { 76 | file.createNewFile(); 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | } 80 | 81 | BufferedOutputStream bos = null; 82 | try { 83 | bos = new BufferedOutputStream(new FileOutputStream(file)); 84 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos); 85 | bos.flush(); 86 | return file.getAbsolutePath(); 87 | } catch (FileNotFoundException e) { 88 | e.printStackTrace(); 89 | } catch (IOException e) { 90 | e.printStackTrace(); 91 | } finally { 92 | if (bos != null) { 93 | try { 94 | bos.close(); 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | return null; 100 | } 101 | } 102 | 103 | public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) { 104 | try { 105 | return Bitmap.createBitmap(width, height, config); 106 | } catch (OutOfMemoryError e) { 107 | e.printStackTrace(); 108 | if (retryCount > 0) { 109 | System.gc(); 110 | return createBitmapSafely(width, height, config, retryCount - 1); 111 | } 112 | return null; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /explosionfield/explosionfield.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /explosionfield/src/main/java/tyrantgit/explosionfield/ExplosionAnimator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package tyrantgit.explosionfield; 17 | 18 | import android.animation.ValueAnimator; 19 | import android.graphics.Bitmap; 20 | import android.graphics.Canvas; 21 | import android.graphics.Color; 22 | import android.graphics.Paint; 23 | import android.graphics.Rect; 24 | import android.view.View; 25 | import android.view.animation.AccelerateInterpolator; 26 | import android.view.animation.Interpolator; 27 | 28 | import java.util.Random; 29 | 30 | public class ExplosionAnimator extends ValueAnimator { 31 | 32 | private static long DEFAULT_DURATION = 0x400; 33 | private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(0.6f); 34 | private static final float END_VALUE = 1.4f; 35 | private static final float X = Utils.dp2Px(5); 36 | private static final float Y = Utils.dp2Px(20); 37 | private static final float V = Utils.dp2Px(2); 38 | private static final float W = Utils.dp2Px(1); 39 | 40 | private Paint mPaint; 41 | private Particle[] mParticles; 42 | private Rect mBound; 43 | private View mContainer; 44 | 45 | public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) { 46 | mPaint = new Paint(); 47 | mBound = new Rect(bound); 48 | int partLen = 15; 49 | mParticles = new Particle[partLen * partLen]; 50 | // roll in 0f ~ 1f. 51 | Random random = new Random(System.currentTimeMillis()); 52 | int w = bitmap.getWidth() / (partLen + 2); 53 | int h = bitmap.getHeight() / (partLen + 2); 54 | // mParticles[0, 0], mParticles[partLen+1, partLen+1], mParticles[partLen+2, partLen+2] have been droped. 55 | for (int i = 0; i < partLen; i++) { 56 | for (int j = 0; j < partLen; j++) { 57 | mParticles[(i * partLen) + j] = generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random); 58 | } 59 | } 60 | mContainer = container; 61 | setFloatValues(0f, END_VALUE); 62 | setInterpolator(DEFAULT_INTERPOLATOR); 63 | setDuration(DEFAULT_DURATION); 64 | } 65 | 66 | private Particle generateParticle(int color, Random random) { 67 | Particle particle = new Particle(); 68 | particle.color = color; 69 | particle.radius = V; 70 | if (random.nextFloat() < 0.2f) { 71 | particle.baseRadius = V + ((X - V) * random.nextFloat()); 72 | } else { 73 | particle.baseRadius = W + ((V - W) * random.nextFloat()); 74 | } 75 | float nextFloat = random.nextFloat(); 76 | particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f); 77 | particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat()); 78 | particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f; 79 | float f = nextFloat < 0.2f ? particle.bottom : nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f; 80 | particle.bottom = f; 81 | particle.mag = 4.0f * particle.top / particle.bottom; 82 | particle.neg = (-particle.mag) / particle.bottom; 83 | f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f)); 84 | particle.baseCx = f; 85 | particle.cx = f; 86 | f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f)); 87 | particle.baseCy = f; 88 | particle.cy = f; 89 | 90 | // start k threshold, some is quick, some is slow. 91 | particle.life = 0.14f * random.nextFloat(); 92 | // stop k threshold, some is finish early, some is to the end. 93 | particle.overflow = 0.4f * random.nextFloat(); 94 | particle.alpha = 1f; 95 | return particle; 96 | } 97 | 98 | public boolean draw(Canvas canvas) { 99 | if (!isStarted()) { 100 | return false; 101 | } 102 | for (Particle particle : mParticles) { 103 | particle.advance((float) getAnimatedValue()); 104 | if (particle.alpha > 0f) { 105 | mPaint.setColor(particle.color); 106 | mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha)); 107 | canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint); 108 | } 109 | } 110 | mContainer.invalidate(); 111 | return true; 112 | } 113 | 114 | @Override 115 | public void start() { 116 | super.start(); 117 | mContainer.invalidate(mBound); 118 | } 119 | 120 | private class Particle { 121 | float alpha; 122 | int color; 123 | float cx; 124 | float cy; 125 | float radius; 126 | float baseCx; 127 | float baseCy; 128 | float baseRadius; 129 | float top; 130 | float bottom; 131 | float mag; 132 | float neg; 133 | float life; 134 | float overflow; 135 | 136 | public void advance(float factor) { 137 | float f = 0f; 138 | float normalization = factor / END_VALUE; 139 | if (normalization < life || normalization > 1f - overflow) { 140 | alpha = 0f; 141 | return; 142 | } 143 | // real k 144 | normalization = (normalization - life) / (1f - life - overflow); 145 | // real value 146 | float f2 = normalization * END_VALUE; 147 | // if real k >=0.7f, start dismiss 148 | if (normalization >= 0.7f) { 149 | f = (normalization - 0.7f) / 0.3f; 150 | } 151 | alpha = 1f - f; 152 | 153 | f = bottom * f2; 154 | // normal distribution 155 | cx = baseCx + f; 156 | // parabola's right 157 | cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag; 158 | // 20% bigger 80% smaller 159 | radius = V + (baseRadius - V) * f2; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /explosionfield/src/main/java/tyrantgit/explosionfield/ExplosionField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package tyrantgit.explosionfield; 17 | 18 | import android.animation.Animator; 19 | import android.animation.AnimatorListenerAdapter; 20 | import android.animation.ValueAnimator; 21 | import android.app.Activity; 22 | import android.content.Context; 23 | import android.graphics.Canvas; 24 | import android.graphics.Rect; 25 | import android.util.AttributeSet; 26 | import android.view.View; 27 | import android.view.ViewGroup; 28 | import android.view.Window; 29 | 30 | import java.util.ArrayList; 31 | import java.util.Arrays; 32 | import java.util.List; 33 | import java.util.Random; 34 | 35 | public class ExplosionField extends View { 36 | 37 | private List mExplosions = new ArrayList<>(); 38 | private int[] mExpandInset = new int[2]; 39 | 40 | public static final int MODE_EXPLOSION = 0; 41 | public static final int MODE_CONFETTI = 1; 42 | private int mMode = MODE_CONFETTI; 43 | 44 | public static final int FLAG_SUPPORT_GRAVITY = 0x1; 45 | private int mFlag; 46 | 47 | public ExplosionField(Context context) { 48 | super(context); 49 | init(); 50 | } 51 | 52 | public ExplosionField(Context context, AttributeSet attrs) { 53 | super(context, attrs); 54 | init(); 55 | } 56 | 57 | public ExplosionField(Context context, AttributeSet attrs, int defStyleAttr) { 58 | super(context, attrs, defStyleAttr); 59 | init(); 60 | } 61 | 62 | private void init() { 63 | Arrays.fill(mExpandInset, Utils.dp2Px(32)); 64 | } 65 | 66 | @Override 67 | protected void onDraw(Canvas canvas) { 68 | super.onDraw(canvas); 69 | for (ExplosionAnimator explosion : mExplosions) { 70 | explosion.draw(canvas); 71 | } 72 | } 73 | 74 | public void expandExplosionBound(int dx, int dy) { 75 | mExpandInset[0] = dx; 76 | mExpandInset[1] = dy; 77 | } 78 | 79 | public void setMode(int mode) { 80 | if (this.mMode != mode) { 81 | this.mMode = mode; 82 | } 83 | } 84 | 85 | public void explode(ExplosionAnimator explosion, long startDelay, long duration) { 86 | explosion.addListener(new AnimatorListenerAdapter() { 87 | @Override 88 | public void onAnimationEnd(Animator animation) { 89 | mExplosions.remove(animation); 90 | } 91 | }); 92 | explosion.setStartDelay(startDelay); 93 | explosion.setDuration(duration); 94 | mExplosions.add(explosion); 95 | explosion.start(); 96 | } 97 | 98 | public void setFlag(int flag) { 99 | this.mFlag |= flag; 100 | } 101 | 102 | public boolean checkFlag(int flag) { 103 | return (this.mFlag & flag) > 0; 104 | } 105 | 106 | public void explode(final View view) { 107 | Rect r = new Rect(); 108 | view.getGlobalVisibleRect(r); 109 | int[] location = new int[2]; 110 | getLocationOnScreen(location); 111 | r.offset(-location[0], -location[1]); 112 | r.inset(-mExpandInset[0], -mExpandInset[1]); 113 | 114 | ExplosionAnimator explosion = null; 115 | long delay = 0; 116 | 117 | if (mMode == MODE_EXPLOSION) { 118 | explosion = new ExplosionAnimator(this, Utils.createBitmapFromView(view), r); 119 | ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150); 120 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 121 | 122 | Random random = new Random(); 123 | 124 | @Override 125 | public void onAnimationUpdate(ValueAnimator animation) { 126 | view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f); 127 | view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f); 128 | 129 | } 130 | }); 131 | animator.start(); 132 | 133 | view.animate().setDuration(150).setStartDelay(animator.getDuration()) 134 | .scaleX(0f).scaleY(0f).alpha(0f).start(); 135 | 136 | delay = animator.getDuration(); 137 | 138 | } else if (mMode == MODE_CONFETTI) { 139 | explosion = new PieceAnimator(this, Utils.createBitmapFromView(view), r, checkFlag(FLAG_SUPPORT_GRAVITY)); 140 | view.animate().setDuration(50).alpha(0f).start(); 141 | delay = 25; 142 | } 143 | 144 | if (explosion != null) { 145 | explode(explosion, delay, explosion.getDuration()); 146 | } 147 | } 148 | 149 | public void clear() { 150 | mExplosions.clear(); 151 | invalidate(); 152 | } 153 | 154 | public static ExplosionField attach2Window(Activity activity) { 155 | ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT); 156 | ExplosionField explosionField = new ExplosionField(activity); 157 | rootView.addView(explosionField, new ViewGroup.LayoutParams( 158 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 159 | return explosionField; 160 | } 161 | 162 | public void addListener(View root) { 163 | if (root instanceof ViewGroup) { 164 | ViewGroup parent = (ViewGroup) root; 165 | for (int i = 0; i < parent.getChildCount(); i++) { 166 | addListener(parent.getChildAt(i)); 167 | } 168 | } else { 169 | root.setClickable(true); 170 | root.setOnClickListener(new View.OnClickListener() { 171 | @Override 172 | public void onClick(View v) { 173 | explode(v); 174 | v.setOnClickListener(null); 175 | } 176 | }); 177 | } 178 | } 179 | 180 | public void reset(View root) { 181 | if (root instanceof ViewGroup) { 182 | ViewGroup parent = (ViewGroup) root; 183 | for (int i = 0; i < parent.getChildCount(); i++) { 184 | reset(parent.getChildAt(i)); 185 | } 186 | } else { 187 | root.setScaleX(1); 188 | root.setScaleY(1); 189 | root.setAlpha(1); 190 | } 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /explosionfield/src/main/java/tyrantgit/explosionfield/PieceAnimator.java: -------------------------------------------------------------------------------- 1 | package tyrantgit.explosionfield;/* 2 | * Copyright (C) 2015 tyrantgit 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import android.graphics.Bitmap; 18 | import android.graphics.Camera; 19 | import android.graphics.Canvas; 20 | import android.graphics.Matrix; 21 | import android.graphics.Paint; 22 | import android.graphics.PaintFlagsDrawFilter; 23 | import android.graphics.Rect; 24 | import android.graphics.RectF; 25 | import android.view.View; 26 | import android.view.animation.DecelerateInterpolator; 27 | import android.view.animation.Interpolator; 28 | 29 | import java.util.Random; 30 | 31 | public class PieceAnimator extends ExplosionAnimator { 32 | 33 | private static final long DEFAULT_DURATION = 1200; 34 | private static final float END_VALUE = 1.3f; 35 | private static final Interpolator KEEP_FINISH_INTERPOLATOR = new KeepFinishInterpolator(0.6f, 0.6f, 0.7f); 36 | private static final Interpolator DEFAULT_INTERPOLATOR = new DecelerateInterpolator(0.6f); 37 | private static final float INFLEXION_K = 0.3f; 38 | private static final int COLUMN_COUNT = 15; 39 | private static final float START_ROTATE = 360; 40 | private static final float END_ROTATE = 360; 41 | 42 | private Paint mPaint; 43 | private Particle[] mParticles; 44 | private Rect mBound; 45 | private View mContainer; 46 | private Matrix matrix = new Matrix(); 47 | private PaintFlagsDrawFilter mFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG); 48 | 49 | public static class KeepFinishInterpolator extends DecelerateInterpolator { 50 | 51 | private float mStartFinishFactor = 0f; 52 | private float mKeepFinishFactor = 0.7f; 53 | 54 | public KeepFinishInterpolator(float factor, float startFinish, float keepFinish) { 55 | super(factor); 56 | mStartFinishFactor = startFinish; 57 | mKeepFinishFactor = keepFinish; 58 | } 59 | 60 | @Override 61 | public float getInterpolation(float input) { 62 | if (input < mStartFinishFactor) { 63 | return 0; 64 | } else if (input < mKeepFinishFactor) { 65 | return 0.4f * super.getInterpolation( 66 | (input - mStartFinishFactor) / (mKeepFinishFactor - mStartFinishFactor)); 67 | } else if (input < 1) { 68 | return 0.4f + 0.6f * super.getInterpolation((input - mKeepFinishFactor) / (1 - mKeepFinishFactor)); 69 | } else { 70 | return 1; 71 | } 72 | } 73 | } 74 | 75 | public PieceAnimator(View container, Bitmap bitmap, Rect bound) { 76 | this(container, bitmap, bound, false); 77 | } 78 | 79 | public PieceAnimator(View container, Bitmap bitmap, Rect bound, boolean supportGravity) { 80 | super(container, bitmap, bound); 81 | 82 | mPaint = new Paint(); 83 | mBound = new Rect(bound); 84 | mBound.inset((mBound.width() - bitmap.getWidth()) / 2, (mBound.height() - bitmap.getHeight()) / 2); 85 | 86 | Random random = new Random(System.currentTimeMillis()); 87 | 88 | float radius = bitmap.getWidth() / COLUMN_COUNT; 89 | int partLineCount = (int) (bitmap.getHeight() / radius); 90 | if (partLineCount == 0) { 91 | radius = bitmap.getHeight() / 4; 92 | partLineCount = (int) (bitmap.getHeight() / radius); 93 | } 94 | int partColumnCount = (int) (bitmap.getWidth() / radius); 95 | 96 | mParticles = new Particle[partLineCount * partColumnCount]; 97 | for (int i = 0; i < partLineCount; i++) { 98 | for (int j = 0; j < partColumnCount; j++) { 99 | mParticles[(i * partColumnCount) + j] 100 | = generateParticle(i, j, (int) radius, supportGravity, bitmap, random); 101 | } 102 | } 103 | 104 | mContainer = container; 105 | 106 | setFloatValues(0, END_VALUE); 107 | setInterpolator(DEFAULT_INTERPOLATOR); 108 | setDuration(DEFAULT_DURATION); 109 | } 110 | 111 | private Particle generateParticle(int line, int column, int radius, boolean supportGravity, 112 | Bitmap bitmap, Random random) { 113 | Particle particle = new Particle(); 114 | particle.piece = Bitmap.createBitmap(bitmap, 115 | column * radius, 116 | line * radius, 117 | radius, 118 | radius, 119 | null, true); 120 | 121 | matrix.reset(); 122 | matrix.setTranslate(-mBound.centerX(), -mBound.centerY()); 123 | matrix.postScale(END_VALUE, END_VALUE, 0, 0); 124 | matrix.postTranslate(mBound.centerX(), mBound.centerY()); 125 | 126 | particle.srcRectF = new RectF(); 127 | particle.srcRectF.left = mBound.left + column * radius; 128 | particle.srcRectF.top = mBound.top + line * radius; 129 | particle.srcRectF.right = particle.srcRectF.left + radius; 130 | particle.srcRectF.bottom = particle.srcRectF.top + radius; 131 | 132 | float src[] = new float[]{particle.srcRectF.left, particle.srcRectF.top}; 133 | float dst[] = new float[2]; 134 | matrix.mapPoints(dst, src); 135 | 136 | particle.dstRectF = new RectF(particle.srcRectF); 137 | particle.dstRectF.offset(dst[0] - src[0], dst[1] - src[1]); 138 | 139 | particle.rectF = new RectF(particle.srcRectF); 140 | 141 | if (supportGravity) { 142 | particle.gravity = (float) (particle.srcRectF.height() * (END_VALUE - 1) / Math.pow(INFLEXION_K / 2, 2)); 143 | } 144 | 145 | particle.startRotateX = START_ROTATE * random.nextFloat(); 146 | particle.startRotateY = START_ROTATE * random.nextFloat(); 147 | particle.startRotateZ = START_ROTATE * random.nextFloat(); 148 | 149 | particle.endRotateX = random.nextFloat() < 0.3f ? END_ROTATE * random.nextFloat() : 150 | random.nextFloat() < 0.7f ? END_ROTATE + 180 * random.nextFloat() : 151 | END_ROTATE * 2 + 180 * random.nextFloat(); 152 | particle.endRotateY = random.nextFloat() < 0.3f ? END_ROTATE * random.nextFloat() : 153 | random.nextFloat() < 0.7f ? END_ROTATE + 180 * random.nextFloat() : 154 | END_ROTATE * 2 + 180 * random.nextFloat(); 155 | particle.endRotateZ = random.nextFloat() < 0.3f ? END_ROTATE * random.nextFloat() : 156 | random.nextFloat() < 0.7f ? END_ROTATE + 180 * random.nextFloat() : 157 | END_ROTATE * 2 + 180 * random.nextFloat(); 158 | 159 | particle.life = 0f; 160 | particle.overflow = 0f; 161 | particle.alpha = 1f; 162 | 163 | return particle; 164 | } 165 | 166 | @Override 167 | public void start() { 168 | super.start(); 169 | mContainer.invalidate(mBound); 170 | } 171 | 172 | public boolean draw(Canvas canvas) { 173 | if (!isStarted()) { 174 | return false; 175 | } 176 | 177 | canvas.setDrawFilter(mFilter); 178 | for (Particle particle : mParticles) { 179 | particle.advance(); 180 | if (particle.alpha > 0) { 181 | mPaint.setAlpha((int) (255 * particle.alpha)); 182 | canvas.save(); 183 | canvas.concat(particle.matrix); 184 | canvas.drawBitmap(particle.piece, null, particle.rectF, mPaint); 185 | canvas.restore(); 186 | } 187 | } 188 | mContainer.invalidate(); 189 | return true; 190 | } 191 | 192 | private class Particle { 193 | RectF srcRectF; 194 | RectF dstRectF; 195 | RectF rectF; 196 | float gravity; 197 | 198 | float startRotateX; 199 | float startRotateY; 200 | float startRotateZ; 201 | float endRotateX; 202 | float endRotateY; 203 | float endRotateZ; 204 | float rotateX; 205 | float rotateY; 206 | float rotateZ; 207 | 208 | float life; 209 | float overflow; 210 | 211 | Bitmap piece; 212 | float alpha; 213 | final Matrix matrix = new Matrix(); 214 | final Camera camera = new Camera(); 215 | 216 | public float getValue(float start, float end, float k) { 217 | return start + (end - start) * k; 218 | } 219 | 220 | public void advance() { 221 | float normalization = getAnimatedFraction(); 222 | if (normalization < life || normalization > 1f - overflow) { 223 | alpha = 0f; 224 | return; 225 | } 226 | 227 | normalization = (normalization - life) / (1f - life - overflow); 228 | alpha = 1f - KEEP_FINISH_INTERPOLATOR.getInterpolation(normalization); 229 | 230 | rectF.left = getValue(srcRectF.left, dstRectF.left, normalization); 231 | rectF.top = (float) (getValue(srcRectF.top, dstRectF.top, normalization) 232 | + gravity * Math.pow(normalization - INFLEXION_K, 2)); 233 | rectF.right = rectF.left + srcRectF.width(); 234 | rectF.bottom = rectF.top + srcRectF.width(); 235 | 236 | rotateX = getValue(startRotateX, endRotateX, normalization); 237 | rotateY = getValue(startRotateY, endRotateY, normalization); 238 | rotateZ = getValue(startRotateZ, endRotateZ, normalization); 239 | 240 | matrix.reset(); 241 | camera.save(); 242 | camera.rotateX(rotateX); 243 | camera.rotateY(rotateY); 244 | camera.rotateZ(-rotateZ); 245 | camera.getMatrix(matrix); 246 | camera.restore(); 247 | 248 | matrix.preTranslate(-rectF.centerX(), -rectF.centerY()); 249 | matrix.postTranslate(rectF.centerX(), rectF.centerY()); 250 | } 251 | 252 | } 253 | } 254 | --------------------------------------------------------------------------------