├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── material_colors.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── wang
│ │ │ └── bookloadingview
│ │ │ └── MainActivity.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── wang
│ │ │ └── bookloadingview
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── wang
│ │ └── bookloadingview
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── bookloading
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── attrs.xml
│ │ │ ├── drawable
│ │ │ │ └── bg_book_loading.xml
│ │ │ └── layout
│ │ │ │ └── layout_book_loading.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── wang
│ │ │ └── bookloading
│ │ │ ├── ViewUtil.java
│ │ │ ├── Rotate3dAnimation.java
│ │ │ ├── JumpingBeansSpan.java
│ │ │ ├── widget
│ │ │ ├── TextMaskView.java
│ │ │ ├── TickView.java
│ │ │ └── BookLoadingView.java
│ │ │ └── JumpingBeans.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── wang
│ │ │ └── bookloading
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── wang
│ │ └── bookloading
│ │ └── ExampleInstrumentedTest.java
├── build.gradle
└── proguard-rules.pro
├── settings.gradle
├── img
├── show.gif
└── source.gif
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew.bat
├── README.md
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/bookloading/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':bookloading'
2 |
--------------------------------------------------------------------------------
/img/show.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/img/show.gif
--------------------------------------------------------------------------------
/img/source.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/img/source.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/bookloading/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF03A9F4
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kingwang666/BookLoadingView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/bookloading/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BookLoading
3 | 加载中...
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 23 16:54:30 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BookLoadingView
3 |
4 | 加载中...
5 |
6 | Append Jumping Dots
7 |
8 | Make first word jump
9 |
10 |
--------------------------------------------------------------------------------
/bookloading/src/main/res/drawable/bg_book_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #fffafbfc
7 | #ffe7e7e8
8 |
9 |
--------------------------------------------------------------------------------
/bookloading/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/ViewUtil.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * Created on 2017/8/23.
7 | * Author: wang
8 | */
9 |
10 | public class ViewUtil {
11 |
12 | public static int dp2px(Context context, float dpValue) {
13 | final float scale = context.getResources().getDisplayMetrics().density;
14 | return (int) (dpValue * scale + 0.5f);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/test/java/com/wang/bookloadingview/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloadingview;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/bookloading/src/test/java/com/wang/bookloading/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/wang/bookloadingview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloadingview;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.wang.bookloadingview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/bookloading/src/androidTest/java/com/wang/bookloading/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.wang.bookloading.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/bookloading/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 18
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | compile 'com.android.support:appcompat-v7:25.3.1'
30 | testCompile 'junit:junit:4.12'
31 | }
32 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/bookloading/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/wang/bookloadingview/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloadingview;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.view.View;
6 |
7 | import com.wang.bookloading.widget.BookLoadingView;
8 |
9 | import butterknife.BindView;
10 | import butterknife.ButterKnife;
11 | import butterknife.OnClick;
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 |
16 | @BindView(R.id.book_loading_view)
17 | BookLoadingView mBookLoadingView;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 | ButterKnife.bind(this);
24 | }
25 |
26 | @OnClick(R.id.show_btn)
27 | public void onMShowBtnClicked() {
28 | //start loading
29 | mBookLoadingView.setVisibility(View.VISIBLE, 0);
30 | }
31 |
32 | @OnClick(R.id.hide_btn)
33 | public void onMHideBtnClicked() {
34 | //stop loading
35 | mBookLoadingView.setVisibility(View.GONE);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.3"
6 | defaultConfig {
7 | applicationId "com.wang.bookloadingview"
8 | minSdkVersion 18
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.3.1'
28 | compile 'com.android.support:cardview-v7:25.3.1'
29 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
30 | testCompile 'junit:junit:4.12'
31 | compile 'com.jakewharton:butterknife:8.8.1'
32 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
33 | compile project(path: ':bookloading')
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
26 |
27 |
28 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/bookloading/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/bookloading/src/main/res/layout/layout_book_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
17 |
18 |
24 |
25 |
26 |
32 |
33 |
39 |
40 |
41 |
42 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/Rotate3dAnimation.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading;
2 |
3 | import android.graphics.Camera;
4 | import android.graphics.Matrix;
5 | import android.view.animation.Animation;
6 | import android.view.animation.Transformation;
7 |
8 | /**
9 | * Created on 2017/8/24.
10 | * Author: wang
11 | */
12 | public class Rotate3dAnimation extends Animation {
13 | private final float mFromDegrees;
14 | private final float mToDegrees;
15 | private final float mCenterX;
16 | private final float mCenterY;
17 | private final float mDepthZ;
18 | private final boolean mReverse;
19 | private Camera mCamera;
20 |
21 | /**
22 | * Creates a new 3D rotation on the Y axis. The rotation is defined by its
23 | * start angle and its end angle. Both angles are in degrees. The rotation
24 | * is performed around a center point on the 2D space, definied by a pair
25 | * of X and Y coordinates, called centerX and centerY. When the animation
26 | * starts, a translation on the Z axis (depth) is performed. The length
27 | * of the translation can be specified, as well as whether the translation
28 | * should be reversed in time.
29 | *
30 | * @param fromDegrees the start angle of the 3D rotation //起始角度
31 | * @param toDegrees the end angle of the 3D rotation //结束角度
32 | * @param centerX the X center of the 3D rotation //x中轴线
33 | * @param centerY the Y center of the 3D rotation //y中轴线
34 | * @param reverse true if the translation should be reversed, false otherwise//是否反转
35 | */
36 | public Rotate3dAnimation(float fromDegrees, float toDegrees,
37 | float centerX, float centerY, float depthZ, boolean reverse) {
38 | mFromDegrees = fromDegrees;
39 | mToDegrees = toDegrees;
40 | mCenterX = centerX;
41 | mCenterY = centerY;
42 | mDepthZ = depthZ;//Z轴移动的距离,这个来影响视觉效果,可以解决flip animation那个给人看似放大的效果
43 | mReverse = reverse;
44 | }
45 |
46 | @Override
47 | public void initialize(int width, int height, int parentWidth, int parentHeight) {
48 | super.initialize(width, height, parentWidth, parentHeight);
49 | mCamera = new Camera();
50 | }
51 |
52 | @Override
53 | protected void applyTransformation(float interpolatedTime, Transformation t) {
54 | final float fromDegrees = mFromDegrees;
55 | float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
56 |
57 | final float centerX = mCenterX;
58 | final float centerY = mCenterY;
59 | final Camera camera = mCamera;
60 |
61 | final Matrix matrix = t.getMatrix();
62 |
63 | camera.save();
64 | if (mReverse) {
65 | camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
66 | } else {
67 | camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
68 | }
69 | camera.rotateY(degrees);
70 | camera.getMatrix(matrix);
71 | camera.restore();
72 |
73 | matrix.preTranslate(-centerX, -centerY);
74 | matrix.postTranslate(centerX, centerY);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BookLoadingView
2 | 不知道有什么用的加载页面
3 | ## 起因 ##
4 | 有一天设计拿了一张gif
5 |
6 |

7 |
8 | 给前端的说实现这么一个加载动画,要求代码实现,不能用gif。前端当时那个一脸不爽的表情我记忆犹新←_←
9 | 由于最近app开发也完成了本人也是比较爱好动画之类的东西,所以我就想实现一个android端的加载图。在经过1个上午与2个下午的时间终于做出来了一个不知道有什么用的加载动画第一版本。文字动画使用的是[JumpingBeans](https://github.com/frakbot/JumpingBeans)
10 |
11 |

12 |
13 |
14 | ## 如何使用 ##
15 | 使用起来也是非常的方便快捷
16 | 在xml中:
17 |
18 |
27 |
28 |
29 |
30 | java中只要:
31 |
32 | @OnClick(R.id.show_btn)
33 | public void onMShowBtnClicked() {
34 | //start loading
35 | mBookLoadingView.setVisibility(View.VISIBLE, 0);
36 | }
37 |
38 | @OnClick(R.id.hide_btn)
39 | public void onMHideBtnClicked() {
40 | //stop loading
41 | mBookLoadingView.setVisibility(View.GONE);
42 | }
43 |
44 | ## Styleable ##
45 |
46 |
47 |
48 | | name |
49 | format |
50 | description |
51 |
52 |
53 |
54 |
55 |
56 | | blv_delay |
57 | integer |
58 | 动画开始的延迟 |
59 |
60 |
61 |
62 | | blv_firstDuration |
63 | integer |
64 | path动画的时间 |
65 |
66 |
67 |
68 | | blv_textDuration |
69 | integer |
70 | 文字动画的时间 |
71 |
72 |
73 |
74 | | blv_pageDuration |
75 | integer |
76 | 翻页动画的时间 |
77 |
78 |
79 |
80 | | blv_startColor |
81 | color |
82 | 被翻页面的开始时和结束时的颜色 |
83 |
84 |
85 |
86 | | blv_endColor |
87 | color |
88 | 被翻页面的中间时的颜色 |
89 |
90 |
91 |
92 | | blv_tickColor |
93 | color |
94 | 左边√的颜色 |
95 |
96 |
97 |
98 | | blv_tickStrokeWidth |
99 | dimension |
100 | 左边√的粗细 |
101 |
102 |
103 |
104 | | blv_lines |
105 | float |
106 | 右边线条书 |
107 |
108 |
109 |
110 | | blv_lineColor |
111 | color |
112 | 右边线条颜色 |
113 |
114 |
115 |
116 | | blv_lineStrokeWidth |
117 | dimension |
118 | 右边线条的粗细 |
119 |
120 |
121 |
122 | | blv_linePadding |
123 | dimension |
124 | 右边线条的间隔大小 |
125 |
126 |
127 |
128 | | blv_textSize |
129 | dimension |
130 | 底部文字大小 |
131 |
132 |
133 |
134 | | blv_textColor |
135 | color |
136 | 底部文字颜色 |
137 |
138 |
139 |
140 | | blv_text |
141 | string |
142 | 底部文字内容 |
143 |
144 |
145 |
146 |
147 |
148 | ## TODO ##
149 |
150 | - 底部文字动画的修改(现有的想法是一个文字就是一个view,然后进行动画处理。但是个人感觉这样很蠢。有好的想法可提issue给我。thanks)
151 |
152 |
153 | ## DONE ##
154 |
155 | - 翻页时被翻页面厚度增加
156 | - 宽度自定义
157 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/JumpingBeansSpan.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Frakbot (Sebastiano Poggi and Francesco Pontillo)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.wang.bookloading;
18 |
19 | import android.animation.TimeInterpolator;
20 | import android.animation.ValueAnimator;
21 | import android.annotation.SuppressLint;
22 | import android.os.Build;
23 | import android.support.annotation.NonNull;
24 | import android.text.TextPaint;
25 | import android.text.style.SuperscriptSpan;
26 | import android.util.Log;
27 | import android.view.View;
28 | import android.widget.TextView;
29 |
30 | import java.lang.ref.WeakReference;
31 |
32 | @SuppressLint("ParcelCreator")
33 | final class JumpingBeansSpan extends SuperscriptSpan implements ValueAnimator.AnimatorUpdateListener {
34 |
35 | private final WeakReference textView;
36 | private final int delay;
37 | private final int loopDuration;
38 | private final float animatedRange;
39 | private int shift;
40 | private ValueAnimator jumpAnimator;
41 |
42 | public JumpingBeansSpan(@NonNull TextView textView, int loopDuration, int position, int waveCharOffset,
43 | float animatedRange) {
44 | this.textView = new WeakReference<>(textView);
45 | this.delay = waveCharOffset * position;
46 | this.loopDuration = loopDuration;
47 | this.animatedRange = animatedRange;
48 | }
49 |
50 | @Override
51 | public void updateMeasureState(@NonNull TextPaint tp) {
52 | initIfNecessary(tp.ascent());
53 | tp.baselineShift = shift;
54 | }
55 |
56 | @Override
57 | public void updateDrawState(@NonNull TextPaint tp) {
58 | initIfNecessary(tp.ascent());
59 | tp.baselineShift = shift;
60 | }
61 |
62 | private void initIfNecessary(float ascent) {
63 | if (jumpAnimator != null) {
64 | return;
65 | }
66 |
67 | this.shift = 0;
68 | int maxShift = (int) ascent / 2;
69 | jumpAnimator = ValueAnimator.ofInt(0, maxShift);
70 | jumpAnimator
71 | .setDuration(loopDuration)
72 | .setStartDelay(delay);
73 | jumpAnimator.setInterpolator(new JumpInterpolator(animatedRange));
74 | // jumpAnimator.setRepeatCount(ValueAnimator.INFINITE);
75 | // jumpAnimator.setRepeatMode(ValueAnimator.RESTART);
76 | jumpAnimator.addUpdateListener(this);
77 | jumpAnimator.start();
78 | }
79 |
80 | @Override
81 | public void onAnimationUpdate(ValueAnimator animation) {
82 | // No need for synchronization as this always run on main thread anyway
83 | TextView v = textView.get();
84 | if (v != null) {
85 | updateAnimationFor(animation, v);
86 | } else {
87 | cleanupAndComplainAboutUserBeingAFool();
88 | }
89 | }
90 |
91 | private void updateAnimationFor(@NonNull ValueAnimator animation, @NonNull TextView v) {
92 | if (isAttachedToHierarchy(v)) {
93 | shift = (int) animation.getAnimatedValue();
94 | v.invalidate();
95 | }
96 | }
97 |
98 | private static boolean isAttachedToHierarchy(View v) {
99 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
100 | return v.isAttachedToWindow();
101 | }
102 | return v.getParent() != null; // Best-effort fallback
103 | }
104 |
105 | private void cleanupAndComplainAboutUserBeingAFool() {
106 | // The textview has been destroyed and teardown() hasn't been called
107 | teardown();
108 | Log.w("JumpingBeans", "!!! Remember to call JumpingBeans.stopJumping() when appropriate !!!");
109 | }
110 |
111 | void teardown() {
112 | if (jumpAnimator != null) {
113 | jumpAnimator.cancel();
114 | jumpAnimator.removeAllListeners();
115 | }
116 | if (textView.get() != null) {
117 | textView.clear();
118 | }
119 | }
120 |
121 | /**
122 | * A tweaked {@link android.view.animation.AccelerateDecelerateInterpolator}
123 | * that covers the full range in a fraction of its input range, and holds on
124 | * the final value on the rest of the input range. By default, this fraction
125 | * is 65% of the full range.
126 | *
127 | * @see JumpingBeans#DEFAULT_ANIMATION_DUTY_CYCLE
128 | */
129 | private static class JumpInterpolator implements TimeInterpolator {
130 |
131 | private final float animRange;
132 |
133 | public JumpInterpolator(float animatedRange) {
134 | animRange = Math.abs(animatedRange);
135 | }
136 |
137 | @Override
138 | public float getInterpolation(float input) {
139 | // We want to map the [0, PI] sine range onto [0, animRange]
140 | double radians = (input / animRange) * Math.PI;
141 | double interpolatedValue = Math.max(0f, Math.sin(radians));
142 | return (float) interpolatedValue;
143 | }
144 |
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/widget/TextMaskView.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.os.Build;
9 | import android.support.annotation.ColorInt;
10 | import android.support.annotation.Nullable;
11 | import android.support.annotation.RequiresApi;
12 | import android.util.AttributeSet;
13 | import android.view.View;
14 |
15 | import com.wang.bookloading.R;
16 | import com.wang.bookloading.ViewUtil;
17 |
18 | /**
19 | * Created on 2017/8/24.
20 | * Author: wang
21 | */
22 |
23 | public class TextMaskView extends View {
24 |
25 | private static final int DEF_DRAW_SIZE = 100;
26 |
27 | Paint mPaint;
28 |
29 | private int mLineLength;
30 | private int mLinePadding;
31 | private int mStrokeWidth;
32 | private float mPercent;
33 | private float mLines;
34 |
35 | private int mOneLineLength;
36 | private int mStartX;
37 | private int mStartY;
38 |
39 |
40 | public TextMaskView(Context context) {
41 | super(context);
42 | init(context, null);
43 | }
44 |
45 | public TextMaskView(Context context, @Nullable AttributeSet attrs) {
46 | super(context, attrs);
47 | init(context, attrs);
48 | }
49 |
50 | public TextMaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
51 | super(context, attrs, defStyleAttr);
52 | init(context, attrs);
53 | }
54 |
55 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
56 | public TextMaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
57 | super(context, attrs, defStyleAttr, defStyleRes);
58 | init(context, attrs);
59 | }
60 |
61 | private void init(Context context, AttributeSet attrs) {
62 |
63 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TextMaskView);
64 | mLines = typedArray.getFloat(R.styleable.TextMaskView_tmv_lines, 0f);
65 | mLinePadding = typedArray.getDimensionPixelSize(R.styleable.TextMaskView_tmv_linePadding, 0);
66 | mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.TextMaskView_tmv_strokeWidth, 0);
67 | mPercent = typedArray.getFloat(R.styleable.TextMaskView_tmv_percent, 0f);
68 | int color = typedArray.getColor(R.styleable.TextMaskView_tmv_color, Color.RED);
69 | typedArray.recycle();
70 |
71 |
72 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
73 | mPaint.setStyle(Paint.Style.STROKE);
74 | mPaint.setColor(color);
75 | }
76 |
77 |
78 | @Override
79 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
80 | setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec));
81 | if (mLines == 0f) {
82 | return;
83 | }
84 | int width = getMeasuredWidth();
85 | int height = getMeasuredHeight();
86 | mStartX = getPaddingLeft();
87 | mStartY = getPaddingTop();
88 | mOneLineLength = width - mStartX - getPaddingRight();
89 | mLineLength = (int) (mOneLineLength * mLines);
90 |
91 | int maxHeight = height - mStartY - getPaddingBottom();
92 |
93 | if (mStrokeWidth <= 0 && mLinePadding <= 0) {
94 | int count = (int) Math.ceil(mLines);
95 | mStrokeWidth = mLinePadding = maxHeight / (count * 2 - 1);
96 | } else if (mStrokeWidth <= 0) {
97 | int count = (int) Math.ceil(mLines);
98 | if (count > 1) {
99 | if (mLinePadding * (2 * count - 1) > maxHeight) {
100 | mStrokeWidth = (maxHeight - mLinePadding * (count - 1)) / count;
101 | } else {
102 | mStrokeWidth = mLinePadding;
103 | }
104 | } else {
105 | mStrokeWidth = mLineLength;
106 | }
107 | } else if (mLinePadding <= 0) {
108 | int count = (int) Math.ceil(mLines);
109 | if (count > 1) {
110 | if (mStrokeWidth * (2 * count - 1) > maxHeight) {
111 | mLinePadding = (maxHeight - mStrokeWidth * count) / (count - 1);
112 | } else {
113 | mLinePadding = mStrokeWidth;
114 | }
115 | }
116 | } else {
117 | int count = (int) Math.ceil(mLines);
118 | if (mStrokeWidth * count + mLinePadding * (count - 1) > maxHeight) {
119 | mLinePadding = (maxHeight - mStrokeWidth * count) / (count - 1);
120 | }
121 | }
122 | mPaint.setStrokeWidth(mStrokeWidth);
123 | }
124 |
125 | private int measureSize(int measureSpec) {
126 | int defSize = ViewUtil.dp2px(getContext(), DEF_DRAW_SIZE);
127 | int specSize = MeasureSpec.getSize(measureSpec);
128 | int specMode = MeasureSpec.getMode(measureSpec);
129 |
130 | int result = 0;
131 | switch (specMode) {
132 | case MeasureSpec.UNSPECIFIED:
133 | case MeasureSpec.AT_MOST:
134 | result = Math.min(defSize, specSize);
135 | break;
136 | case MeasureSpec.EXACTLY:
137 | result = specSize;
138 | break;
139 | }
140 | return result;
141 | }
142 |
143 | @Override
144 | protected void onDraw(Canvas canvas) {
145 | super.onDraw(canvas);
146 | if (mLines == 0f) {
147 | return;
148 | }
149 | int startY = mStartY + mStrokeWidth / 2;
150 | int currentLength = (int) (mPercent * mLineLength);
151 | int count = currentLength / mOneLineLength;
152 | int last = currentLength % mOneLineLength;
153 | for (int i = 0; i < count; i++) {
154 | canvas.drawLine(mStartX, startY, mStartX + mOneLineLength, startY, mPaint);
155 | startY += (mStrokeWidth + mLinePadding);
156 | }
157 | if (last > 0) {
158 | canvas.drawLine(mStartX, startY, mStartX + last, startY, mPaint);
159 | }
160 | }
161 |
162 | public void setPercent(float percent) {
163 | mPercent = percent;
164 | invalidate();
165 | }
166 |
167 | public void setLinePadding(int linePadding) {
168 | mLinePadding = linePadding;
169 | }
170 |
171 | public void setStrokeWidth(int strokeWidth) {
172 | mStrokeWidth = strokeWidth;
173 | }
174 |
175 | public void setLines(float lines) {
176 | mLines = lines;
177 | }
178 |
179 | public void setColor(@ColorInt int color){
180 | mPaint.setColor(color);
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/widget/TickView.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading.widget;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.Point;
10 | import android.graphics.RectF;
11 | import android.os.Build;
12 | import android.support.annotation.ColorInt;
13 | import android.support.annotation.Nullable;
14 | import android.support.annotation.RequiresApi;
15 | import android.util.AttributeSet;
16 | import android.view.View;
17 |
18 | import com.wang.bookloading.R;
19 | import com.wang.bookloading.ViewUtil;
20 |
21 | /**
22 | * Created on 2017/8/23.
23 | * Author: wang
24 | * padding 只有left和Top有用
25 | */
26 |
27 | public class TickView extends View {
28 |
29 | private static final int DEF_DRAW_SIZE = 100;
30 |
31 | private Paint mPaint;
32 | private RectF mArcRect;
33 | private Point[] mTickPoints;
34 | private Path mTickPath;
35 |
36 | private int mDiameter;
37 | private float mLeftLineDistance;
38 | private float mRightLineDistance;
39 |
40 | private int mStrokeWidth;
41 |
42 | private float mPercent = 0f;
43 |
44 |
45 | public TickView(Context context) {
46 | super(context);
47 | init(context, null);
48 | }
49 |
50 | public TickView(Context context, @Nullable AttributeSet attrs) {
51 | super(context, attrs);
52 | init(context, attrs);
53 | }
54 |
55 | public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
56 | super(context, attrs, defStyleAttr);
57 | init(context, attrs);
58 | }
59 |
60 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
61 | public TickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
62 | super(context, attrs, defStyleAttr, defStyleRes);
63 | init(context, attrs);
64 | }
65 |
66 | private void init(Context context, AttributeSet attrs) {
67 |
68 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TickView);
69 | mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.TickView_tv_strokeWidth, 0);
70 | int color = typedArray.getColor(R.styleable.TickView_tv_color, Color.RED);
71 | mPercent = typedArray.getFloat(R.styleable.TickView_tv_percent, 0f);
72 | typedArray.recycle();
73 |
74 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
75 | mPaint.setStyle(Paint.Style.STROKE);
76 | mPaint.setStrokeCap(Paint.Cap.ROUND);
77 | mPaint.setColor(color);
78 |
79 | mTickPath = new Path();
80 | mArcRect = new RectF();
81 | mTickPoints = new Point[3];
82 | mTickPoints[0] = new Point();
83 | mTickPoints[1] = new Point();
84 | mTickPoints[2] = new Point();
85 | }
86 |
87 | private int measureSize(int measureSpec) {
88 | int defSize = ViewUtil.dp2px(getContext(), DEF_DRAW_SIZE);
89 | int specSize = MeasureSpec.getSize(measureSpec);
90 | int specMode = MeasureSpec.getMode(measureSpec);
91 |
92 | int result = 0;
93 | switch (specMode) {
94 | case MeasureSpec.UNSPECIFIED:
95 | case MeasureSpec.AT_MOST:
96 | result = Math.min(defSize, specSize);
97 | break;
98 | case MeasureSpec.EXACTLY:
99 | result = specSize;
100 | break;
101 | }
102 | return result;
103 | }
104 |
105 | @Override
106 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
107 | setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec));
108 | int width = getMeasuredWidth();
109 | int height = getMeasuredHeight();
110 | int left = getPaddingLeft();
111 | int top = getPaddingTop();
112 |
113 | mDiameter = Math.min(height - top * 2, width - left * 2);
114 | if (mDiameter <= 0) {
115 | return;
116 | }
117 | mStrokeWidth = (mStrokeWidth == 0 ? mDiameter / 10 : mStrokeWidth);
118 | mStrokeWidth = mStrokeWidth > mDiameter / 5 ? mDiameter / 5 : mStrokeWidth;
119 | mStrokeWidth = (mStrokeWidth < 3) ? 3 : mStrokeWidth;
120 |
121 | mDiameter = mDiameter - mStrokeWidth;
122 |
123 |
124 | mArcRect.left = (float) (width - mDiameter) / 2;
125 | mArcRect.top = (float) (height - mDiameter) / 2;
126 | mArcRect.right = (float) (width + mDiameter) / 2;
127 | mArcRect.bottom = (float) (height + mDiameter) / 2;
128 |
129 |
130 | mTickPoints[0].x = Math.round((float) mDiameter / 30 * 7 + mArcRect.left);
131 | mTickPoints[0].y = Math.round((float) mDiameter / 30 * 14 + mArcRect.top);
132 | mTickPoints[1].x = Math.round((float) mDiameter / 30 * 13 + mArcRect.left);
133 | mTickPoints[1].y = Math.round((float) mDiameter / 30 * 20 + mArcRect.top);
134 | mTickPoints[2].x = Math.round((float) mDiameter / 30 * 22 + mArcRect.left);
135 | mTickPoints[2].y = Math.round((float) mDiameter / 30 * 10 + mArcRect.top);
136 |
137 | mLeftLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[1].x - mTickPoints[0].x, 2) +
138 | Math.pow(mTickPoints[1].y - mTickPoints[0].y, 2));
139 | mRightLineDistance = (float) Math.sqrt(Math.pow(mTickPoints[2].x - mTickPoints[1].x, 2) +
140 | Math.pow(mTickPoints[2].y - mTickPoints[1].y, 2));
141 | mPaint.setStrokeWidth(mStrokeWidth);
142 | }
143 |
144 | @Override
145 | protected void onDraw(Canvas canvas) {
146 | super.onDraw(canvas);
147 | if (mPercent == 0) {
148 | return;
149 | }
150 | mTickPath.reset();
151 | float angle = 360 * mPercent;
152 | if (angle < 360) {
153 | mTickPath.addArc(mArcRect, 270, -angle);
154 | } else {
155 | mTickPath.addCircle(mArcRect.centerX(), mArcRect.centerY(), mDiameter / 2, Path.Direction.CW);
156 | }
157 | float drewDistance = mPercent * (mLeftLineDistance + mRightLineDistance);
158 | if (drewDistance < mLeftLineDistance) {
159 | float stopX = mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * drewDistance / mLeftLineDistance;
160 | float stopY = mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * drewDistance / mLeftLineDistance;
161 |
162 | mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y);
163 | mTickPath.lineTo(stopX, stopY);
164 | canvas.drawPath(mTickPath, mPaint);
165 | } else {
166 | mTickPath.moveTo(mTickPoints[0].x, mTickPoints[0].y);
167 | mTickPath.lineTo(mTickPoints[1].x, mTickPoints[1].y);
168 |
169 | // draw right of the tick
170 | if (drewDistance < mLeftLineDistance + mRightLineDistance) {
171 | float stopX = mTickPoints[1].x + (mTickPoints[2].x - mTickPoints[1].x) * (drewDistance - mLeftLineDistance) / mRightLineDistance;
172 | float stopY = mTickPoints[1].y - (mTickPoints[1].y - mTickPoints[2].y) * (drewDistance - mLeftLineDistance) / mRightLineDistance;
173 |
174 | mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y);
175 | mTickPath.lineTo(stopX, stopY);
176 | canvas.drawPath(mTickPath, mPaint);
177 | } else {
178 | mTickPath.moveTo(mTickPoints[1].x, mTickPoints[1].y);
179 | mTickPath.lineTo(mTickPoints[2].x, mTickPoints[2].y);
180 | canvas.drawPath(mTickPath, mPaint);
181 | }
182 | }
183 | }
184 |
185 | public void setPercent(float percent) {
186 | mPercent = percent;
187 | invalidate();
188 | }
189 |
190 | public void setStrokeWidth(int strokeWidth) {
191 | mStrokeWidth = strokeWidth;
192 | }
193 |
194 | public void setColor(@ColorInt int color){
195 | mPaint.setColor(color);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/app/src/main/res/values/material_colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #FFFFEBEE
7 | #FFFFCDD2
8 | #FFEF9A9A
9 | #FFE57373
10 | #FFEF5350
11 | #FFF44336
12 | #FFE53935
13 | #FFD32F2F
14 | #FFC62828
15 | #FFB71C1C
16 |
17 | #FFFF8A80
18 | #FFFF5252
19 | #FFFF1744
20 | #FFD50000
21 |
22 |
23 | #FFFCE4EC
24 | #FFF8BBD0
25 | #FFF48FB1
26 | #FFF06292
27 | #FFEC407A
28 | #FFE91E63
29 | #FFD81B60
30 | #FFC2185B
31 | #FFAD1457
32 | #FF880E4F
33 |
34 | #FFFF80AB
35 | #FFFF4081
36 | #FFF50057
37 | #FFC51162
38 |
39 |
40 | #FFF3E5F5
41 | #FFE1BEE7
42 | #FFCE93D8
43 | #FFBA68C8
44 | #FFAB47BC
45 | #FF9C27B0
46 | #FF8E24AA
47 | #FF7B1FA2
48 | #FF6A1B9A
49 | #FF4A148C
50 |
51 | #FFEA80FC
52 | #FFE040FB
53 | #FFD500F9
54 | #FFAA00FF
55 |
56 |
57 | #FFEDE7F6
58 | #FFD1C4E9
59 | #FFB39DDB
60 | #FF9575CD
61 | #FF7E57C2
62 | #FF673AB7
63 | #FF5E35B1
64 | #FF512DA8
65 | #FF4527A0
66 | #FF311B92
67 |
68 | #FFB388FF
69 | #FF7C4DFF
70 | #FF651FFF
71 | #FF6200EA
72 |
73 |
74 | #FFE8EAF6
75 | #FFC5CAE9
76 | #FF9FA8DA
77 | #FF7986CB
78 | #FF5C6BC0
79 | #FF3F51B5
80 | #FF3949AB
81 | #FF303F9F
82 | #FF283593
83 | #FF1A237E
84 |
85 | #FF8C9EFF
86 | #FF536DFE
87 | #FF3D5AFE
88 | #FF304FFE
89 |
90 |
91 | #FFE3F2FD
92 | #FFBBDEFB
93 | #FF90CAF9
94 | #FF64B5F6
95 | #FF42A5F5
96 | #FF2196F3
97 | #FF1E88E5
98 | #FF1976D2
99 | #FF1565C0
100 | #FF0D47A1
101 |
102 | #FF82B1FF
103 | #FF448AFF
104 | #FF2979FF
105 | #FF2962FF
106 |
107 |
108 | #FFE1F5FE
109 | #FFB3E5FC
110 | #FF81D4FA
111 | #FF4FC3F7
112 | #FF29B6F6
113 | #FF03A9F4
114 | #FF039BE5
115 | #FF0288D1
116 | #FF0277BD
117 | #FF01579B
118 |
119 | #FF80D8FF
120 | #FF40C4FF
121 | #FF00B0FF
122 | #FF0091EA
123 |
124 |
125 | #FFE0F7FA
126 | #FFB2EBF2
127 | #FF80DEEA
128 | #FF4DD0E1
129 | #FF26C6DA
130 | #FF00BCD4
131 | #FF00AAC1
132 | #FF0097A7
133 | #FF00838F
134 | #FF006064
135 |
136 | #FF84FFFF
137 | #FF18FFFF
138 | #FF00E5FF
139 | #FF00B8D4
140 |
141 |
142 | #FFE0F2F1
143 | #FFB2DFDB
144 | #FF80CBC4
145 | #FF4DB6AC
146 | #FF26A69A
147 | #FF009688
148 | #FF00897B
149 | #FF00796B
150 | #FF00695C
151 | #FF004D40
152 |
153 | #FFA7FFEB
154 | #FF64EFDA
155 | #FF1DE9B6
156 | #FF00BFA5
157 |
158 |
159 | #FFE8F5E9
160 | #FFC8E6C9
161 | #FFA5D6A7
162 | #FF81C784
163 | #FF66BB6A
164 | #FF4CAF50
165 | #FF43A047
166 | #FF388E3C
167 | #FF2E7D32
168 | #FF1B5E20
169 |
170 | #FFB9F6CA
171 | #FF69F0AE
172 | #FF00E676
173 | #FF00C853
174 |
175 |
176 | #FFF1F8E9
177 | #FFDCEDC8
178 | #FFC5E1A5
179 | #FFAED581
180 | #FF9CCC65
181 | #FF8BC34A
182 | #FF7CB342
183 | #FF689F38
184 | #FF558B2F
185 | #FF336913
186 |
187 | #FFCCFF90
188 | #FFB2FF59
189 | #FF76FF03
190 | #FF64DD17
191 |
192 |
193 | #FFF9FBE7
194 | #FFF0F4C3
195 | #FFE6EE9C
196 | #FFDCE775
197 | #FFD4E157
198 | #FFCDDC39
199 | #FFC0CA33
200 | #FFAFB42B
201 | #FF9E9D24
202 | #FF827717
203 |
204 | #FFF4FF81
205 | #FFEEFF41
206 | #FFC6FF00
207 | #FFAEEA00
208 |
209 |
210 | #FFFFFDE7
211 | #FFFFF9C4
212 | #FFFFF59D
213 | #FFFFF176
214 | #FFFFEE58
215 | #FFFFEB3B
216 | #FFFDD835
217 | #FFFBC02D
218 | #FFF9A825
219 | #FFF57F17
220 |
221 | #FFFFFF8D
222 | #FFFFFF00
223 | #FFFFEA00
224 | #FFFFD600
225 |
226 |
227 | #FFFFF8E1
228 | #FFFFECB3
229 | #FFFFE082
230 | #FFFFD54F
231 | #FFFFCA28
232 | #FFFFC107
233 | #FFFFB300
234 | #FFFFA000
235 | #FFFF8F00
236 | #FFFF6F00
237 |
238 | #FFFFE57F
239 | #FFFFD740
240 | #FFFFC400
241 | #FFFFAB00
242 |
243 |
244 | #FFFFF3E0
245 | #FFFFE0B2
246 | #FFFFCC80
247 | #FFFFB74D
248 | #FFFFA726
249 | #FFFF9800
250 | #FFFB8C00
251 | #FFF57C00
252 | #FFEF6C00
253 | #FFE65100
254 |
255 | #FFFFD180
256 | #FFFFAB40
257 | #FFFF9100
258 | #FFFF6D00
259 |
260 |
261 | #FFFBE9E7
262 | #FFFFCCBC
263 | #FFFFAB91
264 | #FFFF8A65
265 | #FFFF7043
266 | #FFFF5722
267 | #FFF4511E
268 | #FFE64A19
269 | #FFD84315
270 | #FFBF360C
271 |
272 | #FFFF9E80
273 | #FFFF6E40
274 | #FFFF3D00
275 | #FFDD2C00
276 |
277 |
278 | #FFEFEBE9
279 | #FFD7CCC8
280 | #FFBCAAA4
281 | #FFA1887F
282 | #FF8D6E63
283 | #FF795548
284 | #FF6D4C41
285 | #FF5D4037
286 | #FF4E342E
287 | #FF3E2723
288 |
289 |
290 | #FFFAFAFA
291 | #FFF5F5F5
292 | #FFEEEEEE
293 | #FFE0E0E0
294 | #FFBDBDBD
295 | #FF9E9E9E
296 | #FF757575
297 | #FF616161
298 | #FF424242
299 | #FF212121
300 |
301 |
302 | #FFECEFF1
303 | #FFCFD8DC
304 | #FFB0BEC5
305 | #FF90A4AE
306 | #FF78909C
307 | #FF607D8B
308 | #FF546E7A
309 | #FF455A64
310 | #FF37474F
311 | #FF263238
312 |
313 | #FF000000
314 | #FFFFFFFF
315 |
316 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/JumpingBeans.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014 Frakbot (Sebastiano Poggi and Francesco Pontillo)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.wang.bookloading;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.text.SpannableStringBuilder;
21 | import android.text.Spanned;
22 | import android.text.TextUtils;
23 | import android.widget.TextView;
24 |
25 | import java.lang.ref.WeakReference;
26 |
27 | public final class JumpingBeans {
28 |
29 | /**
30 | * The default fraction of the whole animation time spent actually animating.
31 | * The rest of the range will be spent in "resting" state.
32 | * This the "duty cycle" of the jumping animation.
33 | */
34 | public static final float DEFAULT_ANIMATION_DUTY_CYCLE = 0.65f;
35 |
36 | /**
37 | * The default duration of a whole jumping animation loop, in milliseconds.
38 | */
39 | public static final int DEFAULT_LOOP_DURATION = 1300; // ms
40 |
41 | public static final String ELLIPSIS_GLYPH = "…";
42 | public static final String THREE_DOTS_ELLIPSIS = "...";
43 | public static final int THREE_DOTS_ELLIPSIS_LENGTH = 3;
44 |
45 | private final JumpingBeansSpan[] jumpingBeans;
46 | private final WeakReference textView;
47 |
48 | private JumpingBeans(@NonNull JumpingBeansSpan[] beans, @NonNull TextView textView) {
49 | this.jumpingBeans = beans;
50 | this.textView = new WeakReference<>(textView);
51 | }
52 |
53 | /**
54 | * Create an instance of the {@link Builder}
55 | * applied to the provided {@code TextView}.
56 | *
57 | * @param textView The TextView to apply the JumpingBeans to
58 | * @return the {@link Builder}
59 | */
60 | public static Builder with(@NonNull TextView textView) {
61 | return new Builder(textView);
62 | }
63 |
64 | /**
65 | * Stops the jumping animation and frees up the animations.
66 | */
67 | public void stopJumping() {
68 | for (JumpingBeansSpan bean : jumpingBeans) {
69 | if (bean != null) {
70 | bean.teardown();
71 | }
72 | }
73 |
74 | cleanupSpansFrom(textView.get());
75 | }
76 |
77 | private static void cleanupSpansFrom(TextView tv) {
78 | if (tv != null) {
79 | CharSequence text = tv.getText();
80 | if (text instanceof Spanned) {
81 | CharSequence cleanText = removeJumpingBeansSpansFrom((Spanned) text);
82 | tv.setText(cleanText);
83 | }
84 | }
85 | }
86 |
87 | private static CharSequence removeJumpingBeansSpansFrom(Spanned text) {
88 | SpannableStringBuilder sbb = new SpannableStringBuilder(text.toString());
89 | Object[] spans = text.getSpans(0, text.length(), Object.class);
90 | for (Object span : spans) {
91 | if (!(span instanceof JumpingBeansSpan)) {
92 | sbb.setSpan(span, text.getSpanStart(span),
93 | text.getSpanEnd(span), text.getSpanFlags(span));
94 | }
95 | }
96 | return sbb;
97 | }
98 |
99 | private static CharSequence appendThreeDotsEllipsisTo(TextView textView) {
100 | CharSequence text = getTextSafe(textView);
101 | if (text.length() > 0 && endsWithEllipsisGlyph(text)) {
102 | text = text.subSequence(0, text.length() - 1);
103 | }
104 |
105 | if (!endsWithThreeEllipsisDots(text)) {
106 | text = new SpannableStringBuilder(text).append(THREE_DOTS_ELLIPSIS); // Preserve spans in original text
107 | }
108 | return text;
109 | }
110 |
111 | private static CharSequence getTextSafe(TextView textView) {
112 | return !TextUtils.isEmpty(textView.getText()) ? textView.getText() : "";
113 | }
114 |
115 | private static boolean endsWithEllipsisGlyph(CharSequence text) {
116 | return TextUtils.equals(text.subSequence(text.length() - 1, text.length()), ELLIPSIS_GLYPH);
117 | }
118 |
119 | private static boolean endsWithThreeEllipsisDots(@NonNull CharSequence text) {
120 | if (text.length() < THREE_DOTS_ELLIPSIS_LENGTH) {
121 | // TODO we should try to normalize "invalid" ellipsis (e.g., ".." or "....")
122 | return false;
123 | }
124 | return TextUtils.equals(text.subSequence(text.length() - THREE_DOTS_ELLIPSIS_LENGTH, text.length()), THREE_DOTS_ELLIPSIS);
125 | }
126 |
127 | private static CharSequence ensureTextCanJump(int startPos, int endPos, CharSequence text) {
128 | if (text == null) {
129 | throw new NullPointerException("The textView text must not be null");
130 | }
131 |
132 | if (endPos < startPos) {
133 | throw new IllegalArgumentException("The start position must be smaller than the end position");
134 | }
135 |
136 | if (startPos < 0) {
137 | throw new IndexOutOfBoundsException("The start position must be non-negative");
138 | }
139 |
140 | if (endPos > text.length()) {
141 | throw new IndexOutOfBoundsException("The end position must be smaller than the text length");
142 | }
143 | return text;
144 | }
145 |
146 | /**
147 | * Builder class for {@link JumpingBeans} objects.
148 | *
149 | * Provides a way to set the fields of a {@link JumpingBeans} and generate
150 | * the desired jumping beans effect. With this builder you can easily append
151 | * a Hangouts-style trio of jumping suspension points to any TextView, or
152 | * apply the effect to any other subset of a TextView's text.
153 | *
154 | * Example:
155 | *
156 | *
157 | * JumpingBeans jumpingBeans = JumpingBeans.with(myTextView)
158 | * .appendJumpingDots()
159 | * .setLoopDuration(1500)
160 | * .build();
161 | *
162 | */
163 | public static class Builder {
164 |
165 | private int startPos, endPos;
166 | private float animRange = DEFAULT_ANIMATION_DUTY_CYCLE;
167 | private int loopDuration = DEFAULT_LOOP_DURATION;
168 | private int waveCharDelay = -1;
169 | private CharSequence text;
170 | private TextView textView;
171 | private boolean wave;
172 |
173 | /*package*/ Builder(TextView textView) {
174 | this.textView = textView;
175 | }
176 |
177 | /**
178 | * Appends three jumping dots to the end of a TextView text.
179 | *
180 | * This implies that the animation will by default be a wave.
181 | *
182 | * If the TextView has no text, the resulting TextView text will
183 | * consist of the three dots only.
184 | *
185 | * The TextView text is cached to the current value at
186 | * this time and set again in the {@link #build()} method, so any
187 | * change to the TextView text done in the meantime will be lost.
188 | * This means that you should do all changes to the TextView text
189 | * before you begin using this builder.
190 | *
191 | * Call the {@link #build()} method once you're done to get the
192 | * resulting {@link JumpingBeans}.
193 | *
194 | * @see #setIsWave(boolean)
195 | */
196 | public Builder appendJumpingDots() {
197 | CharSequence text = appendThreeDotsEllipsisTo(textView);
198 |
199 | this.text = text;
200 | this.wave = true;
201 | this.startPos = text.length() - THREE_DOTS_ELLIPSIS_LENGTH;
202 | this.endPos = text.length();
203 |
204 | return this;
205 | }
206 |
207 | /**
208 | * Appends three jumping dots to the end of a TextView text.
209 | *
210 | * This implies that the animation will by default be a wave.
211 | *
212 | * If the TextView has no text, the resulting TextView text will
213 | * consist of the three dots only.
214 | *
215 | * The TextView text is cached to the current value at
216 | * this time and set again in the {@link #build()} method, so any
217 | * change to the TextView text done in the meantime will be lost.
218 | * This means that you should do all changes to the TextView text
219 | * before you begin using this builder.
220 | *
221 | * Call the {@link #build()} method once you're done to get the
222 | * resulting {@link JumpingBeans}.
223 | *
224 | * @param startPos The position of the first character to animate
225 | * @param endPos The position after the one the animated range ends at
226 | * (just like in {@link String#substring(int)})
227 | * @see #setIsWave(boolean)
228 | */
229 | public Builder makeTextJump(int startPos, int endPos) {
230 | CharSequence text = textView.getText();
231 | ensureTextCanJump(startPos, endPos, text);
232 |
233 | this.text = text;
234 | this.wave = true;
235 | this.startPos = startPos;
236 | this.endPos = endPos;
237 |
238 | return this;
239 | }
240 |
241 | /**
242 | * Sets the fraction of the animation loop time spent actually animating.
243 | * The rest of the time will be spent "resting".
244 | * The default value is
245 | * {@link JumpingBeans#DEFAULT_ANIMATION_DUTY_CYCLE}.
246 | *
247 | * @param animatedRange The fraction of the animation loop time spent
248 | * actually animating the characters
249 | */
250 | public Builder setAnimatedDutyCycle(float animatedRange) {
251 | if (animatedRange <= 0f || animatedRange > 1f) {
252 | throw new IllegalArgumentException("The animated range must be in the (0, 1] range");
253 | }
254 | this.animRange = animatedRange;
255 | return this;
256 | }
257 |
258 | /**
259 | * Sets the jumping loop duration. The default value is
260 | * {@link JumpingBeans#DEFAULT_LOOP_DURATION}.
261 | *
262 | * @param loopDuration The jumping animation loop duration, in milliseconds
263 | */
264 | public Builder setLoopDuration(int loopDuration) {
265 | if (loopDuration < 1) {
266 | throw new IllegalArgumentException("The loop duration must be bigger than zero");
267 | }
268 | this.loopDuration = loopDuration;
269 | return this;
270 | }
271 |
272 | /**
273 | * Sets the delay for starting the animation of every single dot over the
274 | * start of the previous one, in milliseconds. The default value is
275 | * the loop length divided by three times the number of character animated
276 | * by this instance of JumpingBeans.
277 | *
278 | * Only has a meaning when the animation is a wave.
279 | *
280 | * @param waveCharOffset The start delay for the animation of every single
281 | * character over the previous one, in milliseconds
282 | * @see #setIsWave(boolean)
283 | */
284 | public Builder setWavePerCharDelay(int waveCharOffset) {
285 | if (waveCharOffset < 0) {
286 | throw new IllegalArgumentException("The wave char offset must be non-negative");
287 | }
288 | this.waveCharDelay = waveCharOffset;
289 | return this;
290 | }
291 |
292 | /**
293 | * Sets a flag that determines if the characters will jump in a wave
294 | * (i.e., with a delay between each other) or all at the same
295 | * time.
296 | *
297 | * @param wave If true, the animation is going to be a wave; if
298 | * false, all characters will jump ay the same time
299 | * @see #setWavePerCharDelay(int)
300 | */
301 | public Builder setIsWave(boolean wave) {
302 | this.wave = wave;
303 | return this;
304 | }
305 |
306 | /**
307 | * Combine all of the options that have been set and return a new
308 | * {@link JumpingBeans} instance.
309 | *
310 | * Remember to call the {@link #stopJumping()} method once you're done
311 | * using the JumpingBeans (that is, when you detach the TextView from
312 | * the view tree, you hide it, or the parent Activity/Fragment goes in
313 | * the paused status). This will allow to release the animations and
314 | * free up memory and CPU that would be otherwise wasted.
315 | */
316 | public JumpingBeans build() {
317 | SpannableStringBuilder sbb = new SpannableStringBuilder(text);
318 | JumpingBeansSpan[] spans;
319 | if (wave) {
320 | spans = getJumpingBeansSpans(sbb);
321 | } else {
322 | spans = buildSingleSpan(sbb);
323 | }
324 |
325 | textView.setText(sbb);
326 | return new JumpingBeans(spans, textView);
327 | }
328 |
329 | private JumpingBeansSpan[] getJumpingBeansSpans(SpannableStringBuilder sbb) {
330 | JumpingBeansSpan[] spans;
331 | if (waveCharDelay == -1) {
332 | waveCharDelay = loopDuration / (3 * (endPos - startPos));
333 | }
334 |
335 | spans = new JumpingBeansSpan[endPos - startPos];
336 | for (int pos = startPos; pos < endPos; pos++) {
337 | JumpingBeansSpan jumpingBean =
338 | new JumpingBeansSpan(textView, loopDuration, pos - startPos, waveCharDelay, animRange);
339 | sbb.setSpan(jumpingBean, pos, pos + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
340 | spans[pos - startPos] = jumpingBean;
341 | }
342 | return spans;
343 | }
344 |
345 | private JumpingBeansSpan[] buildSingleSpan(SpannableStringBuilder sbb) {
346 | JumpingBeansSpan[] spans;
347 | spans = new JumpingBeansSpan[]{new JumpingBeansSpan(textView, loopDuration, 0, 0, animRange)};
348 | sbb.setSpan(spans[0], startPos, endPos, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
349 | return spans;
350 | }
351 |
352 | }
353 |
354 | }
355 |
--------------------------------------------------------------------------------
/bookloading/src/main/java/com/wang/bookloading/widget/BookLoadingView.java:
--------------------------------------------------------------------------------
1 | package com.wang.bookloading.widget;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ObjectAnimator;
5 | import android.animation.TypeEvaluator;
6 | import android.animation.ValueAnimator;
7 | import android.content.Context;
8 | import android.content.res.TypedArray;
9 | import android.graphics.Color;
10 | import android.os.Build;
11 | import android.support.annotation.AttrRes;
12 | import android.support.annotation.ColorInt;
13 | import android.support.annotation.NonNull;
14 | import android.support.annotation.Nullable;
15 | import android.support.annotation.Px;
16 | import android.support.annotation.RequiresApi;
17 | import android.support.annotation.StyleRes;
18 | import android.support.v4.view.ViewCompat;
19 | import android.util.AttributeSet;
20 | import android.util.TypedValue;
21 | import android.view.LayoutInflater;
22 | import android.view.View;
23 | import android.view.ViewGroup;
24 | import android.view.animation.AccelerateDecelerateInterpolator;
25 | import android.view.animation.AccelerateInterpolator;
26 | import android.view.animation.Animation;
27 | import android.view.animation.DecelerateInterpolator;
28 | import android.widget.FrameLayout;
29 | import android.widget.TextView;
30 |
31 | import com.wang.bookloading.JumpingBeans;
32 | import com.wang.bookloading.R;
33 | import com.wang.bookloading.Rotate3dAnimation;
34 |
35 |
36 | /**
37 | * Created on 2017/8/25.
38 | * Author: wang
39 | */
40 |
41 | public class BookLoadingView extends FrameLayout {
42 |
43 | private static final int DEFAULT_FIRST_DURATION = 800;
44 | private static final int DEFAULT_TEXT_DURATION = 1000;
45 | private static final int DEFAULT_PAGE_DURATION = 1000;
46 |
47 | private static final int DEFAULT_START_COLOR = Color.WHITE;
48 | private static final int DEFAULT_END_COLOR = 0xffe7e7e8;
49 | private static final int DEFAULT_COLOR = Color.RED;
50 | private static final float DEFAULT_LINES = 3.5f;
51 |
52 | private TickView mTickView;
53 | private TextMaskView mTextMaskView;
54 | private TextView mLoadingTV;
55 | private View mMaskView;
56 | private View mParentView;
57 |
58 | private ValueAnimator mPathAnim;
59 | private Rotate3dAnimation mFirst3DAnim;
60 | private Rotate3dAnimation mSec3DAnim;
61 | private ObjectAnimator mColorAnim;
62 | private JumpingBeans.Builder mBuilder;
63 | private JumpingBeans mTextJumpAnim;
64 |
65 | private int mDelay;
66 | private int mFirstDuration;
67 | private int mTextDuration;
68 | private int mPageDuration;
69 | private int mStartColor;
70 | private int mEndColor;
71 |
72 | private int mWidth;
73 |
74 | private boolean mStopped = true;
75 |
76 |
77 | public BookLoadingView(@NonNull Context context) {
78 | super(context);
79 | init(context, null);
80 | }
81 |
82 | public BookLoadingView(@NonNull Context context, @Nullable AttributeSet attrs) {
83 | super(context, attrs);
84 | init(context, attrs);
85 | }
86 |
87 | public BookLoadingView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
88 | super(context, attrs, defStyleAttr);
89 | init(context, attrs);
90 | }
91 |
92 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
93 | public BookLoadingView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
94 | super(context, attrs, defStyleAttr, defStyleRes);
95 | init(context, attrs);
96 | }
97 |
98 | private void init(Context context, AttributeSet attrs) {
99 | setClipChildren(false);
100 | setClipToPadding(false);
101 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
102 | setClipToOutline(false);
103 | }
104 | LayoutInflater.from(context).inflate(R.layout.layout_book_loading, this, true);
105 |
106 | mTickView = (TickView) findViewById(R.id.tick_view);
107 | mTextMaskView = (TextMaskView) findViewById(R.id.text_mask_view);
108 | mLoadingTV = (TextView) findViewById(R.id.loading_tv);
109 | mMaskView = findViewById(R.id.mask_view);
110 | mParentView = findViewById(R.id.parent_view);
111 |
112 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BookLoadingView);
113 | mDelay = typedArray.getInteger(R.styleable.BookLoadingView_blv_delay, 0);
114 | mFirstDuration = typedArray.getInteger(R.styleable.BookLoadingView_blv_firstDuration, DEFAULT_FIRST_DURATION);
115 | mTextDuration = typedArray.getInteger(R.styleable.BookLoadingView_blv_textDuration, DEFAULT_TEXT_DURATION);
116 | mPageDuration = typedArray.getInteger(R.styleable.BookLoadingView_blv_pageDuration, DEFAULT_PAGE_DURATION);
117 | mStartColor = typedArray.getColor(R.styleable.BookLoadingView_blv_startColor, DEFAULT_START_COLOR);
118 | mEndColor = typedArray.getColor(R.styleable.BookLoadingView_blv_endColor, DEFAULT_END_COLOR);
119 | setTickColor(typedArray.getColor(R.styleable.BookLoadingView_blv_tickColor, DEFAULT_COLOR));
120 | setTickStrokeWidth(typedArray.getDimensionPixelSize(R.styleable.BookLoadingView_blv_tickStrokeWidth, 0));
121 | setLines(typedArray.getFloat(R.styleable.BookLoadingView_blv_lines, DEFAULT_LINES));
122 | setLinePadding(typedArray.getDimensionPixelSize(R.styleable.BookLoadingView_blv_linePadding, 0));
123 | setLineStrokeWidth(typedArray.getDimensionPixelSize(R.styleable.BookLoadingView_blv_lineStrokeWidth, 0));
124 | setLineColor(typedArray.getColor(R.styleable.BookLoadingView_blv_lineColor, DEFAULT_COLOR));
125 | setTextSize(typedArray.getDimensionPixelSize(R.styleable.BookLoadingView_blv_textSize, (int) mLoadingTV.getTextSize()));
126 | setTextColor(typedArray.getColor(R.styleable.BookLoadingView_blv_textColor, mLoadingTV.getCurrentTextColor()));
127 | int elevation = typedArray.getDimensionPixelSize(R.styleable.BookLoadingView_blv_elevation, 0);
128 | initView(elevation);
129 | if (typedArray.hasValue(R.styleable.BookLoadingView_blv_text)) {
130 | setText(typedArray.getString(R.styleable.BookLoadingView_blv_text));
131 | }
132 | typedArray.recycle();
133 | }
134 |
135 | private void initView(int elevation) {
136 | if (!isInEditMode()) {
137 | mTickView.setBackgroundColor(mStartColor);
138 | mTextMaskView.setBackgroundColor(mStartColor);
139 | mMaskView.setBackgroundColor(mStartColor);
140 | }
141 | ViewCompat.setElevation(mTickView, elevation);
142 | ViewCompat.setElevation(mTextMaskView, elevation);
143 | ViewCompat.setElevation(mMaskView, elevation);
144 | }
145 |
146 | @Override
147 | protected void onFinishInflate() {
148 | super.onFinishInflate();
149 | if (getVisibility() == VISIBLE) {
150 | startLoading(mDelay);
151 | }
152 | }
153 |
154 | @Override
155 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
156 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
157 | int width = (mParentView.getMeasuredWidth() - mParentView.getPaddingLeft() - mParentView.getPaddingRight()) / 2;
158 | if (mWidth == 0 || mWidth != width) {
159 | ViewGroup.LayoutParams params = mTickView.getLayoutParams();
160 | params.width = width;
161 | params = mTextMaskView.getLayoutParams();
162 | params.width = width;
163 | params = mMaskView.getLayoutParams();
164 | params.width = width;
165 | }
166 | }
167 |
168 | private void startLoading(long delay) {
169 | if (!mStopped
170 | || (mPathAnim != null && mPathAnim.isStarted())
171 | || (mColorAnim != null && mColorAnim.isStarted())
172 | || (mFirst3DAnim != null && mFirst3DAnim.hasStarted())
173 | || (mSec3DAnim != null && mSec3DAnim.hasStarted())) {
174 | return;
175 | }
176 | mStopped = false;
177 | mTickView.setPercent(0);
178 | mTextMaskView.setPercent(0);
179 | mTextMaskView.clearAnimation();
180 | if (mTextJumpAnim != null) {
181 | mTextJumpAnim.stopJumping();
182 | }
183 | startPathAnim(delay);
184 | }
185 |
186 | private void startPathAnim(long delay) {
187 | if (mPathAnim == null) {
188 | initPathAnim();
189 | }
190 | mPathAnim.setStartDelay(delay);
191 | mPathAnim.start();
192 | }
193 |
194 | private void initPathAnim() {
195 | mPathAnim = ValueAnimator.ofFloat(0f, 1.0f);
196 | mPathAnim.setDuration(mFirstDuration);
197 | mPathAnim.setInterpolator(new AccelerateDecelerateInterpolator());
198 | mPathAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
199 | @Override
200 | public void onAnimationUpdate(ValueAnimator animation) {
201 | float value = (float) animation.getAnimatedValue();
202 | mTickView.setPercent(value * 1.5f);
203 | mTextMaskView.setPercent(value);
204 | }
205 | });
206 |
207 | mPathAnim.addListener(new Animator.AnimatorListener() {
208 | @Override
209 | public void onAnimationStart(Animator animation) {
210 |
211 | }
212 |
213 | @Override
214 | public void onAnimationEnd(Animator animation) {
215 | if (!mStopped) {
216 | startTextAnim();
217 | start3DAnim();
218 | }
219 | }
220 |
221 | @Override
222 | public void onAnimationCancel(Animator animation) {
223 |
224 | }
225 |
226 | @Override
227 | public void onAnimationRepeat(Animator animation) {
228 |
229 | }
230 | });
231 | }
232 |
233 | private void startTextAnim() {
234 | if (mBuilder == null) {
235 | mBuilder = JumpingBeans.with(mLoadingTV)
236 | .makeTextJump(0, mLoadingTV.getText().length())
237 | .setIsWave(true)
238 | .setLoopDuration(mTextDuration);
239 | }
240 | mTextJumpAnim = mBuilder.build();
241 | }
242 |
243 | private void start3DAnim() {
244 | float centerY = (float) mTextMaskView.getMeasuredHeight() / 2;
245 | if (mFirst3DAnim == null) {
246 | initStart3DAnim(centerY);
247 | }
248 | if (mSec3DAnim == null) {
249 | initSec3DAnim(centerY);
250 | }
251 | if (mColorAnim == null) {
252 | initColorAnim();
253 | }
254 | mTextMaskView.startAnimation(mFirst3DAnim);
255 | mColorAnim.start();
256 | }
257 |
258 | private void initStart3DAnim(float centerY) {
259 | mFirst3DAnim = new Rotate3dAnimation(0, -90, 0, centerY, 0, false);
260 | mFirst3DAnim.setFillAfter(true);
261 | mFirst3DAnim.setDuration(mPageDuration / 2);
262 | mFirst3DAnim.setStartOffset(mTextDuration > 500 ? mTextDuration - 200 : mTextDuration);
263 | mFirst3DAnim.setInterpolator(new AccelerateInterpolator());
264 | mFirst3DAnim.setAnimationListener(new Animation.AnimationListener() {
265 | @Override
266 | public void onAnimationStart(Animation animation) {
267 | // mTextMaskView.setBackgroundColor(Color.GRAY);
268 | }
269 |
270 | @Override
271 | public void onAnimationEnd(Animation animation) {
272 | if (!mStopped) {
273 | mTextMaskView.setPercent(0);
274 | mTextMaskView.startAnimation(mSec3DAnim);
275 | }
276 | }
277 |
278 | @Override
279 | public void onAnimationRepeat(Animation animation) {
280 |
281 | }
282 | });
283 | }
284 |
285 | private void initSec3DAnim(float centerY) {
286 | mSec3DAnim = new Rotate3dAnimation(-90, -180, 0, centerY, 0, false);
287 | mSec3DAnim.setDuration(mPageDuration / 2);
288 | mSec3DAnim.setInterpolator(new DecelerateInterpolator());
289 | }
290 |
291 | private void initColorAnim() {
292 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
293 | mColorAnim = ObjectAnimator.ofArgb(mTextMaskView, "backgroundColor", mStartColor, mEndColor, mStartColor);
294 | } else {
295 | mColorAnim = ObjectAnimator.ofObject(mTextMaskView, "backgroundColor", new TypeEvaluator() {
296 | @Override
297 | public Object evaluate(float fraction, Object startValue, Object endValue) {
298 | int startInt = (Integer) startValue;
299 | int startA = (startInt >> 24) & 0xff;
300 | int startR = (startInt >> 16) & 0xff;
301 | int startG = (startInt >> 8) & 0xff;
302 | int startB = startInt & 0xff;
303 |
304 | int endInt = (Integer) endValue;
305 | int endA = (endInt >> 24) & 0xff;
306 | int endR = (endInt >> 16) & 0xff;
307 | int endG = (endInt >> 8) & 0xff;
308 | int endB = endInt & 0xff;
309 |
310 | return (startA + (int) (fraction * (endA - startA))) << 24 |
311 | (startR + (int) (fraction * (endR - startR))) << 16 |
312 | (startG + (int) (fraction * (endG - startG))) << 8 |
313 | (startB + (int) (fraction * (endB - startB)));
314 | }
315 | }, mStartColor, mEndColor, mStartColor);
316 | }
317 | mColorAnim.setInterpolator(new AccelerateDecelerateInterpolator());
318 | mColorAnim.setStartDelay(mTextDuration > 500 ? mTextDuration - 200 : mTextDuration);
319 | mColorAnim.setDuration(mPageDuration);
320 | mColorAnim.addListener(new Animator.AnimatorListener() {
321 | @Override
322 | public void onAnimationStart(Animator animation) {
323 | if (!mStopped) {
324 | mMaskView.setVisibility(VISIBLE);
325 | }
326 | }
327 |
328 | @Override
329 | public void onAnimationEnd(Animator animation) {
330 | if (!mStopped) {
331 | mTickView.setPercent(0);
332 | mMaskView.setVisibility(GONE);
333 | mTextMaskView.clearAnimation();
334 | startPathAnim(200);
335 | }
336 | }
337 |
338 | @Override
339 | public void onAnimationCancel(Animator animation) {
340 |
341 | }
342 |
343 | @Override
344 | public void onAnimationRepeat(Animator animation) {
345 |
346 | }
347 | });
348 | }
349 |
350 | @Override
351 | protected void onDetachedFromWindow() {
352 | stopLoading();
353 | super.onDetachedFromWindow();
354 | }
355 |
356 | private void stopLoading() {
357 | mStopped = true;
358 | if (mPathAnim != null) {
359 | if (mPathAnim.isStarted()) {
360 | mPathAnim.cancel();
361 | }
362 | mPathAnim.removeAllListeners();
363 | mPathAnim.removeAllUpdateListeners();
364 | mPathAnim = null;
365 | }
366 | mTextMaskView.clearAnimation();
367 | if (mFirst3DAnim != null) {
368 | mFirst3DAnim.setAnimationListener(null);
369 | mFirst3DAnim = null;
370 | }
371 | if (mSec3DAnim != null) {
372 | mSec3DAnim = null;
373 | }
374 | if (mColorAnim != null) {
375 | if (mColorAnim.isStarted()) {
376 | mColorAnim.cancel();
377 | }
378 | mColorAnim.removeAllListeners();
379 | mColorAnim = null;
380 | }
381 | mTextJumpAnim.stopJumping();
382 | }
383 |
384 | @Override
385 | public void setVisibility(int visibility) {
386 | this.setVisibility(visibility, mDelay);
387 | }
388 |
389 | public void setVisibility(int visibility, int delay) {
390 | super.setVisibility(visibility);
391 | if (visibility == View.VISIBLE) {
392 | startLoading(delay);
393 | } else {
394 | stopLoading();
395 | }
396 | }
397 |
398 | public void setDelay(int delay) {
399 | mDelay = delay;
400 | }
401 |
402 | public int getDelay() {
403 | return mDelay;
404 | }
405 |
406 | public void setLinePadding(int linePadding) {
407 | mTextMaskView.setLinePadding(linePadding);
408 | }
409 |
410 | public void setLineStrokeWidth(@Px int strokeWidth) {
411 | mTextMaskView.setStrokeWidth(strokeWidth);
412 | }
413 |
414 | public void setLines(float lines) {
415 | mTextMaskView.setLines(lines);
416 | }
417 |
418 | public void setLineColor(@ColorInt int color) {
419 | mTextMaskView.setColor(color);
420 | }
421 |
422 | public void setTickStrokeWidth(@Px int strokeWidth) {
423 | mTickView.setStrokeWidth(strokeWidth);
424 | }
425 |
426 | public void setTickColor(@ColorInt int color) {
427 | mTickView.setColor(color);
428 | }
429 |
430 | public void setTextSize(@Px int size) {
431 | mLoadingTV.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
432 | }
433 |
434 | public void setTextColor(@ColorInt int color) {
435 | mLoadingTV.setTextColor(color);
436 | }
437 |
438 | public void setText(CharSequence text) {
439 | mLoadingTV.setText(text);
440 | }
441 | }
442 |
--------------------------------------------------------------------------------