├── .gitignore ├── Preview ├── AnimalDrawable.gif ├── CircleJumpDrawable.gif ├── CircleRotateDrawable.gif ├── GoodsDrawable.gif ├── SceneryDrawable.gif └── ShapeChangeDrawable.gif ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── app │ │ └── dinus │ │ └── com │ │ └── example │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── app │ │ └── dinus │ │ └── com │ │ └── example │ │ ├── AnimalActivity.java │ │ ├── CircleJumpActivity.java │ │ ├── CircleRotateActivity.java │ │ ├── GoodsActivity.java │ │ ├── MainActivity.java │ │ ├── SceneryActivity.java │ │ └── ShapeChangeActivity.java │ └── res │ ├── layout │ ├── activity_animal.xml │ ├── activity_circle_jump.xml │ ├── activity_circle_rotate.xml │ ├── activity_goods.xml │ ├── activity_main.xml │ ├── activity_scenery.xml │ └── activity_shape_change.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── app │ │ └── dinus │ │ └── com │ │ └── loadingdrawable │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── app │ │ └── dinus │ │ └── com │ │ └── loadingdrawable │ │ ├── DensityUtil.java │ │ ├── LoadingView.java │ │ └── render │ │ ├── LoadingDrawable.java │ │ ├── LoadingRenderer.java │ │ ├── LoadingRendererFactory.java │ │ ├── animal │ │ ├── FishLoadingRenderer.java │ │ └── GhostsEyeLoadingRenderer.java │ │ ├── circle │ │ ├── jump │ │ │ ├── CollisionLoadingRenderer.java │ │ │ ├── DanceLoadingRenderer.java │ │ │ ├── GuardLoadingRenderer.java │ │ │ └── SwapLoadingRenderer.java │ │ └── rotate │ │ │ ├── GearLoadingRenderer.java │ │ │ ├── LevelLoadingRenderer.java │ │ │ ├── MaterialLoadingRenderer.java │ │ │ └── WhorlLoadingRenderer.java │ │ ├── goods │ │ ├── BalloonLoadingRenderer.java │ │ └── WaterBottleLoadingRenderer.java │ │ ├── scenery │ │ ├── DayNightLoadingRenderer.java │ │ └── ElectricFanLoadingRenderer.java │ │ └── shapechange │ │ ├── CircleBroodLoadingRenderer.java │ │ └── CoolWaitLoadingRenderer.java │ └── res │ ├── drawable-xhdpi │ ├── ic_eletric_fan.png │ ├── ic_leaf.png │ └── ic_loading.png │ └── values │ ├── attrs.xml │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea 4 | .DS_Store 5 | /build 6 | /captures 7 | *.iml -------------------------------------------------------------------------------- /Preview/AnimalDrawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/Preview/AnimalDrawable.gif -------------------------------------------------------------------------------- /Preview/CircleJumpDrawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/Preview/CircleJumpDrawable.gif -------------------------------------------------------------------------------- /Preview/CircleRotateDrawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/Preview/CircleRotateDrawable.gif -------------------------------------------------------------------------------- /Preview/GoodsDrawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/Preview/GoodsDrawable.gif -------------------------------------------------------------------------------- /Preview/SceneryDrawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/Preview/SceneryDrawable.gif -------------------------------------------------------------------------------- /Preview/ShapeChangeDrawable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/Preview/ShapeChangeDrawable.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## LoadingDrawable: Android cool animation collection 3 | [前言](http://www.jianshu.com/p/6e0ac5af4e8b) 4 | [CircleRotate源码解析](http://www.jianshu.com/p/1c3c6fc1b7ff) 5 | [Fish源码解析](http://blog.csdn.net/XSF50717/article/details/51494266)<br/> 6 | [](http://android-arsenal.com/details/1/3450) 7 | 8 | LoadingDrawable is some android animations implement of drawable: a library can be used in the pull-down to refresh, the placeholders of image loading and the time-consuming tasks. This project idea is from the [link](http://mp.weixin.qq.com/s?__biz=MjM5MDMxOTE5NA==&mid=402703079&idx=2&sn=2fcc6746a866dcc003c68ead9b68e595&scene=2&srcid=0302A7p723KK8E5gSzLKb2ZL&from=timeline&isappinstalled=0#wechat_redirect).<br/> 9 | 10 | The following content show a brief overview of LoadingDrawable 11 | 12 | * It extends `Drawable` and implement the interface `Animatable` 13 | * it uses strategy mode 14 | * It can be used as the background of View or content of `ImageView` 15 | * It's constructor must be passed a `LoadingRenderer` 16 | * It interact with `LoadingRenderer` by the callback `Callback` 17 | * `LoadingRenderer` is used for measuring and drawing the `LoadingDrawable`. note: 18 | `LoadingRenderer` is the core 19 | * `LoadingRenderer` only can be created by their `Builder`. 20 | 21 | Learn more about LoadingDrawable on the [Wiki Home](https://github.com/dinuscxj/LoadingDrawable/wiki). 22 | 23 |  24 |  25 |  26 |  27 |  28 |  29 | 30 | ## LoadingRenderer Style 31 | 32 | #### ShapeChange 33 | * CircleBroodLoadingRenderer 34 | * CoolWaitLoadingRenderer 35 | 36 | #### Goods 37 | * BalloonLoadingRenderer 38 | * WaterBottleLoadingRenderer 39 | 40 | #### Animal 41 | * FishLoadingRenderer 42 | * GhostsEyeLoadingEyeRenderer 43 | 44 | #### Scenery 45 | * ElectricFanLoadingRenderer 46 | * DayNightLoadingRenderer 47 | 48 | #### Circle Jump 49 | * CollisionLoadingRenderer 50 | * SwapLoadingRenderer 51 | * GuardLoadingRenderer 52 | * DanceLoadingRenderer 53 | 54 | #### Circle Rotate 55 | * WhorlLoadingRenderer 56 | * MaterialLoadingRenderer 57 | * GearLoadingRenderer 58 | * LevelLoadingRenderer 59 | 60 | ## Usage 61 | Define the `LoadingView` in XML and specify the `LoadingRenderer` style: 62 | ```xml 63 | <app.dinus.com.loadingdrawable.LoadingView 64 | android:id="@+id/level_view" 65 | android:layout_width="0dp" 66 | android:layout_height="match_parent" 67 | android:layout_weight="1" 68 | android:background="#fff1c02e" 69 | app:loading_renderer="LevelLoadingRenderer"/> 70 | ``` 71 | Or specify the `LoadingRenderer` style in Java 72 | ```java 73 | ***LoadingRenderer.Builder builder = new ***LoadingRenderer.Builder(context); 74 | LoadingView.setLoadingRenderer(builder.build()); 75 | ``` 76 | 77 | ## TODO 78 | When I feel less bugs enough, I will add a gradle dependency. So I hope you will make more Suggestions or Issues. 79 | 80 | ## Misc 81 | If you like LoadingDrawable or use it, could you please: 82 | 83 | * star this repo 84 | * send me some feedback. Thanks! 85 | 86 | ***QQ Group:*** **342748245** 87 | 88 | ## License 89 | Copyright 2015-2019 dinus 90 | 91 | Licensed under the Apache License, Version 2.0 (the "License"); 92 | you may not use this file except in compliance with the License. 93 | You may obtain a copy of the License at 94 | 95 | http://www.apache.org/licenses/LICENSE-2.0 96 | 97 | Unless required by applicable law or agreed to in writing, software 98 | distributed under the License is distributed on an "AS IS" BASIS, 99 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 100 | See the License for the specific language governing permissions and 101 | limitations under the License. 102 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "app.dinus.com.example" 9 | minSdkVersion 12 10 | targetSdkVersion 23 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 'com.android.support:appcompat-v7:23.2.1' 25 | compile project(':library') 26 | } 27 | -------------------------------------------------------------------------------- /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 /Users/dinus/workspace/android-sdk-macosx/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/androidTest/java/app/dinus/com/example/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase<Application> { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 | package="app.dinus.com.example" > 4 | 5 | <application 6 | android:allowBackup="true" 7 | android:icon="@mipmap/ic_launcher" 8 | android:label="@string/app_name" 9 | android:theme="@style/AppTheme" > 10 | <activity 11 | android:name=".MainActivity" 12 | android:label="@string/app_name" > 13 | <intent-filter> 14 | <action android:name="android.intent.action.MAIN" /> 15 | 16 | <category android:name="android.intent.category.LAUNCHER" /> 17 | </intent-filter> 18 | </activity> 19 | 20 | <activity android:name=".CircleJumpActivity" 21 | android:label="@string/label_circle_jump"/> 22 | 23 | <activity android:name=".CircleRotateActivity" 24 | android:label="@string/label_circle_rotate"/> 25 | 26 | <activity android:name=".SceneryActivity" 27 | android:label="@string/label_scenery"/> 28 | 29 | <activity android:name=".AnimalActivity" 30 | android:hardwareAccelerated="false" 31 | android:label="@string/label_animal"/> 32 | 33 | <activity android:name=".GoodsActivity" 34 | android:hardwareAccelerated="false" 35 | android:label="@string/label_goods"/> 36 | 37 | <activity android:name=".ShapeChangeActivity" 38 | android:hardwareAccelerated="false" 39 | android:label="@string/label_shape_change"/> 40 | </application> 41 | 42 | </manifest> 43 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/AnimalActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | public class AnimalActivity extends AppCompatActivity { 9 | public static void startActivity(Context context) { 10 | Intent intent = new Intent(context, AnimalActivity.class); 11 | context.startActivity(intent); 12 | } 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_animal); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/CircleJumpActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | public class CircleJumpActivity extends AppCompatActivity { 9 | public static void startActivity(Context context) { 10 | Intent intent = new Intent(context, CircleJumpActivity.class); 11 | context.startActivity(intent); 12 | } 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_circle_jump); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/CircleRotateActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | 9 | import app.dinus.com.loadingdrawable.DensityUtil; 10 | import app.dinus.com.loadingdrawable.LoadingView; 11 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 12 | import app.dinus.com.loadingdrawable.render.circle.rotate.GearLoadingRenderer; 13 | import app.dinus.com.loadingdrawable.render.circle.rotate.LevelLoadingRenderer; 14 | import app.dinus.com.loadingdrawable.render.circle.rotate.MaterialLoadingRenderer; 15 | import app.dinus.com.loadingdrawable.render.circle.rotate.WhorlLoadingRenderer; 16 | 17 | public class CircleRotateActivity extends AppCompatActivity { 18 | public static void startActivity(Context context) { 19 | Intent intent = new Intent(context, CircleRotateActivity.class); 20 | context.startActivity(intent); 21 | } 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_circle_rotate); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/GoodsActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | public class GoodsActivity extends AppCompatActivity { 9 | 10 | public static void startActivity(Context context) { 11 | Intent intent = new Intent(context, GoodsActivity.class); 12 | context.startActivity(intent); 13 | } 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_goods); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | import android.widget.Button; 7 | 8 | 9 | public class MainActivity extends AppCompatActivity implements View.OnClickListener{ 10 | private Button mBtnGoods; 11 | private Button mBtnAnimal; 12 | private Button mBtnScenery; 13 | private Button mBtnCircleJump; 14 | private Button mBtnShapeChange; 15 | private Button mBtnCircleRotate; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | mBtnGoods = (Button) findViewById(R.id.goods); 23 | mBtnAnimal = (Button) findViewById(R.id.animal); 24 | mBtnScenery = (Button) findViewById(R.id.scenery); 25 | mBtnCircleJump = (Button) findViewById(R.id.circle_jump); 26 | mBtnShapeChange = (Button) findViewById(R.id.shape_change); 27 | mBtnCircleRotate = (Button) findViewById(R.id.circle_rotate); 28 | 29 | mBtnGoods.setOnClickListener(this); 30 | mBtnAnimal.setOnClickListener(this); 31 | mBtnScenery.setOnClickListener(this); 32 | mBtnCircleJump.setOnClickListener(this); 33 | mBtnShapeChange.setOnClickListener(this); 34 | mBtnCircleRotate.setOnClickListener(this); 35 | } 36 | 37 | @Override 38 | public void onClick(View v) { 39 | switch (v.getId()){ 40 | case R.id.shape_change: 41 | ShapeChangeActivity.startActivity(this); 42 | break; 43 | case R.id.goods: 44 | GoodsActivity.startActivity(this); 45 | break; 46 | case R.id.animal: 47 | AnimalActivity.startActivity(this); 48 | break; 49 | case R.id.scenery: 50 | SceneryActivity.startActivity(this); 51 | break; 52 | case R.id.circle_jump: 53 | CircleJumpActivity.startActivity(this); 54 | break; 55 | case R.id.circle_rotate: 56 | CircleRotateActivity.startActivity(this); 57 | break; 58 | default: 59 | break; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/SceneryActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | public class SceneryActivity extends AppCompatActivity { 9 | 10 | public static void startActivity(Context context) { 11 | Intent intent = new Intent(context, SceneryActivity.class); 12 | context.startActivity(intent); 13 | } 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_scenery); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/app/dinus/com/example/ShapeChangeActivity.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.example; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | public class ShapeChangeActivity extends AppCompatActivity { 9 | 10 | public static void startActivity(Context context) { 11 | Intent intent = new Intent(context, ShapeChangeActivity.class); 12 | context.startActivity(intent); 13 | } 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_shape_change); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_animal.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | xmlns:app="http://schemas.android.com/apk/res-auto" 3 | android:layout_width="match_parent" 4 | android:layout_height="match_parent" 5 | android:orientation="vertical"> 6 | 7 | <app.dinus.com.loadingdrawable.LoadingView 8 | android:id="@+id/fish_view" 9 | android:layout_width="match_parent" 10 | android:layout_height="0dp" 11 | android:layout_weight="1" 12 | android:background="#ff071c28" 13 | app:loading_renderer="FishLoadingRenderer" /> 14 | 15 | <app.dinus.com.loadingdrawable.LoadingView 16 | android:id="@+id/ghosts_eye_view" 17 | android:layout_width="match_parent" 18 | android:layout_height="0dp" 19 | android:layout_weight="1" 20 | android:background="#ffcdcdcd" 21 | app:loading_renderer="GhostsEyeLoadingRenderer" /> 22 | 23 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_circle_jump.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | xmlns:app="http://schemas.android.com/apk/res-auto" 3 | android:layout_width="match_parent" 4 | android:layout_height="match_parent" 5 | android:orientation="vertical"> 6 | 7 | <LinearLayout 8 | android:layout_width="match_parent" 9 | android:layout_height="0dp" 10 | android:layout_weight="1" 11 | android:orientation="horizontal"> 12 | 13 | <app.dinus.com.loadingdrawable.LoadingView 14 | android:id="@+id/collision_view" 15 | android:layout_width="0dp" 16 | android:layout_height="match_parent" 17 | android:layout_weight="1" 18 | android:background="#ffe9e4d1" 19 | app:loading_renderer="CollisionLoadingRenderer" /> 20 | 21 | <app.dinus.com.loadingdrawable.LoadingView 22 | android:id="@+id/swap_view" 23 | android:layout_width="0dp" 24 | android:layout_height="match_parent" 25 | android:layout_weight="1" 26 | android:background="#ff9c0000" 27 | app:loading_renderer="SwapLoadingRenderer" /> 28 | </LinearLayout> 29 | 30 | <LinearLayout 31 | android:layout_width="match_parent" 32 | android:layout_height="0dp" 33 | android:layout_weight="1" 34 | android:orientation="horizontal"> 35 | 36 | <app.dinus.com.loadingdrawable.LoadingView 37 | android:id="@+id/guard_view" 38 | android:layout_width="0dp" 39 | android:layout_height="match_parent" 40 | android:layout_weight="1" 41 | android:background="#ff000000" 42 | app:loading_renderer="GuardLoadingRenderer" /> 43 | 44 | <app.dinus.com.loadingdrawable.LoadingView 45 | android:id="@+id/dance_view" 46 | android:layout_width="0dp" 47 | android:layout_height="match_parent" 48 | android:layout_weight="1" 49 | android:background="#ff94d0c1" 50 | app:loading_renderer="DanceLoadingRenderer" /> 51 | </LinearLayout> 52 | 53 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_circle_rotate.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:layout_width="match_parent" 3 | android:layout_height="match_parent" 4 | xmlns:app="http://schemas.android.com/apk/res-auto" 5 | android:orientation="vertical"> 6 | 7 | <LinearLayout 8 | android:layout_width="match_parent" 9 | android:layout_height="0dp" 10 | android:layout_weight="1" 11 | android:orientation="horizontal"> 12 | 13 | <app.dinus.com.loadingdrawable.LoadingView 14 | android:id="@+id/whorl_view" 15 | android:layout_width="0dp" 16 | android:layout_height="match_parent" 17 | android:layout_weight="1" 18 | android:background="#ffe6ebed" 19 | app:loading_renderer="WhorlLoadingRenderer"/> 20 | 21 | <app.dinus.com.loadingdrawable.LoadingView 22 | android:id="@+id/material_view" 23 | android:layout_width="0dp" 24 | android:layout_height="match_parent" 25 | android:layout_weight="1" 26 | android:background="#ff2a8cc8" 27 | app:loading_renderer="MaterialLoadingRenderer"/> 28 | </LinearLayout> 29 | 30 | <LinearLayout 31 | android:layout_width="match_parent" 32 | android:layout_height="0dp" 33 | android:layout_weight="1" 34 | android:orientation="horizontal"> 35 | 36 | <app.dinus.com.loadingdrawable.LoadingView 37 | android:id="@+id/gear_view" 38 | android:layout_width="0dp" 39 | android:layout_height="match_parent" 40 | android:layout_weight="1" 41 | android:background="#fffa644e" 42 | app:loading_renderer="GearLoadingRenderer"/> 43 | 44 | <app.dinus.com.loadingdrawable.LoadingView 45 | android:id="@+id/level_view" 46 | android:layout_width="0dp" 47 | android:layout_height="match_parent" 48 | android:layout_weight="1" 49 | android:background="#fff1c02e" 50 | app:loading_renderer="LevelLoadingRenderer"/> 51 | </LinearLayout> 52 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_goods.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:layout_width="match_parent" 3 | android:layout_height="match_parent" 4 | xmlns:app="http://schemas.android.com/apk/res-auto" 5 | android:orientation="vertical"> 6 | 7 | 8 | <app.dinus.com.loadingdrawable.LoadingView 9 | android:id="@+id/balloon_view" 10 | android:layout_width="match_parent" 11 | android:layout_height="0dp" 12 | android:layout_weight="1" 13 | android:background="#ffd4d9da" 14 | app:loading_renderer="BalloonLoadingRenderer"/> 15 | 16 | <app.dinus.com.loadingdrawable.LoadingView 17 | android:id="@+id/water_bottle_view" 18 | android:layout_width="match_parent" 19 | android:layout_height="0dp" 20 | android:layout_weight="1" 21 | android:background="#ff15181d" 22 | app:loading_renderer="WaterBottleLoadingRenderer"/> 23 | 24 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:layout_width="match_parent" 3 | android:layout_height="match_parent" 4 | xmlns:app="http://schemas.android.com/apk/res-auto" 5 | android:layout_marginTop="50dp" 6 | android:gravity="center" 7 | android:orientation="vertical"> 8 | 9 | <Button 10 | android:id="@+id/shape_change" 11 | android:layout_width="wrap_content" 12 | android:layout_height="wrap_content" 13 | android:text="ShapeChangeActivity" 14 | android:textAllCaps="false" /> 15 | 16 | <Button 17 | android:id="@+id/goods" 18 | android:layout_width="wrap_content" 19 | android:layout_height="wrap_content" 20 | android:layout_marginTop="30dp" 21 | android:text="GoodsActivity" 22 | android:textAllCaps="false" /> 23 | 24 | <Button 25 | android:id="@+id/animal" 26 | android:layout_width="wrap_content" 27 | android:layout_height="wrap_content" 28 | android:layout_marginTop="30dp" 29 | android:text="AnimalActivity" 30 | android:textAllCaps="false" /> 31 | 32 | <Button 33 | android:id="@+id/scenery" 34 | android:layout_width="wrap_content" 35 | android:layout_height="wrap_content" 36 | android:layout_marginTop="30dp" 37 | android:text="SceneryActivity" 38 | android:textAllCaps="false" /> 39 | 40 | <Button 41 | android:id="@+id/circle_rotate" 42 | android:layout_width="wrap_content" 43 | android:layout_height="wrap_content" 44 | android:layout_marginTop="30dp" 45 | android:text="CircleRotateActivity" 46 | android:textAllCaps="false" /> 47 | 48 | <Button 49 | android:id="@+id/circle_jump" 50 | android:layout_width="wrap_content" 51 | android:layout_height="wrap_content" 52 | android:layout_marginTop="30dp" 53 | android:text="CircleJumpActivity" 54 | android:textAllCaps="false" /> 55 | 56 | </LinearLayout> 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_scenery.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 | xmlns:app="http://schemas.android.com/apk/res-auto" 3 | android:layout_width="match_parent" 4 | android:layout_height="match_parent" 5 | android:orientation="vertical"> 6 | 7 | <app.dinus.com.loadingdrawable.LoadingView 8 | android:id="@+id/electric_fan_view" 9 | android:layout_width="match_parent" 10 | android:layout_height="0dp" 11 | android:layout_weight="1" 12 | android:background="#fffcd03c" 13 | app:loading_renderer="ElectricFanLoadingRenderer" /> 14 | 15 | <app.dinus.com.loadingdrawable.LoadingView 16 | android:id="@+id/day_night_view" 17 | android:layout_width="match_parent" 18 | android:layout_height="0dp" 19 | android:layout_weight="1" 20 | android:background="#ff071c28" 21 | app:loading_renderer="DayNightLoadingRenderer" /> 22 | 23 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_shape_change.xml: -------------------------------------------------------------------------------- 1 | <LinearLayout android:orientation="vertical" 2 | android:layout_height="match_parent" 3 | android:layout_width="match_parent" 4 | xmlns:android="http://schemas.android.com/apk/res/android" 5 | xmlns:app="http://schemas.android.com/apk/res-auto"> 6 | 7 | <app.dinus.com.loadingdrawable.LoadingView 8 | android:id="@+id/circle_brood_view" 9 | android:layout_weight="1" 10 | android:layout_width="match_parent" 11 | android:background="#ffd4d9da" 12 | android:layout_height="0dp" 13 | app:loading_renderer="CircleBroodLoadingRenderer"/> 14 | 15 | <app.dinus.com.loadingdrawable.LoadingView 16 | android:id="@+id/cool_wait_view" 17 | android:layout_weight="1" 18 | android:layout_height="0dp" 19 | android:background="#ff1B78DD" 20 | android:layout_width="match_parent" 21 | app:loading_renderer="CoolWaitLoadingRenderer"/> 22 | 23 | </LinearLayout> -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | <menu xmlns:android="http://schemas.android.com/apk/res/android" 2 | xmlns:app="http://schemas.android.com/apk/res-auto" 3 | xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> 4 | <item android:id="@+id/action_settings" android:title="@string/action_settings" 5 | android:orderInCategory="100" app:showAsAction="never" /> 6 | </menu> 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <!-- Example customization of dimensions originally defined in res/values/dimens.xml 3 | (such as screen margins) for screens with more than 820dp of available width. This 4 | would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> 5 | <dimen name="activity_horizontal_margin">64dp</dimen> 6 | </resources> 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <!-- Default screen margins, per the Android Design guidelines. --> 3 | <dimen name="activity_horizontal_margin">16dp</dimen> 4 | <dimen name="activity_vertical_margin">16dp</dimen> 5 | </resources> 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <string name="app_name">LoadingDrawable</string> 3 | <string name="label_circle_rotate">CircleRotateActivity</string> 4 | <string name="label_circle_jump">CircleJumpActivity</string> 5 | <string name="label_scenery">SceneryActivity</string> 6 | <string name="label_animal">AnimalActivity</string> 7 | <string name="label_goods">GoodsActivity</string> 8 | <string name="label_shape_change">ShapeChangeActivity</string> 9 | 10 | <string name="hello_world">Hello world!</string> 11 | <string name="action_settings">Settings</string> 12 | </resources> 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | 3 | <!-- Base application theme. --> 4 | <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 5 | <!-- Customize your theme here. --> 6 | </style> 7 | 8 | </resources> 9 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.1.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 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.10-all.zip 7 | -------------------------------------------------------------------------------- /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" : '.*-> \(.*\)#39;` 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | minSdkVersion 12 9 | targetSdkVersion 23 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 | compile 'com.android.support:appcompat-v7:23.2.1' 24 | } 25 | -------------------------------------------------------------------------------- /library/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 /Users/dinus/workspace/android-sdk-macosx/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 | -------------------------------------------------------------------------------- /library/src/androidTest/java/app/dinus/com/loadingdrawable/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase<Application> { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="app.dinus.com.loadingdrawable"> 3 | 4 | <application android:allowBackup="true" android:label="@string/app_name"> 5 | 6 | </application> 7 | 8 | </manifest> 9 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/DensityUtil.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable; 2 | 3 | import android.content.Context; 4 | 5 | public class DensityUtil { 6 | 7 | public static float dip2px(Context context, float dpValue) { 8 | float scale = context.getResources().getDisplayMetrics().density; 9 | return dpValue * scale; 10 | } 11 | } -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/LoadingView.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.ImageView; 8 | 9 | import app.dinus.com.loadingdrawable.render.LoadingDrawable; 10 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 11 | import app.dinus.com.loadingdrawable.render.LoadingRendererFactory; 12 | 13 | public class LoadingView extends ImageView { 14 | private LoadingDrawable mLoadingDrawable; 15 | 16 | public LoadingView(Context context) { 17 | super(context); 18 | } 19 | 20 | public LoadingView(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | initAttrs(context, attrs); 23 | } 24 | 25 | private void initAttrs(Context context, AttributeSet attrs) { 26 | try { 27 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); 28 | int loadingRendererId = ta.getInt(R.styleable.LoadingView_loading_renderer, 0); 29 | LoadingRenderer loadingRenderer = LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId); 30 | setLoadingRenderer(loadingRenderer); 31 | ta.recycle(); 32 | } catch (Exception e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | 37 | public void setLoadingRenderer(LoadingRenderer loadingRenderer) { 38 | mLoadingDrawable = new LoadingDrawable(loadingRenderer); 39 | setImageDrawable(mLoadingDrawable); 40 | } 41 | 42 | @Override 43 | protected void onAttachedToWindow() { 44 | super.onAttachedToWindow(); 45 | startAnimation(); 46 | } 47 | 48 | @Override 49 | protected void onDetachedFromWindow() { 50 | super.onDetachedFromWindow(); 51 | stopAnimation(); 52 | } 53 | 54 | @Override 55 | protected void onVisibilityChanged(View changedView, int visibility) { 56 | super.onVisibilityChanged(changedView, visibility); 57 | 58 | final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE; 59 | if (visible) { 60 | startAnimation(); 61 | } else { 62 | stopAnimation(); 63 | } 64 | } 65 | 66 | private void startAnimation() { 67 | if (mLoadingDrawable != null) { 68 | mLoadingDrawable.start(); 69 | } 70 | } 71 | 72 | private void stopAnimation() { 73 | if (mLoadingDrawable != null) { 74 | mLoadingDrawable.stop(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/LoadingDrawable.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.ColorFilter; 5 | import android.graphics.PixelFormat; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Animatable; 8 | import android.graphics.drawable.Drawable; 9 | 10 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 11 | 12 | public class LoadingDrawable extends Drawable implements Animatable { 13 | private final LoadingRenderer mLoadingRender; 14 | 15 | private final Callback mCallback = new Callback() { 16 | @Override 17 | public void invalidateDrawable(Drawable d) { 18 | invalidateSelf(); 19 | } 20 | 21 | @Override 22 | public void scheduleDrawable(Drawable d, Runnable what, long when) { 23 | scheduleSelf(what, when); 24 | } 25 | 26 | @Override 27 | public void unscheduleDrawable(Drawable d, Runnable what) { 28 | unscheduleSelf(what); 29 | } 30 | }; 31 | 32 | public LoadingDrawable(LoadingRenderer loadingRender) { 33 | this.mLoadingRender = loadingRender; 34 | this.mLoadingRender.setCallback(mCallback); 35 | } 36 | 37 | @Override 38 | protected void onBoundsChange(Rect bounds) { 39 | super.onBoundsChange(bounds); 40 | this.mLoadingRender.setBounds(bounds); 41 | } 42 | 43 | @Override 44 | public void draw(Canvas canvas) { 45 | if (!getBounds().isEmpty()) { 46 | this.mLoadingRender.draw(canvas); 47 | } 48 | } 49 | 50 | @Override 51 | public void setAlpha(int alpha) { 52 | this.mLoadingRender.setAlpha(alpha); 53 | } 54 | 55 | @Override 56 | public void setColorFilter(ColorFilter cf) { 57 | this.mLoadingRender.setColorFilter(cf); 58 | } 59 | 60 | @Override 61 | public int getOpacity() { 62 | return PixelFormat.TRANSLUCENT; 63 | } 64 | 65 | @Override 66 | public void start() { 67 | this.mLoadingRender.start(); 68 | } 69 | 70 | @Override 71 | public void stop() { 72 | this.mLoadingRender.stop(); 73 | } 74 | 75 | @Override 76 | public boolean isRunning() { 77 | return this.mLoadingRender.isRunning(); 78 | } 79 | 80 | @Override 81 | public int getIntrinsicHeight() { 82 | return (int) this.mLoadingRender.mHeight; 83 | } 84 | 85 | @Override 86 | public int getIntrinsicWidth() { 87 | return (int) this.mLoadingRender.mWidth; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.ColorFilter; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Handler; 12 | import android.os.Looper; 13 | import android.util.Log; 14 | import android.view.animation.Animation; 15 | import android.view.animation.LinearInterpolator; 16 | 17 | import app.dinus.com.loadingdrawable.DensityUtil; 18 | 19 | public abstract class LoadingRenderer { 20 | private static final long ANIMATION_DURATION = 1333; 21 | private static final float DEFAULT_SIZE = 56.0f; 22 | 23 | private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener 24 | = new ValueAnimator.AnimatorUpdateListener() { 25 | @Override 26 | public void onAnimationUpdate(ValueAnimator animation) { 27 | computeRender((float) animation.getAnimatedValue()); 28 | invalidateSelf(); 29 | } 30 | }; 31 | 32 | /** 33 | * Whenever {@link LoadingDrawable} boundary changes mBounds will be updated. 34 | * More details you can see {@link LoadingDrawable#onBoundsChange(Rect)} 35 | */ 36 | protected final Rect mBounds = new Rect(); 37 | 38 | private Drawable.Callback mCallback; 39 | private ValueAnimator mRenderAnimator; 40 | 41 | protected long mDuration; 42 | 43 | protected float mWidth; 44 | protected float mHeight; 45 | 46 | public LoadingRenderer(Context context) { 47 | initParams(context); 48 | setupAnimators(); 49 | } 50 | 51 | @Deprecated 52 | protected void draw(Canvas canvas, Rect bounds) { 53 | } 54 | 55 | protected void draw(Canvas canvas) { 56 | draw(canvas, mBounds); 57 | } 58 | 59 | protected abstract void computeRender(float renderProgress); 60 | 61 | protected abstract void setAlpha(int alpha); 62 | 63 | protected abstract void setColorFilter(ColorFilter cf); 64 | 65 | protected abstract void reset(); 66 | 67 | protected void addRenderListener(Animator.AnimatorListener animatorListener) { 68 | mRenderAnimator.addListener(animatorListener); 69 | } 70 | 71 | void start() { 72 | reset(); 73 | mRenderAnimator.addUpdateListener(mAnimatorUpdateListener); 74 | 75 | mRenderAnimator.setRepeatCount(ValueAnimator.INFINITE); 76 | mRenderAnimator.setDuration(mDuration); 77 | mRenderAnimator.start(); 78 | } 79 | 80 | void stop() { 81 | // if I just call mRenderAnimator.end(), 82 | // it will always call the method onAnimationUpdate(ValueAnimator animation) 83 | // why ? if you know why please send email to me (dinus_developer@163.com) 84 | mRenderAnimator.removeUpdateListener(mAnimatorUpdateListener); 85 | 86 | mRenderAnimator.setRepeatCount(0); 87 | mRenderAnimator.setDuration(0); 88 | mRenderAnimator.end(); 89 | } 90 | 91 | boolean isRunning() { 92 | return mRenderAnimator.isRunning(); 93 | } 94 | 95 | void setCallback(Drawable.Callback callback) { 96 | this.mCallback = callback; 97 | } 98 | 99 | void setBounds(Rect bounds) { 100 | mBounds.set(bounds); 101 | } 102 | 103 | private void initParams(Context context) { 104 | mWidth = DensityUtil.dip2px(context, DEFAULT_SIZE); 105 | mHeight = DensityUtil.dip2px(context, DEFAULT_SIZE); 106 | 107 | mDuration = ANIMATION_DURATION; 108 | } 109 | 110 | private void setupAnimators() { 111 | mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); 112 | mRenderAnimator.setRepeatCount(Animation.INFINITE); 113 | mRenderAnimator.setRepeatMode(Animation.RESTART); 114 | mRenderAnimator.setDuration(mDuration); 115 | //fuck you! the default interpolator is AccelerateDecelerateInterpolator 116 | mRenderAnimator.setInterpolator(new LinearInterpolator()); 117 | mRenderAnimator.addUpdateListener(mAnimatorUpdateListener); 118 | } 119 | 120 | private void invalidateSelf() { 121 | mCallback.invalidateDrawable(null); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRendererFactory.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render; 2 | 3 | import android.content.Context; 4 | import android.util.SparseArray; 5 | 6 | import java.lang.reflect.Constructor; 7 | 8 | import app.dinus.com.loadingdrawable.render.animal.FishLoadingRenderer; 9 | import app.dinus.com.loadingdrawable.render.animal.GhostsEyeLoadingRenderer; 10 | import app.dinus.com.loadingdrawable.render.circle.jump.CollisionLoadingRenderer; 11 | import app.dinus.com.loadingdrawable.render.circle.jump.DanceLoadingRenderer; 12 | import app.dinus.com.loadingdrawable.render.circle.jump.GuardLoadingRenderer; 13 | import app.dinus.com.loadingdrawable.render.circle.jump.SwapLoadingRenderer; 14 | import app.dinus.com.loadingdrawable.render.circle.rotate.GearLoadingRenderer; 15 | import app.dinus.com.loadingdrawable.render.circle.rotate.LevelLoadingRenderer; 16 | import app.dinus.com.loadingdrawable.render.circle.rotate.MaterialLoadingRenderer; 17 | import app.dinus.com.loadingdrawable.render.circle.rotate.WhorlLoadingRenderer; 18 | import app.dinus.com.loadingdrawable.render.goods.BalloonLoadingRenderer; 19 | import app.dinus.com.loadingdrawable.render.goods.WaterBottleLoadingRenderer; 20 | import app.dinus.com.loadingdrawable.render.scenery.DayNightLoadingRenderer; 21 | import app.dinus.com.loadingdrawable.render.scenery.ElectricFanLoadingRenderer; 22 | import app.dinus.com.loadingdrawable.render.shapechange.CircleBroodLoadingRenderer; 23 | import app.dinus.com.loadingdrawable.render.shapechange.CoolWaitLoadingRenderer; 24 | 25 | public final class LoadingRendererFactory { 26 | private static final SparseArray<Class<? extends LoadingRenderer>> LOADING_RENDERERS = new SparseArray<>(); 27 | 28 | static { 29 | //circle rotate 30 | LOADING_RENDERERS.put(0, MaterialLoadingRenderer.class); 31 | LOADING_RENDERERS.put(1, LevelLoadingRenderer.class); 32 | LOADING_RENDERERS.put(2, WhorlLoadingRenderer.class); 33 | LOADING_RENDERERS.put(3, GearLoadingRenderer.class); 34 | //circle jump 35 | LOADING_RENDERERS.put(4, SwapLoadingRenderer.class); 36 | LOADING_RENDERERS.put(5, GuardLoadingRenderer.class); 37 | LOADING_RENDERERS.put(6, DanceLoadingRenderer.class); 38 | LOADING_RENDERERS.put(7, CollisionLoadingRenderer.class); 39 | //scenery 40 | LOADING_RENDERERS.put(8, DayNightLoadingRenderer.class); 41 | LOADING_RENDERERS.put(9, ElectricFanLoadingRenderer.class); 42 | //animal 43 | LOADING_RENDERERS.put(10, FishLoadingRenderer.class); 44 | LOADING_RENDERERS.put(11, GhostsEyeLoadingRenderer.class); 45 | //goods 46 | LOADING_RENDERERS.put(12, BalloonLoadingRenderer.class); 47 | LOADING_RENDERERS.put(13, WaterBottleLoadingRenderer.class); 48 | //shape change 49 | LOADING_RENDERERS.put(14, CircleBroodLoadingRenderer.class); 50 | LOADING_RENDERERS.put(15, CoolWaitLoadingRenderer.class); 51 | } 52 | 53 | private LoadingRendererFactory() { 54 | } 55 | 56 | public static LoadingRenderer createLoadingRenderer(Context context, int loadingRendererId) throws Exception { 57 | Class<?> loadingRendererClazz = LOADING_RENDERERS.get(loadingRendererId); 58 | Constructor<?>[] constructors = loadingRendererClazz.getDeclaredConstructors(); 59 | for (Constructor<?> constructor : constructors) { 60 | Class<?>[] parameterTypes = constructor.getParameterTypes(); 61 | if (parameterTypes != null 62 | && parameterTypes.length == 1 63 | && parameterTypes[0].equals(Context.class)) { 64 | constructor.setAccessible(true); 65 | return (LoadingRenderer) constructor.newInstance(context); 66 | } 67 | } 68 | 69 | throw new InstantiationException(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/animal/FishLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.animal; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.DashPathEffect; 8 | import android.graphics.Matrix; 9 | import android.graphics.Paint; 10 | import android.graphics.Path; 11 | import android.graphics.PathMeasure; 12 | import android.graphics.Rect; 13 | import android.graphics.RectF; 14 | import android.graphics.Region; 15 | import android.util.DisplayMetrics; 16 | import android.view.animation.Interpolator; 17 | 18 | import app.dinus.com.loadingdrawable.DensityUtil; 19 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 20 | 21 | public class FishLoadingRenderer extends LoadingRenderer { 22 | private Interpolator FISH_INTERPOLATOR = new FishInterpolator(); 23 | 24 | private static final float DEFAULT_PATH_FULL_LINE_SIZE = 7.0f; 25 | private static final float DEFAULT_PATH_DOTTED_LINE_SIZE = DEFAULT_PATH_FULL_LINE_SIZE / 2.0f; 26 | private static final float DEFAULT_RIVER_HEIGHT = DEFAULT_PATH_FULL_LINE_SIZE * 8.5f; 27 | private static final float DEFAULT_RIVER_WIDTH = DEFAULT_PATH_FULL_LINE_SIZE * 5.5f; 28 | 29 | private static final float DEFAULT_FISH_EYE_SIZE = DEFAULT_PATH_FULL_LINE_SIZE * 0.5f; 30 | private static final float DEFAULT_FISH_WIDTH = DEFAULT_PATH_FULL_LINE_SIZE * 3.0f; 31 | private static final float DEFAULT_FISH_HEIGHT = DEFAULT_PATH_FULL_LINE_SIZE * 4.5f; 32 | 33 | private static final float DEFAULT_WIDTH = 200.0f; 34 | private static final float DEFAULT_HEIGHT = 150.0f; 35 | private static final float DEFAULT_RIVER_BANK_WIDTH = DEFAULT_PATH_FULL_LINE_SIZE; 36 | 37 | private static final long ANIMATION_DURATION = 800; 38 | private static final float DOTTED_LINE_WIDTH_COUNT = (8.5f + 5.5f - 2.0f) * 2.0f * 2.0f; 39 | private static final float DOTTED_LINE_WIDTH_RATE = 1.0f / DOTTED_LINE_WIDTH_COUNT; 40 | 41 | private final float[] FISH_MOVE_POINTS = new float[]{ 42 | DOTTED_LINE_WIDTH_RATE * 3.0f, DOTTED_LINE_WIDTH_RATE * 6.0f, 43 | DOTTED_LINE_WIDTH_RATE * 15f, DOTTED_LINE_WIDTH_RATE * 18f, 44 | DOTTED_LINE_WIDTH_RATE * 27.0f, DOTTED_LINE_WIDTH_RATE * 30.0f, 45 | DOTTED_LINE_WIDTH_RATE * 39f, DOTTED_LINE_WIDTH_RATE * 42f, 46 | }; 47 | 48 | private final float FISH_MOVE_POINTS_RATE = 1.0f / FISH_MOVE_POINTS.length; 49 | 50 | private static final int DEFAULT_COLOR = Color.parseColor("#fffefed6"); 51 | 52 | private final Paint mPaint = new Paint(); 53 | private final RectF mTempBounds = new RectF(); 54 | 55 | private final float[] mFishHeadPos = new float[2]; 56 | 57 | private Path mRiverPath; 58 | private PathMeasure mRiverMeasure; 59 | 60 | private float mFishRotateDegrees; 61 | 62 | private float mRiverBankWidth; 63 | private float mRiverWidth; 64 | private float mRiverHeight; 65 | private float mFishWidth; 66 | private float mFishHeight; 67 | private float mFishEyeSize; 68 | private float mPathFullLineSize; 69 | private float mPathDottedLineSize; 70 | 71 | private int mColor; 72 | 73 | private FishLoadingRenderer(Context context) { 74 | super(context); 75 | init(context); 76 | setupPaint(); 77 | } 78 | 79 | private void init(Context context) { 80 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 81 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 82 | mRiverBankWidth = DensityUtil.dip2px(context, DEFAULT_RIVER_BANK_WIDTH); 83 | 84 | mPathFullLineSize = DensityUtil.dip2px(context, DEFAULT_PATH_FULL_LINE_SIZE); 85 | mPathDottedLineSize = DensityUtil.dip2px(context, DEFAULT_PATH_DOTTED_LINE_SIZE); 86 | mFishWidth = DensityUtil.dip2px(context, DEFAULT_FISH_WIDTH); 87 | mFishHeight = DensityUtil.dip2px(context, DEFAULT_FISH_HEIGHT); 88 | mFishEyeSize = DensityUtil.dip2px(context, DEFAULT_FISH_EYE_SIZE); 89 | mRiverWidth = DensityUtil.dip2px(context, DEFAULT_RIVER_WIDTH); 90 | mRiverHeight = DensityUtil.dip2px(context, DEFAULT_RIVER_HEIGHT); 91 | 92 | mColor = DEFAULT_COLOR; 93 | 94 | mDuration = ANIMATION_DURATION; 95 | } 96 | 97 | private void setupPaint() { 98 | mPaint.setAntiAlias(true); 99 | mPaint.setStrokeWidth(mRiverBankWidth); 100 | mPaint.setStyle(Paint.Style.STROKE); 101 | mPaint.setStrokeJoin(Paint.Join.MITER); 102 | mPaint.setPathEffect(new DashPathEffect(new float[]{mPathFullLineSize, mPathDottedLineSize}, mPathDottedLineSize)); 103 | } 104 | 105 | @Override 106 | protected void draw(Canvas canvas, Rect bounds) { 107 | int saveCount = canvas.save(); 108 | RectF arcBounds = mTempBounds; 109 | arcBounds.set(bounds); 110 | 111 | mPaint.setColor(mColor); 112 | 113 | //calculate fish clip bounds 114 | //clip the width of the fish need to increase mPathDottedLineSize * 1.2f 115 | RectF fishRectF = new RectF(mFishHeadPos[0] - mFishWidth / 2.0f - mPathDottedLineSize * 1.2f, mFishHeadPos[1] - mFishHeight / 2.0f, 116 | mFishHeadPos[0] + mFishWidth / 2.0f + mPathDottedLineSize * 1.2f, mFishHeadPos[1] + mFishHeight / 2.0f); 117 | Matrix matrix = new Matrix(); 118 | matrix.postRotate(mFishRotateDegrees, fishRectF.centerX(), fishRectF.centerY()); 119 | matrix.mapRect(fishRectF); 120 | 121 | //draw river 122 | int riverSaveCount = canvas.save(); 123 | mPaint.setStyle(Paint.Style.STROKE); 124 | canvas.clipRect(fishRectF, Region.Op.DIFFERENCE); 125 | canvas.drawPath(createRiverPath(arcBounds), mPaint); 126 | canvas.restoreToCount(riverSaveCount); 127 | 128 | //draw fish 129 | int fishSaveCount = canvas.save(); 130 | mPaint.setStyle(Paint.Style.FILL); 131 | canvas.rotate(mFishRotateDegrees, mFishHeadPos[0], mFishHeadPos[1]); 132 | canvas.clipPath(createFishEyePath(mFishHeadPos[0], mFishHeadPos[1] - mFishHeight * 0.06f), Region.Op.DIFFERENCE); 133 | canvas.drawPath(createFishPath(mFishHeadPos[0], mFishHeadPos[1]), mPaint); 134 | canvas.restoreToCount(fishSaveCount); 135 | 136 | canvas.restoreToCount(saveCount); 137 | } 138 | 139 | private float calculateRotateDegrees(float fishProgress) { 140 | if (fishProgress < FISH_MOVE_POINTS_RATE * 2) { 141 | return 90; 142 | } 143 | 144 | if (fishProgress < FISH_MOVE_POINTS_RATE * 4) { 145 | return 180; 146 | } 147 | 148 | if (fishProgress < FISH_MOVE_POINTS_RATE * 6) { 149 | return 270; 150 | } 151 | 152 | return 0.0f; 153 | } 154 | 155 | @Override 156 | protected void computeRender(float renderProgress) { 157 | if (mRiverPath == null) { 158 | return; 159 | } 160 | 161 | if (mRiverMeasure == null) { 162 | mRiverMeasure = new PathMeasure(mRiverPath, false); 163 | } 164 | 165 | float fishProgress = FISH_INTERPOLATOR.getInterpolation(renderProgress); 166 | 167 | mRiverMeasure.getPosTan(mRiverMeasure.getLength() * fishProgress, mFishHeadPos, null); 168 | mFishRotateDegrees = calculateRotateDegrees(fishProgress); 169 | } 170 | 171 | @Override 172 | protected void setAlpha(int alpha) { 173 | 174 | } 175 | 176 | @Override 177 | protected void setColorFilter(ColorFilter cf) { 178 | 179 | } 180 | 181 | @Override 182 | protected void reset() { 183 | } 184 | 185 | private Path createFishEyePath(float fishEyeCenterX, float fishEyeCenterY) { 186 | Path path = new Path(); 187 | path.addCircle(fishEyeCenterX, fishEyeCenterY, mFishEyeSize, Path.Direction.CW); 188 | 189 | return path; 190 | } 191 | 192 | private Path createFishPath(float fishCenterX, float fishCenterY) { 193 | Path path = new Path(); 194 | 195 | float fishHeadX = fishCenterX; 196 | float fishHeadY = fishCenterY - mFishHeight / 2.0f; 197 | 198 | //the head of the fish 199 | path.moveTo(fishHeadX, fishHeadY); 200 | //the left body of the fish 201 | path.quadTo(fishHeadX - mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.222f, fishHeadX - mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.444f); 202 | path.lineTo(fishHeadX - mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.666f); 203 | path.lineTo(fishHeadX - mFishWidth * 0.5f, fishHeadY + mFishHeight * 0.8f); 204 | path.lineTo(fishHeadX - mFishWidth * 0.5f, fishHeadY + mFishHeight); 205 | 206 | //the tail of the fish 207 | path.lineTo(fishHeadX, fishHeadY + mFishHeight * 0.9f); 208 | 209 | //the right body of the fish 210 | path.lineTo(fishHeadX + mFishWidth * 0.5f, fishHeadY + mFishHeight); 211 | path.lineTo(fishHeadX + mFishWidth * 0.5f, fishHeadY + mFishHeight * 0.8f); 212 | path.lineTo(fishHeadX + mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.666f); 213 | path.lineTo(fishHeadX + mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.444f); 214 | path.quadTo(fishHeadX + mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.222f, fishHeadX, fishHeadY); 215 | 216 | path.close(); 217 | 218 | return path; 219 | } 220 | 221 | private Path createRiverPath(RectF arcBounds) { 222 | if (mRiverPath != null) { 223 | return mRiverPath; 224 | } 225 | 226 | mRiverPath = new Path(); 227 | 228 | RectF rectF = new RectF(arcBounds.centerX() - mRiverWidth / 2.0f, arcBounds.centerY() - mRiverHeight / 2.0f, 229 | arcBounds.centerX() + mRiverWidth / 2.0f, arcBounds.centerY() + mRiverHeight / 2.0f); 230 | 231 | rectF.inset(mRiverBankWidth / 2.0f, mRiverBankWidth / 2.0f); 232 | 233 | mRiverPath.addRect(rectF, Path.Direction.CW); 234 | 235 | return mRiverPath; 236 | } 237 | 238 | private class FishInterpolator implements Interpolator { 239 | @Override 240 | public float getInterpolation(float input) { 241 | int index = ((int) (input / FISH_MOVE_POINTS_RATE)); 242 | if (index >= FISH_MOVE_POINTS.length) { 243 | index = FISH_MOVE_POINTS.length - 1; 244 | } 245 | 246 | return FISH_MOVE_POINTS[index]; 247 | } 248 | } 249 | 250 | public static class Builder { 251 | private Context mContext; 252 | 253 | public Builder(Context mContext) { 254 | this.mContext = mContext; 255 | } 256 | 257 | public FishLoadingRenderer build() { 258 | FishLoadingRenderer loadingRenderer = new FishLoadingRenderer(mContext); 259 | return loadingRenderer; 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/animal/GhostsEyeLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.animal; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.util.DisplayMetrics; 12 | import android.view.animation.Interpolator; 13 | 14 | import app.dinus.com.loadingdrawable.DensityUtil; 15 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 16 | 17 | public class GhostsEyeLoadingRenderer extends LoadingRenderer { 18 | private Interpolator EYE_BALL_INTERPOLATOR = new EyeBallInterpolator(); 19 | private Interpolator EYE_CIRCLE_INTERPOLATOR = new EyeCircleInterpolator(); 20 | 21 | private static final float DEFAULT_WIDTH = 200.0f; 22 | private static final float DEFAULT_HEIGHT = 176.0f; 23 | private static final float DEFAULT_EYE_EDGE_WIDTH = 5.0f; 24 | 25 | private static final float DEFAULT_EYE_BALL_HEIGHT = 9.0f; 26 | private static final float DEFAULT_EYE_BALL_WIDTH = 11.0f; 27 | 28 | private static final float DEFAULT_EYE_CIRCLE_INTERVAL = 8.0f; 29 | private static final float DEFAULT_EYE_BALL_OFFSET_Y = 2.0f; 30 | private static final float DEFAULT_ABOVE_RADIAN_EYE_CIRCLE_OFFSET = 6.0f; 31 | private static final float DEFAULT_EYE_CIRCLE_RADIUS = 21.0f; 32 | private static final float DEFAULT_MAX_EYE_JUMP_DISTANCE = 11.0f; 33 | 34 | private static final float LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET = 0.0f; 35 | private static final float RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET = 0.067f; 36 | 37 | private static final float LEFT_EYE_BALL_END_JUMP_OFFSET = 0.4f; 38 | private static final float LEFT_EYE_CIRCLE_END_JUMP_OFFSET = 0.533f; 39 | private static final float RIGHT_EYE_BALL_END_JUMP_OFFSET = 0.467f; 40 | private static final float RIGHT_EYE_CIRCLE_END_JUMP_OFFSET = 0.60f; 41 | 42 | private static final int DEGREE_180 = 180; 43 | 44 | private static final long ANIMATION_DURATION = 2333; 45 | 46 | private static final int DEFAULT_COLOR = Color.parseColor("#ff484852"); 47 | 48 | private final Paint mPaint = new Paint(); 49 | private final RectF mTempBounds = new RectF(); 50 | 51 | private float mEyeInterval; 52 | private float mEyeCircleRadius; 53 | private float mMaxEyeJumptDistance; 54 | private float mAboveRadianEyeOffsetX; 55 | private float mEyeBallOffsetY; 56 | 57 | private float mEyeEdgeWidth; 58 | private float mEyeBallWidth; 59 | private float mEyeBallHeight; 60 | 61 | private float mLeftEyeCircleOffsetY; 62 | private float mRightEyeCircleOffsetY; 63 | private float mLeftEyeBallOffsetY; 64 | private float mRightEyeBallOffsetY; 65 | 66 | private int mColor; 67 | 68 | private GhostsEyeLoadingRenderer(Context context) { 69 | super(context); 70 | init(context); 71 | setupPaint(); 72 | } 73 | 74 | private void init(Context context) { 75 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 76 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 77 | mEyeEdgeWidth = DensityUtil.dip2px(context, DEFAULT_EYE_EDGE_WIDTH); 78 | 79 | mEyeInterval = DensityUtil.dip2px(context, DEFAULT_EYE_CIRCLE_INTERVAL); 80 | mEyeBallOffsetY = DensityUtil.dip2px(context, DEFAULT_EYE_BALL_OFFSET_Y); 81 | mEyeCircleRadius = DensityUtil.dip2px(context, DEFAULT_EYE_CIRCLE_RADIUS); 82 | mMaxEyeJumptDistance = DensityUtil.dip2px(context, DEFAULT_MAX_EYE_JUMP_DISTANCE); 83 | mAboveRadianEyeOffsetX = DensityUtil.dip2px(context, DEFAULT_ABOVE_RADIAN_EYE_CIRCLE_OFFSET); 84 | 85 | mEyeBallWidth = DensityUtil.dip2px(context, DEFAULT_EYE_BALL_WIDTH); 86 | mEyeBallHeight = DensityUtil.dip2px(context, DEFAULT_EYE_BALL_HEIGHT); 87 | 88 | mColor = DEFAULT_COLOR; 89 | 90 | mDuration = ANIMATION_DURATION; 91 | } 92 | 93 | private void setupPaint() { 94 | mPaint.setAntiAlias(true); 95 | mPaint.setStrokeWidth(mEyeEdgeWidth); 96 | mPaint.setStrokeJoin(Paint.Join.ROUND); 97 | mPaint.setStyle(Paint.Style.STROKE); 98 | mPaint.setStrokeCap(Paint.Cap.ROUND); 99 | } 100 | 101 | @Override 102 | protected void draw(Canvas canvas, Rect bounds) { 103 | int saveCount = canvas.save(); 104 | RectF arcBounds = mTempBounds; 105 | arcBounds.set(bounds); 106 | 107 | mPaint.setColor(mColor); 108 | 109 | mPaint.setStyle(Paint.Style.STROKE); 110 | canvas.drawPath(createLeftEyeCircle(arcBounds, mLeftEyeCircleOffsetY), mPaint); 111 | canvas.drawPath(createRightEyeCircle(arcBounds, mRightEyeCircleOffsetY), mPaint); 112 | 113 | mPaint.setStyle(Paint.Style.FILL); 114 | //create left eye ball 115 | canvas.drawOval(createLeftEyeBall(arcBounds, mLeftEyeBallOffsetY), mPaint); 116 | //create right eye ball 117 | canvas.drawOval(createRightEyeBall(arcBounds, mRightEyeBallOffsetY), mPaint); 118 | 119 | canvas.restoreToCount(saveCount); 120 | } 121 | 122 | @Override 123 | protected void computeRender(float renderProgress) { 124 | if (renderProgress <= LEFT_EYE_BALL_END_JUMP_OFFSET && renderProgress >= LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) { 125 | float eyeCircle$BallJumpUpProgress = (renderProgress - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (LEFT_EYE_BALL_END_JUMP_OFFSET - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET); 126 | mLeftEyeBallOffsetY = -mMaxEyeJumptDistance * EYE_BALL_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress); 127 | } 128 | 129 | if (renderProgress <= LEFT_EYE_CIRCLE_END_JUMP_OFFSET && renderProgress >= LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) { 130 | float eyeCircle$BallJumpUpProgress = (renderProgress - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (LEFT_EYE_CIRCLE_END_JUMP_OFFSET - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET); 131 | mLeftEyeCircleOffsetY = -mMaxEyeJumptDistance * EYE_CIRCLE_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress); 132 | } 133 | 134 | if (renderProgress <= RIGHT_EYE_BALL_END_JUMP_OFFSET && renderProgress >= RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) { 135 | float eyeCircle$BallJumpUpProgress = (renderProgress - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (RIGHT_EYE_BALL_END_JUMP_OFFSET - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET); 136 | mRightEyeBallOffsetY = -mMaxEyeJumptDistance * EYE_BALL_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress); 137 | } 138 | 139 | if (renderProgress <= RIGHT_EYE_CIRCLE_END_JUMP_OFFSET && renderProgress >= RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) { 140 | float eyeCircle$BallJumpUpProgress = (renderProgress - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (RIGHT_EYE_CIRCLE_END_JUMP_OFFSET - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET); 141 | mRightEyeCircleOffsetY = -mMaxEyeJumptDistance * EYE_CIRCLE_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress); 142 | } 143 | } 144 | 145 | @Override 146 | protected void setAlpha(int alpha) { 147 | 148 | } 149 | 150 | @Override 151 | protected void setColorFilter(ColorFilter cf) { 152 | 153 | } 154 | 155 | @Override 156 | protected void reset() { 157 | mLeftEyeBallOffsetY = 0.0f; 158 | mRightEyeBallOffsetY = 0.0f; 159 | mLeftEyeCircleOffsetY = 0.0f; 160 | mRightEyeCircleOffsetY = 0.0f; 161 | } 162 | 163 | private RectF createLeftEyeBall(RectF arcBounds, float offsetY) { 164 | //the center of the left eye 165 | float leftEyeCenterX = arcBounds.centerX() - mEyeInterval / 2.0f - mEyeCircleRadius; 166 | float leftEyeCenterY = arcBounds.centerY() - mEyeBallOffsetY + offsetY; 167 | 168 | RectF rectF = new RectF(leftEyeCenterX - mEyeBallWidth / 2.0f, leftEyeCenterY - mEyeBallHeight / 2.0f, 169 | leftEyeCenterX + mEyeBallWidth / 2.0f, leftEyeCenterY + mEyeBallHeight / 2.0f); 170 | 171 | return rectF; 172 | } 173 | 174 | private RectF createRightEyeBall(RectF arcBounds, float offsetY) { 175 | //the center of the right eye 176 | float rightEyeCenterX = arcBounds.centerX() + mEyeInterval / 2.0f + mEyeCircleRadius; 177 | float rightEyeCenterY = arcBounds.centerY() - mEyeBallOffsetY + offsetY; 178 | 179 | RectF rectF = new RectF(rightEyeCenterX - mEyeBallWidth / 2.0f, rightEyeCenterY - mEyeBallHeight / 2.0f, 180 | rightEyeCenterX + mEyeBallWidth / 2.0f, rightEyeCenterY + mEyeBallHeight / 2.0f); 181 | 182 | return rectF; 183 | } 184 | 185 | 186 | private Path createLeftEyeCircle(RectF arcBounds, float offsetY) { 187 | Path path = new Path(); 188 | 189 | //the center of the left eye 190 | float leftEyeCenterX = arcBounds.centerX() - mEyeInterval / 2.0f - mEyeCircleRadius; 191 | float leftEyeCenterY = arcBounds.centerY() + offsetY; 192 | //the bounds of left eye 193 | RectF leftEyeBounds = new RectF(leftEyeCenterX - mEyeCircleRadius, leftEyeCenterY - mEyeCircleRadius, 194 | leftEyeCenterX + mEyeCircleRadius, leftEyeCenterY + mEyeCircleRadius); 195 | path.addArc(leftEyeBounds, 0, DEGREE_180 + 15); 196 | //the above radian of of the eye 197 | path.quadTo(leftEyeBounds.left + mAboveRadianEyeOffsetX, leftEyeBounds.top + mEyeCircleRadius * 0.2f, 198 | leftEyeBounds.left + mAboveRadianEyeOffsetX / 4.0f, leftEyeBounds.top - mEyeCircleRadius * 0.15f); 199 | 200 | return path; 201 | } 202 | 203 | private Path createRightEyeCircle(RectF arcBounds, float offsetY) { 204 | Path path = new Path(); 205 | 206 | //the center of the right eye 207 | float rightEyeCenterX = arcBounds.centerX() + mEyeInterval / 2.0f + mEyeCircleRadius; 208 | float rightEyeCenterY = arcBounds.centerY() + offsetY; 209 | //the bounds of left eye 210 | RectF leftEyeBounds = new RectF(rightEyeCenterX - mEyeCircleRadius, rightEyeCenterY - mEyeCircleRadius, 211 | rightEyeCenterX + mEyeCircleRadius, rightEyeCenterY + mEyeCircleRadius); 212 | path.addArc(leftEyeBounds, 180, -(DEGREE_180 + 15)); 213 | //the above radian of of the eye 214 | path.quadTo(leftEyeBounds.right - mAboveRadianEyeOffsetX, leftEyeBounds.top + mEyeCircleRadius * 0.2f, 215 | leftEyeBounds.right - mAboveRadianEyeOffsetX / 4.0f, leftEyeBounds.top - mEyeCircleRadius * 0.15f); 216 | 217 | return path; 218 | } 219 | 220 | private class EyeCircleInterpolator implements Interpolator { 221 | 222 | @Override 223 | public float getInterpolation(float input) { 224 | if (input < 0.25f) { 225 | return input * 4.0f; 226 | } else if (input < 0.5f) { 227 | return 1.0f - (input - 0.25f) * 4.0f; 228 | } else if (input < 0.75f) { 229 | return (input - 0.5f) * 2.0f; 230 | } else { 231 | return 0.5f - (input - 0.75f) * 2.0f; 232 | } 233 | 234 | } 235 | } 236 | 237 | private class EyeBallInterpolator implements Interpolator { 238 | 239 | @Override 240 | public float getInterpolation(float input) { 241 | if (input < 0.333333f) { 242 | return input * 3.0f; 243 | } else { 244 | return 1.0f - (input - 0.333333f) * 1.5f; 245 | } 246 | } 247 | } 248 | 249 | public static class Builder { 250 | private Context mContext; 251 | 252 | public Builder(Context mContext) { 253 | this.mContext = mContext; 254 | } 255 | 256 | public GhostsEyeLoadingRenderer build() { 257 | GhostsEyeLoadingRenderer loadingRenderer = new GhostsEyeLoadingRenderer(mContext); 258 | return loadingRenderer; 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/CollisionLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.jump; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.LinearGradient; 8 | import android.graphics.Paint; 9 | import android.graphics.RectF; 10 | import android.graphics.Shader; 11 | import android.support.annotation.Size; 12 | import android.view.animation.AccelerateInterpolator; 13 | import android.view.animation.DecelerateInterpolator; 14 | import android.view.animation.Interpolator; 15 | 16 | import app.dinus.com.loadingdrawable.DensityUtil; 17 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 18 | 19 | public class CollisionLoadingRenderer extends LoadingRenderer { 20 | private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); 21 | private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); 22 | 23 | private static final int MAX_ALPHA = 255; 24 | private static final int OVAL_ALPHA = 64; 25 | 26 | private static final int DEFAULT_BALL_COUNT = 7; 27 | 28 | private static final float DEFAULT_OVAL_HEIGHT = 1.5f; 29 | private static final float DEFAULT_BALL_RADIUS = 7.5f; 30 | private static final float DEFAULT_WIDTH = 15.0f * 11; 31 | private static final float DEFAULT_HEIGHT = 15.0f * 4; 32 | 33 | private static final float START_LEFT_DURATION_OFFSET = 0.25f; 34 | private static final float START_RIGHT_DURATION_OFFSET = 0.5f; 35 | private static final float END_RIGHT_DURATION_OFFSET = 0.75f; 36 | private static final float END_LEFT_DURATION_OFFSET = 1.0f; 37 | 38 | private static final int[] DEFAULT_COLORS = new int[]{ 39 | Color.parseColor("#FF28435D"), Color.parseColor("#FFC32720") 40 | }; 41 | 42 | private static final float[] DEFAULT_POSITIONS = new float[]{ 43 | 0.0f, 1.0f 44 | }; 45 | 46 | private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 47 | private final RectF mOvalRect = new RectF(); 48 | 49 | @Size(2) 50 | private int[] mColors; 51 | private float[] mPositions; 52 | 53 | private float mOvalVerticalRadius; 54 | 55 | private float mBallRadius; 56 | private float mBallCenterY; 57 | private float mBallSideOffsets; 58 | private float mBallMoveXOffsets; 59 | private float mBallQuadCoefficient; 60 | 61 | private float mLeftBallMoveXOffsets; 62 | private float mLeftBallMoveYOffsets; 63 | private float mRightBallMoveXOffsets; 64 | private float mRightBallMoveYOffsets; 65 | 66 | private float mLeftOvalShapeRate; 67 | private float mRightOvalShapeRate; 68 | 69 | private int mBallCount; 70 | 71 | private CollisionLoadingRenderer(Context context) { 72 | super(context); 73 | init(context); 74 | adjustParams(); 75 | setupPaint(); 76 | } 77 | 78 | private void init(Context context) { 79 | mBallRadius = DensityUtil.dip2px(context, DEFAULT_BALL_RADIUS); 80 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 81 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 82 | mOvalVerticalRadius = DensityUtil.dip2px(context, DEFAULT_OVAL_HEIGHT); 83 | 84 | mColors = DEFAULT_COLORS; 85 | mPositions = DEFAULT_POSITIONS; 86 | 87 | mBallCount = DEFAULT_BALL_COUNT; 88 | 89 | //mBallMoveYOffsets = mBallQuadCoefficient * mBallMoveXOffsets ^ 2 90 | // ==> if mBallMoveYOffsets == mBallMoveXOffsets 91 | // ==> mBallQuadCoefficient = 1.0f / mBallMoveXOffsets; 92 | mBallMoveXOffsets = 1.5f * (2 * mBallRadius); 93 | mBallQuadCoefficient = 1.0f / mBallMoveXOffsets; 94 | } 95 | 96 | private void adjustParams() { 97 | mBallCenterY = mHeight / 2.0f; 98 | mBallSideOffsets = (mWidth - mBallRadius * 2.0f * (mBallCount - 2)) / 2; 99 | } 100 | 101 | private void setupPaint() { 102 | mPaint.setStyle(Paint.Style.FILL); 103 | mPaint.setShader(new LinearGradient(mBallSideOffsets, 0, mWidth - mBallSideOffsets, 0, 104 | mColors, mPositions, Shader.TileMode.CLAMP)); 105 | } 106 | 107 | @Override 108 | protected void draw(Canvas canvas) { 109 | int saveCount = canvas.save(); 110 | 111 | for (int i = 1; i < mBallCount - 1; i++) { 112 | mPaint.setAlpha(MAX_ALPHA); 113 | canvas.drawCircle(mBallRadius * (i * 2 - 1) + mBallSideOffsets, mBallCenterY, mBallRadius, mPaint); 114 | 115 | mOvalRect.set(mBallRadius * (i * 2 - 2) + mBallSideOffsets, mHeight - mOvalVerticalRadius * 2, 116 | mBallRadius * (i * 2) + mBallSideOffsets, mHeight); 117 | mPaint.setAlpha(OVAL_ALPHA); 118 | canvas.drawOval(mOvalRect, mPaint); 119 | } 120 | 121 | //draw the first ball 122 | mPaint.setAlpha(MAX_ALPHA); 123 | canvas.drawCircle(mBallSideOffsets - mBallRadius - mLeftBallMoveXOffsets, 124 | mBallCenterY - mLeftBallMoveYOffsets, mBallRadius, mPaint); 125 | 126 | mOvalRect.set(mBallSideOffsets - mBallRadius - mBallRadius * mLeftOvalShapeRate - mLeftBallMoveXOffsets, 127 | mHeight - mOvalVerticalRadius - mOvalVerticalRadius * mLeftOvalShapeRate, 128 | mBallSideOffsets - mBallRadius + mBallRadius * mLeftOvalShapeRate - mLeftBallMoveXOffsets, 129 | mHeight - mOvalVerticalRadius + mOvalVerticalRadius * mLeftOvalShapeRate); 130 | mPaint.setAlpha(OVAL_ALPHA); 131 | canvas.drawOval(mOvalRect, mPaint); 132 | 133 | //draw the last ball 134 | mPaint.setAlpha(MAX_ALPHA); 135 | canvas.drawCircle(mBallRadius * (mBallCount * 2 - 3) + mBallSideOffsets + mRightBallMoveXOffsets, 136 | mBallCenterY - mRightBallMoveYOffsets, mBallRadius, mPaint); 137 | 138 | mOvalRect.set(mBallRadius * (mBallCount * 2 - 3) - mBallRadius * mRightOvalShapeRate + mBallSideOffsets + mRightBallMoveXOffsets, 139 | mHeight - mOvalVerticalRadius - mOvalVerticalRadius * mRightOvalShapeRate, 140 | mBallRadius * (mBallCount * 2 - 3) + mBallRadius * mRightOvalShapeRate + mBallSideOffsets + mRightBallMoveXOffsets, 141 | mHeight - mOvalVerticalRadius + mOvalVerticalRadius * mRightOvalShapeRate); 142 | mPaint.setAlpha(OVAL_ALPHA); 143 | canvas.drawOval(mOvalRect, mPaint); 144 | 145 | canvas.restoreToCount(saveCount); 146 | } 147 | 148 | @Override 149 | protected void computeRender(float renderProgress) { 150 | // Moving the left ball to the left sides only occurs in the first 25% of a jump animation 151 | if (renderProgress <= START_LEFT_DURATION_OFFSET) { 152 | float startLeftOffsetProgress = renderProgress / START_LEFT_DURATION_OFFSET; 153 | computeLeftBallMoveOffsets(DECELERATE_INTERPOLATOR.getInterpolation(startLeftOffsetProgress)); 154 | return; 155 | } 156 | 157 | // Moving the left ball to the origin location only occurs between 25% and 50% of a jump ring animation 158 | if (renderProgress <= START_RIGHT_DURATION_OFFSET) { 159 | float startRightOffsetProgress = (renderProgress - START_LEFT_DURATION_OFFSET) / (START_RIGHT_DURATION_OFFSET - START_LEFT_DURATION_OFFSET); 160 | computeLeftBallMoveOffsets(ACCELERATE_INTERPOLATOR.getInterpolation(1.0f - startRightOffsetProgress)); 161 | return; 162 | } 163 | 164 | // Moving the right ball to the right sides only occurs between 50% and 75% of a jump animation 165 | if (renderProgress <= END_RIGHT_DURATION_OFFSET) { 166 | float endRightOffsetProgress = (renderProgress - START_RIGHT_DURATION_OFFSET) / (END_RIGHT_DURATION_OFFSET - START_RIGHT_DURATION_OFFSET); 167 | computeRightBallMoveOffsets(DECELERATE_INTERPOLATOR.getInterpolation(endRightOffsetProgress)); 168 | return; 169 | } 170 | 171 | // Moving the right ball to the origin location only occurs after 75% of a jump animation 172 | if (renderProgress <= END_LEFT_DURATION_OFFSET) { 173 | float endRightOffsetProgress = (renderProgress - END_RIGHT_DURATION_OFFSET) / (END_LEFT_DURATION_OFFSET - END_RIGHT_DURATION_OFFSET); 174 | computeRightBallMoveOffsets(ACCELERATE_INTERPOLATOR.getInterpolation(1 - endRightOffsetProgress)); 175 | return; 176 | } 177 | 178 | } 179 | 180 | private void computeLeftBallMoveOffsets(float progress) { 181 | mRightBallMoveXOffsets = 0.0f; 182 | mRightBallMoveYOffsets = 0.0f; 183 | 184 | mLeftOvalShapeRate = 1.0f - progress; 185 | mLeftBallMoveXOffsets = mBallMoveXOffsets * progress; 186 | mLeftBallMoveYOffsets = (float) (Math.pow(mLeftBallMoveXOffsets, 2) * mBallQuadCoefficient); 187 | } 188 | 189 | private void computeRightBallMoveOffsets(float progress) { 190 | mLeftBallMoveXOffsets = 0.0f; 191 | mLeftBallMoveYOffsets = 0.0f; 192 | 193 | mRightOvalShapeRate = 1.0f - progress; 194 | mRightBallMoveXOffsets = mBallMoveXOffsets * progress; 195 | mRightBallMoveYOffsets = (float) (Math.pow(mRightBallMoveXOffsets, 2) * mBallQuadCoefficient); 196 | } 197 | 198 | @Override 199 | protected void setAlpha(int alpha) { 200 | mPaint.setAlpha(alpha); 201 | } 202 | 203 | @Override 204 | protected void setColorFilter(ColorFilter cf) { 205 | mPaint.setColorFilter(cf); 206 | } 207 | 208 | @Override 209 | protected void reset() { 210 | } 211 | 212 | private void apply(Builder builder) { 213 | this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; 214 | this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; 215 | 216 | this.mOvalVerticalRadius = builder.mOvalVerticalRadius > 0 ? builder.mOvalVerticalRadius : this.mOvalVerticalRadius; 217 | this.mBallRadius = builder.mBallRadius > 0 ? builder.mBallRadius : this.mBallRadius; 218 | this.mBallMoveXOffsets = builder.mBallMoveXOffsets > 0 ? builder.mBallMoveXOffsets : this.mBallMoveXOffsets; 219 | this.mBallQuadCoefficient = builder.mBallQuadCoefficient > 0 ? builder.mBallQuadCoefficient : this.mBallQuadCoefficient; 220 | this.mBallCount = builder.mBallCount > 0 ? builder.mBallCount : this.mBallCount; 221 | 222 | this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; 223 | 224 | this.mColors = builder.mColors != null ? builder.mColors : this.mColors; 225 | 226 | adjustParams(); 227 | setupPaint(); 228 | } 229 | 230 | public static class Builder { 231 | private Context mContext; 232 | 233 | private int mWidth; 234 | private int mHeight; 235 | 236 | private float mOvalVerticalRadius; 237 | 238 | private int mBallCount; 239 | private float mBallRadius; 240 | private float mBallMoveXOffsets; 241 | private float mBallQuadCoefficient; 242 | 243 | private int mDuration; 244 | 245 | @Size(2) 246 | private int[] mColors; 247 | 248 | public Builder(Context mContext) { 249 | this.mContext = mContext; 250 | } 251 | 252 | public Builder setWidth(int width) { 253 | this.mWidth = width; 254 | return this; 255 | } 256 | 257 | public Builder setHeight(int height) { 258 | this.mHeight = height; 259 | return this; 260 | } 261 | 262 | public Builder setOvalVerticalRadius(int ovalVerticalRadius) { 263 | this.mOvalVerticalRadius = ovalVerticalRadius; 264 | return this; 265 | } 266 | 267 | public Builder setBallRadius(int ballRadius) { 268 | this.mBallRadius = ballRadius; 269 | return this; 270 | } 271 | 272 | public Builder setBallMoveXOffsets(int ballMoveXOffsets) { 273 | this.mBallMoveXOffsets = ballMoveXOffsets; 274 | return this; 275 | } 276 | 277 | public Builder setBallQuadCoefficient(int ballQuadCoefficient) { 278 | this.mBallQuadCoefficient = ballQuadCoefficient; 279 | return this; 280 | } 281 | 282 | public Builder setBallCount(int ballCount) { 283 | this.mBallCount = ballCount; 284 | return this; 285 | } 286 | 287 | public Builder setColors(@Size(2) int[] colors) { 288 | this.mColors = colors; 289 | return this; 290 | } 291 | 292 | public Builder setDuration(int duration) { 293 | this.mDuration = duration; 294 | return this; 295 | } 296 | 297 | public CollisionLoadingRenderer build() { 298 | CollisionLoadingRenderer loadingRenderer = new CollisionLoadingRenderer(mContext); 299 | loadingRenderer.apply(this); 300 | return loadingRenderer; 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/DanceLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.jump; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Rect; 9 | import android.graphics.RectF; 10 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 11 | import android.util.DisplayMetrics; 12 | import android.util.TypedValue; 13 | import android.view.animation.AccelerateInterpolator; 14 | import android.view.animation.DecelerateInterpolator; 15 | import android.view.animation.Interpolator; 16 | 17 | import app.dinus.com.loadingdrawable.DensityUtil; 18 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 19 | 20 | public class DanceLoadingRenderer extends LoadingRenderer { 21 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 22 | private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); 23 | private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); 24 | 25 | private static final long ANIMATION_DURATION = 1888; 26 | 27 | private static final float DEFAULT_CENTER_RADIUS = 12.5f; 28 | private static final float DEFAULT_STROKE_WIDTH = 1.5f; 29 | private static final float DEFAULT_DANCE_BALL_RADIUS = 2.0f; 30 | 31 | private static final int NUM_POINTS = 3; 32 | private static final int DEGREE_360 = 360; 33 | private static final int RING_START_ANGLE = -90; 34 | private static final int DANCE_START_ANGLE = 0; 35 | private static final int DANCE_INTERVAL_ANGLE = 60; 36 | 37 | private static final int DEFAULT_COLOR = Color.WHITE; 38 | 39 | //the center coordinate of the oval 40 | private static final float[] POINT_X = new float[NUM_POINTS]; 41 | private static final float[] POINT_Y = new float[NUM_POINTS]; 42 | //1: the coordinate x from small to large; -1: the coordinate x from large to small 43 | private static final int[] DIRECTION = new int[]{1, 1, -1}; 44 | 45 | private static final float BALL_FORWARD_START_ENTER_DURATION_OFFSET = 0f; 46 | private static final float BALL_FORWARD_END_ENTER_DURATION_OFFSET = 0.125f; 47 | 48 | private static final float RING_FORWARD_START_ROTATE_DURATION_OFFSET = 0.125f; 49 | private static final float RING_FORWARD_END_ROTATE_DURATION_OFFSET = 0.375f; 50 | 51 | private static final float CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET = 0.225f; 52 | private static final float CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET = 0.475f; 53 | 54 | private static final float BALL_FORWARD_START_EXIT_DURATION_OFFSET = 0.375f; 55 | private static final float BALL_FORWARD_END_EXIT_DURATION_OFFSET = 0.54f; 56 | 57 | private static final float RING_REVERSAL_START_ROTATE_DURATION_OFFSET = 0.5f; 58 | private static final float RING_REVERSAL_END_ROTATE_DURATION_OFFSET = 0.75f; 59 | 60 | private static final float BALL_REVERSAL_START_ENTER_DURATION_OFFSET = 0.6f; 61 | private static final float BALL_REVERSAL_END_ENTER_DURATION_OFFSET = 0.725f; 62 | 63 | private static final float CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET = 0.675f; 64 | private static final float CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET = 0.875f; 65 | 66 | private static final float BALL_REVERSAL_START_EXIT_DURATION_OFFSET = 0.875f; 67 | private static final float BALL_REVERSAL_END_EXIT_DURATION_OFFSET = 1.0f; 68 | 69 | private final Paint mPaint = new Paint(); 70 | private final RectF mTempBounds = new RectF(); 71 | private final RectF mCurrentBounds = new RectF(); 72 | 73 | private float mScale; 74 | private float mRotation; 75 | private float mStrokeInset; 76 | 77 | private float mCenterRadius; 78 | private float mStrokeWidth; 79 | private float mDanceBallRadius; 80 | private float mShapeChangeWidth; 81 | private float mShapeChangeHeight; 82 | 83 | private int mColor; 84 | private int mArcColor; 85 | 86 | private DanceLoadingRenderer(Context context) { 87 | super(context); 88 | init(context); 89 | setupPaint(); 90 | } 91 | 92 | private void init(Context context) { 93 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 94 | mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); 95 | mDanceBallRadius = DensityUtil.dip2px(context, DEFAULT_DANCE_BALL_RADIUS); 96 | 97 | setColor(DEFAULT_COLOR); 98 | setInsets((int) mWidth, (int) mHeight); 99 | mDuration = ANIMATION_DURATION; 100 | } 101 | 102 | private void setupPaint() { 103 | mPaint.setAntiAlias(true); 104 | mPaint.setStrokeWidth(mStrokeWidth); 105 | mPaint.setStyle(Paint.Style.STROKE); 106 | } 107 | 108 | @Override 109 | protected void draw(Canvas canvas, Rect bounds) { 110 | int saveCount = canvas.save(); 111 | 112 | mTempBounds.set(bounds); 113 | mTempBounds.inset(mStrokeInset, mStrokeInset); 114 | mCurrentBounds.set(mTempBounds); 115 | 116 | float outerCircleRadius = Math.min(mTempBounds.height(), mTempBounds.width()) / 2.0f; 117 | float interCircleRadius = outerCircleRadius / 2.0f; 118 | float centerRingWidth = interCircleRadius - mStrokeWidth / 2; 119 | 120 | mPaint.setStyle(Paint.Style.STROKE); 121 | mPaint.setColor(mColor); 122 | mPaint.setStrokeWidth(mStrokeWidth); 123 | canvas.drawCircle(mTempBounds.centerX(), mTempBounds.centerY(), outerCircleRadius, mPaint); 124 | mPaint.setStyle(Paint.Style.FILL); 125 | canvas.drawCircle(mTempBounds.centerX(), mTempBounds.centerY(), interCircleRadius * mScale, mPaint); 126 | 127 | if (mRotation != 0) { 128 | mPaint.setColor(mArcColor); 129 | mPaint.setStyle(Paint.Style.STROKE); 130 | //strokeWidth / 2.0f + mStrokeWidth / 2.0f is the center of the inter circle width 131 | mTempBounds.inset(centerRingWidth / 2.0f + mStrokeWidth / 2.0f, centerRingWidth / 2.0f + mStrokeWidth / 2.0f); 132 | mPaint.setStrokeWidth(centerRingWidth); 133 | canvas.drawArc(mTempBounds, RING_START_ANGLE, mRotation, false, mPaint); 134 | } 135 | 136 | mPaint.setColor(mColor); 137 | mPaint.setStyle(Paint.Style.FILL); 138 | for (int i = 0; i < NUM_POINTS; i++) { 139 | canvas.rotate(i * DANCE_INTERVAL_ANGLE, POINT_X[i], POINT_Y[i]); 140 | RectF rectF = new RectF(POINT_X[i] - mDanceBallRadius - mShapeChangeWidth / 2.0f, 141 | POINT_Y[i] - mDanceBallRadius - mShapeChangeHeight / 2.0f, 142 | POINT_X[i] + mDanceBallRadius + mShapeChangeWidth / 2.0f, 143 | POINT_Y[i] + mDanceBallRadius + mShapeChangeHeight / 2.0f); 144 | canvas.drawOval(rectF, mPaint); 145 | canvas.rotate(-i * DANCE_INTERVAL_ANGLE, POINT_X[i], POINT_Y[i]); 146 | } 147 | 148 | canvas.restoreToCount(saveCount); 149 | } 150 | 151 | @Override 152 | protected void computeRender(float renderProgress) { 153 | float radius = Math.min(mCurrentBounds.height(), mCurrentBounds.width()) / 2.0f; 154 | //the origin coordinate is the centerLeft of the field mCurrentBounds 155 | float originCoordinateX = mCurrentBounds.left; 156 | float originCoordinateY = mCurrentBounds.top + radius; 157 | 158 | if (renderProgress <= BALL_FORWARD_END_ENTER_DURATION_OFFSET && renderProgress > BALL_FORWARD_START_ENTER_DURATION_OFFSET) { 159 | final float ballForwardEnterProgress = (renderProgress - BALL_FORWARD_START_ENTER_DURATION_OFFSET) / (BALL_FORWARD_END_ENTER_DURATION_OFFSET - BALL_FORWARD_START_ENTER_DURATION_OFFSET); 160 | 161 | mShapeChangeHeight = (0.5f - ballForwardEnterProgress) * mDanceBallRadius / 2.0f; 162 | mShapeChangeWidth = -mShapeChangeHeight; 163 | //y = k(x - r)--> k = tan(angle) 164 | //(x - r)^2 + y^2 = r^2 165 | // compute crossover point --> (k(x -r)) ^ 2 + (x - )^2 = r^2 166 | // so x --> [r + r / sqrt(k ^ 2 + 1), r - r / sqrt(k ^ 2 + 1)] 167 | for (int i = 0; i < NUM_POINTS; i++) { 168 | float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI)); 169 | // progress[-1, 1] 170 | float progress = (ACCELERATE_INTERPOLATOR.getInterpolation(ballForwardEnterProgress) / 2.0f - 0.5f) * 2.0f * DIRECTION[i]; 171 | POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f))); 172 | POINT_Y[i] = k * (POINT_X[i] - radius); 173 | 174 | POINT_X[i] += originCoordinateX; 175 | POINT_Y[i] += originCoordinateY; 176 | } 177 | } 178 | 179 | if (renderProgress <= RING_FORWARD_END_ROTATE_DURATION_OFFSET && renderProgress > RING_FORWARD_START_ROTATE_DURATION_OFFSET) { 180 | final float forwardRotateProgress = (renderProgress - RING_FORWARD_START_ROTATE_DURATION_OFFSET) / (RING_FORWARD_END_ROTATE_DURATION_OFFSET - RING_FORWARD_START_ROTATE_DURATION_OFFSET); 181 | mRotation = DEGREE_360 * MATERIAL_INTERPOLATOR.getInterpolation(forwardRotateProgress); 182 | } 183 | 184 | if (renderProgress <= CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET && renderProgress > CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET) { 185 | final float centerCircleScaleProgress = (renderProgress - CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET) / (CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET - CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET); 186 | 187 | if (centerCircleScaleProgress <= 0.5f) { 188 | mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(centerCircleScaleProgress * 2.0f) * 0.2f; 189 | } else { 190 | mScale = 1.2f - ACCELERATE_INTERPOLATOR.getInterpolation((centerCircleScaleProgress - 0.5f) * 2.0f) * 0.2f; 191 | } 192 | 193 | } 194 | 195 | if (renderProgress <= BALL_FORWARD_END_EXIT_DURATION_OFFSET && renderProgress > BALL_FORWARD_START_EXIT_DURATION_OFFSET) { 196 | final float ballForwardExitProgress = (renderProgress - BALL_FORWARD_START_EXIT_DURATION_OFFSET) / (BALL_FORWARD_END_EXIT_DURATION_OFFSET - BALL_FORWARD_START_EXIT_DURATION_OFFSET); 197 | mShapeChangeHeight = (ballForwardExitProgress - 0.5f) * mDanceBallRadius / 2.0f; 198 | mShapeChangeWidth = -mShapeChangeHeight; 199 | for (int i = 0; i < NUM_POINTS; i++) { 200 | float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI)); 201 | float progress = (DECELERATE_INTERPOLATOR.getInterpolation(ballForwardExitProgress) / 2.0f) * 2.0f * DIRECTION[i]; 202 | POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f))); 203 | POINT_Y[i] = k * (POINT_X[i] - radius); 204 | 205 | POINT_X[i] += originCoordinateX; 206 | POINT_Y[i] += originCoordinateY; 207 | } 208 | } 209 | 210 | if (renderProgress <= RING_REVERSAL_END_ROTATE_DURATION_OFFSET && renderProgress > RING_REVERSAL_START_ROTATE_DURATION_OFFSET) { 211 | float scaledTime = (renderProgress - RING_REVERSAL_START_ROTATE_DURATION_OFFSET) / (RING_REVERSAL_END_ROTATE_DURATION_OFFSET - RING_REVERSAL_START_ROTATE_DURATION_OFFSET); 212 | mRotation = DEGREE_360 * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime) - 360; 213 | } else if (renderProgress > RING_REVERSAL_END_ROTATE_DURATION_OFFSET) { 214 | mRotation = 0.0f; 215 | } 216 | 217 | if (renderProgress <= BALL_REVERSAL_END_ENTER_DURATION_OFFSET && renderProgress > BALL_REVERSAL_START_ENTER_DURATION_OFFSET) { 218 | final float ballReversalEnterProgress = (renderProgress - BALL_REVERSAL_START_ENTER_DURATION_OFFSET) / (BALL_REVERSAL_END_ENTER_DURATION_OFFSET - BALL_REVERSAL_START_ENTER_DURATION_OFFSET); 219 | mShapeChangeHeight = (0.5f - ballReversalEnterProgress) * mDanceBallRadius / 2.0f; 220 | mShapeChangeWidth = -mShapeChangeHeight; 221 | 222 | for (int i = 0; i < NUM_POINTS; i++) { 223 | float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI)); 224 | float progress = (0.5f - ACCELERATE_INTERPOLATOR.getInterpolation(ballReversalEnterProgress) / 2.0f) * 2.0f * DIRECTION[i]; 225 | POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f))); 226 | POINT_Y[i] = k * (POINT_X[i] - radius); 227 | 228 | POINT_X[i] += originCoordinateX; 229 | POINT_Y[i] += originCoordinateY; 230 | } 231 | } 232 | 233 | if (renderProgress <= CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET && renderProgress > CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET) { 234 | final float centerCircleScaleProgress = (renderProgress - CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET) / (CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET - CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET); 235 | 236 | if (centerCircleScaleProgress <= 0.5f) { 237 | mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(centerCircleScaleProgress * 2.0f) * 0.2f; 238 | } else { 239 | mScale = 1.2f - ACCELERATE_INTERPOLATOR.getInterpolation((centerCircleScaleProgress - 0.5f) * 2.0f) * 0.2f; 240 | } 241 | 242 | } 243 | 244 | if (renderProgress <= BALL_REVERSAL_END_EXIT_DURATION_OFFSET && renderProgress > BALL_REVERSAL_START_EXIT_DURATION_OFFSET) { 245 | final float ballReversalExitProgress = (renderProgress - BALL_REVERSAL_START_EXIT_DURATION_OFFSET) / (BALL_REVERSAL_END_EXIT_DURATION_OFFSET - BALL_REVERSAL_START_EXIT_DURATION_OFFSET); 246 | mShapeChangeHeight = (ballReversalExitProgress - 0.5f) * mDanceBallRadius / 2.0f; 247 | mShapeChangeWidth = -mShapeChangeHeight; 248 | 249 | for (int i = 0; i < NUM_POINTS; i++) { 250 | float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI)); 251 | float progress = (0.0f - DECELERATE_INTERPOLATOR.getInterpolation(ballReversalExitProgress) / 2.0f) * 2.0f * DIRECTION[i]; 252 | POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f))); 253 | POINT_Y[i] = k * (POINT_X[i] - radius); 254 | 255 | POINT_X[i] += originCoordinateX; 256 | POINT_Y[i] += originCoordinateY; 257 | } 258 | } 259 | } 260 | 261 | @Override 262 | protected void setAlpha(int alpha) { 263 | mPaint.setAlpha(alpha); 264 | 265 | } 266 | 267 | @Override 268 | protected void setColorFilter(ColorFilter cf) { 269 | mPaint.setColorFilter(cf); 270 | 271 | } 272 | 273 | @Override 274 | protected void reset() { 275 | mScale = 1.0f; 276 | mRotation = 0; 277 | } 278 | 279 | private void setColor(int color) { 280 | mColor = color; 281 | mArcColor = halfAlphaColor(mColor); 282 | } 283 | 284 | private void setRotation(float rotation) { 285 | mRotation = rotation; 286 | 287 | } 288 | 289 | private void setDanceBallRadius(float danceBallRadius) { 290 | this.mDanceBallRadius = danceBallRadius; 291 | 292 | } 293 | 294 | private float getDanceBallRadius() { 295 | return mDanceBallRadius; 296 | } 297 | 298 | private float getRotation() { 299 | return mRotation; 300 | } 301 | 302 | private void setInsets(int width, int height) { 303 | final float minEdge = (float) Math.min(width, height); 304 | float insets; 305 | if (mCenterRadius <= 0 || minEdge < 0) { 306 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 307 | } else { 308 | insets = minEdge / 2.0f - mCenterRadius; 309 | } 310 | mStrokeInset = insets; 311 | } 312 | 313 | private int halfAlphaColor(int colorValue) { 314 | int startA = (colorValue >> 24) & 0xff; 315 | int startR = (colorValue >> 16) & 0xff; 316 | int startG = (colorValue >> 8) & 0xff; 317 | int startB = colorValue & 0xff; 318 | 319 | return ((startA / 2) << 24) 320 | | (startR << 16) 321 | | (startG << 8) 322 | | startB; 323 | } 324 | 325 | public static class Builder { 326 | private Context mContext; 327 | 328 | public Builder(Context mContext) { 329 | this.mContext = mContext; 330 | } 331 | 332 | public DanceLoadingRenderer build() { 333 | DanceLoadingRenderer loadingRenderer = new DanceLoadingRenderer(mContext); 334 | return loadingRenderer; 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/GuardLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.jump; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.PathMeasure; 10 | import android.graphics.Rect; 11 | import android.graphics.RectF; 12 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 13 | import android.util.DisplayMetrics; 14 | import android.view.animation.AccelerateInterpolator; 15 | import android.view.animation.DecelerateInterpolator; 16 | import android.view.animation.Interpolator; 17 | 18 | import app.dinus.com.loadingdrawable.DensityUtil; 19 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 20 | 21 | public class GuardLoadingRenderer extends LoadingRenderer { 22 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 23 | private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); 24 | private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); 25 | 26 | private static final long ANIMATION_DURATION = 5000; 27 | 28 | private static final float DEFAULT_STROKE_WIDTH = 1.0f; 29 | private static final float DEFAULT_CENTER_RADIUS = 12.5f; 30 | private static final float DEFAULT_SKIP_BALL_RADIUS = 1.0f; 31 | 32 | private static final float START_TRIM_INIT_ROTATION = -0.5f; 33 | private static final float START_TRIM_MAX_ROTATION = -0.25f; 34 | private static final float END_TRIM_INIT_ROTATION = 0.25f; 35 | private static final float END_TRIM_MAX_ROTATION = 0.75f; 36 | 37 | private static final float START_TRIM_DURATION_OFFSET = 0.23f; 38 | private static final float WAVE_DURATION_OFFSET = 0.36f; 39 | private static final float BALL_SKIP_DURATION_OFFSET = 0.74f; 40 | private static final float BALL_SCALE_DURATION_OFFSET = 0.82f; 41 | private static final float END_TRIM_DURATION_OFFSET = 1.0f; 42 | 43 | private static final int DEFAULT_COLOR = Color.WHITE; 44 | private static final int DEFAULT_BALL_COLOR = Color.RED; 45 | 46 | private final Paint mPaint = new Paint(); 47 | private final RectF mTempBounds = new RectF(); 48 | private final RectF mCurrentBounds = new RectF(); 49 | private final float[] mCurrentPosition = new float[2]; 50 | 51 | private float mStrokeInset; 52 | private float mSkipBallSize; 53 | 54 | private float mScale; 55 | private float mEndTrim; 56 | private float mRotation; 57 | private float mStartTrim; 58 | private float mWaveProgress; 59 | 60 | private float mStrokeWidth; 61 | private float mCenterRadius; 62 | 63 | private int mColor; 64 | private int mBallColor; 65 | 66 | private PathMeasure mPathMeasure; 67 | 68 | private GuardLoadingRenderer(Context context) { 69 | super(context); 70 | 71 | mDuration = ANIMATION_DURATION; 72 | init(context); 73 | setupPaint(); 74 | } 75 | 76 | private void init(Context context) { 77 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 78 | mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); 79 | mSkipBallSize = DensityUtil.dip2px(context, DEFAULT_SKIP_BALL_RADIUS); 80 | 81 | mColor = DEFAULT_COLOR; 82 | mBallColor = DEFAULT_BALL_COLOR; 83 | } 84 | 85 | private void setupPaint() { 86 | mPaint.setAntiAlias(true); 87 | mPaint.setStrokeWidth(mStrokeWidth); 88 | mPaint.setStyle(Paint.Style.STROKE); 89 | mPaint.setStrokeCap(Paint.Cap.ROUND); 90 | 91 | setInsets((int) mWidth, (int) mHeight); 92 | } 93 | 94 | @Override 95 | protected void draw(Canvas canvas, Rect bounds) { 96 | RectF arcBounds = mTempBounds; 97 | arcBounds.set(bounds); 98 | arcBounds.inset(mStrokeInset, mStrokeInset); 99 | mCurrentBounds.set(arcBounds); 100 | 101 | int saveCount = canvas.save(); 102 | 103 | //draw circle trim 104 | float startAngle = (mStartTrim + mRotation) * 360; 105 | float endAngle = (mEndTrim + mRotation) * 360; 106 | float sweepAngle = endAngle - startAngle; 107 | if (sweepAngle != 0) { 108 | mPaint.setColor(mColor); 109 | mPaint.setStyle(Paint.Style.STROKE); 110 | canvas.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 111 | } 112 | 113 | //draw water wave 114 | if (mWaveProgress < 1.0f) { 115 | mPaint.setColor(Color.argb((int) (Color.alpha(mColor) * (1.0f - mWaveProgress)), 116 | Color.red(mColor), Color.green(mColor), Color.blue(mColor))); 117 | mPaint.setStyle(Paint.Style.STROKE); 118 | float radius = Math.min(arcBounds.width(), arcBounds.height()) / 2.0f; 119 | canvas.drawCircle(arcBounds.centerX(), arcBounds.centerY(), radius * (1.0f + mWaveProgress), mPaint); 120 | } 121 | //draw ball bounce 122 | if (mPathMeasure != null) { 123 | mPaint.setColor(mBallColor); 124 | mPaint.setStyle(Paint.Style.FILL); 125 | canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], mSkipBallSize * mScale, mPaint); 126 | } 127 | 128 | canvas.restoreToCount(saveCount); 129 | } 130 | 131 | @Override 132 | protected void computeRender(float renderProgress) { 133 | if (renderProgress <= START_TRIM_DURATION_OFFSET) { 134 | final float startTrimProgress = (renderProgress) / START_TRIM_DURATION_OFFSET; 135 | mEndTrim = -MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress); 136 | mRotation = START_TRIM_INIT_ROTATION + START_TRIM_MAX_ROTATION 137 | * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress); 138 | } 139 | 140 | if (renderProgress <= WAVE_DURATION_OFFSET && renderProgress > START_TRIM_DURATION_OFFSET) { 141 | final float waveProgress = (renderProgress - START_TRIM_DURATION_OFFSET) 142 | / (WAVE_DURATION_OFFSET - START_TRIM_DURATION_OFFSET); 143 | mWaveProgress = ACCELERATE_INTERPOLATOR.getInterpolation(waveProgress); 144 | } 145 | 146 | if (renderProgress <= BALL_SKIP_DURATION_OFFSET && renderProgress > WAVE_DURATION_OFFSET) { 147 | if (mPathMeasure == null) { 148 | mPathMeasure = new PathMeasure(createSkipBallPath(), false); 149 | } 150 | 151 | final float ballSkipProgress = (renderProgress - WAVE_DURATION_OFFSET) 152 | / (BALL_SKIP_DURATION_OFFSET - WAVE_DURATION_OFFSET); 153 | mPathMeasure.getPosTan(ballSkipProgress * mPathMeasure.getLength(), mCurrentPosition, null); 154 | 155 | mWaveProgress = 1.0f; 156 | } 157 | 158 | if (renderProgress <= BALL_SCALE_DURATION_OFFSET && renderProgress > BALL_SKIP_DURATION_OFFSET) { 159 | final float ballScaleProgress = 160 | (renderProgress - BALL_SKIP_DURATION_OFFSET) 161 | / (BALL_SCALE_DURATION_OFFSET - BALL_SKIP_DURATION_OFFSET); 162 | if (ballScaleProgress < 0.5f) { 163 | mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(ballScaleProgress * 2.0f); 164 | } else { 165 | mScale = 2.0f - ACCELERATE_INTERPOLATOR.getInterpolation((ballScaleProgress - 0.5f) * 2.0f) * 2.0f; 166 | } 167 | } 168 | 169 | if (renderProgress >= BALL_SCALE_DURATION_OFFSET) { 170 | final float endTrimProgress = 171 | (renderProgress - BALL_SKIP_DURATION_OFFSET) 172 | / (END_TRIM_DURATION_OFFSET - BALL_SKIP_DURATION_OFFSET); 173 | mEndTrim = -1 + MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress); 174 | mRotation = END_TRIM_INIT_ROTATION + END_TRIM_MAX_ROTATION 175 | * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress); 176 | 177 | mScale = 1.0f; 178 | mPathMeasure = null; 179 | } 180 | } 181 | 182 | @Override 183 | protected void setAlpha(int alpha) { 184 | mPaint.setAlpha(alpha); 185 | 186 | } 187 | 188 | @Override 189 | protected void setColorFilter(ColorFilter cf) { 190 | mPaint.setColorFilter(cf); 191 | 192 | } 193 | 194 | @Override 195 | protected void reset() { 196 | mScale = 1.0f; 197 | mEndTrim = 0.0f; 198 | mRotation = 0.0f; 199 | mStartTrim = 0.0f; 200 | mWaveProgress = 1.0f; 201 | } 202 | 203 | private Path createSkipBallPath() { 204 | float radius = Math.min(mCurrentBounds.width(), mCurrentBounds.height()) / 2.0f; 205 | float radiusPow2 = (float) Math.pow(radius, 2.0f); 206 | float originCoordinateX = mCurrentBounds.centerX(); 207 | float originCoordinateY = mCurrentBounds.centerY(); 208 | 209 | float[] coordinateX = new float[]{0.0f, 0.0f, -0.8f * radius, 0.75f * radius, 210 | -0.45f * radius, 0.9f * radius, -0.5f * radius}; 211 | float[] sign = new float[]{1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f}; 212 | 213 | Path path = new Path(); 214 | for (int i = 0; i < coordinateX.length; i++) { 215 | // x^2 + y^2 = radius^2 --> y = sqrt(radius^2 - x^2) 216 | if (i == 0) { 217 | path.moveTo( 218 | originCoordinateX + coordinateX[i], 219 | originCoordinateY + sign[i] 220 | * (float) Math.sqrt(radiusPow2 - Math.pow(coordinateX[i], 2.0f))); 221 | continue; 222 | } 223 | 224 | path.lineTo( 225 | originCoordinateX + coordinateX[i], 226 | originCoordinateY + sign[i] 227 | * (float) Math.sqrt(radiusPow2 - Math.pow(coordinateX[i], 2.0f))); 228 | 229 | if (i == coordinateX.length - 1) { 230 | path.lineTo(originCoordinateX, originCoordinateY); 231 | } 232 | } 233 | return path; 234 | } 235 | 236 | private void setInsets(int width, int height) { 237 | final float minEdge = (float) Math.min(width, height); 238 | float insets; 239 | if (mCenterRadius <= 0 || minEdge < 0) { 240 | insets = (float) Math.ceil(mStrokeWidth / 2.0f); 241 | } else { 242 | insets = minEdge / 2.0f - mCenterRadius; 243 | } 244 | mStrokeInset = insets; 245 | } 246 | 247 | public static class Builder { 248 | private Context mContext; 249 | 250 | public Builder(Context mContext) { 251 | this.mContext = mContext; 252 | } 253 | 254 | public GuardLoadingRenderer build() { 255 | GuardLoadingRenderer loadingRenderer = new GuardLoadingRenderer(mContext); 256 | return loadingRenderer; 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/SwapLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.jump; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.view.animation.AccelerateDecelerateInterpolator; 9 | import android.view.animation.Interpolator; 10 | 11 | import app.dinus.com.loadingdrawable.DensityUtil; 12 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 13 | 14 | public class SwapLoadingRenderer extends LoadingRenderer { 15 | private static final Interpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 16 | 17 | private static final long ANIMATION_DURATION = 2500; 18 | 19 | private static final int DEFAULT_CIRCLE_COUNT = 5; 20 | 21 | private static final float DEFAULT_BALL_RADIUS = 7.5f; 22 | private static final float DEFAULT_WIDTH = 15.0f * 11; 23 | private static final float DEFAULT_HEIGHT = 15.0f * 5; 24 | private static final float DEFAULT_STROKE_WIDTH = 1.5f; 25 | 26 | private static final int DEFAULT_COLOR = Color.WHITE; 27 | 28 | private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 29 | 30 | private int mColor; 31 | 32 | private int mSwapIndex; 33 | private int mBallCount; 34 | 35 | private float mBallSideOffsets; 36 | private float mBallCenterY; 37 | private float mBallRadius; 38 | private float mBallInterval; 39 | private float mSwapBallOffsetX; 40 | private float mSwapBallOffsetY; 41 | private float mASwapThreshold; 42 | 43 | private float mStrokeWidth; 44 | 45 | private SwapLoadingRenderer(Context context) { 46 | super(context); 47 | 48 | init(context); 49 | adjustParams(); 50 | setupPaint(); 51 | } 52 | 53 | private void init(Context context) { 54 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 55 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 56 | mBallRadius = DensityUtil.dip2px(context, DEFAULT_BALL_RADIUS); 57 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 58 | 59 | mColor = DEFAULT_COLOR; 60 | mDuration = ANIMATION_DURATION; 61 | mBallCount = DEFAULT_CIRCLE_COUNT; 62 | 63 | mBallInterval = mBallRadius; 64 | } 65 | 66 | private void adjustParams() { 67 | mBallCenterY = mHeight / 2.0f; 68 | mBallSideOffsets = (mWidth - mBallRadius * 2 * mBallCount - mBallInterval * (mBallCount - 1)) / 2.0f; 69 | 70 | mASwapThreshold = 1.0f / mBallCount; 71 | } 72 | 73 | private void setupPaint() { 74 | mPaint.setColor(mColor); 75 | mPaint.setStrokeWidth(mStrokeWidth); 76 | } 77 | 78 | @Override 79 | protected void draw(Canvas canvas) { 80 | int saveCount = canvas.save(); 81 | 82 | for (int i = 0; i < mBallCount; i++) { 83 | if (i == mSwapIndex) { 84 | mPaint.setStyle(Paint.Style.FILL); 85 | canvas.drawCircle(mBallSideOffsets + mBallRadius * (i * 2 + 1) + i * mBallInterval + mSwapBallOffsetX 86 | , mBallCenterY - mSwapBallOffsetY, mBallRadius, mPaint); 87 | } else if (i == (mSwapIndex + 1) % mBallCount) { 88 | mPaint.setStyle(Paint.Style.STROKE); 89 | canvas.drawCircle(mBallSideOffsets + mBallRadius * (i * 2 + 1) + i * mBallInterval - mSwapBallOffsetX 90 | , mBallCenterY + mSwapBallOffsetY, mBallRadius - mStrokeWidth / 2, mPaint); 91 | } else { 92 | mPaint.setStyle(Paint.Style.STROKE); 93 | canvas.drawCircle(mBallSideOffsets + mBallRadius * (i * 2 + 1) + i * mBallInterval, mBallCenterY 94 | , mBallRadius - mStrokeWidth / 2, mPaint); 95 | } 96 | } 97 | 98 | canvas.restoreToCount(saveCount); 99 | } 100 | 101 | @Override 102 | protected void computeRender(float renderProgress) { 103 | mSwapIndex = (int) (renderProgress / mASwapThreshold); 104 | 105 | // Swap trace : x^2 + y^2 = r ^ 2 106 | float swapTraceProgress = ACCELERATE_DECELERATE_INTERPOLATOR.getInterpolation( 107 | (renderProgress - mSwapIndex * mASwapThreshold) / mASwapThreshold); 108 | 109 | float swapTraceRadius = mSwapIndex == mBallCount - 1 110 | ? (mBallRadius * 2 * (mBallCount - 1) + mBallInterval * (mBallCount - 1)) / 2 111 | : (mBallRadius * 2 + mBallInterval) / 2; 112 | 113 | // Calculate the X offset of the swap ball 114 | mSwapBallOffsetX = mSwapIndex == mBallCount - 1 115 | ? -swapTraceProgress * swapTraceRadius * 2 116 | : swapTraceProgress * swapTraceRadius * 2; 117 | 118 | // if mSwapIndex == mBallCount - 1 then (swapTraceRadius, swapTraceRadius) as the origin of coordinates 119 | // else (-swapTraceRadius, -swapTraceRadius) as the origin of coordinates 120 | float xCoordinate = mSwapIndex == mBallCount - 1 121 | ? mSwapBallOffsetX + swapTraceRadius 122 | : mSwapBallOffsetX - swapTraceRadius; 123 | 124 | // Calculate the Y offset of the swap ball 125 | mSwapBallOffsetY = (float) (mSwapIndex % 2 == 0 && mSwapIndex != mBallCount - 1 126 | ? Math.sqrt(Math.pow(swapTraceRadius, 2.0f) - Math.pow(xCoordinate, 2.0f)) 127 | : -Math.sqrt(Math.pow(swapTraceRadius, 2.0f) - Math.pow(xCoordinate, 2.0f))); 128 | 129 | } 130 | 131 | @Override 132 | protected void setAlpha(int alpha) { 133 | mPaint.setAlpha(alpha); 134 | } 135 | 136 | @Override 137 | protected void setColorFilter(ColorFilter cf) { 138 | mPaint.setColorFilter(cf); 139 | } 140 | 141 | @Override 142 | protected void reset() { 143 | } 144 | 145 | private void apply(Builder builder) { 146 | this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; 147 | this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; 148 | this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth; 149 | 150 | this.mBallRadius = builder.mBallRadius > 0 ? builder.mBallRadius : this.mBallRadius; 151 | this.mBallInterval = builder.mBallInterval > 0 ? builder.mBallInterval : this.mBallInterval; 152 | this.mBallCount = builder.mBallCount > 0 ? builder.mBallCount : this.mBallCount; 153 | 154 | this.mColor = builder.mColor != 0 ? builder.mColor : this.mColor; 155 | 156 | this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; 157 | 158 | adjustParams(); 159 | setupPaint(); 160 | } 161 | 162 | public static class Builder { 163 | private Context mContext; 164 | 165 | private int mWidth; 166 | private int mHeight; 167 | private int mStrokeWidth; 168 | 169 | private int mBallCount; 170 | private int mBallRadius; 171 | private int mBallInterval; 172 | 173 | private int mDuration; 174 | 175 | private int mColor; 176 | 177 | public Builder(Context mContext) { 178 | this.mContext = mContext; 179 | } 180 | 181 | public Builder setWidth(int width) { 182 | this.mWidth = width; 183 | return this; 184 | } 185 | 186 | public Builder setHeight(int height) { 187 | this.mHeight = height; 188 | return this; 189 | } 190 | 191 | public Builder setStrokeWidth(int strokeWidth) { 192 | this.mStrokeWidth = strokeWidth; 193 | return this; 194 | } 195 | 196 | public Builder setBallRadius(int ballRadius) { 197 | this.mBallRadius = ballRadius; 198 | return this; 199 | } 200 | 201 | public Builder setBallInterval(int ballInterval) { 202 | this.mBallInterval = ballInterval; 203 | return this; 204 | } 205 | 206 | public Builder setBallCount(int ballCount) { 207 | this.mBallCount = ballCount; 208 | return this; 209 | } 210 | 211 | public Builder setColor(int color) { 212 | this.mColor = color; 213 | return this; 214 | } 215 | 216 | public Builder setDuration(int duration) { 217 | this.mDuration = duration; 218 | return this; 219 | } 220 | 221 | public SwapLoadingRenderer build() { 222 | SwapLoadingRenderer loadingRenderer = new SwapLoadingRenderer(mContext); 223 | loadingRenderer.apply(this); 224 | return loadingRenderer; 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/GearLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.rotate; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.ColorFilter; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.support.annotation.IntRange; 12 | import android.view.animation.AccelerateInterpolator; 13 | import android.view.animation.DecelerateInterpolator; 14 | import android.view.animation.Interpolator; 15 | 16 | import app.dinus.com.loadingdrawable.DensityUtil; 17 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 18 | 19 | public class GearLoadingRenderer extends LoadingRenderer { 20 | private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); 21 | private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); 22 | 23 | private static final int GEAR_COUNT = 4; 24 | private static final int NUM_POINTS = 3; 25 | private static final int MAX_ALPHA = 255; 26 | private static final int DEGREE_360 = 360; 27 | 28 | private static final int DEFAULT_GEAR_SWIPE_DEGREES = 60; 29 | 30 | private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360; 31 | 32 | private static final float START_SCALE_DURATION_OFFSET = 0.3f; 33 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 34 | private static final float END_TRIM_DURATION_OFFSET = 0.7f; 35 | private static final float END_SCALE_DURATION_OFFSET = 1.0f; 36 | 37 | private static final float DEFAULT_CENTER_RADIUS = 12.5f; 38 | private static final float DEFAULT_STROKE_WIDTH = 2.5f; 39 | 40 | private static final int DEFAULT_COLOR = Color.WHITE; 41 | 42 | private final Paint mPaint = new Paint(); 43 | private final RectF mTempBounds = new RectF(); 44 | 45 | private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { 46 | @Override 47 | public void onAnimationRepeat(Animator animator) { 48 | super.onAnimationRepeat(animator); 49 | storeOriginals(); 50 | 51 | mStartDegrees = mEndDegrees; 52 | mRotationCount = (mRotationCount + 1) % NUM_POINTS; 53 | } 54 | 55 | @Override 56 | public void onAnimationStart(Animator animation) { 57 | super.onAnimationStart(animation); 58 | mRotationCount = 0; 59 | } 60 | }; 61 | 62 | private int mColor; 63 | 64 | private int mGearCount; 65 | private int mGearSwipeDegrees; 66 | 67 | private float mStrokeInset; 68 | 69 | private float mRotationCount; 70 | private float mGroupRotation; 71 | 72 | private float mScale; 73 | private float mEndDegrees; 74 | private float mStartDegrees; 75 | private float mSwipeDegrees; 76 | private float mOriginEndDegrees; 77 | private float mOriginStartDegrees; 78 | 79 | private float mStrokeWidth; 80 | private float mCenterRadius; 81 | 82 | private GearLoadingRenderer(Context context) { 83 | super(context); 84 | 85 | init(context); 86 | setupPaint(); 87 | addRenderListener(mAnimatorListener); 88 | } 89 | 90 | private void init(Context context) { 91 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 92 | mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); 93 | 94 | mColor = DEFAULT_COLOR; 95 | 96 | mGearCount = GEAR_COUNT; 97 | mGearSwipeDegrees = DEFAULT_GEAR_SWIPE_DEGREES; 98 | } 99 | 100 | private void setupPaint() { 101 | mPaint.setAntiAlias(true); 102 | mPaint.setStrokeWidth(mStrokeWidth); 103 | mPaint.setStyle(Paint.Style.STROKE); 104 | mPaint.setStrokeCap(Paint.Cap.ROUND); 105 | 106 | initStrokeInset(mWidth, mHeight); 107 | } 108 | 109 | @Override 110 | protected void draw(Canvas canvas) { 111 | int saveCount = canvas.save(); 112 | 113 | mTempBounds.set(mBounds); 114 | mTempBounds.inset(mStrokeInset, mStrokeInset); 115 | mTempBounds.inset(mTempBounds.width() * (1.0f - mScale) / 2.0f, mTempBounds.width() * (1.0f - mScale) / 2.0f); 116 | 117 | canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY()); 118 | 119 | mPaint.setColor(mColor); 120 | mPaint.setAlpha((int) (MAX_ALPHA * mScale)); 121 | mPaint.setStrokeWidth(mStrokeWidth * mScale); 122 | 123 | if (mSwipeDegrees != 0) { 124 | for (int i = 0; i < mGearCount; i++) { 125 | canvas.drawArc(mTempBounds, mStartDegrees + DEGREE_360 / mGearCount * i, mSwipeDegrees, false, mPaint); 126 | } 127 | } 128 | 129 | canvas.restoreToCount(saveCount); 130 | } 131 | 132 | @Override 133 | protected void computeRender(float renderProgress) { 134 | // Scaling up the start size only occurs in the first 20% of a single ring animation 135 | if (renderProgress <= START_SCALE_DURATION_OFFSET) { 136 | float startScaleProgress = (renderProgress) / START_SCALE_DURATION_OFFSET; 137 | mScale = DECELERATE_INTERPOLATOR.getInterpolation(startScaleProgress); 138 | } 139 | 140 | // Moving the start trim only occurs between 20% to 50% of a single ring animation 141 | if (renderProgress <= START_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) { 142 | float startTrimProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (START_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET); 143 | mStartDegrees = mOriginStartDegrees + mGearSwipeDegrees * startTrimProgress; 144 | } 145 | 146 | // Moving the end trim starts between 50% to 80% of a single ring animation 147 | if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_TRIM_DURATION_OFFSET) { 148 | float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET); 149 | mEndDegrees = mOriginEndDegrees + mGearSwipeDegrees * endTrimProgress; 150 | } 151 | 152 | // Scaling down the end size starts after 80% of a single ring animation 153 | if (renderProgress > END_TRIM_DURATION_OFFSET) { 154 | float endScaleProgress = (renderProgress - END_TRIM_DURATION_OFFSET) / (END_SCALE_DURATION_OFFSET - END_TRIM_DURATION_OFFSET); 155 | mScale = 1.0f - ACCELERATE_INTERPOLATOR.getInterpolation(endScaleProgress); 156 | } 157 | 158 | if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) { 159 | float rotateProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET); 160 | mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * rotateProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS)); 161 | } 162 | 163 | if (Math.abs(mEndDegrees - mStartDegrees) > 0) { 164 | mSwipeDegrees = mEndDegrees - mStartDegrees; 165 | } 166 | } 167 | 168 | @Override 169 | protected void setAlpha(int alpha) { 170 | mPaint.setAlpha(alpha); 171 | } 172 | 173 | @Override 174 | protected void setColorFilter(ColorFilter cf) { 175 | mPaint.setColorFilter(cf); 176 | } 177 | 178 | @Override 179 | protected void reset() { 180 | resetOriginals(); 181 | } 182 | 183 | private void initStrokeInset(float width, float height) { 184 | float minSize = Math.min(width, height); 185 | float strokeInset = minSize / 2.0f - mCenterRadius; 186 | float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f); 187 | mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset; 188 | } 189 | 190 | private void storeOriginals() { 191 | mOriginEndDegrees = mEndDegrees; 192 | mOriginStartDegrees = mEndDegrees; 193 | } 194 | 195 | private void resetOriginals() { 196 | mOriginEndDegrees = 0; 197 | mOriginStartDegrees = 0; 198 | 199 | mEndDegrees = 0; 200 | mStartDegrees = 0; 201 | 202 | mSwipeDegrees = 1; 203 | } 204 | 205 | private void apply(Builder builder) { 206 | this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; 207 | this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; 208 | this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth; 209 | this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius; 210 | 211 | this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; 212 | 213 | this.mColor = builder.mColor != 0 ? builder.mColor : this.mColor; 214 | 215 | this.mGearCount = builder.mGearCount > 0 ? builder.mGearCount : this.mGearCount; 216 | this.mGearSwipeDegrees = builder.mGearSwipeDegrees > 0 ? builder.mGearSwipeDegrees : this.mGearSwipeDegrees; 217 | 218 | setupPaint(); 219 | initStrokeInset(this.mWidth, this.mHeight); 220 | } 221 | 222 | public static class Builder { 223 | private Context mContext; 224 | 225 | private int mWidth; 226 | private int mHeight; 227 | private int mStrokeWidth; 228 | private int mCenterRadius; 229 | 230 | private int mDuration; 231 | 232 | private int mColor; 233 | 234 | private int mGearCount; 235 | private int mGearSwipeDegrees; 236 | 237 | public Builder(Context mContext) { 238 | this.mContext = mContext; 239 | } 240 | 241 | public Builder setWidth(int width) { 242 | this.mWidth = width; 243 | return this; 244 | } 245 | 246 | public Builder setHeight(int height) { 247 | this.mHeight = height; 248 | return this; 249 | } 250 | 251 | public Builder setStrokeWidth(int strokeWidth) { 252 | this.mStrokeWidth = strokeWidth; 253 | return this; 254 | } 255 | 256 | public Builder setCenterRadius(int centerRadius) { 257 | this.mCenterRadius = centerRadius; 258 | return this; 259 | } 260 | 261 | public Builder setDuration(int duration) { 262 | this.mDuration = duration; 263 | return this; 264 | } 265 | 266 | public Builder setColor(int color) { 267 | this.mColor = color; 268 | return this; 269 | } 270 | 271 | public Builder setGearCount(int gearCount) { 272 | this.mGearCount = gearCount; 273 | return this; 274 | } 275 | 276 | public Builder setGearSwipeDegrees(@IntRange(from = 0, to = 360) int gearSwipeDegrees) { 277 | this.mGearSwipeDegrees = gearSwipeDegrees; 278 | return this; 279 | } 280 | 281 | public GearLoadingRenderer build() { 282 | GearLoadingRenderer loadingRenderer = new GearLoadingRenderer(mContext); 283 | loadingRenderer.apply(this); 284 | return loadingRenderer; 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/LevelLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.rotate; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.ColorFilter; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.RectF; 12 | import android.support.annotation.Size; 13 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 14 | import android.view.animation.AccelerateInterpolator; 15 | import android.view.animation.DecelerateInterpolator; 16 | import android.view.animation.Interpolator; 17 | import android.view.animation.LinearInterpolator; 18 | 19 | import app.dinus.com.loadingdrawable.DensityUtil; 20 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 21 | 22 | public class LevelLoadingRenderer extends LoadingRenderer { 23 | private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 24 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 25 | private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); 26 | private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); 27 | 28 | private static final int NUM_POINTS = 5; 29 | private static final int DEGREE_360 = 360; 30 | 31 | private static final float MAX_SWIPE_DEGREES = 0.8f * DEGREE_360; 32 | private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360; 33 | 34 | private static final float[] LEVEL_SWEEP_ANGLE_OFFSETS = new float[]{1.0f, 7.0f / 8.0f, 5.0f / 8.0f}; 35 | 36 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 37 | private static final float END_TRIM_DURATION_OFFSET = 1.0f; 38 | 39 | private static final float DEFAULT_CENTER_RADIUS = 12.5f; 40 | private static final float DEFAULT_STROKE_WIDTH = 2.5f; 41 | 42 | private static final int[] DEFAULT_LEVEL_COLORS = new int[]{Color.parseColor("#55ffffff"), 43 | Color.parseColor("#b1ffffff"), Color.parseColor("#ffffffff")}; 44 | 45 | private final Paint mPaint = new Paint(); 46 | private final RectF mTempBounds = new RectF(); 47 | 48 | private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { 49 | @Override 50 | public void onAnimationRepeat(Animator animator) { 51 | super.onAnimationRepeat(animator); 52 | storeOriginals(); 53 | 54 | mStartDegrees = mEndDegrees; 55 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 56 | } 57 | 58 | @Override 59 | public void onAnimationStart(Animator animation) { 60 | super.onAnimationStart(animation); 61 | mRotationCount = 0; 62 | } 63 | }; 64 | 65 | @Size(3) 66 | private int[] mLevelColors; 67 | @Size(3) 68 | private float[] mLevelSwipeDegrees; 69 | 70 | private float mStrokeInset; 71 | 72 | private float mRotationCount; 73 | private float mGroupRotation; 74 | 75 | private float mEndDegrees; 76 | private float mStartDegrees; 77 | private float mOriginEndDegrees; 78 | private float mOriginStartDegrees; 79 | 80 | private float mStrokeWidth; 81 | private float mCenterRadius; 82 | 83 | private LevelLoadingRenderer(Context context) { 84 | super(context); 85 | init(context); 86 | setupPaint(); 87 | addRenderListener(mAnimatorListener); 88 | } 89 | 90 | private void init(Context context) { 91 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 92 | mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); 93 | 94 | mLevelSwipeDegrees = new float[3]; 95 | mLevelColors = DEFAULT_LEVEL_COLORS; 96 | } 97 | 98 | private void setupPaint() { 99 | 100 | mPaint.setAntiAlias(true); 101 | mPaint.setStrokeWidth(mStrokeWidth); 102 | mPaint.setStyle(Paint.Style.STROKE); 103 | mPaint.setStrokeCap(Paint.Cap.ROUND); 104 | 105 | initStrokeInset((int) mWidth, (int) mHeight); 106 | } 107 | 108 | @Override 109 | protected void draw(Canvas canvas) { 110 | int saveCount = canvas.save(); 111 | 112 | mTempBounds.set(mBounds); 113 | mTempBounds.inset(mStrokeInset, mStrokeInset); 114 | canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY()); 115 | 116 | for (int i = 0; i < 3; i++) { 117 | if (mLevelSwipeDegrees[i] != 0) { 118 | mPaint.setColor(mLevelColors[i]); 119 | canvas.drawArc(mTempBounds, mEndDegrees, mLevelSwipeDegrees[i], false, mPaint); 120 | } 121 | } 122 | 123 | canvas.restoreToCount(saveCount); 124 | } 125 | 126 | @Override 127 | protected void computeRender(float renderProgress) { 128 | // Moving the start trim only occurs in the first 50% of a single ring animation 129 | if (renderProgress <= START_TRIM_DURATION_OFFSET) { 130 | float startTrimProgress = (renderProgress) / START_TRIM_DURATION_OFFSET; 131 | mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress); 132 | 133 | float mSwipeDegrees = mEndDegrees - mStartDegrees; 134 | float levelSwipeDegreesProgress = Math.abs(mSwipeDegrees) / MAX_SWIPE_DEGREES; 135 | 136 | float level1Increment = DECELERATE_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress) - LINEAR_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress); 137 | float level3Increment = ACCELERATE_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress) - LINEAR_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress); 138 | 139 | mLevelSwipeDegrees[0] = -mSwipeDegrees * LEVEL_SWEEP_ANGLE_OFFSETS[0] * (1.0f + level1Increment); 140 | mLevelSwipeDegrees[1] = -mSwipeDegrees * LEVEL_SWEEP_ANGLE_OFFSETS[1] * 1.0f; 141 | mLevelSwipeDegrees[2] = -mSwipeDegrees * LEVEL_SWEEP_ANGLE_OFFSETS[2] * (1.0f + level3Increment); 142 | } 143 | 144 | // Moving the end trim starts after 50% of a single ring animation 145 | if (renderProgress > START_TRIM_DURATION_OFFSET) { 146 | float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET); 147 | mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress); 148 | 149 | float mSwipeDegrees = mEndDegrees - mStartDegrees; 150 | float levelSwipeDegreesProgress = Math.abs(mSwipeDegrees) / MAX_SWIPE_DEGREES; 151 | 152 | if (levelSwipeDegreesProgress > LEVEL_SWEEP_ANGLE_OFFSETS[1]) { 153 | mLevelSwipeDegrees[0] = -mSwipeDegrees; 154 | mLevelSwipeDegrees[1] = MAX_SWIPE_DEGREES * LEVEL_SWEEP_ANGLE_OFFSETS[1]; 155 | mLevelSwipeDegrees[2] = MAX_SWIPE_DEGREES * LEVEL_SWEEP_ANGLE_OFFSETS[2]; 156 | } else if (levelSwipeDegreesProgress > LEVEL_SWEEP_ANGLE_OFFSETS[2]) { 157 | mLevelSwipeDegrees[0] = 0; 158 | mLevelSwipeDegrees[1] = -mSwipeDegrees; 159 | mLevelSwipeDegrees[2] = MAX_SWIPE_DEGREES * LEVEL_SWEEP_ANGLE_OFFSETS[2]; 160 | } else { 161 | mLevelSwipeDegrees[0] = 0; 162 | mLevelSwipeDegrees[1] = 0; 163 | mLevelSwipeDegrees[2] = -mSwipeDegrees; 164 | } 165 | } 166 | 167 | mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS)); 168 | } 169 | 170 | @Override 171 | protected void setAlpha(int alpha) { 172 | mPaint.setAlpha(alpha); 173 | } 174 | 175 | @Override 176 | protected void setColorFilter(ColorFilter cf) { 177 | mPaint.setColorFilter(cf); 178 | } 179 | 180 | @Override 181 | protected void reset() { 182 | resetOriginals(); 183 | } 184 | 185 | private void initStrokeInset(float width, float height) { 186 | float minSize = Math.min(width, height); 187 | float strokeInset = minSize / 2.0f - mCenterRadius; 188 | float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f); 189 | mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset; 190 | } 191 | 192 | private void storeOriginals() { 193 | mOriginEndDegrees = mEndDegrees; 194 | mOriginStartDegrees = mEndDegrees; 195 | } 196 | 197 | private void resetOriginals() { 198 | mOriginEndDegrees = 0; 199 | mOriginStartDegrees = 0; 200 | 201 | mEndDegrees = 0; 202 | mStartDegrees = 0; 203 | 204 | mLevelSwipeDegrees[0] = 0; 205 | mLevelSwipeDegrees[1] = 0; 206 | mLevelSwipeDegrees[2] = 0; 207 | } 208 | 209 | private void apply(Builder builder) { 210 | this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; 211 | this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; 212 | this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth; 213 | this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius; 214 | 215 | this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; 216 | 217 | this.mLevelColors = builder.mLevelColors != null ? builder.mLevelColors : this.mLevelColors; 218 | 219 | setupPaint(); 220 | initStrokeInset(this.mWidth, this.mHeight); 221 | } 222 | 223 | public static class Builder { 224 | private Context mContext; 225 | 226 | private int mWidth; 227 | private int mHeight; 228 | private int mStrokeWidth; 229 | private int mCenterRadius; 230 | 231 | private int mDuration; 232 | 233 | @Size(3) 234 | private int[] mLevelColors; 235 | 236 | public Builder(Context mContext) { 237 | this.mContext = mContext; 238 | } 239 | 240 | public Builder setWidth(int width) { 241 | this.mWidth = width; 242 | return this; 243 | } 244 | 245 | public Builder setHeight(int height) { 246 | this.mHeight = height; 247 | return this; 248 | } 249 | 250 | public Builder setStrokeWidth(int strokeWidth) { 251 | this.mStrokeWidth = strokeWidth; 252 | return this; 253 | } 254 | 255 | public Builder setCenterRadius(int centerRadius) { 256 | this.mCenterRadius = centerRadius; 257 | return this; 258 | } 259 | 260 | public Builder setDuration(int duration) { 261 | this.mDuration = duration; 262 | return this; 263 | } 264 | 265 | public Builder setLevelColors(@Size(3) int[] colors) { 266 | this.mLevelColors = colors; 267 | return this; 268 | } 269 | 270 | public Builder setLevelColor(int color) { 271 | return setLevelColors(new int[]{oneThirdAlphaColor(color), twoThirdAlphaColor(color), color}); 272 | } 273 | 274 | public LevelLoadingRenderer build() { 275 | LevelLoadingRenderer loadingRenderer = new LevelLoadingRenderer(mContext); 276 | loadingRenderer.apply(this); 277 | return loadingRenderer; 278 | } 279 | 280 | private int oneThirdAlphaColor(int colorValue) { 281 | int startA = (colorValue >> 24) & 0xff; 282 | int startR = (colorValue >> 16) & 0xff; 283 | int startG = (colorValue >> 8) & 0xff; 284 | int startB = colorValue & 0xff; 285 | 286 | return (startA / 3 << 24) | (startR << 16) | (startG << 8) | startB; 287 | } 288 | 289 | private int twoThirdAlphaColor(int colorValue) { 290 | int startA = (colorValue >> 24) & 0xff; 291 | int startR = (colorValue >> 16) & 0xff; 292 | int startG = (colorValue >> 8) & 0xff; 293 | int startB = colorValue & 0xff; 294 | 295 | return (startA * 2 / 3 << 24) | (startR << 16) | (startG << 8) | startB; 296 | } 297 | } 298 | 299 | } 300 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/MaterialLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.rotate; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.ColorFilter; 9 | import android.graphics.Paint; 10 | import android.graphics.RectF; 11 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 12 | import android.view.animation.Interpolator; 13 | 14 | import app.dinus.com.loadingdrawable.DensityUtil; 15 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 16 | 17 | public class MaterialLoadingRenderer extends LoadingRenderer { 18 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 19 | 20 | private static final int DEGREE_360 = 360; 21 | private static final int NUM_POINTS = 5; 22 | 23 | private static final float MAX_SWIPE_DEGREES = 0.8f * DEGREE_360; 24 | private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360; 25 | 26 | private static final float COLOR_START_DELAY_OFFSET = 0.8f; 27 | private static final float END_TRIM_DURATION_OFFSET = 1.0f; 28 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 29 | 30 | private static final float DEFAULT_CENTER_RADIUS = 12.5f; 31 | private static final float DEFAULT_STROKE_WIDTH = 2.5f; 32 | 33 | private static final int[] DEFAULT_COLORS = new int[]{ 34 | Color.RED, Color.GREEN, Color.BLUE 35 | }; 36 | 37 | private final Paint mPaint = new Paint(); 38 | private final RectF mTempBounds = new RectF(); 39 | 40 | private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { 41 | @Override 42 | public void onAnimationRepeat(Animator animator) { 43 | super.onAnimationRepeat(animator); 44 | storeOriginals(); 45 | goToNextColor(); 46 | 47 | mStartDegrees = mEndDegrees; 48 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 49 | } 50 | 51 | @Override 52 | public void onAnimationStart(Animator animation) { 53 | super.onAnimationStart(animation); 54 | mRotationCount = 0; 55 | } 56 | }; 57 | 58 | private int[] mColors; 59 | private int mColorIndex; 60 | private int mCurrentColor; 61 | 62 | private float mStrokeInset; 63 | 64 | private float mRotationCount; 65 | private float mGroupRotation; 66 | 67 | private float mEndDegrees; 68 | private float mStartDegrees; 69 | private float mSwipeDegrees; 70 | private float mOriginEndDegrees; 71 | private float mOriginStartDegrees; 72 | 73 | private float mStrokeWidth; 74 | private float mCenterRadius; 75 | 76 | private MaterialLoadingRenderer(Context context) { 77 | super(context); 78 | init(context); 79 | setupPaint(); 80 | addRenderListener(mAnimatorListener); 81 | } 82 | 83 | private void init(Context context) { 84 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 85 | mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); 86 | 87 | mColors = DEFAULT_COLORS; 88 | 89 | setColorIndex(0); 90 | initStrokeInset(mWidth, mHeight); 91 | } 92 | 93 | private void setupPaint() { 94 | mPaint.setAntiAlias(true); 95 | mPaint.setStrokeWidth(mStrokeWidth); 96 | mPaint.setStyle(Paint.Style.STROKE); 97 | mPaint.setStrokeCap(Paint.Cap.ROUND); 98 | } 99 | 100 | @Override 101 | protected void draw(Canvas canvas) { 102 | int saveCount = canvas.save(); 103 | 104 | mTempBounds.set(mBounds); 105 | mTempBounds.inset(mStrokeInset, mStrokeInset); 106 | 107 | canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY()); 108 | 109 | if (mSwipeDegrees != 0) { 110 | mPaint.setColor(mCurrentColor); 111 | canvas.drawArc(mTempBounds, mStartDegrees, mSwipeDegrees, false, mPaint); 112 | } 113 | 114 | canvas.restoreToCount(saveCount); 115 | } 116 | 117 | @Override 118 | protected void computeRender(float renderProgress) { 119 | updateRingColor(renderProgress); 120 | 121 | // Moving the start trim only occurs in the first 50% of a single ring animation 122 | if (renderProgress <= START_TRIM_DURATION_OFFSET) { 123 | float startTrimProgress = renderProgress / START_TRIM_DURATION_OFFSET; 124 | mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES 125 | * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress); 126 | } 127 | 128 | // Moving the end trim starts after 50% of a single ring animation completes 129 | if (renderProgress > START_TRIM_DURATION_OFFSET) { 130 | float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) 131 | / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET); 132 | mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES 133 | * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress); 134 | } 135 | 136 | if (Math.abs(mEndDegrees - mStartDegrees) > 0) { 137 | mSwipeDegrees = mEndDegrees - mStartDegrees; 138 | } 139 | 140 | mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) 141 | + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS)); 142 | } 143 | 144 | @Override 145 | protected void setAlpha(int alpha) { 146 | mPaint.setAlpha(alpha); 147 | } 148 | 149 | @Override 150 | protected void setColorFilter(ColorFilter cf) { 151 | mPaint.setColorFilter(cf); 152 | } 153 | 154 | @Override 155 | protected void reset() { 156 | resetOriginals(); 157 | } 158 | 159 | private void setColorIndex(int index) { 160 | mColorIndex = index; 161 | mCurrentColor = mColors[mColorIndex]; 162 | } 163 | 164 | private int getNextColor() { 165 | return mColors[getNextColorIndex()]; 166 | } 167 | 168 | private int getNextColorIndex() { 169 | return (mColorIndex + 1) % (mColors.length); 170 | } 171 | 172 | private void goToNextColor() { 173 | setColorIndex(getNextColorIndex()); 174 | } 175 | 176 | private void initStrokeInset(float width, float height) { 177 | float minSize = Math.min(width, height); 178 | float strokeInset = minSize / 2.0f - mCenterRadius; 179 | float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f); 180 | mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset; 181 | } 182 | 183 | private void storeOriginals() { 184 | mOriginEndDegrees = mEndDegrees; 185 | mOriginStartDegrees = mEndDegrees; 186 | } 187 | 188 | private void resetOriginals() { 189 | mOriginEndDegrees = 0; 190 | mOriginStartDegrees = 0; 191 | 192 | mEndDegrees = 0; 193 | mStartDegrees = 0; 194 | } 195 | 196 | private int getStartingColor() { 197 | return mColors[mColorIndex]; 198 | } 199 | 200 | private void updateRingColor(float interpolatedTime) { 201 | if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 202 | mCurrentColor = evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 203 | / (1.0f - COLOR_START_DELAY_OFFSET), getStartingColor(), getNextColor()); 204 | } 205 | } 206 | 207 | private int evaluateColorChange(float fraction, int startValue, int endValue) { 208 | int startA = (startValue >> 24) & 0xff; 209 | int startR = (startValue >> 16) & 0xff; 210 | int startG = (startValue >> 8) & 0xff; 211 | int startB = startValue & 0xff; 212 | 213 | int endA = (endValue >> 24) & 0xff; 214 | int endR = (endValue >> 16) & 0xff; 215 | int endG = (endValue >> 8) & 0xff; 216 | int endB = endValue & 0xff; 217 | 218 | return ((startA + (int) (fraction * (endA - startA))) << 24) 219 | | ((startR + (int) (fraction * (endR - startR))) << 16) 220 | | ((startG + (int) (fraction * (endG - startG))) << 8) 221 | | ((startB + (int) (fraction * (endB - startB)))); 222 | } 223 | 224 | private void apply(Builder builder) { 225 | this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; 226 | this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; 227 | this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth; 228 | this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius; 229 | 230 | this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; 231 | 232 | this.mColors = builder.mColors != null && builder.mColors.length > 0 ? builder.mColors : this.mColors; 233 | 234 | setColorIndex(0); 235 | setupPaint(); 236 | initStrokeInset(this.mWidth, this.mHeight); 237 | } 238 | 239 | public static class Builder { 240 | private Context mContext; 241 | 242 | private int mWidth; 243 | private int mHeight; 244 | private int mStrokeWidth; 245 | private int mCenterRadius; 246 | 247 | private int mDuration; 248 | 249 | private int[] mColors; 250 | 251 | public Builder(Context mContext) { 252 | this.mContext = mContext; 253 | } 254 | 255 | public Builder setWidth(int width) { 256 | this.mWidth = width; 257 | return this; 258 | } 259 | 260 | public Builder setHeight(int height) { 261 | this.mHeight = height; 262 | return this; 263 | } 264 | 265 | public Builder setStrokeWidth(int strokeWidth) { 266 | this.mStrokeWidth = strokeWidth; 267 | return this; 268 | } 269 | 270 | public Builder setCenterRadius(int centerRadius) { 271 | this.mCenterRadius = centerRadius; 272 | return this; 273 | } 274 | 275 | public Builder setDuration(int duration) { 276 | this.mDuration = duration; 277 | return this; 278 | } 279 | 280 | public Builder setColors(int[] colors) { 281 | this.mColors = colors; 282 | return this; 283 | } 284 | 285 | public MaterialLoadingRenderer build() { 286 | MaterialLoadingRenderer loadingRenderer = new MaterialLoadingRenderer(mContext); 287 | loadingRenderer.apply(this); 288 | return loadingRenderer; 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/WhorlLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.circle.rotate; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.ColorFilter; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.RectF; 12 | import android.support.annotation.NonNull; 13 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 14 | import android.view.animation.Interpolator; 15 | 16 | import app.dinus.com.loadingdrawable.DensityUtil; 17 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 18 | 19 | public class WhorlLoadingRenderer extends LoadingRenderer { 20 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 21 | 22 | private static final int DEGREE_180 = 180; 23 | private static final int DEGREE_360 = 360; 24 | private static final int NUM_POINTS = 5; 25 | 26 | private static final float MAX_SWIPE_DEGREES = 0.6f * DEGREE_360; 27 | private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360; 28 | 29 | private static final float START_TRIM_DURATION_OFFSET = 0.5f; 30 | private static final float END_TRIM_DURATION_OFFSET = 1.0f; 31 | 32 | private static final float DEFAULT_CENTER_RADIUS = 12.5f; 33 | private static final float DEFAULT_STROKE_WIDTH = 2.5f; 34 | 35 | private static final int[] DEFAULT_COLORS = new int[]{ 36 | Color.RED, Color.GREEN, Color.BLUE 37 | }; 38 | 39 | private final Paint mPaint = new Paint(); 40 | private final RectF mTempBounds = new RectF(); 41 | private final RectF mTempArcBounds = new RectF(); 42 | 43 | private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { 44 | @Override 45 | public void onAnimationRepeat(Animator animator) { 46 | super.onAnimationRepeat(animator); 47 | storeOriginals(); 48 | 49 | mStartDegrees = mEndDegrees; 50 | mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 51 | } 52 | 53 | @Override 54 | public void onAnimationStart(Animator animation) { 55 | super.onAnimationStart(animation); 56 | mRotationCount = 0; 57 | } 58 | }; 59 | 60 | private int[] mColors; 61 | 62 | private float mStrokeInset; 63 | 64 | private float mRotationCount; 65 | private float mGroupRotation; 66 | 67 | private float mEndDegrees; 68 | private float mStartDegrees; 69 | private float mSwipeDegrees; 70 | private float mOriginEndDegrees; 71 | private float mOriginStartDegrees; 72 | 73 | private float mStrokeWidth; 74 | private float mCenterRadius; 75 | 76 | private WhorlLoadingRenderer(Context context) { 77 | super(context); 78 | init(context); 79 | setupPaint(); 80 | addRenderListener(mAnimatorListener); 81 | } 82 | 83 | private void init(Context context) { 84 | mColors = DEFAULT_COLORS; 85 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 86 | mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); 87 | 88 | initStrokeInset(mWidth, mHeight); 89 | } 90 | 91 | private void setupPaint() { 92 | mPaint.setAntiAlias(true); 93 | mPaint.setStrokeWidth(mStrokeWidth); 94 | mPaint.setStyle(Paint.Style.STROKE); 95 | mPaint.setStrokeCap(Paint.Cap.ROUND); 96 | } 97 | 98 | @Override 99 | protected void draw(Canvas canvas) { 100 | int saveCount = canvas.save(); 101 | 102 | mTempBounds.set(mBounds); 103 | mTempBounds.inset(mStrokeInset, mStrokeInset); 104 | 105 | canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY()); 106 | 107 | if (mSwipeDegrees != 0) { 108 | for (int i = 0; i < mColors.length; i++) { 109 | mPaint.setStrokeWidth(mStrokeWidth / (i + 1)); 110 | mPaint.setColor(mColors[i]); 111 | canvas.drawArc(createArcBounds(mTempBounds, i), mStartDegrees + DEGREE_180 * (i % 2), 112 | mSwipeDegrees, false, mPaint); 113 | } 114 | } 115 | 116 | canvas.restoreToCount(saveCount); 117 | } 118 | 119 | private RectF createArcBounds(RectF sourceArcBounds, int index) { 120 | int intervalWidth = 0; 121 | 122 | for (int i = 0; i < index; i++) { 123 | intervalWidth += mStrokeWidth / (i + 1.0f) * 1.5f; 124 | } 125 | 126 | int arcBoundsLeft = (int) (sourceArcBounds.left + intervalWidth); 127 | int arcBoundsTop = (int) (sourceArcBounds.top + intervalWidth); 128 | int arcBoundsRight = (int) (sourceArcBounds.right - intervalWidth); 129 | int arcBoundsBottom = (int) (sourceArcBounds.bottom - intervalWidth); 130 | mTempArcBounds.set(arcBoundsLeft, arcBoundsTop, arcBoundsRight, arcBoundsBottom); 131 | 132 | return mTempArcBounds; 133 | } 134 | 135 | @Override 136 | protected void computeRender(float renderProgress) { 137 | // Moving the start trim only occurs in the first 50% of a single ring animation 138 | if (renderProgress <= START_TRIM_DURATION_OFFSET) { 139 | float startTrimProgress = (renderProgress) / (1.0f - START_TRIM_DURATION_OFFSET); 140 | mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress); 141 | } 142 | 143 | // Moving the end trim starts after 50% of a single ring animation 144 | if (renderProgress > START_TRIM_DURATION_OFFSET) { 145 | float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET); 146 | mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress); 147 | } 148 | 149 | if (Math.abs(mEndDegrees - mStartDegrees) > 0) { 150 | mSwipeDegrees = mEndDegrees - mStartDegrees; 151 | } 152 | 153 | mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS)); 154 | } 155 | 156 | @Override 157 | protected void setAlpha(int alpha) { 158 | mPaint.setAlpha(alpha); 159 | 160 | } 161 | 162 | @Override 163 | protected void setColorFilter(ColorFilter cf) { 164 | mPaint.setColorFilter(cf); 165 | 166 | } 167 | 168 | @Override 169 | protected void reset() { 170 | resetOriginals(); 171 | } 172 | 173 | private void initStrokeInset(float width, float height) { 174 | float minSize = Math.min(width, height); 175 | float strokeInset = minSize / 2.0f - mCenterRadius; 176 | float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f); 177 | mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset; 178 | } 179 | 180 | private void storeOriginals() { 181 | mOriginEndDegrees = mEndDegrees; 182 | mOriginStartDegrees = mEndDegrees; 183 | } 184 | 185 | private void resetOriginals() { 186 | mOriginEndDegrees = 0; 187 | mOriginStartDegrees = 0; 188 | 189 | mEndDegrees = 0; 190 | mStartDegrees = 0; 191 | 192 | mSwipeDegrees = 0; 193 | } 194 | 195 | private void apply(Builder builder) { 196 | this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; 197 | this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; 198 | this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth; 199 | this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius; 200 | 201 | this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; 202 | 203 | this.mColors = builder.mColors != null && builder.mColors.length > 0 ? builder.mColors : this.mColors; 204 | 205 | setupPaint(); 206 | initStrokeInset(this.mWidth, this.mHeight); 207 | } 208 | 209 | public static class Builder { 210 | private Context mContext; 211 | 212 | private int mWidth; 213 | private int mHeight; 214 | private int mStrokeWidth; 215 | private int mCenterRadius; 216 | 217 | private int mDuration; 218 | 219 | private int[] mColors; 220 | 221 | public Builder(Context mContext) { 222 | this.mContext = mContext; 223 | } 224 | 225 | public Builder setWidth(int width) { 226 | this.mWidth = width; 227 | return this; 228 | } 229 | 230 | public Builder setHeight(int height) { 231 | this.mHeight = height; 232 | return this; 233 | } 234 | 235 | public Builder setStrokeWidth(int strokeWidth) { 236 | this.mStrokeWidth = strokeWidth; 237 | return this; 238 | } 239 | 240 | public Builder setCenterRadius(int centerRadius) { 241 | this.mCenterRadius = centerRadius; 242 | return this; 243 | } 244 | 245 | public Builder setDuration(int duration) { 246 | this.mDuration = duration; 247 | return this; 248 | } 249 | 250 | public Builder setColors(int[] colors) { 251 | this.mColors = colors; 252 | return this; 253 | } 254 | 255 | public WhorlLoadingRenderer build() { 256 | WhorlLoadingRenderer loadingRenderer = new WhorlLoadingRenderer(mContext); 257 | loadingRenderer.apply(this); 258 | return loadingRenderer; 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/goods/BalloonLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.goods; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.util.DisplayMetrics; 12 | import android.view.animation.AccelerateInterpolator; 13 | import android.view.animation.Interpolator; 14 | 15 | import app.dinus.com.loadingdrawable.DensityUtil; 16 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 17 | 18 | public class BalloonLoadingRenderer extends LoadingRenderer { 19 | private static final String PERCENT_SIGN = "%"; 20 | 21 | private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); 22 | 23 | private static final float START_INHALE_DURATION_OFFSET = 0.4f; 24 | 25 | private static final float DEFAULT_WIDTH = 200.0f; 26 | private static final float DEFAULT_HEIGHT = 150.0f; 27 | private static final float DEFAULT_STROKE_WIDTH = 2.0f; 28 | private static final float DEFAULT_GAS_TUBE_WIDTH = 48; 29 | private static final float DEFAULT_GAS_TUBE_HEIGHT = 20; 30 | private static final float DEFAULT_CANNULA_WIDTH = 13; 31 | private static final float DEFAULT_CANNULA_HEIGHT = 37; 32 | private static final float DEFAULT_CANNULA_OFFSET_Y = 3; 33 | private static final float DEFAULT_CANNULA_MAX_OFFSET_Y = 15; 34 | private static final float DEFAULT_PIPE_BODY_WIDTH = 16; 35 | private static final float DEFAULT_PIPE_BODY_HEIGHT = 36; 36 | private static final float DEFAULT_BALLOON_WIDTH = 38; 37 | private static final float DEFAULT_BALLOON_HEIGHT = 48; 38 | private static final float DEFAULT_RECT_CORNER_RADIUS = 2; 39 | 40 | private static final int DEFAULT_BALLOON_COLOR = Color.parseColor("#ffF3C211"); 41 | private static final int DEFAULT_GAS_TUBE_COLOR = Color.parseColor("#ff174469"); 42 | private static final int DEFAULT_PIPE_BODY_COLOR = Color.parseColor("#aa2369B1"); 43 | private static final int DEFAULT_CANNULA_COLOR = Color.parseColor("#ff174469"); 44 | 45 | private static final float DEFAULT_TEXT_SIZE = 7.0f; 46 | 47 | private static final long ANIMATION_DURATION = 3333; 48 | 49 | private final Paint mPaint = new Paint(); 50 | private final RectF mCurrentBounds = new RectF(); 51 | private final RectF mGasTubeBounds = new RectF(); 52 | private final RectF mPipeBodyBounds = new RectF(); 53 | private final RectF mCannulaBounds = new RectF(); 54 | private final RectF mBalloonBounds = new RectF(); 55 | 56 | private final Rect mProgressBounds = new Rect(); 57 | 58 | private float mTextSize; 59 | private float mProgress; 60 | 61 | private String mProgressText; 62 | 63 | private float mGasTubeWidth; 64 | private float mGasTubeHeight; 65 | private float mCannulaWidth; 66 | private float mCannulaHeight; 67 | private float mCannulaMaxOffsetY; 68 | private float mCannulaOffsetY; 69 | private float mPipeBodyWidth; 70 | private float mPipeBodyHeight; 71 | private float mBalloonWidth; 72 | private float mBalloonHeight; 73 | private float mRectCornerRadius; 74 | private float mStrokeWidth; 75 | 76 | private int mBalloonColor; 77 | private int mGasTubeColor; 78 | private int mCannulaColor; 79 | private int mPipeBodyColor; 80 | 81 | private BalloonLoadingRenderer(Context context) { 82 | super(context); 83 | init(context); 84 | setupPaint(); 85 | } 86 | 87 | private void init(Context context) { 88 | mTextSize = DensityUtil.dip2px(context, DEFAULT_TEXT_SIZE); 89 | 90 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 91 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 92 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 93 | 94 | mGasTubeWidth = DensityUtil.dip2px(context, DEFAULT_GAS_TUBE_WIDTH); 95 | mGasTubeHeight = DensityUtil.dip2px(context, DEFAULT_GAS_TUBE_HEIGHT); 96 | mCannulaWidth = DensityUtil.dip2px(context, DEFAULT_CANNULA_WIDTH); 97 | mCannulaHeight = DensityUtil.dip2px(context, DEFAULT_CANNULA_HEIGHT); 98 | mCannulaOffsetY = DensityUtil.dip2px(context, DEFAULT_CANNULA_OFFSET_Y); 99 | mCannulaMaxOffsetY = DensityUtil.dip2px(context, DEFAULT_CANNULA_MAX_OFFSET_Y); 100 | mPipeBodyWidth = DensityUtil.dip2px(context, DEFAULT_PIPE_BODY_WIDTH); 101 | mPipeBodyHeight = DensityUtil.dip2px(context, DEFAULT_PIPE_BODY_HEIGHT); 102 | mBalloonWidth = DensityUtil.dip2px(context, DEFAULT_BALLOON_WIDTH); 103 | mBalloonHeight = DensityUtil.dip2px(context, DEFAULT_BALLOON_HEIGHT); 104 | mRectCornerRadius = DensityUtil.dip2px(context, DEFAULT_RECT_CORNER_RADIUS); 105 | 106 | mBalloonColor = DEFAULT_BALLOON_COLOR; 107 | mGasTubeColor = DEFAULT_GAS_TUBE_COLOR; 108 | mCannulaColor = DEFAULT_CANNULA_COLOR; 109 | mPipeBodyColor = DEFAULT_PIPE_BODY_COLOR; 110 | 111 | mProgressText = 10 + PERCENT_SIGN; 112 | 113 | mDuration = ANIMATION_DURATION; 114 | } 115 | 116 | private void setupPaint() { 117 | mPaint.setAntiAlias(true); 118 | mPaint.setStrokeWidth(mStrokeWidth); 119 | } 120 | 121 | @Override 122 | protected void draw(Canvas canvas, Rect bounds) { 123 | int saveCount = canvas.save(); 124 | 125 | RectF arcBounds = mCurrentBounds; 126 | arcBounds.set(bounds); 127 | 128 | //draw draw gas tube 129 | mPaint.setColor(mGasTubeColor); 130 | mPaint.setStyle(Paint.Style.STROKE); 131 | mPaint.setStrokeWidth(mStrokeWidth); 132 | canvas.drawPath(createGasTubePath(mGasTubeBounds), mPaint); 133 | 134 | //draw balloon 135 | mPaint.setColor(mBalloonColor); 136 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 137 | canvas.drawPath(createBalloonPath(mBalloonBounds, mProgress), mPaint); 138 | 139 | //draw progress 140 | mPaint.setColor(mGasTubeColor); 141 | mPaint.setTextSize(mTextSize); 142 | mPaint.setStrokeWidth(mStrokeWidth / 5.0f); 143 | canvas.drawText(mProgressText, arcBounds.centerX() - mProgressBounds.width() / 2.0f, 144 | mGasTubeBounds.centerY() + mProgressBounds.height() / 2.0f, mPaint); 145 | 146 | //draw cannula 147 | mPaint.setColor(mCannulaColor); 148 | mPaint.setStyle(Paint.Style.STROKE); 149 | mPaint.setStrokeWidth(mStrokeWidth); 150 | canvas.drawPath(createCannulaHeadPath(mCannulaBounds), mPaint); 151 | mPaint.setStyle(Paint.Style.FILL); 152 | canvas.drawPath(createCannulaBottomPath(mCannulaBounds), mPaint); 153 | 154 | //draw pipe body 155 | mPaint.setColor(mPipeBodyColor); 156 | mPaint.setStyle(Paint.Style.FILL); 157 | canvas.drawRoundRect(mPipeBodyBounds, mRectCornerRadius, mRectCornerRadius, mPaint); 158 | 159 | canvas.restoreToCount(saveCount); 160 | } 161 | 162 | @Override 163 | protected void computeRender(float renderProgress) { 164 | RectF arcBounds = mCurrentBounds; 165 | //compute gas tube bounds 166 | mGasTubeBounds.set(arcBounds.centerX() - mGasTubeWidth / 2.0f, arcBounds.centerY(), 167 | arcBounds.centerX() + mGasTubeWidth / 2.0f, arcBounds.centerY() + mGasTubeHeight); 168 | //compute pipe body bounds 169 | mPipeBodyBounds.set(arcBounds.centerX() + mGasTubeWidth / 2.0f - mPipeBodyWidth / 2.0f, arcBounds.centerY() - mPipeBodyHeight, 170 | arcBounds.centerX() + mGasTubeWidth / 2.0f + mPipeBodyWidth / 2.0f, arcBounds.centerY()); 171 | //compute cannula bounds 172 | mCannulaBounds.set(arcBounds.centerX() + mGasTubeWidth / 2.0f - mCannulaWidth / 2.0f, arcBounds.centerY() - mCannulaHeight - mCannulaOffsetY, 173 | arcBounds.centerX() + mGasTubeWidth / 2.0f + mCannulaWidth / 2.0f, arcBounds.centerY() - mCannulaOffsetY); 174 | //compute balloon bounds 175 | float insetX = mBalloonWidth * 0.333f * (1 - mProgress); 176 | float insetY = mBalloonHeight * 0.667f * (1 - mProgress); 177 | mBalloonBounds.set(arcBounds.centerX() - mGasTubeWidth / 2.0f - mBalloonWidth / 2.0f + insetX, arcBounds.centerY() - mBalloonHeight + insetY, 178 | arcBounds.centerX() - mGasTubeWidth / 2.0f + mBalloonWidth / 2.0f - insetX, arcBounds.centerY()); 179 | 180 | if (renderProgress <= START_INHALE_DURATION_OFFSET) { 181 | mCannulaBounds.offset(0, -mCannulaMaxOffsetY * renderProgress / START_INHALE_DURATION_OFFSET); 182 | 183 | mProgress = 0.0f; 184 | mProgressText = 10 + PERCENT_SIGN; 185 | 186 | mPaint.setTextSize(mTextSize); 187 | mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mProgressBounds); 188 | } else { 189 | float exhaleProgress = ACCELERATE_INTERPOLATOR.getInterpolation(1.0f - (renderProgress - START_INHALE_DURATION_OFFSET) / (1.0f - START_INHALE_DURATION_OFFSET)); 190 | mCannulaBounds.offset(0, -mCannulaMaxOffsetY * exhaleProgress); 191 | 192 | mProgress = 1.0f - exhaleProgress; 193 | mProgressText = adjustProgress((int) (exhaleProgress * 100.0f)) + PERCENT_SIGN; 194 | 195 | mPaint.setTextSize(mTextSize); 196 | mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mProgressBounds); 197 | } 198 | } 199 | 200 | private int adjustProgress(int progress) { 201 | progress = progress / 10 * 10; 202 | progress = 100 - progress + 10; 203 | if (progress > 100) { 204 | progress = 100; 205 | } 206 | 207 | return progress; 208 | } 209 | 210 | private Path createGasTubePath(RectF gasTubeRect) { 211 | Path path = new Path(); 212 | path.moveTo(gasTubeRect.left, gasTubeRect.top); 213 | path.lineTo(gasTubeRect.left, gasTubeRect.bottom); 214 | path.lineTo(gasTubeRect.right, gasTubeRect.bottom); 215 | path.lineTo(gasTubeRect.right, gasTubeRect.top); 216 | return path; 217 | } 218 | 219 | private Path createCannulaHeadPath(RectF cannulaRect) { 220 | Path path = new Path(); 221 | path.moveTo(cannulaRect.left, cannulaRect.top); 222 | path.lineTo(cannulaRect.right, cannulaRect.top); 223 | path.moveTo(cannulaRect.centerX(), cannulaRect.top); 224 | path.lineTo(cannulaRect.centerX(), cannulaRect.bottom - 0.833f * cannulaRect.width()); 225 | return path; 226 | } 227 | 228 | private Path createCannulaBottomPath(RectF cannulaRect) { 229 | RectF cannulaHeadRect = new RectF(cannulaRect.left, cannulaRect.bottom - 0.833f * cannulaRect.width(), 230 | cannulaRect.right, cannulaRect.bottom); 231 | 232 | Path path = new Path(); 233 | path.addRoundRect(cannulaHeadRect, mRectCornerRadius, mRectCornerRadius, Path.Direction.CCW); 234 | return path; 235 | } 236 | 237 | /** 238 | * Coordinates are approximate, you have better cooperate with the designer's design draft 239 | */ 240 | private Path createBalloonPath(RectF balloonRect, float progress) { 241 | 242 | Path path = new Path(); 243 | path.moveTo(balloonRect.centerX(), balloonRect.bottom); 244 | 245 | float progressWidth = balloonRect.width() * progress; 246 | float progressHeight = balloonRect.height() * progress; 247 | //draw left half 248 | float leftIncrementX1 = progressWidth * -0.48f; 249 | float leftIncrementY1 = progressHeight * 0.75f; 250 | float leftIncrementX2 = progressWidth * -0.03f; 251 | float leftIncrementY2 = progressHeight * -1.6f; 252 | float leftIncrementX3 = progressWidth * 0.9f; 253 | float leftIncrementY3 = progressHeight * -1.0f; 254 | 255 | path.cubicTo(balloonRect.left + balloonRect.width() * 0.25f + leftIncrementX1, balloonRect.centerY() - balloonRect.height() * 0.4f + leftIncrementY1, 256 | balloonRect.left - balloonRect.width() * 0.20f + leftIncrementX2, balloonRect.centerY() + balloonRect.height() * 1.15f + leftIncrementY2, 257 | balloonRect.left - balloonRect.width() * 0.4f + leftIncrementX3, balloonRect.bottom + leftIncrementY3); 258 | 259 | // the results of the left final transformation 260 | // path.cubicTo(balloonRect.left - balloonRect.width() * 0.13f, balloonRect.centerY() + balloonRect.height() * 0.35f, 261 | // balloonRect.left - balloonRect.width() * 0.23f, balloonRect.centerY() - balloonRect.height() * 0.45f, 262 | // balloonRect.left + balloonRect.width() * 0.5f, balloonRect.bottom - balloonRect.height()); 263 | 264 | //draw right half 265 | float rightIncrementX1 = progressWidth * 1.51f; 266 | float rightIncrementY1 = progressHeight * -0.05f; 267 | float rightIncrementX2 = progressWidth * 0.03f; 268 | float rightIncrementY2 = progressHeight * 0.5f; 269 | float rightIncrementX3 = 0.0f; 270 | float rightIncrementY3 = 0.0f; 271 | 272 | path.cubicTo(balloonRect.left - balloonRect.width() * 0.38f + rightIncrementX1, balloonRect.centerY() - balloonRect.height() * 0.4f + rightIncrementY1, 273 | balloonRect.left + balloonRect.width() * 1.1f + rightIncrementX2, balloonRect.centerY() - balloonRect.height() * 0.15f + rightIncrementY2, 274 | balloonRect.left + balloonRect.width() * 0.5f + rightIncrementX3, balloonRect.bottom + rightIncrementY3); 275 | 276 | // the results of the right final transformation 277 | // path.cubicTo(balloonRect.left + balloonRect.width() * 1.23f, balloonRect.centerY() - balloonRect.height() * 0.45f, 278 | // balloonRect.left + balloonRect.width() * 1.13f, balloonRect.centerY() + balloonRect.height() * 0.35f, 279 | // balloonRect.left + balloonRect.width() * 0.5f, balloonRect.bottom); 280 | 281 | return path; 282 | } 283 | 284 | @Override 285 | protected void setAlpha(int alpha) { 286 | mPaint.setAlpha(alpha); 287 | 288 | } 289 | 290 | @Override 291 | protected void setColorFilter(ColorFilter cf) { 292 | mPaint.setColorFilter(cf); 293 | } 294 | 295 | @Override 296 | protected void reset() { 297 | } 298 | 299 | public static class Builder { 300 | private Context mContext; 301 | 302 | public Builder(Context mContext) { 303 | this.mContext = mContext; 304 | } 305 | 306 | public BalloonLoadingRenderer build() { 307 | BalloonLoadingRenderer loadingRenderer = new BalloonLoadingRenderer(mContext); 308 | return loadingRenderer; 309 | } 310 | } 311 | } -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/goods/WaterBottleLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.goods; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.graphics.RectF; 11 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 12 | import android.util.DisplayMetrics; 13 | import android.view.animation.Interpolator; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Random; 18 | 19 | import app.dinus.com.loadingdrawable.DensityUtil; 20 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 21 | 22 | public class WaterBottleLoadingRenderer extends LoadingRenderer { 23 | private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 24 | 25 | private static final float DEFAULT_WIDTH = 200.0f; 26 | private static final float DEFAULT_HEIGHT = 150.0f; 27 | private static final float DEFAULT_STROKE_WIDTH = 1.5f; 28 | private static final float DEFAULT_BOTTLE_WIDTH = 30; 29 | private static final float DEFAULT_BOTTLE_HEIGHT = 43; 30 | private static final float WATER_LOWEST_POINT_TO_BOTTLENECK_DISTANCE = 30; 31 | 32 | private static final int DEFAULT_WAVE_COUNT = 5; 33 | private static final int DEFAULT_WATER_DROP_COUNT = 25; 34 | 35 | private static final int MAX_WATER_DROP_RADIUS = 5; 36 | private static final int MIN_WATER_DROP_RADIUS = 1; 37 | 38 | private static final int DEFAULT_BOTTLE_COLOR = Color.parseColor("#FFDAEBEB"); 39 | private static final int DEFAULT_WATER_COLOR = Color.parseColor("#FF29E3F2"); 40 | 41 | private static final float DEFAULT_TEXT_SIZE = 7.0f; 42 | 43 | private static final String LOADING_TEXT = "loading"; 44 | 45 | private static final long ANIMATION_DURATION = 11111; 46 | 47 | private final Random mRandom = new Random(); 48 | 49 | private final Paint mPaint = new Paint(); 50 | private final RectF mCurrentBounds = new RectF(); 51 | private final RectF mBottleBounds = new RectF(); 52 | private final RectF mWaterBounds = new RectF(); 53 | private final Rect mLoadingBounds = new Rect(); 54 | private final List<WaterDropHolder> mWaterDropHolders = new ArrayList<>(); 55 | 56 | private float mTextSize; 57 | private float mProgress; 58 | 59 | private float mBottleWidth; 60 | private float mBottleHeight; 61 | private float mStrokeWidth; 62 | private float mWaterLowestPointToBottleneckDistance; 63 | 64 | private int mBottleColor; 65 | private int mWaterColor; 66 | 67 | private int mWaveCount; 68 | 69 | private WaterBottleLoadingRenderer(Context context) { 70 | super(context); 71 | init(context); 72 | setupPaint(); 73 | } 74 | 75 | private void init(Context context) { 76 | mTextSize = DensityUtil.dip2px(context, DEFAULT_TEXT_SIZE); 77 | 78 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 79 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 80 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 81 | 82 | mBottleWidth = DensityUtil.dip2px(context, DEFAULT_BOTTLE_WIDTH); 83 | mBottleHeight = DensityUtil.dip2px(context, DEFAULT_BOTTLE_HEIGHT); 84 | mWaterLowestPointToBottleneckDistance = DensityUtil.dip2px(context, WATER_LOWEST_POINT_TO_BOTTLENECK_DISTANCE); 85 | 86 | mBottleColor = DEFAULT_BOTTLE_COLOR; 87 | mWaterColor = DEFAULT_WATER_COLOR; 88 | 89 | mWaveCount = DEFAULT_WAVE_COUNT; 90 | 91 | mDuration = ANIMATION_DURATION; 92 | } 93 | 94 | private void setupPaint() { 95 | mPaint.setAntiAlias(true); 96 | mPaint.setStrokeWidth(mStrokeWidth); 97 | mPaint.setStrokeJoin(Paint.Join.ROUND); 98 | } 99 | 100 | @Override 101 | protected void draw(Canvas canvas, Rect bounds) { 102 | int saveCount = canvas.save(); 103 | 104 | RectF arcBounds = mCurrentBounds; 105 | arcBounds.set(bounds); 106 | //draw bottle 107 | mPaint.setStyle(Paint.Style.STROKE); 108 | mPaint.setColor(mBottleColor); 109 | canvas.drawPath(createBottlePath(mBottleBounds), mPaint); 110 | 111 | //draw water 112 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 113 | mPaint.setColor(mWaterColor); 114 | canvas.drawPath(createWaterPath(mWaterBounds, mProgress), mPaint); 115 | 116 | //draw water drop 117 | mPaint.setStyle(Paint.Style.FILL); 118 | mPaint.setColor(mWaterColor); 119 | for (WaterDropHolder waterDropHolder : mWaterDropHolders) { 120 | if (waterDropHolder.mNeedDraw) { 121 | canvas.drawCircle(waterDropHolder.mInitX, waterDropHolder.mCurrentY, waterDropHolder.mRadius, mPaint); 122 | } 123 | } 124 | 125 | //draw loading text 126 | mPaint.setColor(mBottleColor); 127 | canvas.drawText(LOADING_TEXT, mBottleBounds.centerX() - mLoadingBounds.width() / 2.0f, 128 | mBottleBounds.bottom + mBottleBounds.height() * 0.2f, mPaint); 129 | canvas.restoreToCount(saveCount); 130 | } 131 | 132 | @Override 133 | protected void computeRender(float renderProgress) { 134 | if (mCurrentBounds.width() <= 0) { 135 | return; 136 | } 137 | 138 | RectF arcBounds = mCurrentBounds; 139 | //compute gas tube bounds 140 | mBottleBounds.set(arcBounds.centerX() - mBottleWidth / 2.0f, arcBounds.centerY() - mBottleHeight / 2.0f, 141 | arcBounds.centerX() + mBottleWidth / 2.0f, arcBounds.centerY() + mBottleHeight / 2.0f); 142 | //compute pipe body bounds 143 | mWaterBounds.set(mBottleBounds.left + mStrokeWidth * 1.5f, mBottleBounds.top + mWaterLowestPointToBottleneckDistance, 144 | mBottleBounds.right - mStrokeWidth * 1.5f, mBottleBounds.bottom - mStrokeWidth * 1.5f); 145 | 146 | //compute wave progress 147 | float totalWaveProgress = renderProgress * mWaveCount; 148 | float currentWaveProgress = totalWaveProgress - ((int) totalWaveProgress); 149 | 150 | if (currentWaveProgress > 0.5f) { 151 | mProgress = 1.0f - MATERIAL_INTERPOLATOR.getInterpolation((currentWaveProgress - 0.5f) * 2.0f); 152 | } else { 153 | mProgress = MATERIAL_INTERPOLATOR.getInterpolation(currentWaveProgress * 2.0f); 154 | } 155 | 156 | //init water drop holders 157 | if (mWaterDropHolders.isEmpty()) { 158 | initWaterDropHolders(mBottleBounds, mWaterBounds); 159 | } 160 | 161 | //compute the location of these water drops 162 | for (WaterDropHolder waterDropHolder : mWaterDropHolders) { 163 | if (waterDropHolder.mDelayDuration < renderProgress 164 | && waterDropHolder.mDelayDuration + waterDropHolder.mDuration > renderProgress) { 165 | float riseProgress = (renderProgress - waterDropHolder.mDelayDuration) / waterDropHolder.mDuration; 166 | riseProgress = riseProgress < 0.5f ? riseProgress * 2.0f : 1.0f - (riseProgress - 0.5f) * 2.0f; 167 | waterDropHolder.mCurrentY = waterDropHolder.mInitY - 168 | MATERIAL_INTERPOLATOR.getInterpolation(riseProgress) * waterDropHolder.mRiseHeight; 169 | waterDropHolder.mNeedDraw = true; 170 | } else { 171 | waterDropHolder.mNeedDraw = false; 172 | } 173 | } 174 | 175 | //measure loading text 176 | mPaint.setTextSize(mTextSize); 177 | mPaint.getTextBounds(LOADING_TEXT, 0, LOADING_TEXT.length(), mLoadingBounds); 178 | } 179 | 180 | private Path createBottlePath(RectF bottleRect) { 181 | float bottleneckWidth = bottleRect.width() * 0.3f; 182 | float bottleneckHeight = bottleRect.height() * 0.415f; 183 | float bottleneckDecorationWidth = bottleneckWidth * 1.1f; 184 | float bottleneckDecorationHeight = bottleneckHeight * 0.167f; 185 | 186 | Path path = new Path(); 187 | //draw the left side of the bottleneck decoration 188 | path.moveTo(bottleRect.centerX() - bottleneckDecorationWidth * 0.5f, bottleRect.top); 189 | path.quadTo(bottleRect.centerX() - bottleneckDecorationWidth * 0.5f - bottleneckWidth * 0.15f, bottleRect.top + bottleneckDecorationHeight * 0.5f, 190 | bottleRect.centerX() - bottleneckWidth * 0.5f, bottleRect.top + bottleneckDecorationHeight); 191 | path.lineTo(bottleRect.centerX() - bottleneckWidth * 0.5f, bottleRect.top + bottleneckHeight); 192 | 193 | //draw the left side of the bottle's body 194 | float radius = (bottleRect.width() - mStrokeWidth) / 2.0f; 195 | float centerY = bottleRect.bottom - 0.86f * radius; 196 | RectF bodyRect = new RectF(bottleRect.left, centerY - radius, bottleRect.right, centerY + radius); 197 | path.addArc(bodyRect, 255, -135); 198 | 199 | //draw the bottom of the bottle 200 | float bottleBottomWidth = bottleRect.width() / 2.0f; 201 | path.lineTo(bottleRect.centerX() - bottleBottomWidth / 2.0f, bottleRect.bottom); 202 | path.lineTo(bottleRect.centerX() + bottleBottomWidth / 2.0f, bottleRect.bottom); 203 | 204 | //draw the right side of the bottle's body 205 | path.addArc(bodyRect, 60, -135); 206 | 207 | //draw the right side of the bottleneck decoration 208 | path.lineTo(bottleRect.centerX() + bottleneckWidth * 0.5f, bottleRect.top + bottleneckDecorationHeight); 209 | path.quadTo(bottleRect.centerX() + bottleneckDecorationWidth * 0.5f + bottleneckWidth * 0.15f, bottleRect.top + bottleneckDecorationHeight * 0.5f, 210 | bottleRect.centerX() + bottleneckDecorationWidth * 0.5f, bottleRect.top); 211 | 212 | return path; 213 | } 214 | 215 | private Path createWaterPath(RectF waterRect, float progress) { 216 | Path path = new Path(); 217 | 218 | path.moveTo(waterRect.left, waterRect.top); 219 | 220 | //Similar to the way draw the bottle's bottom sides 221 | float radius = (waterRect.width() - mStrokeWidth) / 2.0f; 222 | float centerY = waterRect.bottom - 0.86f * radius; 223 | float bottleBottomWidth = waterRect.width() / 2.0f; 224 | RectF bodyRect = new RectF(waterRect.left, centerY - radius, waterRect.right, centerY + radius); 225 | 226 | path.addArc(bodyRect, 187.5f, -67.5f); 227 | path.lineTo(waterRect.centerX() - bottleBottomWidth / 2.0f, waterRect.bottom); 228 | path.lineTo(waterRect.centerX() + bottleBottomWidth / 2.0f, waterRect.bottom); 229 | path.addArc(bodyRect, 60, -67.5f); 230 | 231 | //draw the water waves 232 | float cubicXChangeSize = waterRect.width() * 0.35f * progress; 233 | float cubicYChangeSize = waterRect.height() * 1.2f * progress; 234 | 235 | path.cubicTo(waterRect.left + waterRect.width() * 0.80f - cubicXChangeSize, waterRect.top - waterRect.height() * 1.2f + cubicYChangeSize, 236 | waterRect.left + waterRect.width() * 0.55f - cubicXChangeSize, waterRect.top - cubicYChangeSize, 237 | waterRect.left, waterRect.top - mStrokeWidth / 2.0f); 238 | 239 | path.lineTo(waterRect.left, waterRect.top); 240 | 241 | return path; 242 | } 243 | 244 | private void initWaterDropHolders(RectF bottleRect, RectF waterRect) { 245 | float bottleRadius = bottleRect.width() / 2.0f; 246 | float lowestWaterPointY = waterRect.top; 247 | float twoSidesInterval = 0.2f * bottleRect.width(); 248 | float atLeastDelayDuration = 0.1f; 249 | 250 | float unitDuration = 0.1f; 251 | float delayDurationRange = 0.6f; 252 | int radiusRandomRange = MAX_WATER_DROP_RADIUS - MIN_WATER_DROP_RADIUS; 253 | float currentXRandomRange = bottleRect.width() * 0.6f; 254 | 255 | for (int i = 0; i < DEFAULT_WATER_DROP_COUNT; i++) { 256 | WaterDropHolder waterDropHolder = new WaterDropHolder(); 257 | waterDropHolder.mRadius = MIN_WATER_DROP_RADIUS + mRandom.nextInt(radiusRandomRange); 258 | waterDropHolder.mInitX = bottleRect.left + twoSidesInterval + mRandom.nextFloat() * currentXRandomRange; 259 | waterDropHolder.mInitY = lowestWaterPointY + waterDropHolder.mRadius / 2.0f; 260 | waterDropHolder.mRiseHeight = getMaxRiseHeight(bottleRadius, waterDropHolder.mRadius, waterDropHolder.mInitX - bottleRect.left) 261 | * (0.2f + 0.8f * mRandom.nextFloat()); 262 | waterDropHolder.mDelayDuration = atLeastDelayDuration + mRandom.nextFloat() * delayDurationRange; 263 | waterDropHolder.mDuration = waterDropHolder.mRiseHeight / bottleRadius * unitDuration; 264 | 265 | mWaterDropHolders.add(waterDropHolder); 266 | } 267 | } 268 | 269 | private float getMaxRiseHeight(float bottleRadius, float waterDropRadius, float currentX) { 270 | float coordinateX = currentX - bottleRadius; 271 | float bottleneckRadius = bottleRadius * 0.3f; 272 | if (coordinateX - waterDropRadius > -bottleneckRadius 273 | && coordinateX + waterDropRadius < bottleneckRadius) { 274 | return bottleRadius * 2.0f; 275 | } 276 | 277 | return (float) (Math.sqrt(Math.pow(bottleRadius, 2.0f) - Math.pow(coordinateX, 2.0f)) - waterDropRadius); 278 | } 279 | 280 | @Override 281 | protected void setAlpha(int alpha) { 282 | mPaint.setAlpha(alpha); 283 | 284 | } 285 | 286 | @Override 287 | protected void setColorFilter(ColorFilter cf) { 288 | mPaint.setColorFilter(cf); 289 | 290 | } 291 | 292 | @Override 293 | protected void reset() { 294 | } 295 | 296 | private class WaterDropHolder { 297 | public float mCurrentY; 298 | 299 | public float mInitX; 300 | public float mInitY; 301 | public float mDelayDuration; 302 | public float mRiseHeight; 303 | 304 | public float mRadius; 305 | public float mDuration; 306 | 307 | public boolean mNeedDraw; 308 | } 309 | 310 | public static class Builder { 311 | private Context mContext; 312 | 313 | public Builder(Context mContext) { 314 | this.mContext = mContext; 315 | } 316 | 317 | public WaterBottleLoadingRenderer build() { 318 | WaterBottleLoadingRenderer loadingRenderer = new WaterBottleLoadingRenderer(mContext); 319 | return loadingRenderer; 320 | } 321 | } 322 | } -------------------------------------------------------------------------------- /library/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CoolWaitLoadingRenderer.java: -------------------------------------------------------------------------------- 1 | package app.dinus.com.loadingdrawable.render.shapechange; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.ColorFilter; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.PathMeasure; 10 | import android.graphics.Rect; 11 | import android.graphics.RectF; 12 | import android.util.DisplayMetrics; 13 | import android.view.animation.AccelerateDecelerateInterpolator; 14 | import android.view.animation.AccelerateInterpolator; 15 | import android.view.animation.DecelerateInterpolator; 16 | import android.view.animation.Interpolator; 17 | 18 | import app.dinus.com.loadingdrawable.DensityUtil; 19 | import app.dinus.com.loadingdrawable.render.LoadingRenderer; 20 | 21 | public class CoolWaitLoadingRenderer extends LoadingRenderer { 22 | private final Interpolator ACCELERATE_INTERPOLATOR08 = new AccelerateInterpolator(0.8f); 23 | private final Interpolator ACCELERATE_INTERPOLATOR10 = new AccelerateInterpolator(1.0f); 24 | private final Interpolator ACCELERATE_INTERPOLATOR15 = new AccelerateInterpolator(1.5f); 25 | 26 | private final Interpolator DECELERATE_INTERPOLATOR03 = new DecelerateInterpolator(0.3f); 27 | private final Interpolator DECELERATE_INTERPOLATOR05 = new DecelerateInterpolator(0.5f); 28 | private final Interpolator DECELERATE_INTERPOLATOR08 = new DecelerateInterpolator(0.8f); 29 | private final Interpolator DECELERATE_INTERPOLATOR10 = new DecelerateInterpolator(1.0f); 30 | 31 | private static final Interpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator(); 32 | 33 | private final float DEFAULT_WIDTH = 200.0f; 34 | private final float DEFAULT_HEIGHT = 150.0f; 35 | private final float DEFAULT_STROKE_WIDTH = 8.0f; 36 | private final float WAIT_CIRCLE_RADIUS = 50.0f; 37 | 38 | private static final float WAIT_TRIM_DURATION_OFFSET = 0.5f; 39 | private static final float END_TRIM_DURATION_OFFSET = 1.0f; 40 | 41 | private final long ANIMATION_DURATION = 2222; 42 | 43 | private final Paint mPaint = new Paint(); 44 | 45 | private final Path mWaitPath = new Path(); 46 | private final Path mCurrentTopWaitPath = new Path(); 47 | private final Path mCurrentMiddleWaitPath = new Path(); 48 | private final Path mCurrentBottomWaitPath = new Path(); 49 | private final PathMeasure mWaitPathMeasure = new PathMeasure(); 50 | 51 | private final RectF mCurrentBounds = new RectF(); 52 | 53 | private float mStrokeWidth; 54 | private float mWaitCircleRadius; 55 | private float mOriginEndDistance; 56 | private float mOriginStartDistance; 57 | private float mWaitPathLength; 58 | 59 | private int mTopColor; 60 | private int mMiddleColor; 61 | private int mBottomColor; 62 | 63 | private CoolWaitLoadingRenderer(Context context) { 64 | super(context); 65 | init(context); 66 | setupPaint(); 67 | } 68 | 69 | private void init(Context context) { 70 | mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); 71 | mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); 72 | mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); 73 | mWaitCircleRadius = DensityUtil.dip2px(context, WAIT_CIRCLE_RADIUS); 74 | 75 | mTopColor = Color.WHITE; 76 | mMiddleColor = Color.parseColor("#FFF3C742"); 77 | mBottomColor = Color.parseColor("#FF89CC59"); 78 | 79 | mDuration = ANIMATION_DURATION; 80 | } 81 | 82 | private void setupPaint() { 83 | mPaint.setAntiAlias(true); 84 | mPaint.setStrokeWidth(mStrokeWidth); 85 | mPaint.setStyle(Paint.Style.STROKE); 86 | mPaint.setStrokeJoin(Paint.Join.ROUND); 87 | mPaint.setStrokeCap(Paint.Cap.ROUND); 88 | } 89 | 90 | @Override 91 | protected void draw(Canvas canvas, Rect bounds) { 92 | int saveCount = canvas.save(); 93 | RectF arcBounds = mCurrentBounds; 94 | arcBounds.set(bounds); 95 | 96 | mPaint.setColor(mBottomColor); 97 | canvas.drawPath(mCurrentBottomWaitPath, mPaint); 98 | 99 | mPaint.setColor(mMiddleColor); 100 | canvas.drawPath(mCurrentMiddleWaitPath, mPaint); 101 | 102 | mPaint.setColor(mTopColor); 103 | canvas.drawPath(mCurrentTopWaitPath, mPaint); 104 | 105 | canvas.restoreToCount(saveCount); 106 | } 107 | 108 | private Path createWaitPath(RectF bounds) { 109 | Path path = new Path(); 110 | //create circle 111 | path.moveTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY()); 112 | 113 | //create w 114 | path.cubicTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius * 0.5f, 115 | bounds.centerX() + mWaitCircleRadius * 0.3f, bounds.centerY() - mWaitCircleRadius, 116 | bounds.centerX() - mWaitCircleRadius * 0.35f, bounds.centerY() + mWaitCircleRadius * 0.5f); 117 | path.quadTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius, 118 | bounds.centerX() + mWaitCircleRadius * 0.05f, bounds.centerY() + mWaitCircleRadius * 0.5f); 119 | path.lineTo(bounds.centerX() + mWaitCircleRadius * 0.75f, bounds.centerY() - mWaitCircleRadius * 0.2f); 120 | 121 | path.cubicTo(bounds.centerX(), bounds.centerY() + mWaitCircleRadius * 1f, 122 | bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius * 0.4f, 123 | bounds.centerX() + mWaitCircleRadius, bounds.centerY()); 124 | 125 | //create arc 126 | path.arcTo(new RectF(bounds.centerX() - mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius, 127 | bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius), 0, -359); 128 | path.arcTo(new RectF(bounds.centerX() - mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius, 129 | bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius), 1, -359); 130 | path.arcTo(new RectF(bounds.centerX() - mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius, 131 | bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius), 2, -2); 132 | //create w 133 | path.cubicTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius * 0.5f, 134 | bounds.centerX() + mWaitCircleRadius * 0.3f, bounds.centerY() - mWaitCircleRadius, 135 | bounds.centerX() - mWaitCircleRadius * 0.35f, bounds.centerY() + mWaitCircleRadius * 0.5f); 136 | path.quadTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius, 137 | bounds.centerX() + mWaitCircleRadius * 0.05f, bounds.centerY() + mWaitCircleRadius * 0.5f); 138 | path.lineTo(bounds.centerX() + mWaitCircleRadius * 0.75f, bounds.centerY() - mWaitCircleRadius * 0.2f); 139 | 140 | path.cubicTo(bounds.centerX(), bounds.centerY() + mWaitCircleRadius * 1f, 141 | bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius * 0.4f, 142 | bounds.centerX() + mWaitCircleRadius, bounds.centerY()); 143 | 144 | return path; 145 | } 146 | 147 | @Override 148 | protected void computeRender(float renderProgress) { 149 | if (mCurrentBounds.isEmpty()) { 150 | return; 151 | } 152 | 153 | if (mWaitPath.isEmpty()) { 154 | mWaitPath.set(createWaitPath(mCurrentBounds)); 155 | mWaitPathMeasure.setPath(mWaitPath, false); 156 | mWaitPathLength = mWaitPathMeasure.getLength(); 157 | 158 | mOriginEndDistance = mWaitPathLength * 0.255f; 159 | mOriginStartDistance = mWaitPathLength * 0.045f; 160 | } 161 | 162 | mCurrentTopWaitPath.reset(); 163 | mCurrentMiddleWaitPath.reset(); 164 | mCurrentBottomWaitPath.reset(); 165 | 166 | //draw the first half : top 167 | if (renderProgress <= WAIT_TRIM_DURATION_OFFSET) { 168 | float topTrimProgress = ACCELERATE_DECELERATE_INTERPOLATOR.getInterpolation(renderProgress / WAIT_TRIM_DURATION_OFFSET); 169 | float topEndDistance = mOriginEndDistance + mWaitPathLength * 0.3f * topTrimProgress; 170 | float topStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f * topTrimProgress; 171 | mWaitPathMeasure.getSegment(topStartDistance, topEndDistance, mCurrentTopWaitPath, true); 172 | } 173 | 174 | //draw the first half : middle 175 | if (renderProgress > 0.02f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET * 0.75f) { 176 | float middleStartTrimProgress = ACCELERATE_INTERPOLATOR10.getInterpolation((renderProgress - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.73f)); 177 | float middleEndTrimProgress = DECELERATE_INTERPOLATOR08.getInterpolation((renderProgress - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.73f)); 178 | 179 | float middleEndDistance = mOriginStartDistance + mWaitPathLength * 0.42f * middleEndTrimProgress; 180 | float middleStartDistance = mOriginStartDistance + mWaitPathLength * 0.42f * middleStartTrimProgress; 181 | mWaitPathMeasure.getSegment(middleStartDistance, middleEndDistance, mCurrentMiddleWaitPath, true); 182 | } 183 | 184 | //draw the first half : bottom 185 | if (renderProgress > 0.04f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET * 0.75f) { 186 | float bottomStartTrimProgress = ACCELERATE_INTERPOLATOR15.getInterpolation((renderProgress - 0.04f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.71f)); 187 | float bottomEndTrimProgress = DECELERATE_INTERPOLATOR05.getInterpolation((renderProgress - 0.04f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.71f)); 188 | 189 | float bottomEndDistance = mOriginStartDistance + mWaitPathLength * 0.42f * bottomEndTrimProgress; 190 | float bottomStartDistance = mOriginStartDistance + mWaitPathLength * 0.42f * bottomStartTrimProgress; 191 | mWaitPathMeasure.getSegment(bottomStartDistance, bottomEndDistance, mCurrentBottomWaitPath, true); 192 | } 193 | 194 | //draw the last half : top 195 | if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > WAIT_TRIM_DURATION_OFFSET) { 196 | float trimProgress = ACCELERATE_DECELERATE_INTERPOLATOR.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - WAIT_TRIM_DURATION_OFFSET)); 197 | float topEndDistance = mOriginEndDistance + mWaitPathLength * 0.3f + mWaitPathLength * 0.45f * trimProgress; 198 | float topStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.27f * trimProgress; 199 | mWaitPathMeasure.getSegment(topStartDistance, topEndDistance, mCurrentTopWaitPath, true); 200 | } 201 | 202 | //draw the last half : middle 203 | if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.02f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET + WAIT_TRIM_DURATION_OFFSET * 0.62f) { 204 | float middleStartTrimProgress = ACCELERATE_INTERPOLATOR08.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f)); 205 | float middleEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f)); 206 | 207 | float middleEndDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.20f * middleEndTrimProgress; 208 | float middleStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.10f * middleStartTrimProgress; 209 | mWaitPathMeasure.getSegment(middleStartDistance, middleEndDistance, mCurrentMiddleWaitPath, true); 210 | } 211 | 212 | if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.62f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= END_TRIM_DURATION_OFFSET) { 213 | float middleStartTrimProgress = DECELERATE_INTERPOLATOR10.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.62f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.38f)); 214 | float middleEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.62f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.38f)); 215 | 216 | float middleEndDistance = mOriginStartDistance + mWaitPathLength * 0.68f + mWaitPathLength * 0.325f * middleEndTrimProgress; 217 | float middleStartDistance = mOriginStartDistance + mWaitPathLength * 0.58f + mWaitPathLength * 0.17f * middleStartTrimProgress; 218 | mWaitPathMeasure.getSegment(middleStartDistance, middleEndDistance, mCurrentMiddleWaitPath, true); 219 | } 220 | 221 | //draw the last half : bottom 222 | if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.10f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET + WAIT_TRIM_DURATION_OFFSET * 0.70f) { 223 | float bottomStartTrimProgress = ACCELERATE_INTERPOLATOR15.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.10f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f)); 224 | float bottomEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.10f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f)); 225 | 226 | float bottomEndDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.20f * bottomEndTrimProgress; 227 | float bottomStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.10f * bottomStartTrimProgress; 228 | mWaitPathMeasure.getSegment(bottomStartDistance, bottomEndDistance, mCurrentBottomWaitPath, true); 229 | } 230 | 231 | if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.70f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= END_TRIM_DURATION_OFFSET) { 232 | float bottomStartTrimProgress = DECELERATE_INTERPOLATOR05.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.70f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.30f)); 233 | float bottomEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.70f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.30f)); 234 | 235 | float bottomEndDistance = mOriginStartDistance + mWaitPathLength * 0.68f + mWaitPathLength * 0.325f * bottomEndTrimProgress; 236 | float bottomStartDistance = mOriginStartDistance + mWaitPathLength * 0.58f + mWaitPathLength * 0.17f * bottomStartTrimProgress; 237 | mWaitPathMeasure.getSegment(bottomStartDistance, bottomEndDistance, mCurrentBottomWaitPath, true); 238 | } 239 | 240 | } 241 | 242 | @Override 243 | protected void setAlpha(int alpha) { 244 | mPaint.setAlpha(alpha); 245 | 246 | } 247 | 248 | @Override 249 | protected void setColorFilter(ColorFilter cf) { 250 | mPaint.setColorFilter(cf); 251 | 252 | } 253 | 254 | @Override 255 | protected void reset() { 256 | } 257 | 258 | public static class Builder { 259 | private Context mContext; 260 | 261 | public Builder(Context mContext) { 262 | this.mContext = mContext; 263 | } 264 | 265 | public CoolWaitLoadingRenderer build() { 266 | CoolWaitLoadingRenderer loadingRenderer = new CoolWaitLoadingRenderer(mContext); 267 | return loadingRenderer; 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/ic_eletric_fan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/library/src/main/res/drawable-xhdpi/ic_eletric_fan.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/ic_leaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/library/src/main/res/drawable-xhdpi/ic_leaf.png -------------------------------------------------------------------------------- /library/src/main/res/drawable-xhdpi/ic_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinuscxj/LoadingDrawable/db4ceb6b90cabee18f33f89e04f6bb3a7a0b1f64/library/src/main/res/drawable-xhdpi/ic_loading.png -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <declare-styleable name="LoadingView"> 4 | <attr name="loading_renderer"> 5 | <!--circle rotate--> 6 | <enum name="MaterialLoadingRenderer" value="0"/> 7 | <enum name="LevelLoadingRenderer" value="1"/> 8 | <enum name="WhorlLoadingRenderer" value="2"/> 9 | <enum name="GearLoadingRenderer" value="3"/> 10 | <!--circle jump--> 11 | <enum name="SwapLoadingRenderer" value="4"/> 12 | <enum name="GuardLoadingRenderer" value="5"/> 13 | <enum name="DanceLoadingRenderer" value="6"/> 14 | <enum name="CollisionLoadingRenderer" value="7"/> 15 | <!--Scenery--> 16 | <enum name="DayNightLoadingRenderer" value="8"/> 17 | <enum name="ElectricFanLoadingRenderer" value="9"/> 18 | <!--Animal--> 19 | <enum name="FishLoadingRenderer" value="10"/> 20 | <enum name="GhostsEyeLoadingRenderer" value="11"/> 21 | <!--Goods--> 22 | <enum name="BalloonLoadingRenderer" value="12"/> 23 | <enum name="WaterBottleLoadingRenderer" value="13"/> 24 | <!--ShapeChange--> 25 | <enum name="CircleBroodLoadingRenderer" value="14"/> 26 | <enum name="CoolWaitLoadingRenderer" value="15"/> 27 | </attr> 28 | </declare-styleable> 29 | </resources> -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources> 2 | <string name="app_name">Library</string> 3 | </resources> 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------