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 ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ Android 1.8 ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ 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* ``` com.carson_ho Kawaii_LoadingView 1.0.0 pom ``` ##### 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* ``` ``` ##### 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* ``` com.carson_ho Kawaii_LoadingView 1.0.0 pom ``` ##### 步骤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* ``` ``` ##### 步骤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 Testing documentation */ @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 ================================================ ================================================ 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 ================================================