Full Code of Carson-Ho/Kawaii_LoadingView for AI

master b8b2fbe1752a cached
44 files
99.1 KB
28.8k tokens
60 symbols
1 requests
Download .txt
Repository: Carson-Ho/Kawaii_LoadingView
Branch: master
Commit: b8b2fbe1752a
Files: 44
Total size: 99.1 KB

Directory structure:
gitextract_ksnah7c3/

├── .gitignore
├── .idea/
│   ├── compiler.xml
│   ├── copyright/
│   │   └── profiles_settings.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── modules.xml
│   └── runConfigurations.xml
├── CONTRIBUTING.md
├── LICENSE
├── README-en.md
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── scut/
│       │           └── carson_ho/
│       │               └── view_testdemo/
│       │                   └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── scut/
│       │   │       └── carson_ho/
│       │   │           └── view_testdemo/
│       │   │               ├── Loading.java
│       │   │               ├── MainActivity.java
│       │   │               ├── Useage2.java
│       │   │               └── Utils.java
│       │   └── res/
│       │       ├── layout/
│       │       │   ├── acitivty_main1.xml
│       │       │   └── activity_main.xml
│       │       └── values/
│       │           ├── attrs.xml
│       │           ├── colors.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── scut/
│                   └── carson_ho/
│                       └── view_testdemo/
│                           └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── kawaii_loadingview/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── scut/
│       │           └── carson_ho/
│       │               └── kawaii_loadingview/
│       │                   └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── scut/
│       │   │       └── carson_ho/
│       │   │           └── kawaii_loadingview/
│       │   │               └── Kawaii_LoadingView.java
│       │   └── res/
│       │       └── values/
│       │           ├── attrs.xml
│       │           ├── colors.xml
│       │           └── strings.xml
│       └── test/
│           └── java/
│               └── scut/
│                   └── carson_ho/
│                       └── kawaii_loadingview/
│                           └── ExampleUnitTest.java
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild


================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <resourceExtensions />
    <wildcardResourcePatterns>
      <entry name="!?*.java" />
      <entry name="!?*.form" />
      <entry name="!?*.class" />
      <entry name="!?*.groovy" />
      <entry name="!?*.scala" />
      <entry name="!?*.flex" />
      <entry name="!?*.kt" />
      <entry name="!?*.clj" />
      <entry name="!?*.aj" />
    </wildcardResourcePatterns>
    <annotationProcessing>
      <profile default="true" name="Default" enabled="false">
        <processorPath useClasspath="true" />
      </profile>
    </annotationProcessing>
  </component>
</project>

================================================
FILE: .idea/copyright/profiles_settings.xml
================================================
<component name="CopyrightManager">
  <settings default="" />
</component>

================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="distributionType" value="DEFAULT_WRAPPED" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
            <option value="$PROJECT_DIR$/kawaii_loadingview" />
          </set>
        </option>
        <option name="resolveModulePerSourceSet" value="false" />
      </GradleProjectSettings>
    </option>
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="EntryPointsManager">
    <entry_points version="2.0" />
  </component>
  <component name="NullableNotNullManager">
    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
    <option name="myNullables">
      <value>
        <list size="4">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
        </list>
      </value>
    </option>
    <option name="myNotNulls">
      <value>
        <list size="4">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
        </list>
      </value>
    </option>
  </component>
  <component name="ProjectInspectionProfilesVisibleTreeState">
    <entry key="Project Default">
      <profile-state>
        <expanded-state>
          <State>
            <id />
          </State>
        </expanded-state>
        <selected-state>
          <State>
            <id>Android</id>
          </State>
        </selected-state>
      </profile-state>
    </entry>
  </component>
  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
    <OptionsSetting value="true" id="Add" />
    <OptionsSetting value="true" id="Remove" />
    <OptionsSetting value="true" id="Checkout" />
    <OptionsSetting value="true" id="Update" />
    <OptionsSetting value="true" id="Status" />
    <OptionsSetting value="true" id="Edit" />
    <ConfirmationsSetting value="0" id="Add" />
    <ConfirmationsSetting value="0" id="Remove" />
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
  <component name="masterDetails">
    <states>
      <state key="ProjectJDKs.UI">
        <settings>
          <last-edited>1.8</last-edited>
          <splitter-proportions>
            <option name="proportions">
              <list>
                <option value="0.2" />
              </list>
            </option>
          </splitter-proportions>
        </settings>
      </state>
    </states>
  </component>
</project>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/View_TestDemo.iml" filepath="$PROJECT_DIR$/View_TestDemo.iml" />
      <module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
      <module fileurl="file://$PROJECT_DIR$/kawaii_loadingview/kawaii_loadingview.iml" filepath="$PROJECT_DIR$/kawaii_loadingview/kawaii_loadingview.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guide
- First,Thank you for your attention to this project. 
- Any bug, doc, examples and suggestion is appreciated. Here are some suggestions for you to create Pull Requests or open Issues.


## 1. Branches Description
- master branch:the latest (pre-)release branch.
- develop branch:the stable developing branch. 
>1. [Github Release](https://help.github.com/articles/creating-releases/) is used to publish a (pre-)release version to master branch.
>2. It's RECOMMENDED to commit bugfix or feature PR to develop.

- {action}/{description} branch:The branch for a developing or bugfix *. 
>DO NOT commit any PR to such a branch.


## 2.  Branch Management

>master
 ↑
develop         <--- PR(bugfix/typo/3rd-PR)
 ↑ PR
{type}/{description}



## 3.  Branch Name

```
{action}/{description}

// 1. {action}:
    // feature: used for developing a new feature.
    // bugfix: used for fixing bugs.
// 2. for example: feature/add_new_condition
```

## 4. Commit Log
```
{action} {description}
```

- {action}
  1. add
  2. update or bugfix
  3. remove
...

- {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. 
>It's useful for responding issues and release flow.

- for example

```
add new condition
fix #123, make compatible to recyclervew 25.2.0
remove abc
```

## 5.  Issue
- Please apply a proper label to an issue.
- Suggested to use English.
- 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.


## 6.  Pull Request And Contributor License Agreement
- In order to contribute code to Kawaii_LoadingView, you (or the legal entity you represent) must sign the Contributor License Agreement (CLA).
- For CLA assistant service works properly, please make sure you have added email address that your commits linked to GitHub account.

### 7.  Code Style Guide
- Java:Use [Google Java Style](https://google.github.io/styleguide/javaguide.html) as basic guidelines of java code.
- Android:Follow [AOSP Code Style](https://source.android.com/source/code-style.html) for rest of android related code style.


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright {yyyy} {name of copyright owner}

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README-en.md
================================================
# Kawaii_LoadingView
>[点击查看中文文档](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/README.md)
- Author:Carson_Ho
- Summary

![示意图](http://upload-images.jianshu.io/upload_images/944365-464f679e6cd66645.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


## 1. Introduction
a cut & elegance  Android DIY View 
>Github:[Kawaii_LoadingView](https://github.com/Carson-Ho/Kawaii_LoadingView)

![示意图](http://upload-images.jianshu.io/upload_images/944365-a9cc736b37b1ed2f.gif?imageMogr2/auto-orient/strip)


## 2. Application Scenarios
Prompting the loading progress & easing the mood when the user is waiting for the App loading progress.



## 3. Feature
- Fresh & concise style
- Adjusting the color could make sense with different App positioning & main color

![示意图](http://upload-images.jianshu.io/upload_images/944365-4bebae5ec5e79c39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- Easy to use
- Secondary Programming costs are low

## 4. Usage

##### Step 1:Import Library
There are two ways to  import Library:

- 1. For Gradle
*build.Gradle*

```
dependencies {
    compile 'com.carson_ho:Kawaii_LoadingView:1.0.0'
}
```

- 2. For Maven
*pom.xml*
```
<dependency>
  <groupId>com.carson_ho</groupId>
  <artifactId>Kawaii_LoadingView</artifactId>
  <version>1.0.0</version>
  <type>pom</type>
</dependency>
```


##### Step 2:Set Animation Attributes
- Attributes Description:

![示意图](http://upload-images.jianshu.io/upload_images/944365-f740123d4f9ad03d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- Specific settings

![示意图](http://upload-images.jianshu.io/upload_images/944365-3bb5cc87eed80e61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- Use examples

*activity_main.xml*
```
<scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView

            android:id="@+id/Kawaii_LoadingView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:background="#FFCCFF" 
            app:blockColor="#ffffff" 

            app:lineNumber="3"
            app:fixBlock_Angle="5"
            app:moveBlock_Angle="20"

            app:blockInterval="8dp"
            app:half_BlockWidth="15dp"
            app:initPosition="0"

            app:isClock_Wise="false"
            app:moveSpeed="500"
            app:move_Interpolator="@android:anim/bounce_interpolator"

            />
```

##### Step 3:API Usage

```
    // 1. Defines the view variable
    private Kawaii_LoadingView Kawaii_LoadingView;

    // 2. Bind the view  variable
    Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
        
    // 3. Use animation(API description)
       // 3.1 start animation
       Kawaii_LoadingView.startMoving();
       // 3.2  stop animation
       Kawaii_LoadingView.stopMoving();
```



## 5. Complete Demo
[Carson_Ho - Github:Kawaii_LoadingView_TestDemo](https://github.com/Carson-Ho/Kawaii_LoadingView)


![最终示意图.gif](http://upload-images.jianshu.io/upload_images/944365-ab7e77a0628d62b3.gif?imageMogr2/auto-orient/strip)



## 6.  Source code analysis
[click here to see](http://www.jianshu.com/p/67b69fc8b63b)



## 7.  LICENSE
Kawaii_LoadingView is available under the Apache 2.0 license.



## 8. Contribute
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.



## 9. Release
2017-07-07 v1.0.0 :add start & stop animation



# About the author
- ID:Carson_Ho
- 简介:CSDN签约作者、简书推荐作者、稀土掘金专栏作者
- E - mail:[carson.ho@foxmail.com](mailto:carson.ho@foxmail.com)
- Github:[https://github.com/Carson-Ho](https://github.com/Carson-Ho)
- CSDN:[http://blog.csdn.net/carson_ho](http://blog.csdn.net/carson_ho)
- 简书:[http://www.jianshu.com/u/383970bef0a0](http://www.jianshu.com/u/383970bef0a0)
- 稀土掘金:[https://juejin.im/user/58d4d9781b69e6006ba65edc](https://juejin.im/user/58d4d9781b69e6006ba65edc)


================================================
FILE: README.md
================================================
# Kawaii_LoadingView
>[English Document](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/README-en.md)
- 作者:Carson_Ho
- 概述


![示意图](http://upload-images.jianshu.io/upload_images/944365-aa402d1b3dc8f60d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


**注:关于该开源项目的意见 & 建议可在Issue上提出。欢迎 Star !**


## 1. 简介
一款 可爱 & 小资风格的 `Android`自定义`View`控件

![示意图](http://upload-images.jianshu.io/upload_images/944365-a9cc736b37b1ed2f.gif?imageMogr2/auto-orient/strip)


## 2. 应用场景
`App` 长时间加载等待时,**用于提示用户进度 & 缓解用户情绪**



## 3. 特点
对比市面上的加载等待自定义控件,该控件`Kawaii_LoadingView` 的特点是:

##### 3.1 样式清新
- 对比市面上 各种酷炫、眼花缭乱的加载等待自定义控件,该款 `Kawaii_LoadingView` 的 **清新 & 小资风格** 简直是一股清流
- 同时,可根据您的`App`定位 & 主色进行颜色调整,使得控件更加符合`App`的形象。具体如下:

![示意图](http://upload-images.jianshu.io/upload_images/944365-4bebae5ec5e79c39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


![示意图](http://upload-images.jianshu.io/upload_images/944365-32a92693dd83eee3.gif?imageMogr2/auto-orient/strip)

![示意图](http://upload-images.jianshu.io/upload_images/944365-d3c24cc2d64f3d90.gif?imageMogr2/auto-orient/strip)


![示意图](http://upload-images.jianshu.io/upload_images/944365-be2cae786f20cd41.gif?imageMogr2/auto-orient/strip)

##### 3.2 使用简单
仅需要3步骤 & 配置简单。
>下面1节会详细介绍其使用方法

##### 3.3 二次开发成本低
- 本项目已在 `Github`上开源:[Kawaii_LoadingView](https://github.com/Carson-Ho/Kawaii_LoadingView)
- 详细的源码分析文档:具体请看文章[Android:你也可以自己写一个可爱 & 小资风格的加载等待自定义View](http://www.jianshu.com/p/67b69fc8b63b)

所以,在其上做二次开发 & 定制化成本非常低。

## 4. 具体使用

##### 步骤1:导入控件库
主要有 `Gradle` & `Maven` 2种方式:

- 方式1:`Gradle`引入依赖
*build.Gradle*

```
dependencies {
    compile 'com.carson_ho:Kawaii_LoadingView:1.0.0'
}
```

- 方式2:`Maven`引入依赖
*pom.xml*
```
<dependency>
  <groupId>com.carson_ho</groupId>
  <artifactId>Kawaii_LoadingView</artifactId>
  <version>1.0.0</version>
  <type>pom</type>
</dependency>
```


##### 步骤2:设置动画属性
- 属性说明:

![示意图](http://upload-images.jianshu.io/upload_images/944365-f740123d4f9ad03d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 具体属性设置

![示意图](http://upload-images.jianshu.io/upload_images/944365-3bb5cc87eed80e61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 使用示例
在`XML`文件中进行设置
*activity_main.xml*
```
<scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView

            android:id="@+id/Kawaii_LoadingView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            android:background="#FFCCFF" 
            app:blockColor="#ffffff" 

            app:lineNumber="3"
            app:fixBlock_Angle="5"
            app:moveBlock_Angle="20"

            app:blockInterval="8dp"
            app:half_BlockWidth="15dp"
            app:initPosition="0"

            app:isClock_Wise="false"
            app:moveSpeed="500"
            app:move_Interpolator="@android:anim/bounce_interpolator"

            />
```

##### 步骤3:通过 `API` 启动自定义控件的动画

```
    // 1. 定义控件变量
    private Kawaii_LoadingView Kawaii_LoadingView;

    // 2. 绑定控件
    Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
        
    // 3. 使用动画(API说明)
       // 3.1 启动动画
       Kawaii_LoadingView.startMoving();
       // 3.2 停止动画
       Kawaii_LoadingView.stopMoving();
```



## 5. 完整Demo地址
[Carson_Ho的Github地址:Kawaii_LoadingView_TestDemo](https://github.com/Carson-Ho/Kawaii_LoadingView)


![最终示意图.gif](http://upload-images.jianshu.io/upload_images/944365-ab7e77a0628d62b3.gif?imageMogr2/auto-orient/strip)



## 6.  源码解析

具体请看文章[Android:你也可以自己写一个可爱 & 小资风格的加载等待自定义View](http://www.jianshu.com/p/67b69fc8b63b)



## 7.  开源协议

`Kawaii_LoadingView` 遵循 `Apache 2.0` 开源协议



## 8. 贡献代码
- 具体请看:[贡献说明](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/CONTRIBUTING.md)
- 关于该开源项目的意见 & 建议可在`Issue`上提出。欢迎 Star !



## 9. 版本说明
2017-07-07 v1.0.0 :新增 启动 & 停止动画



# 关于作者
- ID:Carson_Ho
- 简介:CSDN签约作者、简书推荐作者、稀土掘金专栏作者
- E - mail:[carson.ho@foxmail.com](mailto:carson.ho@foxmail.com)
- Github:[https://github.com/Carson-Ho](https://github.com/Carson-Ho)
- CSDN:[http://blog.csdn.net/carson_ho](http://blog.csdn.net/carson_ho)
- 简书:[http://www.jianshu.com/u/383970bef0a0](http://www.jianshu.com/u/383970bef0a0)
- 稀土掘金:[https://juejin.im/user/58d4d9781b69e6006ba65edc](https://juejin.im/user/58d4d9781b69e6006ba65edc)


================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "scut.carson_ho.view_testdemo"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile project(':kawaii_loadingview')
}


================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Carson_Ho/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


================================================
FILE: app/src/androidTest/java/scut/carson_ho/view_testdemo/ExampleInstrumentedTest.java
================================================
package scut.carson_ho.view_testdemo;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumentation test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("scut.carson_ho.view_testdemo", appContext.getPackageName());
    }
}


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="scut.carson_ho.view_testdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".Useage2">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>
        <activity android:name=".MainActivity"></activity>
    </application>

</manifest>

================================================
FILE: app/src/main/java/scut/carson_ho/view_testdemo/Loading.java
================================================
package scut.carson_ho.view_testdemo;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ProgressBar;

/**
 * Created by Carson_Ho on 17/7/29.
 */

public class Loading extends ProgressBar {



    public Loading(Context context) {
        super(context);
    }

    public Loading(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public Loading(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}


================================================
FILE: app/src/main/java/scut/carson_ho/view_testdemo/MainActivity.java
================================================
package scut.carson_ho.view_testdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView;

public class MainActivity extends AppCompatActivity  {


    private Kawaii_LoadingView Kawaii_LoadingView;
    private View Loading ;
    private Button buttonStart,buttonFinish;

    @Override
    protected void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
//        Loading = findViewById(R.id.loadingView);

        buttonStart = (Button)findViewById(R.id.start);
        buttonFinish = (Button)findViewById(R.id.finish);

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Kawaii_LoadingView.startMoving();
//                Loading.setVisibility(View.VISIBLE);
            }

        });

        buttonFinish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Kawaii_LoadingView.stopMoving();
//                Loading.setVisibility(View.INVISIBLE);
            }

        });



    }


}


================================================
FILE: app/src/main/java/scut/carson_ho/view_testdemo/Useage2.java
================================================
package scut.carson_ho.view_testdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView;

/**
 * Created by Carson_Ho on 17/7/29.
 */

public class Useage2 extends AppCompatActivity {


    private Kawaii_LoadingView Kawaii_LoadingView;
    private View Loading ;
    private Button buttonStart,buttonFinish;

    @Override
    protected void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);
        setContentView(R.layout.acitivty_main1);
        Kawaii_LoadingView = (Kawaii_LoadingView) findViewById(R.id.Kawaii_LoadingView);
        Loading = findViewById(R.id.loadingView);

        buttonStart = (Button)findViewById(R.id.start);
        buttonFinish = (Button)findViewById(R.id.finish);

        buttonStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Kawaii_LoadingView.startMoving();
                Loading.setVisibility(View.VISIBLE);
            }

        });

        buttonFinish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Kawaii_LoadingView.stopMoving();
                Loading.setVisibility(View.INVISIBLE);
            }

        });



    }


}

================================================
FILE: app/src/main/java/scut/carson_ho/view_testdemo/Utils.java
================================================
package scut.carson_ho.view_testdemo;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;

/**
 * Created by Carson_Ho on 17/7/29.
 */

public class Utils extends View {

    // 固定方块 & 移动方块变量
    private fixedBlock[] mfixedBlocks;
    private MoveBlock mMoveBlock;

    // 方块属性(下面会详细介绍)
    private float half_BlockWidth;
    private float blockInterval;
    private Paint mPaint;
    private boolean isClock_Wise;
    private int initPosition;
    private int mCurrEmptyPosition;
    private int lineNumber;
    private int blockColor;

    // 方块的圆角半径
    private float moveBlock_Angle;
    private float fixBlock_Angle;

    // 动画属性
    private float mRotateDegree;
    private boolean mAllowRoll = false;
    private boolean isMoving = false;
    private int moveSpeed = 250;

    // 动画插值器(默认 = 线性)
    private Interpolator move_Interpolator;
    private AnimatorSet mAnimatorSet;

    // 重置动画:一个方块的动画结束的后是否需要重置(再从startEmpty开始)
    // private boolean mIsReset = false;

    // 关闭硬件加速的情况下动画卡顿解决方案
    // private Rect mDirtyRect;

    // 自定义View的构造函数
    public Utils(Context context) {
        this(context, null);
    }

    public Utils(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Utils(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // 步骤1:初始化动画属性
        initAttrs(context, attrs);

        // 步骤2:初始化自定义View
        init();
    }


    /**
     * 步骤1:初始化动画的属性
     */
    private void initAttrs(Context context, AttributeSet attrs) {

        // 控件资源名称
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Kawaii_LoadingView);

        // 方块行数量(最少3行)
        lineNumber = typedArray.getInteger(R.styleable.Kawaii_LoadingView_lineNumber, 3);
        if (lineNumber < 3) {
            lineNumber = 3;
        }

        // 半个方块的宽度(dp)
        half_BlockWidth = typedArray.getDimension(R.styleable.Kawaii_LoadingView_half_BlockWidth, 30);
        // 方块间隔宽度(dp)
        blockInterval = typedArray.getDimension(R.styleable.Kawaii_LoadingView_blockInterval, 10);

        // 移动方块的圆角半径
        moveBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_moveBlock_Angle, 10);
        // 固定方块的圆角半径
        fixBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_fixBlock_Angle, 30);
        // 通过设置两个方块的圆角半径使得二者不同可以得到更好的动画效果哦

        // 方块颜色(使用十六进制代码,如#333、#8e8e8e)
        int defaultColor = context.getResources().getColor(R.color.colorAccent); // 默认颜色
        blockColor = typedArray.getColor(R.styleable.Kawaii_LoadingView_blockColor, defaultColor);

        // 移动方块的初始位置(即空白位置)
        initPosition = typedArray.getInteger(R.styleable.Kawaii_LoadingView_initPosition, 0);

        // 由于移动方块只能是外部方块,所以这里需要判断方块是否属于外部方块 -->关注1
        if (isInsideTheRect(initPosition, lineNumber)) {
            initPosition = 0;
        }
        // 动画方向是否 = 顺时针旋转
        isClock_Wise = typedArray.getBoolean(R.styleable.Kawaii_LoadingView_isClock_Wise, true);

        // 移动方块的移动速度
        // 注:不建议使用者将速度调得过快
        // 因为会导致ValueAnimator动画对象频繁重复的创建,存在内存抖动
        moveSpeed = typedArray.getInteger(R.styleable.Kawaii_LoadingView_moveSpeed, 250);

        // 设置移动方块动画的插值器
        int move_InterpolatorResId = typedArray.getResourceId(R.styleable.Kawaii_LoadingView_move_Interpolator,
                android.R.anim.linear_interpolator);
        move_Interpolator = AnimationUtils.loadInterpolator(context, move_InterpolatorResId);

        // 当方块移动后,需要实时更新的空白方块的位置
        mCurrEmptyPosition = initPosition;

        // 释放资源
        typedArray.recycle();
    }


    /**
     * 关注1:判断方块是否在内部
     */

    private boolean isInsideTheRect(int pos, int lineCount) {
        // 判断方块是否在第1行
        if (pos < lineCount) {
            return false;
            // 是否在最后1行
        } else if (pos > (lineCount * lineCount - 1 - lineCount)) {
            return false;
            // 是否在最后1行
        } else if ((pos + 1) % lineCount == 0) {
            return false;
            // 是否在第1行
        } else if (pos % lineCount == 0) {
            return false;
        }
        // 若不在4边,则在内部
        return true;
    }
    // 回到原处


    /**
     * 步骤2:初始化自定义View
     * 包括初始化画笔 & 初始化方块对象、之间的关系
     */
    private void init() {
        // 初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(blockColor);

        // 初始化方块对象 & 关系 ->>关注1
        initBlocks(initPosition);

    }

    /**
     * 关注1
     * 初始化方块对象、之间的关系
     * 参数说明:initPosition = 移动方块的初始位置
     */
    private void initBlocks(int initPosition) {

        // 1. 创建总方块的数量(固定方块) = lineNumber * lineNumber
        // lineNumber = 方块的行数
        // fixedBlock = 固定方块 类 ->>关注2
        mfixedBlocks = new fixedBlock[lineNumber * lineNumber];

        // 2. 创建方块
        for (int i = 0; i < mfixedBlocks.length; i++) {

            // 创建固定方块 & 保存到数组中
            mfixedBlocks[i] = new fixedBlock();

            // 对固定方块对象里的变量进行赋值
            mfixedBlocks[i].index = i;
            // 对方块是否显示进行判断
            // 若该方块的位置 = 移动方块的初始位置,则隐藏;否则显示
            mfixedBlocks[i].isShow = initPosition == i ? false : true;
            mfixedBlocks[i].rectF = new RectF();
        }

        // 3. 创建移动的方块(1个) ->>关注3
        mMoveBlock = new MoveBlock();
        mMoveBlock.rectF = new RectF();
        mMoveBlock.isShow = false;

        // 4. 关联外部方块的位置
        // 因为外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
        // ->>关注4
        relate_OuterBlock(mfixedBlocks, isClock_Wise);


    }

    /**
     * 关注2:固定方块 类(内部类)
     */
    private class fixedBlock {

        // 存储方块的坐标位置参数
        RectF rectF;

        // 方块对应序号
        int index;

        // 标志位:判断是否需要绘制
        boolean isShow;

        // 指向下一个需要移动的位置
        fixedBlock next;
        // 外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈

    }
    // 请回到原处

    /**
     * 关注3
     *:移动方块类(内部类)
     */
    private class MoveBlock {
        // 存储方块的坐标位置参数
        RectF rectF;

        // 方块对应序号
        int index;

        // 标志位:判断是否需要绘制
        boolean isShow;

        // 旋转中心坐标
        // 移动时的旋转中心(X,Y)
        float cx;
        float cy;
    }
    // 请回到原处



    /**
     * 关注4:将外部方块的位置关联起来
     * 算法思想: 按照第1行、最后1行、第1列 & 最后1列的顺序,分别让每个外部方块的next属性 == 下一个外部方块的位置,最终对整个外部方块的位置进行关联
     *  注:需要考虑移动方向变量isClockwise( 顺 Or 逆时针)
     */

    private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) {
        int lineCount = (int) Math.sqrt(fixedBlocks.length);

        // 情况1:关联第1行
        for (int i = 0; i < lineCount; i++) {
            // 位于最左边
            if (i % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1];
                // 位于最右边
            } else if ((i + 1) % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount];
                // 中间
            } else {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1];
            }
        }
        // 情况2:关联最后1行
        for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) {
            // 位于最左边
            if (i % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
                // 位于最右边
            } else if ((i + 1) % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
                // 中间
            } else {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1];
            }
        }

        // 情况3:关联第1列
        for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) {
            // 若是第1列最后1个
            if (i == (lineCount - 1) * lineCount) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
                continue;
            }
            fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i - lineCount];
        }

        // 情况4:关联最后1列
        for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) {
            // 若是最后1列最后1个
            if (i == lineCount * lineCount - 1) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
                continue;
            }
            fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i + lineCount];
        }
    }
    // 请回到原处




    /**
     * 步骤3:设置固定 & 移动方块的初始位置
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // 调用时刻:onCreate之后onDraw之前调用;view的大小发生改变就会调用该方法
        // 使用场景:用于屏幕的大小改变时,需要根据屏幕宽高来决定的其他变量可以在这里进行初始化操作
        super.onSizeChanged(w, h, oldw, oldh);

        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();

        // 1. 设置移动方块的旋转中心坐标
        int cx = measuredWidth / 2;
        int cy = measuredHeight / 2;

        // 2. 设置固定方块的位置 ->>关注1
        fixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
        // 3. 设置移动方块的位置 ->>关注2
        MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);

//        // 4. 关闭硬件加速的情况下,动画卡顿的解决方案:设置第1个方块 ->>关注3
//        mDirtyRect = getDirtyRect(mfixedBlocks[0].rectF, mfixedBlocks[mfixedBlocks.length - 1].rectF);
    }

    /**
     * 关注1:设置 固定方块位置
     */
    private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy, float dividerWidth, float halfSquareWidth) {

        // 1. 确定第1个方块的位置
        // 分为2种情况:行数 = 偶 / 奇数时
        // 主要是是数学知识,此处不作过多描述
        float squareWidth = halfSquareWidth * 2;
        int lineCount = (int) Math.sqrt(fixedBlocks.length);
        float firstRectLeft = 0;
        float firstRectTop = 0;

        // 情况1:当行数 = 偶数时
        if (lineCount % 2 == 0) {
            int squareCountInAline = lineCount / 2;
            int diviCountInAline = squareCountInAline - 1;
            float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
                    + diviCountInAline * dividerWidth
                    + dividerWidth / 2;
            firstRectLeft = cx - firstRectLeftTopFromCenter;
            firstRectTop = cy - firstRectLeftTopFromCenter;

            // 情况2:当行数 = 奇数时
        } else {
            int squareCountInAline = lineCount / 2;
            int diviCountInAline = squareCountInAline;
            float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
                    + diviCountInAline * dividerWidth
                    + halfSquareWidth;
            firstRectLeft = cx - firstRectLeftTopFromCenter;
            firstRectTop = cy - firstRectLeftTopFromCenter;
            firstRectLeft = cx - firstRectLeftTopFromCenter;
            firstRectTop = cy - firstRectLeftTopFromCenter;
        }

        // 2. 确定剩下的方块位置
        // 思想:把第一行方块位置往下移动即可
        // 通过for循环确定:第一个for循环 = 行,第二个 = 列
        for (int i = 0; i < lineCount; i++) {//行
            for (int j = 0; j < lineCount; j++) {//列
                if (i == 0) {
                    if (j == 0) {
                        fixedBlocks[0].rectF.set(firstRectLeft, firstRectTop,
                                firstRectLeft + squareWidth, firstRectTop + squareWidth);
                    } else {
                        int currIndex = i * lineCount + j;
                        fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - 1].rectF);
                        fixedBlocks[currIndex].rectF.offset(dividerWidth + squareWidth, 0);
                    }
                } else {
                    int currIndex = i * lineCount + j;
                    fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - lineCount].rectF);
                    fixedBlocks[currIndex].rectF.offset(0, dividerWidth + squareWidth);
                }
            }
        }
    }

    /**
     * 关注2:设置移动方块的位置
     */
    private void MoveBlockPosition(fixedBlock[] fixedBlocks,
                                   MoveBlock moveBlock, int initPosition, boolean isClockwise) {

        // 移动方块位置 = 设置初始的空出位置 的下一个位置(next)
        // 下一个位置 通过 连接的外部方块位置确定
        fixedBlock fixedBlock = fixedBlocks[initPosition];
        moveBlock.rectF.set(fixedBlock.next.rectF);
    }

//    /**
//     * 关注3:设置第1个方块
//     */
//    private Rect getDirtyRect(RectF leftTopRectF, RectF rightBottomRectF) {
//        if (leftTopRectF != null && rightBottomRectF != null) {
//            float width = leftTopRectF.width();
//            float height = leftTopRectF.height();
//            float sqrt = (float) Math.sqrt(width * width + height * height);
//            float extra = sqrt - width;
//            Rect dirtyRectF = new Rect((int) (leftTopRectF.left - extra),
//                    (int) (leftTopRectF.top - extra),
//                    (int) (rightBottomRectF.right + extra),
//                    (int) (rightBottomRectF.bottom + extra));
//            return dirtyRectF;
//        }
//        return null;
//    }



    /**
     * 步骤4:绘制方块
     */

    @Override
    protected void onDraw(Canvas canvas) {

        // 1. 绘制内部方块(固定的)
        for (int i = 0; i < mfixedBlocks.length; i++) {
            // 根据标志位判断是否需要绘制
            if (mfixedBlocks[i].isShow) {
                // 传入方块位置参数、圆角 & 画笔属性
                canvas.drawRoundRect(mfixedBlocks[i].rectF, fixBlock_Angle, fixBlock_Angle, mPaint);
            }
        }
        // 2. 绘制移动的方块()
        if (mMoveBlock.isShow) {
            canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy);
            canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint);
        }

    }



    /**
     * 步骤5:启动动画
     */

    public void startMoving() {

        // 1. 根据标志位 & 视图是否可见确定是否需要启动动画
        // 此处设置是为了方便手动 & 自动停止动画
        if (isMoving || getVisibility() != View.VISIBLE ) {
            return;
        }

//        if (isMoving || getVisibility() != View.VISIBLE || getWindowVisibility() != VISIBLE) {
//            return;
//        }

        // 设置标记位:以便是否停止动画
        isMoving = true;
        mAllowRoll = true;

        // 2. 获取固定方块当前的空位置,即移动方块当前位置
        fixedBlock currEmptyfixedBlock = mfixedBlocks[mCurrEmptyPosition];
        // 3. 获取移动方块的到达位置,即固定方块当前空位置的下1个位置
        fixedBlock movedBlock = currEmptyfixedBlock.next;


//        // 设置方块位置
//        initBlocks2();

        // 4. 设置方块动画 = 移动方块平移 + 旋转
        // 原理:设置平移动画(Translate) + 旋转动画(Rotate),最终通过组合动画(AnimatorSet)组合起来

        // 4.1 设置平移动画:createTranslateValueAnimator() ->>关注1
        mAnimatorSet = new AnimatorSet();
        // 平移路径 = 初始位置 - 到达位置
        ValueAnimator translateConrtroller = createTranslateValueAnimator(currEmptyfixedBlock,
                movedBlock);

        // 4.2 设置旋转动画:createMoveValueAnimator(()->>关注3
        ValueAnimator moveConrtroller = createMoveValueAnimator();

        // 4.3 将两个动画组合起来
        // 设置移动的插值器
        mAnimatorSet.setInterpolator(move_Interpolator);
        mAnimatorSet.playTogether(translateConrtroller, moveConrtroller);
        mAnimatorSet.addListener(new AnimatorListenerAdapter() {

            // 动画开始时进行一些设置
            @Override
            public void onAnimationStart(Animator animation) {

                // 每次动画开始前都需要更新移动方块的位置 ->>关注4
                updateMoveBlock();

                // 让移动方块的初始位置的下个位置也隐藏 = 两个隐藏的方块
                mfixedBlocks[mCurrEmptyPosition].next.isShow = false;

                // 通过标志位将移动的方块显示出来
                mMoveBlock.isShow = true;
            }

            // 结束时进行一些设置
            @Override
            public void onAnimationEnd(Animator animation) {
                isMoving = false;
                mfixedBlocks[mCurrEmptyPosition].isShow = true;
                mCurrEmptyPosition = mfixedBlocks[mCurrEmptyPosition].next.index;

                // 将移动的方块隐藏
                mMoveBlock.isShow = false;

                // 通过标志位判断动画是否要循环播放
                if (mAllowRoll) {
                    startMoving();
                }

//                // 重置动画
//                if (mIsReset) {
//                    mCurrEmptyPosition = initPosition;
//                    //重置动画
//                    for (int i = 0; i < mfixedBlocks.length; i++) {
//                        mfixedBlocks[i].isShow = true;
//                    }
//
//                    mfixedBlocks[mCurrEmptyPosition].isShow = false;
//                    updateMoveBlock();
//                    // 关闭硬件加速情况下,动画卡顿解决方案
////                    if (!isHardwareAccelerated()) {
////                        invalidate(mDirtyRect);
////                    } else {
//                        invalidate();
////                    }
//                    startMoving();
//                    mIsReset = false;
//                }
            }
        });

        // 启动动画
        mAnimatorSet.start();
    }

    /**
     * 关注1:设置平移动画
     */
    private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
                                                       fixedBlock moveBlock) {
        float startAnimValue = 0;
        float endAnimValue = 0;
        PropertyValuesHolder left = null;
        PropertyValuesHolder top = null;

        // 1. 设置移动速度
        ValueAnimator valueAnimator = new ValueAnimator().setDuration(moveSpeed);


        // 2. 设置移动方向
        // 情况分为:4种,分别是移动方块向左、右移动 和 上、下移动
        // 注:需考虑 旋转方向(isClock_Wise),即顺逆时针
        if (isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {

            // 情况1:顺时针且在第一行 / 逆时针且在最后一行时,移动方块向右移动
            if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {

                startAnimValue = moveBlock.rectF.left;
                endAnimValue = moveBlock.rectF.left + blockInterval;

                // 情况2:顺时针且在最后一行 / 逆时针且在第一行,移动方块向左移动
            } else if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
                    || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {

                startAnimValue = moveBlock.rectF.left;
                endAnimValue = moveBlock.rectF.left - blockInterval;
            }

            // 设置属性值
            left = PropertyValuesHolder.ofFloat("left", startAnimValue, endAnimValue);
            valueAnimator.setValues(left);

        } else {
            // 情况3:顺时针且在最左列 / 逆时针且在最右列,移动方块向上移动
            if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
                    || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {

                startAnimValue = moveBlock.rectF.top;
                endAnimValue = moveBlock.rectF.top - blockInterval;

                // 情况4:顺时针且在最右列 / 逆时针且在最左列,移动方块向下移动
            } else if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index
                    || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
                startAnimValue = moveBlock.rectF.top;
                endAnimValue = moveBlock.rectF.top + blockInterval;
            }

            // 设置属性值
            top = PropertyValuesHolder.ofFloat("top", startAnimValue, endAnimValue);
            valueAnimator.setValues(top);
        }

        // 3. 通过监听器更新属性值
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Object left = animation.getAnimatedValue("left");
                Object top = animation.getAnimatedValue("top");
                if (left != null) {
                    mMoveBlock.rectF.offsetTo((Float) left, mMoveBlock.rectF.top);
                }
                if (top != null) {
                    mMoveBlock.rectF.offsetTo(mMoveBlock.rectF.left, (Float) top);
                }
                // 实时更新旋转中心 ->>关注2
                setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);

                // 更新绘制
                // 此处考虑到是否开了硬件加速
//                if (!isHardwareAccelerated()) {
//                    invalidate(mDirtyRect);
//                } else {
                invalidate();
//                }
            }
        });
        return valueAnimator;
    }
    // 回到原处


    /**
     * 关注2:实时更新移动方块的旋转中心
     * 因为方块在平移旋转过程中,旋转中心也会跟着改变,因此需要改变MoveBlock的旋转中心(cx,cy)
     */

    private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isClockwise) {

        // 情况1:以移动方块的左上角为旋转中心
        if (moveBlock.index == 0) {
            moveBlock.cx = moveBlock.rectF.right;
            moveBlock.cy = moveBlock.rectF.bottom;

            // 情况2:以移动方块的右下角为旋转中心
        } else if (moveBlock.index == lineNumber * lineNumber - 1) {
            moveBlock.cx = moveBlock.rectF.left;
            moveBlock.cy = moveBlock.rectF.top;

            // 情况3:以移动方块的左下角为旋转中心
        } else if (moveBlock.index == lineNumber * (lineNumber - 1)) {
            moveBlock.cx = moveBlock.rectF.right;
            moveBlock.cy = moveBlock.rectF.top;

            // 情况4:以移动方块的右上角为旋转中心
        } else if (moveBlock.index == lineNumber - 1) {
            moveBlock.cx = moveBlock.rectF.left;
            moveBlock.cy = moveBlock.rectF.bottom;
        }

        //以下判断与旋转方向有关:即顺 or 逆顺时针

        // 情况1:左边
        else if (moveBlock.index % lineNumber == 0) {
            moveBlock.cx = moveBlock.rectF.right;
            moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom;

            // 情况2:上边
        } else if (moveBlock.index < lineNumber) {
            moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left;
            moveBlock.cy = moveBlock.rectF.bottom;

            // 情况3:右边
        } else if ((moveBlock.index + 1) % lineNumber == 0) {
            moveBlock.cx = moveBlock.rectF.left;
            moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top;

            // 情况4:下边
        } else if (moveBlock.index > (lineNumber - 1) * lineNumber) {
            moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right;
            moveBlock.cy = moveBlock.rectF.top;
        }
    }
    // 回到原处

    /**
     * 关注3:设置旋转动画
     */
    private ValueAnimator createMoveValueAnimator() {

        // 通过属性动画进行设置
        ValueAnimator moveAnim = ValueAnimator.ofFloat(0, 90).setDuration(moveSpeed);

        moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Object animatedValue = animation.getAnimatedValue();

                // 赋值
                mRotateDegree = (float) animatedValue;
//                if (!isHardwareAccelerated()) {
//                    invalidate(mDirtyRect);
//                } else {

                // 视图
                invalidate();
//                }
            }
        });
        return moveAnim;
    }
    // 回到原处

    /**
     * 关注4:更新移动方块的位置
     */

    private void updateMoveBlock() {

        mMoveBlock.rectF.set(mfixedBlocks[mCurrEmptyPosition].next.rectF);
        mMoveBlock.index = mfixedBlocks[mCurrEmptyPosition].next.index;
        setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
    }
    // 回到原处

    /**
     * 停止动画
     */
    public void stopMoving() {

        // 通过标记位来设置
        mAllowRoll = false;
    }


//    /**
//     * 重置动画
//     */
//
//    public void resetRoll() {
//        stopRoll();
//        // 通过标记位来设置
//        mIsReset = true;
//    }


    /**
     * 关注5:判断移动方向
     * 即上下 or 左右
     */
    private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {
        if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
            return false;
        } else {
            return true;
        }
    }





//    /**
//     * 设置固定 & 移动方块的位置
//     */
//    private void initBlocks2() {
//
//        int measuredWidth = getMeasuredWidth();
//        int measuredHeight = getMeasuredHeight();
//        System.out.println("变了");
//        // 设置旋转中心坐标
//        int cx = measuredWidth / 2;
//        int cy = measuredHeight / 2;
//
//        // 设置固定方块的位置
//        fixfixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
//        // 设置移动方块的位置
//        fixRollSquarePosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
//
//        mDirtyRect = getDirtyRect(mfixedBlocks[0].rectF, mfixedBlocks[mfixedBlocks.length - 1].rectF);
//    }

    /**
     * 当视图的Visibility改变时启动动画
     */
//    @Override
//    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
//        super.onVisibilityChanged(changedView, visibility);
//        if (changedView == this && visibility == VISIBLE) {
//            startMoving();
//        } else if (changedView == this && visibility != VISIBLE) {
//            stopRoll();
//        }
//    }
//
//    @Override
//    protected void onWindowVisibilityChanged(int visibility) {
//        super.onWindowVisibilityChanged(visibility);
//        if (visibility == VISIBLE && getVisibility() == VISIBLE) {
//            startMoving();
//        } else {
//            stopRoll();
//        }
//    }
}




================================================
FILE: app/src/main/res/layout/acitivty_main1.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="scut.carson_ho.view_testdemo.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="4"
        android:orientation="vertical">

        <scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView
            android:id="@+id/Kawaii_LoadingView"

            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"

            android:background="#FFCCFF"
            app:blockColor="#ffffff"

            app:lineNumber="3"
            app:fixBlock_Angle="5"
            app:moveBlock_Angle="20"

            app:blockInterval="8dp"
            app:half_BlockWidth="15dp"
            app:initPosition="0"

            app:isClock_Wise="false"
            app:moveSpeed="500"
            app:move_Interpolator="@android:anim/bounce_interpolator"
            />

        <scut.carson_ho.view_testdemo.Loading
        android:id="@+id/loadingView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:visibility="invisible" />

    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <Button
            android:id="@+id/start"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="开始动画" />

        <Button
            android:id="@+id/finish"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="停止动画" />

    </LinearLayout>




</LinearLayout>



================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="scut.carson_ho.view_testdemo.MainActivity">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="4"
            android:orientation="vertical">

            <scut.carson_ho.kawaii_loadingview.Kawaii_LoadingView
                android:id="@+id/Kawaii_LoadingView"

                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1"

                android:background="#FFCCFF"
                app:blockColor="#ffffff"

                app:lineNumber="3"
                app:fixBlock_Angle="5"
                app:moveBlock_Angle="20"

                app:blockInterval="8dp"
                app:half_BlockWidth="15dp"
                app:initPosition="0"

                app:isClock_Wise="false"
                app:moveSpeed="500"
                app:move_Interpolator="@android:anim/bounce_interpolator"
                 />
        </LinearLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="horizontal">

            <Button
                android:id="@+id/start"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="开始动画" />

            <Button
                android:id="@+id/finish"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:text="停止动画" />

        </LinearLayout>




    </LinearLayout>



================================================
FILE: app/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Kawaii_LoadingView">
        <attr name="half_BlockWidth" format="dimension" />
        <attr name="blockInterval" format="dimension" />
        <attr name="initPosition" format="integer" />
        <attr name="isClock_Wise" format="boolean" />
        <attr name="lineNumber" format="integer"  />
        <attr name="moveSpeed" format="integer"  />
        <attr name="blockColor" format="color"  />
        <attr name="moveBlock_Angle" format="float"  />
        <attr name="fixBlock_Angle" format="float"  />
        <attr name="move_Interpolator" format="reference"  />
    </declare-styleable>
</resources>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>


================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">View_TestDemo</string>
</resources>


================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>


================================================
FILE: app/src/test/java/scut/carson_ho/view_testdemo/ExampleUnitTest.java
================================================
package scut.carson_ho.view_testdemo;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat Jul 29 10:29:56 CST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true


================================================
FILE: gradlew
================================================
#!/usr/bin/env bash

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn ( ) {
    echo "$*"
}

die ( ) {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
esac

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
    JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"

exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windowz variants

if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*
goto execute

:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: kawaii_loadingview/.gitignore
================================================
/build


================================================
FILE: kawaii_loadingview/build.gradle
================================================
apply plugin: 'com.android.library'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    testCompile 'junit:junit:4.12'
}


================================================
FILE: kawaii_loadingview/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Carson_Ho/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile


================================================
FILE: kawaii_loadingview/src/androidTest/java/scut/carson_ho/kawaii_loadingview/ExampleInstrumentedTest.java
================================================
package scut.carson_ho.kawaii_loadingview;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumentation test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("scut.carson_ho.kawaii_loadingview.test", appContext.getPackageName());
    }
}


================================================
FILE: kawaii_loadingview/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="scut.carson_ho.kawaii_loadingview">

    <application android:allowBackup="true" android:label="@string/app_name"
        android:supportsRtl="true">

    </application>

</manifest>


================================================
FILE: kawaii_loadingview/src/main/java/scut/carson_ho/kawaii_loadingview/Kawaii_LoadingView.java
================================================
package scut.carson_ho.kawaii_loadingview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;

/**
 * Created by Carson_Ho on 17/7/29.
 */

    public class Kawaii_LoadingView extends View {

        // 固定方块 & 移动方块变量
        private fixedBlock[] mfixedBlocks;
        private MoveBlock mMoveBlock;

        // 方块属性(下面会详细介绍)
        private float half_BlockWidth;
        private float blockInterval;
        private Paint mPaint;
        private boolean isClock_Wise;
        private int initPosition;
        private int mCurrEmptyPosition;
        private int lineNumber;
        private int blockColor;

        // 方块的圆角半径
        private float moveBlock_Angle;
        private float fixBlock_Angle;

        // 动画属性
        private float mRotateDegree;
        private boolean mAllowRoll = false;
        private boolean isMoving = false;
        private int moveSpeed = 250;

        // 动画插值器(默认 = 线性)
        private Interpolator move_Interpolator;
        private AnimatorSet mAnimatorSet;


        // 自定义View的构造函数
        public Kawaii_LoadingView(Context context) {
            this(context, null);
        }

        public Kawaii_LoadingView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }

        public Kawaii_LoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);

            // 步骤1:初始化动画属性
            initAttrs(context, attrs);

            // 步骤2:初始化自定义View
            init();
        }


        /**
         * 步骤1:初始化动画的属性
         */
        private void initAttrs(Context context, AttributeSet attrs) {

            // 控件资源名称
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.Kawaii_LoadingView);

            // 方块行数量(最少3行)
            lineNumber = typedArray.getInteger(R.styleable.Kawaii_LoadingView_lineNumber, 3);
            if (lineNumber < 3) {
                lineNumber = 3;
            }

            // 半个方块的宽度(dp)
            half_BlockWidth = typedArray.getDimension(R.styleable.Kawaii_LoadingView_half_BlockWidth, 30);
            // 方块间隔宽度(dp)
            blockInterval = typedArray.getDimension(R.styleable.Kawaii_LoadingView_blockInterval, 10);

            // 移动方块的圆角半径
            moveBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_moveBlock_Angle, 10);
            // 固定方块的圆角半径
            fixBlock_Angle = typedArray.getFloat(R.styleable.Kawaii_LoadingView_fixBlock_Angle, 30);
            // 通过设置两个方块的圆角半径使得二者不同可以得到更好的动画效果哦

            // 方块颜色(使用十六进制代码,如#333、#8e8e8e)
            int defaultColor = context.getResources().getColor(R.color.colorAccent); // 默认颜色
            blockColor = typedArray.getColor(R.styleable.Kawaii_LoadingView_blockColor, defaultColor);

            // 移动方块的初始位置(即空白位置)
            initPosition = typedArray.getInteger(R.styleable.Kawaii_LoadingView_initPosition, 0);

            // 由于移动方块只能是外部方块,所以这里需要判断方块是否属于外部方块 -->关注1
            if (isInsideTheRect(initPosition, lineNumber)) {
                initPosition = 0;
            }
            // 动画方向是否 = 顺时针旋转
            isClock_Wise = typedArray.getBoolean(R.styleable.Kawaii_LoadingView_isClock_Wise, true);

            // 移动方块的移动速度
            // 注:不建议使用者将速度调得过快
            // 因为会导致ValueAnimator动画对象频繁重复的创建,存在内存抖动
            moveSpeed = typedArray.getInteger(R.styleable.Kawaii_LoadingView_moveSpeed, 250);

            // 设置移动方块动画的插值器
            int move_InterpolatorResId = typedArray.getResourceId(R.styleable.Kawaii_LoadingView_move_Interpolator,
                    android.R.anim.linear_interpolator);
            move_Interpolator = AnimationUtils.loadInterpolator(context, move_InterpolatorResId);

            // 当方块移动后,需要实时更新的空白方块的位置
            mCurrEmptyPosition = initPosition;

            // 释放资源
            typedArray.recycle();
        }


        /**
         * 关注1:判断方块是否在内部
         */

        private boolean isInsideTheRect(int pos, int lineCount) {
            // 判断方块是否在第1行
            if (pos < lineCount) {
                return false;
                // 是否在最后1行
            } else if (pos > (lineCount * lineCount - 1 - lineCount)) {
                return false;
                // 是否在最后1行
            } else if ((pos + 1) % lineCount == 0) {
                return false;
                // 是否在第1行
            } else if (pos % lineCount == 0) {
                return false;
            }
            // 若不在4边,则在内部
            return true;
        }
        // 回到原处

    /**
     * 步骤2:初始化方块对象 & 之间的关系
     */
    private void init() {
        // 初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(blockColor);

        // 初始化方块对象 & 关系 ->>关注1
        initBlocks(initPosition);

    }

    /**
     * 关注1
     * 初始化方块对象、之间的关系
     * 参数说明:initPosition = 移动方块的初始位置
     */
    private void initBlocks(int initPosition) {

        // 1. 创建总方块的数量(固定方块) = lineNumber * lineNumber
        // lineNumber = 方块的行数
        // fixedBlock = 固定方块 类 ->>关注2
        mfixedBlocks = new fixedBlock[lineNumber * lineNumber];

        // 2. 创建方块
        for (int i = 0; i < mfixedBlocks.length; i++) {

            // 创建固定方块 & 保存到数组中
            mfixedBlocks[i] = new fixedBlock();

            // 对固定方块对象里的变量进行赋值
            mfixedBlocks[i].index = i;
            // 对方块是否显示进行判断
            // 若该方块的位置 = 移动方块的初始位置,则隐藏;否则显示
            mfixedBlocks[i].isShow = initPosition == i ? false : true;
            mfixedBlocks[i].rectF = new RectF();
        }

        // 3. 创建移动的方块(1个) ->>关注3
        mMoveBlock = new MoveBlock();
        mMoveBlock.rectF = new RectF();
        mMoveBlock.isShow = false;

        // 4. 关联外部方块的位置
        // 因为外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈
        // ->>关注4
        relate_OuterBlock(mfixedBlocks, isClock_Wise);


    }

    /**
     * 关注2:固定方块 类(内部类)
     */
    private class fixedBlock {

        // 存储方块的坐标位置参数
        RectF rectF;

        // 方块对应序号
        int index;

        // 标志位:判断是否需要绘制
        boolean isShow;

        // 指向下一个需要移动的位置
        fixedBlock next;
        // 外部的方块序号 ≠ 0、1、2…排列,通过 next变量(指定其下一个),一个接一个连接 外部方块 成圈

    }
    // 请回到原处

    /**
     * 关注3
     *:移动方块类(内部类)
     */
    private class MoveBlock {
        // 存储方块的坐标位置参数
        RectF rectF;

        // 方块对应序号
        int index;

        // 标志位:判断是否需要绘制
        boolean isShow;

        // 旋转中心坐标
        // 移动时的旋转中心(X,Y)
        float cx;
        float cy;
    }
    // 请回到原处



    /**
     * 关注4:将外部方块的位置关联起来
     * 算法思想: 按照第1行、最后1行、第1列 & 最后1列的顺序,分别让每个外部方块的next属性 == 下一个外部方块的位置,最终对整个外部方块的位置进行关联
     *  注:需要考虑移动方向变量isClockwise( 顺 Or 逆时针)
     */

    private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClockwise) {
        int lineCount = (int) Math.sqrt(fixedBlocks.length);

        // 情况1:关联第1行
        for (int i = 0; i < lineCount; i++) {
            // 位于最左边
            if (i % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i + 1];
                // 位于最右边
            } else if ((i + 1) % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + lineCount];
                // 中间
            } else {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - 1] : fixedBlocks[i + 1];
            }
        }
        // 情况2:关联最后1行
        for (int i = (lineCount - 1) * lineCount; i < lineCount * lineCount; i++) {
            // 位于最左边
            if (i % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
                // 位于最右边
            } else if ((i + 1) % lineCount == 0) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
                // 中间
            } else {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - 1];
            }
        }

        // 情况3:关联第1列
        for (int i = 1 * lineCount; i <= (lineCount - 1) * lineCount; i += lineCount) {
            // 若是第1列最后1个
            if (i == (lineCount - 1) * lineCount) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i + 1] : fixedBlocks[i - lineCount];
                continue;
            }
            fixedBlocks[i].next = isClockwise ? fixedBlocks[i + lineCount] : fixedBlocks[i - lineCount];
        }

        // 情况4:关联最后1列
        for (int i = 2 * lineCount - 1; i <= lineCount * lineCount - 1; i += lineCount) {
            // 若是最后1列最后1个
            if (i == lineCount * lineCount - 1) {
                fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i - 1];
                continue;
            }
            fixedBlocks[i].next = isClockwise ? fixedBlocks[i - lineCount] : fixedBlocks[i + lineCount];
        }
    }
    // 请回到原处

    /**
     * 步骤3:设置固定 & 移动方块的初始位置
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        // 调用时刻:onCreate之后onDraw之前调用;view的大小发生改变就会调用该方法
        // 使用场景:用于屏幕的大小改变时,需要根据屏幕宽高来决定的其他变量可以在这里进行初始化操作
        super.onSizeChanged(w, h, oldw, oldh);

        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();

        // 1. 设置移动方块的旋转中心坐标
        int cx = measuredWidth / 2;
        int cy = measuredHeight / 2;

        // 2. 设置固定方块的位置 ->>关注1
        fixedBlockPosition(mfixedBlocks, cx, cy, blockInterval, half_BlockWidth);
        // 3. 设置移动方块的位置 ->>关注2
        MoveBlockPosition(mfixedBlocks, mMoveBlock, initPosition, isClock_Wise);
    }

    /**
     * 关注1:设置 固定方块位置
     */
    private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int cy, float dividerWidth, float halfSquareWidth) {

        // 1. 确定第1个方块的位置
        // 分为2种情况:行数 = 偶 / 奇数时
        // 主要是是数学知识,此处不作过多描述
        float squareWidth = halfSquareWidth * 2;
        int lineCount = (int) Math.sqrt(fixedBlocks.length);
        float firstRectLeft = 0;
        float firstRectTop = 0;

        // 情况1:当行数 = 偶数时
        if (lineCount % 2 == 0) {
            int squareCountInAline = lineCount / 2;
            int diviCountInAline = squareCountInAline - 1;
            float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
                    + diviCountInAline * dividerWidth
                    + dividerWidth / 2;
            firstRectLeft = cx - firstRectLeftTopFromCenter;
            firstRectTop = cy - firstRectLeftTopFromCenter;

            // 情况2:当行数 = 奇数时
        } else {
            int squareCountInAline = lineCount / 2;
            int diviCountInAline = squareCountInAline;
            float firstRectLeftTopFromCenter = squareCountInAline * squareWidth
                    + diviCountInAline * dividerWidth
                    + halfSquareWidth;
            firstRectLeft = cx - firstRectLeftTopFromCenter;
            firstRectTop = cy - firstRectLeftTopFromCenter;
            firstRectLeft = cx - firstRectLeftTopFromCenter;
            firstRectTop = cy - firstRectLeftTopFromCenter;
        }

        // 2. 确定剩下的方块位置
        // 思想:把第一行方块位置往下移动即可
        // 通过for循环确定:第一个for循环 = 行,第二个 = 列
        for (int i = 0; i < lineCount; i++) {//行
            for (int j = 0; j < lineCount; j++) {//列
                if (i == 0) {
                    if (j == 0) {
                        fixedBlocks[0].rectF.set(firstRectLeft, firstRectTop,
                                firstRectLeft + squareWidth, firstRectTop + squareWidth);
                    } else {
                        int currIndex = i * lineCount + j;
                        fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - 1].rectF);
                        fixedBlocks[currIndex].rectF.offset(dividerWidth + squareWidth, 0);
                    }
                } else {
                    int currIndex = i * lineCount + j;
                    fixedBlocks[currIndex].rectF.set(fixedBlocks[currIndex - lineCount].rectF);
                    fixedBlocks[currIndex].rectF.offset(0, dividerWidth + squareWidth);
                }
            }
        }
    }

    /**
     * 关注2:设置移动方块的位置
     */
    private void MoveBlockPosition(fixedBlock[] fixedBlocks,
                                   MoveBlock moveBlock, int initPosition, boolean isClockwise) {

        // 移动方块位置 = 设置初始的空出位置 的下一个位置(next)
        // 下一个位置 通过 连接的外部方块位置确定
        fixedBlock fixedBlock = fixedBlocks[initPosition];
        moveBlock.rectF.set(fixedBlock.next.rectF);
    }

    /**
     * 步骤4:绘制方块
     */

    @Override
    protected void onDraw(Canvas canvas) {

        // 1. 绘制内部方块(固定的)
        for (int i = 0; i < mfixedBlocks.length; i++) {
            // 根据标志位判断是否需要绘制
            if (mfixedBlocks[i].isShow) {
                // 传入方块位置参数、圆角 & 画笔属性
                canvas.drawRoundRect(mfixedBlocks[i].rectF, fixBlock_Angle, fixBlock_Angle, mPaint);
            }
        }
        // 2. 绘制移动的方块()
        if (mMoveBlock.isShow) {
            canvas.rotate(isClock_Wise ? mRotateDegree : -mRotateDegree, mMoveBlock.cx, mMoveBlock.cy);
            canvas.drawRoundRect(mMoveBlock.rectF, moveBlock_Angle, moveBlock_Angle, mPaint);
        }

    }


    /**
     * 步骤5:启动动画
     */

    public void startMoving() {

        // 1. 根据标志位 & 视图是否可见确定是否需要启动动画
        // 此处设置是为了方便手动 & 自动停止动画
        if (isMoving || getVisibility() != View.VISIBLE ) {
            return;
        }

        // 设置标记位:以便是否停止动画
        isMoving = true;
        mAllowRoll = true;

        // 2. 获取固定方块当前的空位置,即移动方块当前位置
        fixedBlock currEmptyfixedBlock = mfixedBlocks[mCurrEmptyPosition];
        // 3. 获取移动方块的到达位置,即固定方块当前空位置的下1个位置
        fixedBlock movedBlock = currEmptyfixedBlock.next;

        // 4. 设置方块动画 = 移动方块平移 + 旋转
        // 原理:设置平移动画(Translate) + 旋转动画(Rotate),最终通过组合动画(AnimatorSet)组合起来

        // 4.1 设置平移动画:createTranslateValueAnimator() ->>关注1
        mAnimatorSet = new AnimatorSet();
        // 平移路径 = 初始位置 - 到达位置
        ValueAnimator translateConrtroller = createTranslateValueAnimator(currEmptyfixedBlock,
                movedBlock);

        // 4.2 设置旋转动画:createMoveValueAnimator(()->>关注3
        ValueAnimator moveConrtroller = createMoveValueAnimator();

        // 4.3 将两个动画组合起来
        // 设置移动的插值器
        mAnimatorSet.setInterpolator(move_Interpolator);
        mAnimatorSet.playTogether(translateConrtroller, moveConrtroller);
        mAnimatorSet.addListener(new AnimatorListenerAdapter() {

            // 动画开始时进行一些设置
            @Override
            public void onAnimationStart(Animator animation) {

                // 每次动画开始前都需要更新移动方块的位置 ->>关注4
                updateMoveBlock();

                // 让移动方块的初始位置的下个位置也隐藏 = 两个隐藏的方块
                mfixedBlocks[mCurrEmptyPosition].next.isShow = false;

                // 通过标志位将移动的方块显示出来
                mMoveBlock.isShow = true;
            }

            // 结束时进行一些设置
            @Override
            public void onAnimationEnd(Animator animation) {
                isMoving = false;
                mfixedBlocks[mCurrEmptyPosition].isShow = true;
                mCurrEmptyPosition = mfixedBlocks[mCurrEmptyPosition].next.index;

                // 将移动的方块隐藏
                mMoveBlock.isShow = false;

                // 通过标志位判断动画是否要循环播放
                if (mAllowRoll) {
                    startMoving();
                }
            }
        });

        // 启动动画
        mAnimatorSet.start();
    }

    /**
     * 关注1:设置平移动画
     */
    private ValueAnimator createTranslateValueAnimator(fixedBlock currEmptyfixedBlock,
                                                       fixedBlock moveBlock) {
        float startAnimValue = 0;
        float endAnimValue = 0;
        PropertyValuesHolder left = null;
        PropertyValuesHolder top = null;

        // 1. 设置移动速度
        ValueAnimator valueAnimator = new ValueAnimator().setDuration(moveSpeed);


        // 2. 设置移动方向
        // 情况分为:4种,分别是移动方块向左、右移动 和 上、下移动
        // 注:需考虑 旋转方向(isClock_Wise),即顺逆时针 ->>关注1.1
        if (isNextRollLeftOrRight(currEmptyfixedBlock, moveBlock)) {

            // 情况1:顺时针且在第一行 / 逆时针且在最后一行时,移动方块向右移动
            if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {

                startAnimValue = moveBlock.rectF.left;
                endAnimValue = moveBlock.rectF.left + blockInterval;

                // 情况2:顺时针且在最后一行 / 逆时针且在第一行,移动方块向左移动
            } else if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
                    || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {

                startAnimValue = moveBlock.rectF.left;
                endAnimValue = moveBlock.rectF.left - blockInterval;
            }

            // 设置属性值
            left = PropertyValuesHolder.ofFloat("left", startAnimValue, endAnimValue);
            valueAnimator.setValues(left);

        } else {
            // 情况3:顺时针且在最左列 / 逆时针且在最右列,移动方块向上移动
            if (isClock_Wise && currEmptyfixedBlock.index < moveBlock.index
                    || !isClock_Wise && currEmptyfixedBlock.index < moveBlock.index) {

                startAnimValue = moveBlock.rectF.top;
                endAnimValue = moveBlock.rectF.top - blockInterval;

                // 情况4:顺时针且在最右列 / 逆时针且在最左列,移动方块向下移动
            } else if (isClock_Wise && currEmptyfixedBlock.index > moveBlock.index
                    || !isClock_Wise && currEmptyfixedBlock.index > moveBlock.index) {
                startAnimValue = moveBlock.rectF.top;
                endAnimValue = moveBlock.rectF.top + blockInterval;
            }

            // 设置属性值
            top = PropertyValuesHolder.ofFloat("top", startAnimValue, endAnimValue);
            valueAnimator.setValues(top);
        }

        // 3. 通过监听器更新属性值
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Object left = animation.getAnimatedValue("left");
                Object top = animation.getAnimatedValue("top");
                if (left != null) {
                    mMoveBlock.rectF.offsetTo((Float) left, mMoveBlock.rectF.top);
                }
                if (top != null) {
                    mMoveBlock.rectF.offsetTo(mMoveBlock.rectF.left, (Float) top);
                }
                // 实时更新旋转中心 ->>关注2
                setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);

                // 更新绘制
                invalidate();
            }
        });
        return valueAnimator;
    }
    // 回到原处


    /**
     * 关注1.1:判断移动方向
     * 即上下 or 左右
     */
    private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, fixedBlock rollSquare) {
        if (currEmptyfixedBlock.rectF.left - rollSquare.rectF.left == 0) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 关注2:实时更新移动方块的旋转中心
     * 因为方块在平移旋转过程中,旋转中心也会跟着改变,因此需要改变MoveBlock的旋转中心(cx,cy)
     */

    private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isClockwise) {

        // 情况1:以移动方块的左上角为旋转中心
        if (moveBlock.index == 0) {
            moveBlock.cx = moveBlock.rectF.right;
            moveBlock.cy = moveBlock.rectF.bottom;

            // 情况2:以移动方块的右下角为旋转中心
        } else if (moveBlock.index == lineNumber * lineNumber - 1) {
            moveBlock.cx = moveBlock.rectF.left;
            moveBlock.cy = moveBlock.rectF.top;

            // 情况3:以移动方块的左下角为旋转中心
        } else if (moveBlock.index == lineNumber * (lineNumber - 1)) {
            moveBlock.cx = moveBlock.rectF.right;
            moveBlock.cy = moveBlock.rectF.top;

            // 情况4:以移动方块的右上角为旋转中心
        } else if (moveBlock.index == lineNumber - 1) {
            moveBlock.cx = moveBlock.rectF.left;
            moveBlock.cy = moveBlock.rectF.bottom;
        }

        //以下判断与旋转方向有关:即顺 or 逆顺时针

        // 情况1:左边
        else if (moveBlock.index % lineNumber == 0) {
            moveBlock.cx = moveBlock.rectF.right;
            moveBlock.cy = isClockwise ? moveBlock.rectF.top : moveBlock.rectF.bottom;

            // 情况2:上边
        } else if (moveBlock.index < lineNumber) {
            moveBlock.cx = isClockwise ? moveBlock.rectF.right : moveBlock.rectF.left;
            moveBlock.cy = moveBlock.rectF.bottom;

            // 情况3:右边
        } else if ((moveBlock.index + 1) % lineNumber == 0) {
            moveBlock.cx = moveBlock.rectF.left;
            moveBlock.cy = isClockwise ? moveBlock.rectF.bottom : moveBlock.rectF.top;

            // 情况4:下边
        } else if (moveBlock.index > (lineNumber - 1) * lineNumber) {
            moveBlock.cx = isClockwise ? moveBlock.rectF.left : moveBlock.rectF.right;
            moveBlock.cy = moveBlock.rectF.top;
        }
    }
    // 回到原处




    /**
     * 关注3:设置旋转动画
     */
    private ValueAnimator createMoveValueAnimator() {

        // 通过属性动画进行设置
        ValueAnimator moveAnim = ValueAnimator.ofFloat(0, 90).setDuration(moveSpeed);

        moveAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Object animatedValue = animation.getAnimatedValue();

                // 赋值
                mRotateDegree = (float) animatedValue;

                // 更新视图
                invalidate();
            }
        });
        return moveAnim;
    }
    // 回到原处

    /**
     * 关注4:更新移动方块的位置
     */

    private void updateMoveBlock() {

        mMoveBlock.rectF.set(mfixedBlocks[mCurrEmptyPosition].next.rectF);
        mMoveBlock.index = mfixedBlocks[mCurrEmptyPosition].next.index;
        setMoveBlockRotateCenter(mMoveBlock, isClock_Wise);
    }
    // 回到原处


    /**
     * 停止动画
     */
    public void stopMoving() {

        // 通过标记位来设置
        mAllowRoll = false;
    }
}


================================================
FILE: kawaii_loadingview/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Kawaii_LoadingView">
        <attr name="half_BlockWidth" format="dimension" />
        <attr name="blockInterval" format="dimension" />
        <attr name="initPosition" format="integer" />
        <attr name="isClock_Wise" format="boolean" />
        <attr name="lineNumber" format="integer"  />
        <attr name="moveSpeed" format="integer"  />
        <attr name="blockColor" format="color"  />
        <attr name="moveBlock_Angle" format="float"  />
        <attr name="fixBlock_Angle" format="float"  />
        <attr name="move_Interpolator" format="reference"  />
    </declare-styleable>
</resources>

================================================
FILE: kawaii_loadingview/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FFCCFF</color>
</resources>


================================================
FILE: kawaii_loadingview/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">Kawaii_LoadingView</string>
</resources>


================================================
FILE: kawaii_loadingview/src/test/java/scut/carson_ho/kawaii_loadingview/ExampleUnitTest.java
================================================
package scut.carson_ho.kawaii_loadingview;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

================================================
FILE: settings.gradle
================================================
include ':app', ':kawaii_loadingview'
Download .txt
gitextract_ksnah7c3/

├── .gitignore
├── .idea/
│   ├── compiler.xml
│   ├── copyright/
│   │   └── profiles_settings.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── modules.xml
│   └── runConfigurations.xml
├── CONTRIBUTING.md
├── LICENSE
├── README-en.md
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── scut/
│       │           └── carson_ho/
│       │               └── view_testdemo/
│       │                   └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── scut/
│       │   │       └── carson_ho/
│       │   │           └── view_testdemo/
│       │   │               ├── Loading.java
│       │   │               ├── MainActivity.java
│       │   │               ├── Useage2.java
│       │   │               └── Utils.java
│       │   └── res/
│       │       ├── layout/
│       │       │   ├── acitivty_main1.xml
│       │       │   └── activity_main.xml
│       │       └── values/
│       │           ├── attrs.xml
│       │           ├── colors.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── scut/
│                   └── carson_ho/
│                       └── view_testdemo/
│                           └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── kawaii_loadingview/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── scut/
│       │           └── carson_ho/
│       │               └── kawaii_loadingview/
│       │                   └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── scut/
│       │   │       └── carson_ho/
│       │   │           └── kawaii_loadingview/
│       │   │               └── Kawaii_LoadingView.java
│       │   └── res/
│       │       └── values/
│       │           ├── attrs.xml
│       │           ├── colors.xml
│       │           └── strings.xml
│       └── test/
│           └── java/
│               └── scut/
│                   └── carson_ho/
│                       └── kawaii_loadingview/
│                           └── ExampleUnitTest.java
└── settings.gradle
Download .txt
SYMBOL INDEX (60 symbols across 9 files)

FILE: app/src/androidTest/java/scut/carson_ho/view_testdemo/ExampleInstrumentedTest.java
  class ExampleInstrumentedTest (line 17) | @RunWith(AndroidJUnit4.class)
    method useAppContext (line 19) | @Test

FILE: app/src/main/java/scut/carson_ho/view_testdemo/Loading.java
  class Loading (line 11) | public class Loading extends ProgressBar {
    method Loading (line 15) | public Loading(Context context) {
    method Loading (line 19) | public Loading(Context context, AttributeSet attrs) {
    method Loading (line 23) | public Loading(Context context, AttributeSet attrs, int defStyleAttr) {

FILE: app/src/main/java/scut/carson_ho/view_testdemo/MainActivity.java
  class MainActivity (line 9) | public class MainActivity extends AppCompatActivity  {
    method onCreate (line 16) | @Override

FILE: app/src/main/java/scut/carson_ho/view_testdemo/Useage2.java
  class Useage2 (line 13) | public class Useage2 extends AppCompatActivity {
    method onCreate (line 20) | @Override

FILE: app/src/main/java/scut/carson_ho/view_testdemo/Utils.java
  class Utils (line 23) | public class Utils extends View {
    method Utils (line 60) | public Utils(Context context) {
    method Utils (line 64) | public Utils(Context context, @Nullable AttributeSet attrs) {
    method Utils (line 68) | public Utils(Context context, @Nullable AttributeSet attrs, int defSty...
    method initAttrs (line 82) | private void initAttrs(Context context, AttributeSet attrs) {
    method isInsideTheRect (line 140) | private boolean isInsideTheRect(int pos, int lineCount) {
    method init (line 164) | private void init() {
    method initBlocks (line 179) | private void initBlocks(int initPosition) {
    class fixedBlock (line 216) | private class fixedBlock {
    class MoveBlock (line 238) | private class MoveBlock {
    method relate_OuterBlock (line 263) | private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClo...
    method onSizeChanged (line 321) | @Override
    method fixedBlockPosition (line 346) | private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int ...
    method MoveBlockPosition (line 405) | private void MoveBlockPosition(fixedBlock[] fixedBlocks,
    method onDraw (line 438) | @Override
    method startMoving (line 463) | public void startMoving() {
    method createTranslateValueAnimator (line 564) | private ValueAnimator createTranslateValueAnimator(fixedBlock currEmpt...
    method setMoveBlockRotateCenter (line 652) | private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isC...
    method createMoveValueAnimator (line 703) | private ValueAnimator createMoveValueAnimator() {
    method updateMoveBlock (line 732) | private void updateMoveBlock() {
    method stopMoving (line 743) | public void stopMoving() {
    method isNextRollLeftOrRight (line 765) | private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, ...

FILE: app/src/test/java/scut/carson_ho/view_testdemo/ExampleUnitTest.java
  class ExampleUnitTest (line 12) | public class ExampleUnitTest {
    method addition_isCorrect (line 13) | @Test

FILE: kawaii_loadingview/src/androidTest/java/scut/carson_ho/kawaii_loadingview/ExampleInstrumentedTest.java
  class ExampleInstrumentedTest (line 17) | @RunWith(AndroidJUnit4.class)
    method useAppContext (line 19) | @Test

FILE: kawaii_loadingview/src/main/java/scut/carson_ho/kawaii_loadingview/Kawaii_LoadingView.java
  class Kawaii_LoadingView (line 23) | public class Kawaii_LoadingView extends View {
    method Kawaii_LoadingView (line 55) | public Kawaii_LoadingView(Context context) {
    method Kawaii_LoadingView (line 59) | public Kawaii_LoadingView(Context context, @Nullable AttributeSet attr...
    method Kawaii_LoadingView (line 63) | public Kawaii_LoadingView(Context context, @Nullable AttributeSet attr...
    method initAttrs (line 77) | private void initAttrs(Context context, AttributeSet attrs) {
    method isInsideTheRect (line 135) | private boolean isInsideTheRect(int pos, int lineCount) {
    method init (line 157) | private void init() {
    method initBlocks (line 172) | private void initBlocks(int initPosition) {
    class fixedBlock (line 209) | private class fixedBlock {
    class MoveBlock (line 231) | private class MoveBlock {
    method relate_OuterBlock (line 256) | private void relate_OuterBlock(fixedBlock[] fixedBlocks, boolean isClo...
    method onSizeChanged (line 311) | @Override
    method fixedBlockPosition (line 333) | private void fixedBlockPosition(fixedBlock[] fixedBlocks, int cx, int ...
    method MoveBlockPosition (line 392) | private void MoveBlockPosition(fixedBlock[] fixedBlocks,
    method onDraw (line 405) | @Override
    method startMoving (line 429) | public void startMoving() {
    method createTranslateValueAnimator (line 502) | private ValueAnimator createTranslateValueAnimator(fixedBlock currEmpt...
    method isNextRollLeftOrRight (line 584) | private boolean isNextRollLeftOrRight(fixedBlock currEmptyfixedBlock, ...
    method setMoveBlockRotateCenter (line 597) | private void setMoveBlockRotateCenter(MoveBlock moveBlock, boolean isC...
    method createMoveValueAnimator (line 651) | private ValueAnimator createMoveValueAnimator() {
    method updateMoveBlock (line 676) | private void updateMoveBlock() {
    method stopMoving (line 688) | public void stopMoving() {

FILE: kawaii_loadingview/src/test/java/scut/carson_ho/kawaii_loadingview/ExampleUnitTest.java
  class ExampleUnitTest (line 12) | public class ExampleUnitTest {
    method addition_isCorrect (line 13) | @Test
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (118K chars).
[
  {
    "path": ".gitignore",
    "chars": 118,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n"
  },
  {
    "path": ".idea/compiler.xml",
    "chars": 686,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <resourceExt"
  },
  {
    "path": ".idea/copyright/profiles_settings.xml",
    "chars": 74,
    "preview": "<component name=\"CopyrightManager\">\n  <settings default=\"\" />\n</component>"
  },
  {
    "path": ".idea/gradle.xml",
    "chars": 690,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleSettings\">\n    <option name=\"linke"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 3038,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"EntryPointsManager\">\n    <entry_points v"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 520,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 564,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2262,
    "preview": "# Contributing Guide\n- First,Thank you for your attention to this project. \n- Any bug, doc, examples and suggestion is a"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README-en.md",
    "chars": 3945,
    "preview": "# Kawaii_LoadingView\n>[点击查看中文文档](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/README.md)\n- Author:Carson_"
  },
  {
    "path": "README.md",
    "chars": 4241,
    "preview": "# Kawaii_LoadingView\n>[English Document](https://github.com/Carson-Ho/Kawaii_LoadingView/blob/master/README-en.md)\n- 作者:"
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 988,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 25\n    buildToolsVersion \"25.0.2\"\n    defaultCo"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 938,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "app/src/androidTest/java/scut/carson_ho/view_testdemo/ExampleInstrumentedTest.java",
    "chars": 760,
    "preview": "package scut.carson_ho.view_testdemo;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegist"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 777,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/scut/carson_ho/view_testdemo/Loading.java",
    "chars": 526,
    "preview": "package scut.carson_ho.view_testdemo;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android."
  },
  {
    "path": "app/src/main/java/scut/carson_ho/view_testdemo/MainActivity.java",
    "chars": 1385,
    "preview": "package scut.carson_ho.view_testdemo;\n\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport"
  },
  {
    "path": "app/src/main/java/scut/carson_ho/view_testdemo/Useage2.java",
    "chars": 1418,
    "preview": "package scut.carson_ho.view_testdemo;\n\nimport android.os.Bundle;\nimport android.support.v7.app.AppCompatActivity;\nimport"
  },
  {
    "path": "app/src/main/java/scut/carson_ho/view_testdemo/Utils.java",
    "chars": 25735,
    "preview": "package scut.carson_ho.view_testdemo;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListenerAdapt"
  },
  {
    "path": "app/src/main/res/layout/acitivty_main1.xml",
    "chars": 2166,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 2121,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n    <LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    "
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "chars": 691,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"Kawaii_LoadingView\">\n        <attr name="
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 208,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 76,
    "preview": "<resources>\n    <string name=\"app_name\">View_TestDemo</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 383,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar"
  },
  {
    "path": "app/src/test/java/scut/carson_ho/view_testdemo/ExampleUnitTest.java",
    "chars": 406,
    "preview": "package scut.carson_ho.view_testdemo;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local u"
  },
  {
    "path": "build.gradle",
    "chars": 498,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    r"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 230,
    "preview": "#Sat Jul 29 10:29:56 CST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 730,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2404,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
  },
  {
    "path": "kawaii_loadingview/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "kawaii_loadingview/build.gradle",
    "chars": 822,
    "preview": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion 25\n    buildToolsVersion \"25.0.2\"\n\n    defaultConfi"
  },
  {
    "path": "kawaii_loadingview/proguard-rules.pro",
    "chars": 938,
    "preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
  },
  {
    "path": "kawaii_loadingview/src/androidTest/java/scut/carson_ho/kawaii_loadingview/ExampleInstrumentedTest.java",
    "chars": 775,
    "preview": "package scut.carson_ho.kawaii_loadingview;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationR"
  },
  {
    "path": "kawaii_loadingview/src/main/AndroidManifest.xml",
    "chars": 266,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n\n    package=\"scut.carson_ho.kawaii_loadingview\">\n\n"
  },
  {
    "path": "kawaii_loadingview/src/main/java/scut/carson_ho/kawaii_loadingview/Kawaii_LoadingView.java",
    "chars": 22348,
    "preview": "package scut.carson_ho.kawaii_loadingview;\n\nimport android.animation.Animator;\nimport android.animation.AnimatorListener"
  },
  {
    "path": "kawaii_loadingview/src/main/res/values/attrs.xml",
    "chars": 691,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <declare-styleable name=\"Kawaii_LoadingView\">\n        <attr name="
  },
  {
    "path": "kawaii_loadingview/src/main/res/values/colors.xml",
    "chars": 208,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "kawaii_loadingview/src/main/res/values/strings.xml",
    "chars": 81,
    "preview": "<resources>\n    <string name=\"app_name\">Kawaii_LoadingView</string>\n</resources>\n"
  },
  {
    "path": "kawaii_loadingview/src/test/java/scut/carson_ho/kawaii_loadingview/ExampleUnitTest.java",
    "chars": 411,
    "preview": "package scut.carson_ho.kawaii_loadingview;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example lo"
  },
  {
    "path": "settings.gradle",
    "chars": 38,
    "preview": "include ':app', ':kawaii_loadingview'\n"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the Carson-Ho/Kawaii_LoadingView GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (99.1 KB), approximately 28.8k tokens, and a symbol index with 60 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!