├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── attrs.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ └── layout
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── acitivty_main1.xml
│ │ ├── java
│ │ │ └── scut
│ │ │ │ └── carson_ho
│ │ │ │ └── view_testdemo
│ │ │ │ ├── Loading.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── Useage2.java
│ │ │ │ └── Utils.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── scut
│ │ │ └── carson_ho
│ │ │ └── view_testdemo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── scut
│ │ └── carson_ho
│ │ └── view_testdemo
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── kawaii_loadingview
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── attrs.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── scut
│ │ │ └── carson_ho
│ │ │ └── kawaii_loadingview
│ │ │ └── Kawaii_LoadingView.java
│ ├── test
│ │ └── java
│ │ │ └── scut
│ │ │ └── carson_ho
│ │ │ └── kawaii_loadingview
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── scut
│ │ └── carson_ho
│ │ └── kawaii_loadingview
│ │ └── ExampleInstrumentedTest.java
├── build.gradle
└── proguard-rules.pro
├── settings.gradle
├── .idea
├── copyright
│ └── profiles_settings.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── CONTRIBUTING.md
├── gradlew.bat
├── README-en.md
├── README.md
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/kawaii_loadingview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':kawaii_loadingview'
2 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | View_TestDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/kawaii_loadingview/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Kawaii_LoadingView
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Carson-Ho/Kawaii_LoadingView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/kawaii_loadingview/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FFCCFF
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jul 29 10:29:56 CST 2017
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-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/kawaii_loadingview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/scut/carson_ho/view_testdemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.view_testdemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/kawaii_loadingview/src/test/java/scut/carson_ho/kawaii_loadingview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.kawaii_loadingview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/scut/carson_ho/view_testdemo/Loading.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.view_testdemo;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.ProgressBar;
6 |
7 | /**
8 | * Created by Carson_Ho on 17/7/29.
9 | */
10 |
11 | public class Loading extends ProgressBar {
12 |
13 |
14 |
15 | public Loading(Context context) {
16 | super(context);
17 | }
18 |
19 | public Loading(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public Loading(Context context, AttributeSet attrs, int defStyleAttr) {
24 | super(context, attrs, defStyleAttr);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/kawaii_loadingview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/scut/carson_ho/view_testdemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.view_testdemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("scut.carson_ho.view_testdemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/kawaii_loadingview/src/androidTest/java/scut/carson_ho/kawaii_loadingview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.kawaii_loadingview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("scut.carson_ho.kawaii_loadingview.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/kawaii_loadingview/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 19
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | compile 'com.android.support:appcompat-v7:25.3.1'
30 | testCompile 'junit:junit:4.12'
31 | }
32 |
--------------------------------------------------------------------------------
/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/Carson_Ho/Library/Android/sdk/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/kawaii_loadingview/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/Carson_Ho/Library/Android/sdk/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "scut.carson_ho.view_testdemo"
8 | minSdkVersion 19
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
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(include: ['*.jar'], dir: 'libs')
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.3.1'
28 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
29 | testCompile 'junit:junit:4.12'
30 | compile project(':kawaii_loadingview')
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/scut/carson_ho/view_testdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.view_testdemo;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView;
8 |
9 | public class MainActivity extends AppCompatActivity {
10 |
11 |
12 | private Kawaii_LoadingView Kawaii_LoadingView;
13 | private View Loading ;
14 | private Button buttonStart,buttonFinish;
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 |
19 |
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_main);
22 | Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
23 | // Loading = findViewById(R.id.loadingView);
24 |
25 | buttonStart = (Button)findViewById(R.id.start);
26 | buttonFinish = (Button)findViewById(R.id.finish);
27 |
28 | buttonStart.setOnClickListener(new View.OnClickListener() {
29 | @Override
30 | public void onClick(View view) {
31 | Kawaii_LoadingView.startMoving();
32 | // Loading.setVisibility(View.VISIBLE);
33 | }
34 |
35 | });
36 |
37 | buttonFinish.setOnClickListener(new View.OnClickListener() {
38 | @Override
39 | public void onClick(View view) {
40 | Kawaii_LoadingView.stopMoving();
41 | // Loading.setVisibility(View.INVISIBLE);
42 | }
43 |
44 | });
45 |
46 |
47 |
48 | }
49 |
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/scut/carson_ho/view_testdemo/Useage2.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.view_testdemo;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView;
8 |
9 | /**
10 | * Created by Carson_Ho on 17/7/29.
11 | */
12 |
13 | public class Useage2 extends AppCompatActivity {
14 |
15 |
16 | private Kawaii_LoadingView Kawaii_LoadingView;
17 | private View Loading ;
18 | private Button buttonStart,buttonFinish;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 |
23 |
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.acitivty_main1);
26 | Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
27 | Loading = findViewById(R.id.loadingView);
28 |
29 | buttonStart = (Button)findViewById(R.id.start);
30 | buttonFinish = (Button)findViewById(R.id.finish);
31 |
32 | buttonStart.setOnClickListener(new View.OnClickListener() {
33 | @Override
34 | public void onClick(View view) {
35 | Kawaii_LoadingView.startMoving();
36 | Loading.setVisibility(View.VISIBLE);
37 | }
38 |
39 | });
40 |
41 | buttonFinish.setOnClickListener(new View.OnClickListener() {
42 | @Override
43 | public void onClick(View view) {
44 | Kawaii_LoadingView.stopMoving();
45 | Loading.setVisibility(View.INVISIBLE);
46 | }
47 |
48 | });
49 |
50 |
51 |
52 | }
53 |
54 |
55 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
38 |
39 |
40 |
41 |
46 |
47 |
53 |
54 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/acitivty_main1.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
38 |
39 |
45 |
46 |
47 |
48 |
49 |
54 |
55 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guide
2 | - First,Thank you for your attention to this project.
3 | - Any bug, doc, examples and suggestion is appreciated. Here are some suggestions for you to create Pull Requests or open Issues.
4 |
5 |
6 | ## 1. Branches Description
7 | - master branch:the latest (pre-)release branch.
8 | - develop branch:the stable developing branch.
9 | >1. [Github Release](https://help.github.com/articles/creating-releases/) is used to publish a (pre-)release version to master branch.
10 | >2. It's RECOMMENDED to commit bugfix or feature PR to develop.
11 |
12 | - {action}/{description} branch:The branch for a developing or bugfix *.
13 | >DO NOT commit any PR to such a branch.
14 |
15 |
16 | ## 2. Branch Management
17 |
18 | >master
19 | ↑
20 | develop <--- PR(bugfix/typo/3rd-PR)
21 | ↑ PR
22 | {type}/{description}
23 |
24 |
25 |
26 | ## 3. Branch Name
27 |
28 | ```
29 | {action}/{description}
30 |
31 | // 1. {action}:
32 | // feature: used for developing a new feature.
33 | // bugfix: used for fixing bugs.
34 | // 2. for example: feature/add_new_condition
35 | ```
36 |
37 | ## 4. Commit Log
38 | ```
39 | {action} {description}
40 | ```
41 |
42 | - {action}
43 | 1. add
44 | 2. update or bugfix
45 | 3. remove
46 | ...
47 |
48 | - {description}:It's ***RECOMMENDED*** to close issue with syntax #123, see [the doc](https://help.github.com/articles/closing-issues-via-commit-messages/) for more detail.
49 | >It's useful for responding issues and release flow.
50 |
51 | - for example
52 |
53 | ```
54 | add new condition
55 | fix #123, make compatible to recyclervew 25.2.0
56 | remove abc
57 | ```
58 |
59 | ## 5. Issue
60 | - Please apply a proper label to an issue.
61 | - Suggested to use English.
62 | - Provide sufficient instructions to be able to reproduce the issue and make the issues clear. Such as phone model, system version, sdk version, crash logs and screen captures.
63 |
64 |
65 | ## 6. Pull Request And Contributor License Agreement
66 | - In order to contribute code to Kawaii_LoadingView, you (or the legal entity you represent) must sign the Contributor License Agreement (CLA).
67 | - For CLA assistant service works properly, please make sure you have added email address that your commits linked to GitHub account.
68 |
69 | ### 7. Code Style Guide
70 | - Java:Use [Google Java Style](https://google.github.io/styleguide/javaguide.html) as basic guidelines of java code.
71 | - Android:Follow [AOSP Code Style](https://source.android.com/source/code-style.html) for rest of android related code style.
72 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Android
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 1.8
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/README-en.md:
--------------------------------------------------------------------------------
1 | # Kawaii_LoadingView
2 | >[点击查看中文文档](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/README.md)
3 | - Author:Carson_Ho
4 | - Summary
5 |
6 | 
7 |
8 |
9 | ## 1. Introduction
10 | a cut & elegance Android DIY View
11 | >Github:[Kawaii_LoadingView](https://github.com/Carson-Ho/Kawaii_LoadingView)
12 |
13 | 
14 |
15 |
16 | ## 2. Application Scenarios
17 | Prompting the loading progress & easing the mood when the user is waiting for the App loading progress.
18 |
19 |
20 |
21 | ## 3. Feature
22 | - Fresh & concise style
23 | - Adjusting the color could make sense with different App positioning & main color
24 |
25 | 
26 | - Easy to use
27 | - Secondary Programming costs are low
28 |
29 | ## 4. Usage
30 |
31 | ##### Step 1:Import Library
32 | There are two ways to import Library:
33 |
34 | - 1. For Gradle
35 | *build.Gradle*
36 |
37 | ```
38 | dependencies {
39 | compile 'com.carson_ho:Kawaii_LoadingView:1.0.0'
40 | }
41 | ```
42 |
43 | - 2. For Maven
44 | *pom.xml*
45 | ```
46 |
47 | com.carson_ho
48 | Kawaii_LoadingView
49 | 1.0.0
50 | pom
51 |
52 | ```
53 |
54 |
55 | ##### Step 2:Set Animation Attributes
56 | - Attributes Description:
57 |
58 | 
59 |
60 | - Specific settings
61 |
62 | 
63 |
64 | - Use examples
65 |
66 | *activity_main.xml*
67 | ```
68 |
90 | ```
91 |
92 | ##### Step 3:API Usage
93 |
94 | ```
95 | // 1. Defines the view variable
96 | private Kawaii_LoadingView Kawaii_LoadingView;
97 |
98 | // 2. Bind the view variable
99 | Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
100 |
101 | // 3. Use animation(API description)
102 | // 3.1 start animation
103 | Kawaii_LoadingView.startMoving();
104 | // 3.2 stop animation
105 | Kawaii_LoadingView.stopMoving();
106 | ```
107 |
108 |
109 |
110 | ## 5. Complete Demo
111 | [Carson_Ho - Github:Kawaii_LoadingView_TestDemo](https://github.com/Carson-Ho/Kawaii_LoadingView)
112 |
113 |
114 | 
115 |
116 |
117 |
118 | ## 6. Source code analysis
119 | [click here to see](http://www.jianshu.com/p/67b69fc8b63b)
120 |
121 |
122 |
123 | ## 7. LICENSE
124 | Kawaii_LoadingView is available under the Apache 2.0 license.
125 |
126 |
127 |
128 | ## 8. Contribute
129 | Before you open an issue or create a pull request, please read [Contributing Guide](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/CONTRIBUTING.md) first.
130 |
131 |
132 |
133 | ## 9. Release
134 | 2017-07-07 v1.0.0 :add start & stop animation
135 |
136 |
137 |
138 | # About the author
139 | - ID:Carson_Ho
140 | - 简介:CSDN签约作者、简书推荐作者、稀土掘金专栏作者
141 | - E - mail:[carson.ho@foxmail.com](mailto:carson.ho@foxmail.com)
142 | - Github:[https://github.com/Carson-Ho](https://github.com/Carson-Ho)
143 | - CSDN:[http://blog.csdn.net/carson_ho](http://blog.csdn.net/carson_ho)
144 | - 简书:[http://www.jianshu.com/u/383970bef0a0](http://www.jianshu.com/u/383970bef0a0)
145 | - 稀土掘金:[https://juejin.im/user/58d4d9781b69e6006ba65edc](https://juejin.im/user/58d4d9781b69e6006ba65edc)
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kawaii_LoadingView
2 | >[English Document](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/README-en.md)
3 | - 作者:Carson_Ho
4 | - 概述
5 |
6 |
7 | 
8 |
9 |
10 | **注:关于该开源项目的意见 & 建议可在Issue上提出。欢迎 Star !**
11 |
12 |
13 | ## 1. 简介
14 | 一款 可爱 & 小资风格的 `Android`自定义`View`控件
15 |
16 | 
17 |
18 |
19 | ## 2. 应用场景
20 | `App` 长时间加载等待时,**用于提示用户进度 & 缓解用户情绪**
21 |
22 |
23 |
24 | ## 3. 特点
25 | 对比市面上的加载等待自定义控件,该控件`Kawaii_LoadingView` 的特点是:
26 |
27 | ##### 3.1 样式清新
28 | - 对比市面上 各种酷炫、眼花缭乱的加载等待自定义控件,该款 `Kawaii_LoadingView` 的 **清新 & 小资风格** 简直是一股清流
29 | - 同时,可根据您的`App`定位 & 主色进行颜色调整,使得控件更加符合`App`的形象。具体如下:
30 |
31 | 
32 |
33 |
34 | 
35 |
36 | 
37 |
38 |
39 | 
40 |
41 | ##### 3.2 使用简单
42 | 仅需要3步骤 & 配置简单。
43 | >下面1节会详细介绍其使用方法
44 |
45 | ##### 3.3 二次开发成本低
46 | - 本项目已在 `Github`上开源:[Kawaii_LoadingView](https://github.com/Carson-Ho/Kawaii_LoadingView)
47 | - 详细的源码分析文档:具体请看文章[Android:你也可以自己写一个可爱 & 小资风格的加载等待自定义View](http://www.jianshu.com/p/67b69fc8b63b)
48 |
49 | 所以,在其上做二次开发 & 定制化成本非常低。
50 |
51 | ## 4. 具体使用
52 |
53 | ##### 步骤1:导入控件库
54 | 主要有 `Gradle` & `Maven` 2种方式:
55 |
56 | - 方式1:`Gradle`引入依赖
57 | *build.Gradle*
58 |
59 | ```
60 | dependencies {
61 | compile 'com.carson_ho:Kawaii_LoadingView:1.0.0'
62 | }
63 | ```
64 |
65 | - 方式2:`Maven`引入依赖
66 | *pom.xml*
67 | ```
68 |
69 | com.carson_ho
70 | Kawaii_LoadingView
71 | 1.0.0
72 | pom
73 |
74 | ```
75 |
76 |
77 | ##### 步骤2:设置动画属性
78 | - 属性说明:
79 |
80 | 
81 |
82 | - 具体属性设置
83 |
84 | 
85 |
86 | - 使用示例
87 | 在`XML`文件中进行设置
88 | *activity_main.xml*
89 | ```
90 |
112 | ```
113 |
114 | ##### 步骤3:通过 `API` 启动自定义控件的动画
115 |
116 | ```
117 | // 1. 定义控件变量
118 | private Kawaii_LoadingView Kawaii_LoadingView;
119 |
120 | // 2. 绑定控件
121 | Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
122 |
123 | // 3. 使用动画(API说明)
124 | // 3.1 启动动画
125 | Kawaii_LoadingView.startMoving();
126 | // 3.2 停止动画
127 | Kawaii_LoadingView.stopMoving();
128 | ```
129 |
130 |
131 |
132 | ## 5. 完整Demo地址
133 | [Carson_Ho的Github地址:Kawaii_LoadingView_TestDemo](https://github.com/Carson-Ho/Kawaii_LoadingView)
134 |
135 |
136 | 
137 |
138 |
139 |
140 | ## 6. 源码解析
141 |
142 | 具体请看文章[Android:你也可以自己写一个可爱 & 小资风格的加载等待自定义View](http://www.jianshu.com/p/67b69fc8b63b)
143 |
144 |
145 |
146 | ## 7. 开源协议
147 |
148 | `Kawaii_LoadingView` 遵循 `Apache 2.0` 开源协议
149 |
150 |
151 |
152 | ## 8. 贡献代码
153 | - 具体请看:[贡献说明](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/CONTRIBUTING.md)
154 | - 关于该开源项目的意见 & 建议可在`Issue`上提出。欢迎 Star !
155 |
156 |
157 |
158 | ## 9. 版本说明
159 | 2017-07-07 v1.0.0 :新增 启动 & 停止动画
160 |
161 |
162 |
163 | # 关于作者
164 | - ID:Carson_Ho
165 | - 简介:CSDN签约作者、简书推荐作者、稀土掘金专栏作者
166 | - E - mail:[carson.ho@foxmail.com](mailto:carson.ho@foxmail.com)
167 | - Github:[https://github.com/Carson-Ho](https://github.com/Carson-Ho)
168 | - CSDN:[http://blog.csdn.net/carson_ho](http://blog.csdn.net/carson_ho)
169 | - 简书:[http://www.jianshu.com/u/383970bef0a0](http://www.jianshu.com/u/383970bef0a0)
170 | - 稀土掘金:[https://juejin.im/user/58d4d9781b69e6006ba65edc](https://juejin.im/user/58d4d9781b69e6006ba65edc)
171 |
--------------------------------------------------------------------------------
/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 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/kawaii_loadingview/src/main/java/scut/carson_ho/kawaii_loadingview/Kawaii_LoadingView.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.kawaii_loadingview;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.PropertyValuesHolder;
7 | import android.animation.ValueAnimator;
8 | import android.content.Context;
9 | import android.content.res.TypedArray;
10 | import android.graphics.Canvas;
11 | import android.graphics.Paint;
12 | import android.graphics.RectF;
13 | import android.support.annotation.Nullable;
14 | import android.util.AttributeSet;
15 | import android.view.View;
16 | import android.view.animation.AnimationUtils;
17 | import android.view.animation.Interpolator;
18 |
19 | /**
20 | * Created by Carson_Ho on 17/7/29.
21 | */
22 |
23 | public class Kawaii_LoadingView extends View {
24 |
25 | // 固定方块 & 移动方块变量
26 | private fixedBlock[] mfixedBlocks;
27 | private MoveBlock mMoveBlock;
28 |
29 | // 方块属性(下面会详细介绍)
30 | private float half_BlockWidth;
31 | private float blockInterval;
32 | private Paint mPaint;
33 | private boolean isClock_Wise;
34 | private int initPosition;
35 | private int mCurrEmptyPosition;
36 | private int lineNumber;
37 | private int blockColor;
38 |
39 | // 方块的圆角半径
40 | private float moveBlock_Angle;
41 | private float fixBlock_Angle;
42 |
43 | // 动画属性
44 | private float mRotateDegree;
45 | private boolean mAllowRoll = false;
46 | private boolean isMoving = false;
47 | private int moveSpeed = 250;
48 |
49 | // 动画插值器(默认 = 线性)
50 | private Interpolator move_Interpolator;
51 | private AnimatorSet mAnimatorSet;
52 |
53 |
54 | // 自定义View的构造函数
55 | public Kawaii_LoadingView(Context context) {
56 | this(context, null);
57 | }
58 |
59 | public Kawaii_LoadingView(Context context, @Nullable AttributeSet attrs) {
60 | this(context, attrs, 0);
61 | }
62 |
63 | public Kawaii_LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
64 | super(context, attrs, defStyleAttr);
65 |
66 | // 步骤1:初始化动画属性
67 | initAttrs(context, attrs);
68 |
69 | // 步骤2:初始化自定义View
70 | init();
71 | }
72 |
73 |
74 | /**
75 | * 步骤1:初始化动画的属性
76 | */
77 | private void initAttrs(Context context, AttributeSet attrs) {
78 |
79 | // 控件资源名称
80 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Kawaii_LoadingView);
81 |
82 | // 方块行数量(最少3行)
83 | lineNumber = typedArray.getInteger(R.styleable.Kawaii_LoadingView_lineNumber, 3);
84 | if (lineNumber < 3) {
85 | lineNumber = 3;
86 | }
87 |
88 | // 半个方块的宽度(dp)
89 | half_BlockWidth = typedArray.getDimension(R.styleable.Kawaii_LoadingView_half_BlockWidth, 30);
90 | // 方块间隔宽度(dp)
91 | blockInterval = typedArray.getDimension(R.styleable.Kawaii_LoadingView_blockInterval, 10);
92 |
93 | // 移动方块的圆角半径
94 | moveBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_moveBlock_Angle, 10);
95 | // 固定方块的圆角半径
96 | fixBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_fixBlock_Angle, 30);
97 | // 通过设置两个方块的圆角半径使得二者不同可以得到更好的动画效果哦
98 |
99 | // 方块颜色(使用十六进制代码,如#333、#8e8e8e)
100 | int defaultColor = context.getResources().getColor(R.color.colorAccent); // 默认颜色
101 | blockColor = typedArray.getColor(R.styleable.Kawaii_LoadingView_blockColor, defaultColor);
102 |
103 | // 移动方块的初始位置(即空白位置)
104 | initPosition = typedArray.getInteger(R.styleable.Kawaii_LoadingView_initPosition, 0);
105 |
106 | // 由于移动方块只能是外部方块,所以这里需要判断方块是否属于外部方块 -->关注1
107 | if (isInsideTheRect(initPosition, lineNumber)) {
108 | initPosition = 0;
109 | }
110 | // 动画方向是否 = 顺时针旋转
111 | isClock_Wise = typedArray.getBoolean(R.styleable.Kawaii_LoadingView_isClock_Wise, true);
112 |
113 | // 移动方块的移动速度
114 | // 注:不建议使用者将速度调得过快
115 | // 因为会导致ValueAnimator动画对象频繁重复的创建,存在内存抖动
116 | moveSpeed = typedArray.getInteger(R.styleable.Kawaii_LoadingView_moveSpeed, 250);
117 |
118 | // 设置移动方块动画的插值器
119 | int move_InterpolatorResId = typedArray.getResourceId(R.styleable.Kawaii_LoadingView_move_Interpolator,
120 | android.R.anim.linear_interpolator);
121 | move_Interpolator = AnimationUtils.loadInterpolator(context, move_InterpolatorResId);
122 |
123 | // 当方块移动后,需要实时更新的空白方块的位置
124 | mCurrEmptyPosition = initPosition;
125 |
126 | // 释放资源
127 | typedArray.recycle();
128 | }
129 |
130 |
131 | /**
132 | * 关注1:判断方块是否在内部
133 | */
134 |
135 | private boolean isInsideTheRect(int pos, int lineCount) {
136 | // 判断方块是否在第1行
137 | if (pos < lineCount) {
138 | return false;
139 | // 是否在最后1行
140 | } else if (pos > (lineCount * lineCount - 1 - lineCount)) {
141 | return false;
142 | // 是否在最后1行
143 | } else if ((pos + 1) % lineCount == 0) {
144 | return false;
145 | // 是否在第1行
146 | } else if (pos % lineCount == 0) {
147 | return false;
148 | }
149 | // 若不在4边,则在内部
150 | return true;
151 | }
152 | // 回到原处
153 |
154 | /**
155 | * 步骤2:初始化方块对象 & 之间的关系
156 | */
157 | private void init() {
158 | // 初始化画笔
159 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
160 | mPaint.setColor(blockColor);
161 |
162 | // 初始化方块对象 & 关系 ->>关注1
163 | initBlocks(initPosition);
164 |
165 | }
166 |
167 | /**
168 | * 关注1
169 | * 初始化方块对象、之间的关系
170 | * 参数说明:initPosition = 移动方块的初始位置
171 | */
172 | private void initBlocks(int initPosition) {
173 |
174 | // 1. 创建总方块的数量(固定方块) = lineNumber * lineNumber
175 | // lineNumber = 方块的行数
176 | // fixedBlock = 固定方块 类 ->>关注2
177 | mfixedBlocks = new fixedBlock[lineNumber * lineNumber];
178 |
179 | // 2. 创建方块
180 | for (int i = 0; i < mfixedBlocks.length; i++) {
181 |
182 | // 创建固定方块 & 保存到数组中
183 | mfixedBlocks[i] = new fixedBlock();
184 |
185 | // 对固定方块对象里的变量进行赋值
186 | mfixedBlocks[i].index = i;
187 | // 对方块是否显示进行判断
188 | // 若该方块的位置 = 移动方块的初始位置,则隐藏;否则显示
189 | mfixedBlocks[i].isShow = initPosition == i ? false : true;
190 | mfixedBlocks[i].rectF = new RectF();
191 | }
192 |
193 | // 3. 创建移动的方块(1个) ->>关注3
194 | mMoveBlock = new MoveBlock();
195 | mMoveBlock.rectF = new RectF();
196 | mMoveBlock.isShow = false;
197 |
198 | // 4. 关联外部方块的位置
199 | // 因为外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
200 | // ->>关注4
201 | relate_OuterBlock(mfixedBlocks, isClock_Wise);
202 |
203 |
204 | }
205 |
206 | /**
207 | * 关注2:固定方块 类(内部类)
208 | */
209 | private class fixedBlock {
210 |
211 | // 存储方块的坐标位置参数
212 | RectF rectF;
213 |
214 | // 方块对应序号
215 | int index;
216 |
217 | // 标志位:判断是否需要绘制
218 | boolean isShow;
219 |
220 | // 指向下一个需要移动的位置
221 | fixedBlock next;
222 | // 外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
223 |
224 | }
225 | // 请回到原处
226 |
227 | /**
228 | * 关注3
229 | *:移动方块类(内部类)
230 | */
231 | private class MoveBlock {
232 | // 存储方块的坐标位置参数
233 | RectF rectF;
234 |
235 | // 方块对应序号
236 | int index;
237 |
238 | // 标志位:判断是否需要绘制
239 | boolean isShow;
240 |
241 | // 旋转中心坐标
242 | // 移动时的旋转中心(X,Y)
243 | float cx;
244 | float cy;
245 | }
246 | // 请回到原处
247 |
248 |
249 |
250 | /**
251 | * 关注4:将外部方块的位置关联起来
252 | * 算法思想: 按照第1行、最后1行、第1列 & 最后1列的顺序,分别让每个外部方块的next属性 == 下一个外部方块的位置,最终对整个外部方块的位置进行关联
253 | * 注:需要考虑移动方向变量isClockwise( 顺 Or 逆时针)
254 | */
255 |
256 | private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) {
257 | int lineCount = (int) Math.sqrt(fixedBlocks.length);
258 |
259 | // 情况1:关联第1行
260 | for (int i = 0; i < lineCount; i++) {
261 | // 位于最左边
262 | if (i % lineCount == 0) {
263 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1];
264 | // 位于最右边
265 | } else if ((i + 1) % lineCount == 0) {
266 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount];
267 | // 中间
268 | } else {
269 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1];
270 | }
271 | }
272 | // 情况2:关联最后1行
273 | for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) {
274 | // 位于最左边
275 | if (i % lineCount == 0) {
276 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
277 | // 位于最右边
278 | } else if ((i + 1) % lineCount == 0) {
279 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
280 | // 中间
281 | } else {
282 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1];
283 | }
284 | }
285 |
286 | // 情况3:关联第1列
287 | for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) {
288 | // 若是第1列最后1个
289 | if (i == (lineCount - 1) * lineCount) {
290 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
291 | continue;
292 | }
293 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i - lineCount];
294 | }
295 |
296 | // 情况4:关联最后1列
297 | for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) {
298 | // 若是最后1列最后1个
299 | if (i == lineCount * lineCount - 1) {
300 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
301 | continue;
302 | }
303 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i + lineCount];
304 | }
305 | }
306 | // 请回到原处
307 |
308 | /**
309 | * 步骤3:设置固定 & 移动方块的初始位置
310 | */
311 | @Override
312 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
313 | // 调用时刻:onCreate之后onDraw之前调用;view的大小发生改变就会调用该方法
314 | // 使用场景:用于屏幕的大小改变时,需要根据屏幕宽高来决定的其他变量可以在这里进行初始化操作
315 | super.onSizeChanged(w, h, oldw, oldh);
316 |
317 | int measuredWidth = getMeasuredWidth();
318 | int measuredHeight = getMeasuredHeight();
319 |
320 | // 1. 设置移动方块的旋转中心坐标
321 | int cx = measuredWidth / 2;
322 | int cy = measuredHeight / 2;
323 |
324 | // 2. 设置固定方块的位置 ->>关注1
325 | fixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
326 | // 3. 设置移动方块的位置 ->>关注2
327 | MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
328 | }
329 |
330 | /**
331 | * 关注1:设置 固定方块位置
332 | */
333 | private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy, float dividerWidth, float halfSquareWidth) {
334 |
335 | // 1. 确定第1个方块的位置
336 | // 分为2种情况:行数 = 偶 / 奇数时
337 | // 主要是是数学知识,此处不作过多描述
338 | float squareWidth = halfSquareWidth * 2;
339 | int lineCount = (int) Math.sqrt(fixedBlocks.length);
340 | float firstRectLeft = 0;
341 | float firstRectTop = 0;
342 |
343 | // 情况1:当行数 = 偶数时
344 | if (lineCount % 2 == 0) {
345 | int squareCountInAline = lineCount / 2;
346 | int diviCountInAline = squareCountInAline - 1;
347 | float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
348 | + diviCountInAline * dividerWidth
349 | + dividerWidth / 2;
350 | firstRectLeft = cx - firstRectLeftTopFromCenter;
351 | firstRectTop = cy - firstRectLeftTopFromCenter;
352 |
353 | // 情况2:当行数 = 奇数时
354 | } else {
355 | int squareCountInAline = lineCount / 2;
356 | int diviCountInAline = squareCountInAline;
357 | float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
358 | + diviCountInAline * dividerWidth
359 | + halfSquareWidth;
360 | firstRectLeft = cx - firstRectLeftTopFromCenter;
361 | firstRectTop = cy - firstRectLeftTopFromCenter;
362 | firstRectLeft = cx - firstRectLeftTopFromCenter;
363 | firstRectTop = cy - firstRectLeftTopFromCenter;
364 | }
365 |
366 | // 2. 确定剩下的方块位置
367 | // 思想:把第一行方块位置往下移动即可
368 | // 通过for循环确定:第一个for循环 = 行,第二个 = 列
369 | for (int i = 0; i < lineCount; i++) {//行
370 | for (int j = 0; j < lineCount; j++) {//列
371 | if (i == 0) {
372 | if (j == 0) {
373 | fixedBlocks[0].rectF.set(firstRectLeft, firstRectTop,
374 | firstRectLeft + squareWidth, firstRectTop + squareWidth);
375 | } else {
376 | int currIndex = i * lineCount + j;
377 | fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - 1].rectF);
378 | fixedBlocks[currIndex].rectF.offset(dividerWidth + squareWidth, 0);
379 | }
380 | } else {
381 | int currIndex = i * lineCount + j;
382 | fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - lineCount].rectF);
383 | fixedBlocks[currIndex].rectF.offset(0, dividerWidth + squareWidth);
384 | }
385 | }
386 | }
387 | }
388 |
389 | /**
390 | * 关注2:设置移动方块的位置
391 | */
392 | private void MoveBlockPosition(fixedBlock[] fixedBlocks,
393 | MoveBlock moveBlock, int initPosition, boolean isClockwise) {
394 |
395 | // 移动方块位置 = 设置初始的空出位置 的下一个位置(next)
396 | // 下一个位置 通过 连接的外部方块位置确定
397 | fixedBlock fixedBlock = fixedBlocks[initPosition];
398 | moveBlock.rectF.set(fixedBlock.next.rectF);
399 | }
400 |
401 | /**
402 | * 步骤4:绘制方块
403 | */
404 |
405 | @Override
406 | protected void onDraw(Canvas canvas) {
407 |
408 | // 1. 绘制内部方块(固定的)
409 | for (int i = 0; i < mfixedBlocks.length; i++) {
410 | // 根据标志位判断是否需要绘制
411 | if (mfixedBlocks[i].isShow) {
412 | // 传入方块位置参数、圆角 & 画笔属性
413 | canvas.drawRoundRect(mfixedBlocks[i].rectF, fixBlock_Angle, fixBlock_Angle, mPaint);
414 | }
415 | }
416 | // 2. 绘制移动的方块()
417 | if (mMoveBlock.isShow) {
418 | canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy);
419 | canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint);
420 | }
421 |
422 | }
423 |
424 |
425 | /**
426 | * 步骤5:启动动画
427 | */
428 |
429 | public void startMoving() {
430 |
431 | // 1. 根据标志位 & 视图是否可见确定是否需要启动动画
432 | // 此处设置是为了方便手动 & 自动停止动画
433 | if (isMoving || getVisibility() != View.VISIBLE ) {
434 | return;
435 | }
436 |
437 | // 设置标记位:以便是否停止动画
438 | isMoving = true;
439 | mAllowRoll = true;
440 |
441 | // 2. 获取固定方块当前的空位置,即移动方块当前位置
442 | fixedBlock currEmptyfixedBlock = mfixedBlocks[mCurrEmptyPosition];
443 | // 3. 获取移动方块的到达位置,即固定方块当前空位置的下1个位置
444 | fixedBlock movedBlock = currEmptyfixedBlock.next;
445 |
446 | // 4. 设置方块动画 = 移动方块平移 + 旋转
447 | // 原理:设置平移动画(Translate) + 旋转动画(Rotate),最终通过组合动画(AnimatorSet)组合起来
448 |
449 | // 4.1 设置平移动画:createTranslateValueAnimator() ->>关注1
450 | mAnimatorSet = new AnimatorSet();
451 | // 平移路径 = 初始位置 - 到达位置
452 | ValueAnimator translateConrtroller = createTranslateValueAnimator(currEmptyfixedBlock,
453 | movedBlock);
454 |
455 | // 4.2 设置旋转动画:createMoveValueAnimator(()->>关注3
456 | ValueAnimator moveConrtroller = createMoveValueAnimator();
457 |
458 | // 4.3 将两个动画组合起来
459 | // 设置移动的插值器
460 | mAnimatorSet.setInterpolator(move_Interpolator);
461 | mAnimatorSet.playTogether(translateConrtroller, moveConrtroller);
462 | mAnimatorSet.addListener(new AnimatorListenerAdapter() {
463 |
464 | // 动画开始时进行一些设置
465 | @Override
466 | public void onAnimationStart(Animator animation) {
467 |
468 | // 每次动画开始前都需要更新移动方块的位置 ->>关注4
469 | updateMoveBlock();
470 |
471 | // 让移动方块的初始位置的下个位置也隐藏 = 两个隐藏的方块
472 | mfixedBlocks[mCurrEmptyPosition].next.isShow = false;
473 |
474 | // 通过标志位将移动的方块显示出来
475 | mMoveBlock.isShow = true;
476 | }
477 |
478 | // 结束时进行一些设置
479 | @Override
480 | public void onAnimationEnd(Animator animation) {
481 | isMoving = false;
482 | mfixedBlocks[mCurrEmptyPosition].isShow = true;
483 | mCurrEmptyPosition = mfixedBlocks[mCurrEmptyPosition].next.index;
484 |
485 | // 将移动的方块隐藏
486 | mMoveBlock.isShow = false;
487 |
488 | // 通过标志位判断动画是否要循环播放
489 | if (mAllowRoll) {
490 | startMoving();
491 | }
492 | }
493 | });
494 |
495 | // 启动动画
496 | mAnimatorSet.start();
497 | }
498 |
499 | /**
500 | * 关注1:设置平移动画
501 | */
502 | private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
503 | fixedBlock moveBlock) {
504 | float startAnimValue = 0;
505 | float endAnimValue = 0;
506 | PropertyValuesHolder left = null;
507 | PropertyValuesHolder top = null;
508 |
509 | // 1. 设置移动速度
510 | ValueAnimator valueAnimator = new ValueAnimator().setDuration(moveSpeed);
511 |
512 |
513 | // 2. 设置移动方向
514 | // 情况分为:4种,分别是移动方块向左、右移动 和 上、下移动
515 | // 注:需考虑 旋转方向(isClock_Wise),即顺逆时针 ->>关注1.1
516 | if (isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {
517 |
518 | // 情况1:顺时针且在第一行 / 逆时针且在最后一行时,移动方块向右移动
519 | if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
520 |
521 | startAnimValue = moveBlock.rectF.left;
522 | endAnimValue = moveBlock.rectF.left + blockInterval;
523 |
524 | // 情况2:顺时针且在最后一行 / 逆时针且在第一行,移动方块向左移动
525 | } else if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
526 | || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {
527 |
528 | startAnimValue = moveBlock.rectF.left;
529 | endAnimValue = moveBlock.rectF.left - blockInterval;
530 | }
531 |
532 | // 设置属性值
533 | left = PropertyValuesHolder.ofFloat("left", startAnimValue, endAnimValue);
534 | valueAnimator.setValues(left);
535 |
536 | } else {
537 | // 情况3:顺时针且在最左列 / 逆时针且在最右列,移动方块向上移动
538 | if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
539 | || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {
540 |
541 | startAnimValue = moveBlock.rectF.top;
542 | endAnimValue = moveBlock.rectF.top - blockInterval;
543 |
544 | // 情况4:顺时针且在最右列 / 逆时针且在最左列,移动方块向下移动
545 | } else if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index
546 | || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
547 | startAnimValue = moveBlock.rectF.top;
548 | endAnimValue = moveBlock.rectF.top + blockInterval;
549 | }
550 |
551 | // 设置属性值
552 | top = PropertyValuesHolder.ofFloat("top", startAnimValue, endAnimValue);
553 | valueAnimator.setValues(top);
554 | }
555 |
556 | // 3. 通过监听器更新属性值
557 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
558 | @Override
559 | public void onAnimationUpdate(ValueAnimator animation) {
560 | Object left = animation.getAnimatedValue("left");
561 | Object top = animation.getAnimatedValue("top");
562 | if (left != null) {
563 | mMoveBlock.rectF.offsetTo((Float) left, mMoveBlock.rectF.top);
564 | }
565 | if (top != null) {
566 | mMoveBlock.rectF.offsetTo(mMoveBlock.rectF.left, (Float) top);
567 | }
568 | // 实时更新旋转中心 ->>关注2
569 | setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
570 |
571 | // 更新绘制
572 | invalidate();
573 | }
574 | });
575 | return valueAnimator;
576 | }
577 | // 回到原处
578 |
579 |
580 | /**
581 | * 关注1.1:判断移动方向
582 | * 即上下 or 左右
583 | */
584 | private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {
585 | if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
586 | return false;
587 | } else {
588 | return true;
589 | }
590 | }
591 |
592 | /**
593 | * 关注2:实时更新移动方块的旋转中心
594 | * 因为方块在平移旋转过程中,旋转中心也会跟着改变,因此需要改变MoveBlock的旋转中心(cx,cy)
595 | */
596 |
597 | private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isClockwise) {
598 |
599 | // 情况1:以移动方块的左上角为旋转中心
600 | if (moveBlock.index == 0) {
601 | moveBlock.cx = moveBlock.rectF.right;
602 | moveBlock.cy = moveBlock.rectF.bottom;
603 |
604 | // 情况2:以移动方块的右下角为旋转中心
605 | } else if (moveBlock.index == lineNumber * lineNumber - 1) {
606 | moveBlock.cx = moveBlock.rectF.left;
607 | moveBlock.cy = moveBlock.rectF.top;
608 |
609 | // 情况3:以移动方块的左下角为旋转中心
610 | } else if (moveBlock.index == lineNumber * (lineNumber - 1)) {
611 | moveBlock.cx = moveBlock.rectF.right;
612 | moveBlock.cy = moveBlock.rectF.top;
613 |
614 | // 情况4:以移动方块的右上角为旋转中心
615 | } else if (moveBlock.index == lineNumber - 1) {
616 | moveBlock.cx = moveBlock.rectF.left;
617 | moveBlock.cy = moveBlock.rectF.bottom;
618 | }
619 |
620 | //以下判断与旋转方向有关:即顺 or 逆顺时针
621 |
622 | // 情况1:左边
623 | else if (moveBlock.index % lineNumber == 0) {
624 | moveBlock.cx = moveBlock.rectF.right;
625 | moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom;
626 |
627 | // 情况2:上边
628 | } else if (moveBlock.index < lineNumber) {
629 | moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left;
630 | moveBlock.cy = moveBlock.rectF.bottom;
631 |
632 | // 情况3:右边
633 | } else if ((moveBlock.index + 1) % lineNumber == 0) {
634 | moveBlock.cx = moveBlock.rectF.left;
635 | moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top;
636 |
637 | // 情况4:下边
638 | } else if (moveBlock.index > (lineNumber - 1) * lineNumber) {
639 | moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right;
640 | moveBlock.cy = moveBlock.rectF.top;
641 | }
642 | }
643 | // 回到原处
644 |
645 |
646 |
647 |
648 | /**
649 | * 关注3:设置旋转动画
650 | */
651 | private ValueAnimator createMoveValueAnimator() {
652 |
653 | // 通过属性动画进行设置
654 | ValueAnimator moveAnim = ValueAnimator.ofFloat(0, 90).setDuration(moveSpeed);
655 |
656 | moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
657 | @Override
658 | public void onAnimationUpdate(ValueAnimator animation) {
659 | Object animatedValue = animation.getAnimatedValue();
660 |
661 | // 赋值
662 | mRotateDegree = (float) animatedValue;
663 |
664 | // 更新视图
665 | invalidate();
666 | }
667 | });
668 | return moveAnim;
669 | }
670 | // 回到原处
671 |
672 | /**
673 | * 关注4:更新移动方块的位置
674 | */
675 |
676 | private void updateMoveBlock() {
677 |
678 | mMoveBlock.rectF.set(mfixedBlocks[mCurrEmptyPosition].next.rectF);
679 | mMoveBlock.index = mfixedBlocks[mCurrEmptyPosition].next.index;
680 | setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
681 | }
682 | // 回到原处
683 |
684 |
685 | /**
686 | * 停止动画
687 | */
688 | public void stopMoving() {
689 |
690 | // 通过标记位来设置
691 | mAllowRoll = false;
692 | }
693 | }
694 |
--------------------------------------------------------------------------------
/app/src/main/java/scut/carson_ho/view_testdemo/Utils.java:
--------------------------------------------------------------------------------
1 | package scut.carson_ho.view_testdemo;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.PropertyValuesHolder;
7 | import android.animation.ValueAnimator;
8 | import android.content.Context;
9 | import android.content.res.TypedArray;
10 | import android.graphics.Canvas;
11 | import android.graphics.Paint;
12 | import android.graphics.RectF;
13 | import android.support.annotation.Nullable;
14 | import android.util.AttributeSet;
15 | import android.view.View;
16 | import android.view.animation.AnimationUtils;
17 | import android.view.animation.Interpolator;
18 |
19 | /**
20 | * Created by Carson_Ho on 17/7/29.
21 | */
22 |
23 | public class Utils extends View {
24 |
25 | // 固定方块 & 移动方块变量
26 | private fixedBlock[] mfixedBlocks;
27 | private MoveBlock mMoveBlock;
28 |
29 | // 方块属性(下面会详细介绍)
30 | private float half_BlockWidth;
31 | private float blockInterval;
32 | private Paint mPaint;
33 | private boolean isClock_Wise;
34 | private int initPosition;
35 | private int mCurrEmptyPosition;
36 | private int lineNumber;
37 | private int blockColor;
38 |
39 | // 方块的圆角半径
40 | private float moveBlock_Angle;
41 | private float fixBlock_Angle;
42 |
43 | // 动画属性
44 | private float mRotateDegree;
45 | private boolean mAllowRoll = false;
46 | private boolean isMoving = false;
47 | private int moveSpeed = 250;
48 |
49 | // 动画插值器(默认 = 线性)
50 | private Interpolator move_Interpolator;
51 | private AnimatorSet mAnimatorSet;
52 |
53 | // 重置动画:一个方块的动画结束的后是否需要重置(再从startEmpty开始)
54 | // private boolean mIsReset = false;
55 |
56 | // 关闭硬件加速的情况下动画卡顿解决方案
57 | // private Rect mDirtyRect;
58 |
59 | // 自定义View的构造函数
60 | public Utils(Context context) {
61 | this(context, null);
62 | }
63 |
64 | public Utils(Context context, @Nullable AttributeSet attrs) {
65 | this(context, attrs, 0);
66 | }
67 |
68 | public Utils(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
69 | super(context, attrs, defStyleAttr);
70 |
71 | // 步骤1:初始化动画属性
72 | initAttrs(context, attrs);
73 |
74 | // 步骤2:初始化自定义View
75 | init();
76 | }
77 |
78 |
79 | /**
80 | * 步骤1:初始化动画的属性
81 | */
82 | private void initAttrs(Context context, AttributeSet attrs) {
83 |
84 | // 控件资源名称
85 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Kawaii_LoadingView);
86 |
87 | // 方块行数量(最少3行)
88 | lineNumber = typedArray.getInteger(R.styleable.Kawaii_LoadingView_lineNumber, 3);
89 | if (lineNumber < 3) {
90 | lineNumber = 3;
91 | }
92 |
93 | // 半个方块的宽度(dp)
94 | half_BlockWidth = typedArray.getDimension(R.styleable.Kawaii_LoadingView_half_BlockWidth, 30);
95 | // 方块间隔宽度(dp)
96 | blockInterval = typedArray.getDimension(R.styleable.Kawaii_LoadingView_blockInterval, 10);
97 |
98 | // 移动方块的圆角半径
99 | moveBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_moveBlock_Angle, 10);
100 | // 固定方块的圆角半径
101 | fixBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_fixBlock_Angle, 30);
102 | // 通过设置两个方块的圆角半径使得二者不同可以得到更好的动画效果哦
103 |
104 | // 方块颜色(使用十六进制代码,如#333、#8e8e8e)
105 | int defaultColor = context.getResources().getColor(R.color.colorAccent); // 默认颜色
106 | blockColor = typedArray.getColor(R.styleable.Kawaii_LoadingView_blockColor, defaultColor);
107 |
108 | // 移动方块的初始位置(即空白位置)
109 | initPosition = typedArray.getInteger(R.styleable.Kawaii_LoadingView_initPosition, 0);
110 |
111 | // 由于移动方块只能是外部方块,所以这里需要判断方块是否属于外部方块 -->关注1
112 | if (isInsideTheRect(initPosition, lineNumber)) {
113 | initPosition = 0;
114 | }
115 | // 动画方向是否 = 顺时针旋转
116 | isClock_Wise = typedArray.getBoolean(R.styleable.Kawaii_LoadingView_isClock_Wise, true);
117 |
118 | // 移动方块的移动速度
119 | // 注:不建议使用者将速度调得过快
120 | // 因为会导致ValueAnimator动画对象频繁重复的创建,存在内存抖动
121 | moveSpeed = typedArray.getInteger(R.styleable.Kawaii_LoadingView_moveSpeed, 250);
122 |
123 | // 设置移动方块动画的插值器
124 | int move_InterpolatorResId = typedArray.getResourceId(R.styleable.Kawaii_LoadingView_move_Interpolator,
125 | android.R.anim.linear_interpolator);
126 | move_Interpolator = AnimationUtils.loadInterpolator(context, move_InterpolatorResId);
127 |
128 | // 当方块移动后,需要实时更新的空白方块的位置
129 | mCurrEmptyPosition = initPosition;
130 |
131 | // 释放资源
132 | typedArray.recycle();
133 | }
134 |
135 |
136 | /**
137 | * 关注1:判断方块是否在内部
138 | */
139 |
140 | private boolean isInsideTheRect(int pos, int lineCount) {
141 | // 判断方块是否在第1行
142 | if (pos < lineCount) {
143 | return false;
144 | // 是否在最后1行
145 | } else if (pos > (lineCount * lineCount - 1 - lineCount)) {
146 | return false;
147 | // 是否在最后1行
148 | } else if ((pos + 1) % lineCount == 0) {
149 | return false;
150 | // 是否在第1行
151 | } else if (pos % lineCount == 0) {
152 | return false;
153 | }
154 | // 若不在4边,则在内部
155 | return true;
156 | }
157 | // 回到原处
158 |
159 |
160 | /**
161 | * 步骤2:初始化自定义View
162 | * 包括初始化画笔 & 初始化方块对象、之间的关系
163 | */
164 | private void init() {
165 | // 初始化画笔
166 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
167 | mPaint.setColor(blockColor);
168 |
169 | // 初始化方块对象 & 关系 ->>关注1
170 | initBlocks(initPosition);
171 |
172 | }
173 |
174 | /**
175 | * 关注1
176 | * 初始化方块对象、之间的关系
177 | * 参数说明:initPosition = 移动方块的初始位置
178 | */
179 | private void initBlocks(int initPosition) {
180 |
181 | // 1. 创建总方块的数量(固定方块) = lineNumber * lineNumber
182 | // lineNumber = 方块的行数
183 | // fixedBlock = 固定方块 类 ->>关注2
184 | mfixedBlocks = new fixedBlock[lineNumber * lineNumber];
185 |
186 | // 2. 创建方块
187 | for (int i = 0; i < mfixedBlocks.length; i++) {
188 |
189 | // 创建固定方块 & 保存到数组中
190 | mfixedBlocks[i] = new fixedBlock();
191 |
192 | // 对固定方块对象里的变量进行赋值
193 | mfixedBlocks[i].index = i;
194 | // 对方块是否显示进行判断
195 | // 若该方块的位置 = 移动方块的初始位置,则隐藏;否则显示
196 | mfixedBlocks[i].isShow = initPosition == i ? false : true;
197 | mfixedBlocks[i].rectF = new RectF();
198 | }
199 |
200 | // 3. 创建移动的方块(1个) ->>关注3
201 | mMoveBlock = new MoveBlock();
202 | mMoveBlock.rectF = new RectF();
203 | mMoveBlock.isShow = false;
204 |
205 | // 4. 关联外部方块的位置
206 | // 因为外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
207 | // ->>关注4
208 | relate_OuterBlock(mfixedBlocks, isClock_Wise);
209 |
210 |
211 | }
212 |
213 | /**
214 | * 关注2:固定方块 类(内部类)
215 | */
216 | private class fixedBlock {
217 |
218 | // 存储方块的坐标位置参数
219 | RectF rectF;
220 |
221 | // 方块对应序号
222 | int index;
223 |
224 | // 标志位:判断是否需要绘制
225 | boolean isShow;
226 |
227 | // 指向下一个需要移动的位置
228 | fixedBlock next;
229 | // 外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
230 |
231 | }
232 | // 请回到原处
233 |
234 | /**
235 | * 关注3
236 | *:移动方块类(内部类)
237 | */
238 | private class MoveBlock {
239 | // 存储方块的坐标位置参数
240 | RectF rectF;
241 |
242 | // 方块对应序号
243 | int index;
244 |
245 | // 标志位:判断是否需要绘制
246 | boolean isShow;
247 |
248 | // 旋转中心坐标
249 | // 移动时的旋转中心(X,Y)
250 | float cx;
251 | float cy;
252 | }
253 | // 请回到原处
254 |
255 |
256 |
257 | /**
258 | * 关注4:将外部方块的位置关联起来
259 | * 算法思想: 按照第1行、最后1行、第1列 & 最后1列的顺序,分别让每个外部方块的next属性 == 下一个外部方块的位置,最终对整个外部方块的位置进行关联
260 | * 注:需要考虑移动方向变量isClockwise( 顺 Or 逆时针)
261 | */
262 |
263 | private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) {
264 | int lineCount = (int) Math.sqrt(fixedBlocks.length);
265 |
266 | // 情况1:关联第1行
267 | for (int i = 0; i < lineCount; i++) {
268 | // 位于最左边
269 | if (i % lineCount == 0) {
270 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1];
271 | // 位于最右边
272 | } else if ((i + 1) % lineCount == 0) {
273 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount];
274 | // 中间
275 | } else {
276 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1];
277 | }
278 | }
279 | // 情况2:关联最后1行
280 | for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) {
281 | // 位于最左边
282 | if (i % lineCount == 0) {
283 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
284 | // 位于最右边
285 | } else if ((i + 1) % lineCount == 0) {
286 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
287 | // 中间
288 | } else {
289 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1];
290 | }
291 | }
292 |
293 | // 情况3:关联第1列
294 | for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) {
295 | // 若是第1列最后1个
296 | if (i == (lineCount - 1) * lineCount) {
297 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
298 | continue;
299 | }
300 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i - lineCount];
301 | }
302 |
303 | // 情况4:关联最后1列
304 | for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) {
305 | // 若是最后1列最后1个
306 | if (i == lineCount * lineCount - 1) {
307 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
308 | continue;
309 | }
310 | fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i + lineCount];
311 | }
312 | }
313 | // 请回到原处
314 |
315 |
316 |
317 |
318 | /**
319 | * 步骤3:设置固定 & 移动方块的初始位置
320 | */
321 | @Override
322 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
323 | // 调用时刻:onCreate之后onDraw之前调用;view的大小发生改变就会调用该方法
324 | // 使用场景:用于屏幕的大小改变时,需要根据屏幕宽高来决定的其他变量可以在这里进行初始化操作
325 | super.onSizeChanged(w, h, oldw, oldh);
326 |
327 | int measuredWidth = getMeasuredWidth();
328 | int measuredHeight = getMeasuredHeight();
329 |
330 | // 1. 设置移动方块的旋转中心坐标
331 | int cx = measuredWidth / 2;
332 | int cy = measuredHeight / 2;
333 |
334 | // 2. 设置固定方块的位置 ->>关注1
335 | fixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
336 | // 3. 设置移动方块的位置 ->>关注2
337 | MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
338 |
339 | // // 4. 关闭硬件加速的情况下,动画卡顿的解决方案:设置第1个方块 ->>关注3
340 | // mDirtyRect = getDirtyRect(mfixedBlocks[0].rectF, mfixedBlocks[mfixedBlocks.length - 1].rectF);
341 | }
342 |
343 | /**
344 | * 关注1:设置 固定方块位置
345 | */
346 | private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy, float dividerWidth, float halfSquareWidth) {
347 |
348 | // 1. 确定第1个方块的位置
349 | // 分为2种情况:行数 = 偶 / 奇数时
350 | // 主要是是数学知识,此处不作过多描述
351 | float squareWidth = halfSquareWidth * 2;
352 | int lineCount = (int) Math.sqrt(fixedBlocks.length);
353 | float firstRectLeft = 0;
354 | float firstRectTop = 0;
355 |
356 | // 情况1:当行数 = 偶数时
357 | if (lineCount % 2 == 0) {
358 | int squareCountInAline = lineCount / 2;
359 | int diviCountInAline = squareCountInAline - 1;
360 | float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
361 | + diviCountInAline * dividerWidth
362 | + dividerWidth / 2;
363 | firstRectLeft = cx - firstRectLeftTopFromCenter;
364 | firstRectTop = cy - firstRectLeftTopFromCenter;
365 |
366 | // 情况2:当行数 = 奇数时
367 | } else {
368 | int squareCountInAline = lineCount / 2;
369 | int diviCountInAline = squareCountInAline;
370 | float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
371 | + diviCountInAline * dividerWidth
372 | + halfSquareWidth;
373 | firstRectLeft = cx - firstRectLeftTopFromCenter;
374 | firstRectTop = cy - firstRectLeftTopFromCenter;
375 | firstRectLeft = cx - firstRectLeftTopFromCenter;
376 | firstRectTop = cy - firstRectLeftTopFromCenter;
377 | }
378 |
379 | // 2. 确定剩下的方块位置
380 | // 思想:把第一行方块位置往下移动即可
381 | // 通过for循环确定:第一个for循环 = 行,第二个 = 列
382 | for (int i = 0; i < lineCount; i++) {//行
383 | for (int j = 0; j < lineCount; j++) {//列
384 | if (i == 0) {
385 | if (j == 0) {
386 | fixedBlocks[0].rectF.set(firstRectLeft, firstRectTop,
387 | firstRectLeft + squareWidth, firstRectTop + squareWidth);
388 | } else {
389 | int currIndex = i * lineCount + j;
390 | fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - 1].rectF);
391 | fixedBlocks[currIndex].rectF.offset(dividerWidth + squareWidth, 0);
392 | }
393 | } else {
394 | int currIndex = i * lineCount + j;
395 | fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - lineCount].rectF);
396 | fixedBlocks[currIndex].rectF.offset(0, dividerWidth + squareWidth);
397 | }
398 | }
399 | }
400 | }
401 |
402 | /**
403 | * 关注2:设置移动方块的位置
404 | */
405 | private void MoveBlockPosition(fixedBlock[] fixedBlocks,
406 | MoveBlock moveBlock, int initPosition, boolean isClockwise) {
407 |
408 | // 移动方块位置 = 设置初始的空出位置 的下一个位置(next)
409 | // 下一个位置 通过 连接的外部方块位置确定
410 | fixedBlock fixedBlock = fixedBlocks[initPosition];
411 | moveBlock.rectF.set(fixedBlock.next.rectF);
412 | }
413 |
414 | // /**
415 | // * 关注3:设置第1个方块
416 | // */
417 | // private Rect getDirtyRect(RectF leftTopRectF, RectF rightBottomRectF) {
418 | // if (leftTopRectF != null && rightBottomRectF != null) {
419 | // float width = leftTopRectF.width();
420 | // float height = leftTopRectF.height();
421 | // float sqrt = (float) Math.sqrt(width * width + height * height);
422 | // float extra = sqrt - width;
423 | // Rect dirtyRectF = new Rect((int) (leftTopRectF.left - extra),
424 | // (int) (leftTopRectF.top - extra),
425 | // (int) (rightBottomRectF.right + extra),
426 | // (int) (rightBottomRectF.bottom + extra));
427 | // return dirtyRectF;
428 | // }
429 | // return null;
430 | // }
431 |
432 |
433 |
434 | /**
435 | * 步骤4:绘制方块
436 | */
437 |
438 | @Override
439 | protected void onDraw(Canvas canvas) {
440 |
441 | // 1. 绘制内部方块(固定的)
442 | for (int i = 0; i < mfixedBlocks.length; i++) {
443 | // 根据标志位判断是否需要绘制
444 | if (mfixedBlocks[i].isShow) {
445 | // 传入方块位置参数、圆角 & 画笔属性
446 | canvas.drawRoundRect(mfixedBlocks[i].rectF, fixBlock_Angle, fixBlock_Angle, mPaint);
447 | }
448 | }
449 | // 2. 绘制移动的方块()
450 | if (mMoveBlock.isShow) {
451 | canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy);
452 | canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint);
453 | }
454 |
455 | }
456 |
457 |
458 |
459 | /**
460 | * 步骤5:启动动画
461 | */
462 |
463 | public void startMoving() {
464 |
465 | // 1. 根据标志位 & 视图是否可见确定是否需要启动动画
466 | // 此处设置是为了方便手动 & 自动停止动画
467 | if (isMoving || getVisibility() != View.VISIBLE ) {
468 | return;
469 | }
470 |
471 | // if (isMoving || getVisibility() != View.VISIBLE || getWindowVisibility() != VISIBLE) {
472 | // return;
473 | // }
474 |
475 | // 设置标记位:以便是否停止动画
476 | isMoving = true;
477 | mAllowRoll = true;
478 |
479 | // 2. 获取固定方块当前的空位置,即移动方块当前位置
480 | fixedBlock currEmptyfixedBlock = mfixedBlocks[mCurrEmptyPosition];
481 | // 3. 获取移动方块的到达位置,即固定方块当前空位置的下1个位置
482 | fixedBlock movedBlock = currEmptyfixedBlock.next;
483 |
484 |
485 | // // 设置方块位置
486 | // initBlocks2();
487 |
488 | // 4. 设置方块动画 = 移动方块平移 + 旋转
489 | // 原理:设置平移动画(Translate) + 旋转动画(Rotate),最终通过组合动画(AnimatorSet)组合起来
490 |
491 | // 4.1 设置平移动画:createTranslateValueAnimator() ->>关注1
492 | mAnimatorSet = new AnimatorSet();
493 | // 平移路径 = 初始位置 - 到达位置
494 | ValueAnimator translateConrtroller = createTranslateValueAnimator(currEmptyfixedBlock,
495 | movedBlock);
496 |
497 | // 4.2 设置旋转动画:createMoveValueAnimator(()->>关注3
498 | ValueAnimator moveConrtroller = createMoveValueAnimator();
499 |
500 | // 4.3 将两个动画组合起来
501 | // 设置移动的插值器
502 | mAnimatorSet.setInterpolator(move_Interpolator);
503 | mAnimatorSet.playTogether(translateConrtroller, moveConrtroller);
504 | mAnimatorSet.addListener(new AnimatorListenerAdapter() {
505 |
506 | // 动画开始时进行一些设置
507 | @Override
508 | public void onAnimationStart(Animator animation) {
509 |
510 | // 每次动画开始前都需要更新移动方块的位置 ->>关注4
511 | updateMoveBlock();
512 |
513 | // 让移动方块的初始位置的下个位置也隐藏 = 两个隐藏的方块
514 | mfixedBlocks[mCurrEmptyPosition].next.isShow = false;
515 |
516 | // 通过标志位将移动的方块显示出来
517 | mMoveBlock.isShow = true;
518 | }
519 |
520 | // 结束时进行一些设置
521 | @Override
522 | public void onAnimationEnd(Animator animation) {
523 | isMoving = false;
524 | mfixedBlocks[mCurrEmptyPosition].isShow = true;
525 | mCurrEmptyPosition = mfixedBlocks[mCurrEmptyPosition].next.index;
526 |
527 | // 将移动的方块隐藏
528 | mMoveBlock.isShow = false;
529 |
530 | // 通过标志位判断动画是否要循环播放
531 | if (mAllowRoll) {
532 | startMoving();
533 | }
534 |
535 | // // 重置动画
536 | // if (mIsReset) {
537 | // mCurrEmptyPosition = initPosition;
538 | // //重置动画
539 | // for (int i = 0; i < mfixedBlocks.length; i++) {
540 | // mfixedBlocks[i].isShow = true;
541 | // }
542 | //
543 | // mfixedBlocks[mCurrEmptyPosition].isShow = false;
544 | // updateMoveBlock();
545 | // // 关闭硬件加速情况下,动画卡顿解决方案
546 | //// if (!isHardwareAccelerated()) {
547 | //// invalidate(mDirtyRect);
548 | //// } else {
549 | // invalidate();
550 | //// }
551 | // startMoving();
552 | // mIsReset = false;
553 | // }
554 | }
555 | });
556 |
557 | // 启动动画
558 | mAnimatorSet.start();
559 | }
560 |
561 | /**
562 | * 关注1:设置平移动画
563 | */
564 | private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
565 | fixedBlock moveBlock) {
566 | float startAnimValue = 0;
567 | float endAnimValue = 0;
568 | PropertyValuesHolder left = null;
569 | PropertyValuesHolder top = null;
570 |
571 | // 1. 设置移动速度
572 | ValueAnimator valueAnimator = new ValueAnimator().setDuration(moveSpeed);
573 |
574 |
575 | // 2. 设置移动方向
576 | // 情况分为:4种,分别是移动方块向左、右移动 和 上、下移动
577 | // 注:需考虑 旋转方向(isClock_Wise),即顺逆时针
578 | if (isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {
579 |
580 | // 情况1:顺时针且在第一行 / 逆时针且在最后一行时,移动方块向右移动
581 | if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
582 |
583 | startAnimValue = moveBlock.rectF.left;
584 | endAnimValue = moveBlock.rectF.left + blockInterval;
585 |
586 | // 情况2:顺时针且在最后一行 / 逆时针且在第一行,移动方块向左移动
587 | } else if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
588 | || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {
589 |
590 | startAnimValue = moveBlock.rectF.left;
591 | endAnimValue = moveBlock.rectF.left - blockInterval;
592 | }
593 |
594 | // 设置属性值
595 | left = PropertyValuesHolder.ofFloat("left", startAnimValue, endAnimValue);
596 | valueAnimator.setValues(left);
597 |
598 | } else {
599 | // 情况3:顺时针且在最左列 / 逆时针且在最右列,移动方块向上移动
600 | if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
601 | || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {
602 |
603 | startAnimValue = moveBlock.rectF.top;
604 | endAnimValue = moveBlock.rectF.top - blockInterval;
605 |
606 | // 情况4:顺时针且在最右列 / 逆时针且在最左列,移动方块向下移动
607 | } else if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index
608 | || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
609 | startAnimValue = moveBlock.rectF.top;
610 | endAnimValue = moveBlock.rectF.top + blockInterval;
611 | }
612 |
613 | // 设置属性值
614 | top = PropertyValuesHolder.ofFloat("top", startAnimValue, endAnimValue);
615 | valueAnimator.setValues(top);
616 | }
617 |
618 | // 3. 通过监听器更新属性值
619 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
620 | @Override
621 | public void onAnimationUpdate(ValueAnimator animation) {
622 | Object left = animation.getAnimatedValue("left");
623 | Object top = animation.getAnimatedValue("top");
624 | if (left != null) {
625 | mMoveBlock.rectF.offsetTo((Float) left, mMoveBlock.rectF.top);
626 | }
627 | if (top != null) {
628 | mMoveBlock.rectF.offsetTo(mMoveBlock.rectF.left, (Float) top);
629 | }
630 | // 实时更新旋转中心 ->>关注2
631 | setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
632 |
633 | // 更新绘制
634 | // 此处考虑到是否开了硬件加速
635 | // if (!isHardwareAccelerated()) {
636 | // invalidate(mDirtyRect);
637 | // } else {
638 | invalidate();
639 | // }
640 | }
641 | });
642 | return valueAnimator;
643 | }
644 | // 回到原处
645 |
646 |
647 | /**
648 | * 关注2:实时更新移动方块的旋转中心
649 | * 因为方块在平移旋转过程中,旋转中心也会跟着改变,因此需要改变MoveBlock的旋转中心(cx,cy)
650 | */
651 |
652 | private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isClockwise) {
653 |
654 | // 情况1:以移动方块的左上角为旋转中心
655 | if (moveBlock.index == 0) {
656 | moveBlock.cx = moveBlock.rectF.right;
657 | moveBlock.cy = moveBlock.rectF.bottom;
658 |
659 | // 情况2:以移动方块的右下角为旋转中心
660 | } else if (moveBlock.index == lineNumber * lineNumber - 1) {
661 | moveBlock.cx = moveBlock.rectF.left;
662 | moveBlock.cy = moveBlock.rectF.top;
663 |
664 | // 情况3:以移动方块的左下角为旋转中心
665 | } else if (moveBlock.index == lineNumber * (lineNumber - 1)) {
666 | moveBlock.cx = moveBlock.rectF.right;
667 | moveBlock.cy = moveBlock.rectF.top;
668 |
669 | // 情况4:以移动方块的右上角为旋转中心
670 | } else if (moveBlock.index == lineNumber - 1) {
671 | moveBlock.cx = moveBlock.rectF.left;
672 | moveBlock.cy = moveBlock.rectF.bottom;
673 | }
674 |
675 | //以下判断与旋转方向有关:即顺 or 逆顺时针
676 |
677 | // 情况1:左边
678 | else if (moveBlock.index % lineNumber == 0) {
679 | moveBlock.cx = moveBlock.rectF.right;
680 | moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom;
681 |
682 | // 情况2:上边
683 | } else if (moveBlock.index < lineNumber) {
684 | moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left;
685 | moveBlock.cy = moveBlock.rectF.bottom;
686 |
687 | // 情况3:右边
688 | } else if ((moveBlock.index + 1) % lineNumber == 0) {
689 | moveBlock.cx = moveBlock.rectF.left;
690 | moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top;
691 |
692 | // 情况4:下边
693 | } else if (moveBlock.index > (lineNumber - 1) * lineNumber) {
694 | moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right;
695 | moveBlock.cy = moveBlock.rectF.top;
696 | }
697 | }
698 | // 回到原处
699 |
700 | /**
701 | * 关注3:设置旋转动画
702 | */
703 | private ValueAnimator createMoveValueAnimator() {
704 |
705 | // 通过属性动画进行设置
706 | ValueAnimator moveAnim = ValueAnimator.ofFloat(0, 90).setDuration(moveSpeed);
707 |
708 | moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
709 | @Override
710 | public void onAnimationUpdate(ValueAnimator animation) {
711 | Object animatedValue = animation.getAnimatedValue();
712 |
713 | // 赋值
714 | mRotateDegree = (float) animatedValue;
715 | // if (!isHardwareAccelerated()) {
716 | // invalidate(mDirtyRect);
717 | // } else {
718 |
719 | // 视图
720 | invalidate();
721 | // }
722 | }
723 | });
724 | return moveAnim;
725 | }
726 | // 回到原处
727 |
728 | /**
729 | * 关注4:更新移动方块的位置
730 | */
731 |
732 | private void updateMoveBlock() {
733 |
734 | mMoveBlock.rectF.set(mfixedBlocks[mCurrEmptyPosition].next.rectF);
735 | mMoveBlock.index = mfixedBlocks[mCurrEmptyPosition].next.index;
736 | setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
737 | }
738 | // 回到原处
739 |
740 | /**
741 | * 停止动画
742 | */
743 | public void stopMoving() {
744 |
745 | // 通过标记位来设置
746 | mAllowRoll = false;
747 | }
748 |
749 |
750 | // /**
751 | // * 重置动画
752 | // */
753 | //
754 | // public void resetRoll() {
755 | // stopRoll();
756 | // // 通过标记位来设置
757 | // mIsReset = true;
758 | // }
759 |
760 |
761 | /**
762 | * 关注5:判断移动方向
763 | * 即上下 or 左右
764 | */
765 | private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {
766 | if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
767 | return false;
768 | } else {
769 | return true;
770 | }
771 | }
772 |
773 |
774 |
775 |
776 |
777 | // /**
778 | // * 设置固定 & 移动方块的位置
779 | // */
780 | // private void initBlocks2() {
781 | //
782 | // int measuredWidth = getMeasuredWidth();
783 | // int measuredHeight = getMeasuredHeight();
784 | // System.out.println("变了");
785 | // // 设置旋转中心坐标
786 | // int cx = measuredWidth / 2;
787 | // int cy = measuredHeight / 2;
788 | //
789 | // // 设置固定方块的位置
790 | // fixfixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
791 | // // 设置移动方块的位置
792 | // fixRollSquarePosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
793 | //
794 | // mDirtyRect = getDirtyRect(mfixedBlocks[0].rectF, mfixedBlocks[mfixedBlocks.length - 1].rectF);
795 | // }
796 |
797 | /**
798 | * 当视图的Visibility改变时启动动画
799 | */
800 | // @Override
801 | // protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
802 | // super.onVisibilityChanged(changedView, visibility);
803 | // if (changedView == this && visibility == VISIBLE) {
804 | // startMoving();
805 | // } else if (changedView == this && visibility != VISIBLE) {
806 | // stopRoll();
807 | // }
808 | // }
809 | //
810 | // @Override
811 | // protected void onWindowVisibilityChanged(int visibility) {
812 | // super.onWindowVisibilityChanged(visibility);
813 | // if (visibility == VISIBLE && getVisibility() == VISIBLE) {
814 | // startMoving();
815 | // } else {
816 | // stopRoll();
817 | // }
818 | // }
819 | }
820 |
821 |
822 |
--------------------------------------------------------------------------------