├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── dbnavigator.xml
├── gradle.xml
├── jarRepositories.xml
├── kotlinc.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── atman.jks
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── lihang
│ │ └── mysmartloadingview
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── lihang
│ │ │ └── mysmartloadingview
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainJavaActivity.java
│ │ │ └── SecondActivity.java
│ └── res
│ │ ├── anim
│ │ ├── scale_test2.xml
│ │ └── scale_test_home.xml
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── cb_select.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── shape_button.xml
│ │ └── shape_for_login_edit.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── activity_second.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── check_off_1.png
│ │ ├── check_on.png
│ │ ├── girl_head.jpg
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ ├── login_password.png
│ │ ├── login_phone.png
│ │ └── main_bg.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── lihang
│ └── mysmartloadingview
│ └── ExampleUnitTest.java
├── build.gradle
├── gif
├── SmartLoadingView_.png
├── fullScreen.gif
├── fullScreen_fail_show.gif
├── fullScreen_fail_toast.gif
├── smartButton.gif
├── smartButton_fail.gif
├── smartButton_noAnimal.gif
├── smartTick.gif
├── smartTick_hide.gif
└── smartTick_hide_center.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── smartloadview
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── lihang
│ ├── SmartLoadingView.java
│ └── help
│ ├── CirclBigView.java
│ ├── OkView.java
│ └── UIUtil.java
└── res
├── anim
├── alpha_hide.xml
├── scale_test2.xml
└── scale_test_home.xml
└── values
├── attrs.xml
├── colors.xml
├── dimens.xml
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches/build_file_checksums.ser
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | .DS_Store
9 | /build
10 | /captures
11 | .externalNativeBuild
12 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
8 |
22 |
27 |
40 |
27 |
28 | ## 效果展示
29 | 为录制流畅,截图分辨率模糊。可下载apk查看真机效果
30 | * ### smart_full_screen模式:全屏扩散动画(不支持关注)
31 | |全屏扩散页面跳转|加载失败|失败文案在按钮上|
32 | |:---:|:---:|:---:|
33 | ||
|
34 |
35 |
36 | * ### smart_button模式:正常按钮动画(支持关注)
37 | |关注并加载成功|关注失败|不带动画关注|
38 | |:---:|:---:|:---:|
39 | ||
|
40 |
41 |
42 | * ### smart_tick模式:仿抖音打勾关注(支持关注);smart_tick_hide模式:打勾隐藏(支持关注);smart_tick_center_hide模式:打勾隐藏,并移至中间提醒(支持关注)。
43 | |仿抖音打勾关注|打勾隐藏|打勾隐藏,移至中间|
44 | |:---:|:---:|:---:|
45 | ||
|
46 |
47 |
48 |
49 | ## 添加依赖
50 |
51 | - 项目build.gradle添加如下
52 | ```java
53 | allprojects {
54 | repositories {
55 | maven { url 'https://jitpack.io' }
56 | }
57 | }
58 | ```
59 | - app build.gradle添加如下
60 | ```java
61 | dependencies {
62 |
63 | implementation 'com.github.lihangleo2:SmartLoadingView:3.0.0'
64 |
65 | }
66 | ```
67 |
68 |
69 | ## 基本使用
70 | #### 一、简单使用
71 | xml设置如下,注意app:hl_button_type="smart_button"
72 | ```xml
73 |
86 |
87 | 使用如下:没看错就这么简单(5种buttonType均如此)
88 | ```java
89 | //1.1 设置点击事件,点击调用加载loading
90 | smartLoadingView.startLoading()
91 |
92 | //1.2 联网成功,关注成功调用如下代码
93 | smartLoadingView.finishLoading()
94 |
95 | //1.3 联网失败,调用如下代码
96 | smartLoadingView.finishLoading(false)
97 |
98 | ```
99 |
100 |
101 | #### 二、全屏模式:smart_full_screen(注意:此模式不支持关注)
102 | ##### 2.1 全屏扩散及页面跳转:smartLoadingView.finishLoadingWithFullScreen(Activity activity, Class clazz)
103 | xml如下:
104 | ```xml
105 |
117 |
118 | 当点击按钮时,开启加载loading
119 |
120 |
121 | ```java
122 | smartLoadingView.startLoading()
123 | ```
124 |
125 |
126 | 当得到联网结果为success,且需要跳转页面时调用如下:
127 | ```java
128 | smartLoadingView.finishLoadingWithFullScreen(this, SecondActivity::class.java)
129 | ```
130 |
131 |
132 | 如果你不想用封装的api,想再扩散动画结束后自己操作,你可以使用如下方法:
133 | ```java
134 | //kotlin使用如下:
135 | smartLoadingView.finishLoading(true) {
136 | //处理自定义逻辑
137 | }
138 |
139 | //java使用如下:
140 | smartLoadingView.finishLoading(true, success -> {
141 | //处理自定义逻辑
142 | });
143 | ```
144 |
145 |
146 | ##### 2.2 如果联网结果失败fail时
147 | 执行完,就会平滑回到初始状态
148 | ```java
149 | //kotlin使用如下:
150 | smartLoadingView.finishLoading(false) {
151 | ToastUtils.showShort("加载失败")
152 | }
153 |
154 | //java使用如下:
155 | smartLoadingView.finishLoading(false, success -> {
156 | ToastUtils.showShort("加载失败")
157 | });
158 | ```
159 |
160 |
161 | ##### 2.3 如果联网结果失败,你想将错误信息显示在按钮上可以这样
162 | xml如下:
163 | ```xml
164 |
179 |
180 | 调用如下即可。
181 | ```java
182 | //如果想自定义错误文案,在调用finishLoading前,设置文案
183 | //smartLoadingView.setAnimaledText("我是自定义错误文案")
184 | smartLoadingView.finishLoading(false)
185 |
186 | ```
187 |
188 |
189 | #### 三、正常模式详细讲解:smart_button(支持关注)
190 | 特别说明:smart_button、smart_tick、smart_tick_hide、smart_tick_center_hide 这四种模式,用法一致。所以这里以smart_button 讲解为主
191 | ##### 3.1 这里我们用一个关注功能来说:如果我们有个按钮,初始状态显示"点击关注",点击按钮进行网络请求,成功了显示"关注成功";此时再点击,进行网络请求,成功后再显示"点击关注"
192 | 当点击按钮时, 我们要判断当前是什么状态,来进行接下来的逻辑:
193 | ```java
194 | if (!smartLoadingView.isFinished) {
195 | //当前不时结束,状态,开启加载loading
196 | smartLoadingView.startLoading()
197 | } else {
198 | //当前结束时,再次点击回到初始状态
199 | //kotlin使用
200 | smartLoadingView.isFinished = false
201 | //java使用
202 | //smartLoadingView.setFinished(false);
203 | }
204 | ```
205 |
206 |
207 | ##### 3.2 联网出结果,成功 or 失败同样调用如下
208 | ```java
209 | //true则走成功,false则走失败
210 | //kotlin使用
211 | smartLoadingView.finishLoading(true) {
212 |
213 | }
214 |
215 | //java使用
216 | smartLoadingView.finishLoading(true, success -> {
217 |
218 | });
219 | ```
220 |
221 |
222 | ##### 3.3 如果你不想使用动画,可以调用如下api。
223 | ```java
224 | //kotlin使用
225 | smartLoadingView.isFinished = true
226 |
227 | //java使用
228 | smartLoadingView.setFinished(true);
229 | ```
230 |
231 |
232 | ## 属性表格(Attributes)
233 | 因为SmartLoadingView就是TextView的拓展控件。部分属性延用了系统属性
234 |
235 | |name|format|description|系统api|
236 | |:---:|:---:|:---:|:---:|
237 | |android:text|string|文案内容|是|
238 | |app:hl_animaled_text|string|动画结束文案,默认为text|否|
239 | |android:textColor|color|文案颜色|是|
240 | |app:hl_animaled_textColor|color|动画结束文字颜色,默认为textColor|否|
241 | |android:textSize|dimension|文案字体大小|是|
242 | |android:background|color|背景色|是|
243 | |app:hl_animaled_background|color|动画结束背景色,默认为background颜色值|否|
244 | |app:hl_corners_radius|dimension|圆角属性|否|
245 | |android:enabled|boolean|是否可被点击|是|
246 | |app:hl_unEnabled_background|color|不可点击状态下背景色|否|
247 | |app:hl_ellipsize|enum|reverse:来回滚动;marquee:跑马灯。需文字大于控件宽度生效|否|
248 | |app:hl_ellipsize_speed|integer|文字滚动速度|否|
249 | |app:hl_button_type|enum|5种buttonType样式|否|
250 |
251 |
252 |
253 | ## 方法表格(Method)
254 | |name|format|description|
255 | |:---:|:---:|:---:|
256 | |startLoading()|void|开启加载loading|
257 | |setFinished()|boolean|不带动画设置控件结束状态|
258 | |finishLoading()|boolean|带动画设置控件结束状态|
259 |
260 |
261 | ## 赞赏
262 |
263 | 如果你喜欢 SmartLoadingView 的功能,感觉 SmartLoadingView 帮助到了你,可以点右上角 "Star" 支持一下 谢谢! ^_^
264 | 你也还可以扫描下面的二维码~ 请作者喝一杯咖啡。或者遇到工作中比较难实现的需求请作者帮忙。
265 |
266 |  
267 |
268 |
269 | 如果在捐赠留言中备注名称,将会被记录到列表中~ 如果你也是github开源作者,捐赠时可以留下github项目地址或者个人主页地址,链接将会被添加到列表中
270 | ### [捐赠列表](https://github.com/lihangleo2/ShadowLayout/blob/master/showImages/friend.md)
271 |
272 |
273 |
274 | ## 其他作品
275 | [万能ViewPager2适配器SmartViewPager2Adapter](https://github.com/lihangleo2/ViewPager2Demo)
276 | [RichEditTextCopyToutiao](https://github.com/lihangleo2/RichEditTextCopyToutiao)
277 | [ShadowLayout](https://github.com/lihangleo2/ShadowLayout)
278 |
279 |
280 |
281 |
282 |
283 | ## 关于作者。
284 | Android工作多年了。前进的道路上是孤独的。如果你在学习的路上也感觉孤独,请和我一起。让我们在学习道路上少些孤独
285 |
286 | * QQ群: 209010674 (点击图标,可以直接加入)
287 |
288 |
289 | ## LICENSE
290 |
291 | ```
292 | MIT License
293 |
294 | Copyright (c) 2019 leo
295 |
296 | Permission is hereby granted, free of charge, to any person obtaining a copy
297 | of this software and associated documentation files (the "Software"), to deal
298 | in the Software without restriction, including without limitation the rights
299 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
300 | copies of the Software, and to permit persons to whom the Software is
301 | furnished to do so, subject to the following conditions:
302 |
303 | The above copyright notice and this permission notice shall be included in all
304 | copies or substantial portions of the Software.
305 | ```
306 |
307 |
308 |
309 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/atman.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lihangleo2/SmartLoadingView/774085bfede245944df8d5b81fd371752d34af54/app/atman.jks
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'org.jetbrains.kotlin.android'
3 |
4 | android {
5 | compileSdkVersion 28
6 | defaultConfig {
7 | applicationId "com.lihang.mysmartloadingview"
8 | minSdkVersion 15
9 | targetSdkVersion 28
10 | versionCode 3
11 | versionName "2.0.2"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | dataBinding {
15 | enabled = true
16 | }
17 |
18 | //签名 (需要签名文件)
19 | signingConfigs {
20 | key {
21 | storeFile file("atman.jks")//签名文件
22 | storePassword "5888062"
23 | keyAlias "fastMedical"
24 | keyPassword "5888062"//签名密码
25 | }
26 | }
27 |
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
32 | signingConfig signingConfigs.key
33 | }
34 | }
35 |
36 | //多渠道打包
37 | flavorDimensions "mark" //相当于一个标记
38 | productFlavors {
39 | medical { dimension "mark" }
40 | }
41 |
42 | compileOptions {
43 | sourceCompatibility = 1.8
44 | targetCompatibility = 1.8
45 | }
46 |
47 |
48 | productFlavors.all {
49 | flavor -> flavor.manifestPlaceholders = [ATMAN_CHANNEL_VALUE: name]
50 | }
51 |
52 | //自定义打包时apk名字
53 | android.applicationVariants.all { variant ->
54 | variant.outputs.all {
55 | // abc_渠道名_版本名.apk 还可以拼接其他app内容:variant.versionCode variant.buildType.name
56 | outputFileName = "ShadowLayout_${variant.versionName}_${variant.buildType.name}_${new Date().format("yyyy-MM-dd_HH_mm")}.apk"
57 |
58 | }
59 | }
60 | }
61 |
62 | dependencies {
63 | implementation fileTree(include: ['*.jar'], dir: 'libs')
64 | implementation 'com.android.support:appcompat-v7:28.0.0'
65 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
66 | testImplementation 'junit:junit:4.12'
67 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
68 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
69 | implementation project(':smartloadview')
70 | implementation 'com.blankj:utilcode:1.28.0'
71 | }
72 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/lihang/mysmartloadingview/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.lihang.mysmartloadingview;
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 | * Instrumented 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() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.lihang.mysmartloadingview", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
329 | * 矩形到圆角矩形的动画
330 | * &圆角矩形到矩形的动画
331 | */
332 | private void set_rect_to_circle_animation() {
333 | animator_rect_to_square = ValueAnimator.ofInt(0, default_all_distance);
334 | animator_rect_to_square.setDuration(duration);
335 | animator_rect_to_square.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
336 | @Override
337 | public void onAnimationUpdate(ValueAnimator animation) {
338 | current_left = (int) animation.getAnimatedValue();
339 |
340 | int nowAlpha = textAlpha / 2 - (current_left * textAlpha / default_all_distance) < 0 ? 0 : textAlpha / 2 - (current_left * textAlpha / default_all_distance);
341 | textPaint.setColor(addAlpha(textColor, nowAlpha));
342 | if (current_left == default_all_distance) {
343 | isDrawLoading = true;
344 | }
345 | invalidate();
346 |
347 | }
348 | });
349 |
350 |
351 | animator_rect_to_angle = ValueAnimator.ofInt(corners_radius, height / 2);
352 | animator_rect_to_angle.setDuration(duration);
353 | animator_rect_to_angle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
354 | @Override
355 | public void onAnimationUpdate(ValueAnimator animation) {
356 | circleAngle = (int) animation.getAnimatedValue();
357 | invalidate();
358 | }
359 | });
360 |
361 |
362 | animator_squareToRect = ValueAnimator.ofInt(default_all_distance, 0);
363 | animator_squareToRect.setDuration(duration);
364 | animator_squareToRect.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
365 | @Override
366 | public void onAnimationUpdate(ValueAnimator animation) {
367 | current_left = (int) animation.getAnimatedValue();
368 | //当控件扩展到一半时再显示文字,不然当文案过长时,会先显示文字。超过控件
369 | if (current_left <= default_all_distance / 2) {
370 | int nowAlpha = (default_all_distance / 2 - current_left) * textAlpha / (default_all_distance / 2);
371 | textPaint.setColor(addAlpha(textColor, nowAlpha));
372 | }
373 | //错误动画全部走完之后,才能被点击
374 | if (current_left == 0) {
375 | isLoading = false;
376 | setClickable(true);
377 | }
378 | isDrawLoading = false;
379 | startDrawOk = false;
380 | postInvalidate();
381 | }
382 | });
383 |
384 |
385 | animator_angle_to_rect = ValueAnimator.ofInt(height / 2, corners_radius);
386 | animator_angle_to_rect.setDuration(duration);
387 | animator_angle_to_rect.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
388 | @Override
389 | public void onAnimationUpdate(ValueAnimator animation) {
390 | circleAngle = (int) animation.getAnimatedValue();
391 | postInvalidate();
392 | }
393 | });
394 |
395 | }
396 |
397 | /*
398 | * 绘制对勾的动画
399 | */
400 | private void set_draw_ok_animation() {
401 | animator_draw_ok = ValueAnimator.ofFloat(1, 0);
402 | animator_draw_ok.setDuration(duration);
403 | animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
404 | @Override
405 | public void onAnimationUpdate(ValueAnimator animation) {
406 | startDrawOk = true;
407 | isDrawLoading = false;
408 | float value = (Float) animation.getAnimatedValue();
409 | effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
410 | okPaint.setPathEffect(effect);
411 | invalidate();
412 |
413 | }
414 | });
415 | }
416 |
417 |
418 | /*
419 | * 画圆角矩形
420 | * */
421 | private void draw_oval_to_circle(Canvas canvas) {
422 | rectf.left = current_left;
423 | rectf.top = 0;
424 | rectf.right = width - current_left;
425 | rectf.bottom = height;
426 |
427 | //画圆角矩形
428 | canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);
429 | }
430 |
431 | /**
432 | * *
433 | * *********************************************************************************************
434 | */
435 | @Override
436 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
437 | super.onSizeChanged(w, h, oldw, oldh);
438 | if (width == 0) {
439 | width = w;
440 | height = h;
441 | if (corners_radius > (height / 2)) {
442 | corners_radius = height / 2;
443 | }
444 | circleAngle = corners_radius;
445 | default_all_distance = (w - h) / 2;
446 | initOk();
447 | initAnimation();
448 | //如果不是精准模式,我们代码里设置第一次的长宽,成为精准模式
449 | //这样避免,更改文字内容时,总是会改变控件的长宽
450 | setWidth(width);
451 | setHeight(height);
452 | // setEnabled(isEnabled());
453 | }
454 | }
455 |
456 | //绘制文字相关
457 | private void drawText(final Canvas canvas) {
458 | int sc = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
459 | rectf.left = current_left + getPaddingLeft();
460 | rectf.top = 0;
461 | rectf.right = width - current_left - getPaddingRight();
462 | rectf.bottom = height;
463 | //画圆角矩形
464 | canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);
465 |
466 | //设置混合模式
467 | textPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
468 |
469 | textRect.left = getPaddingLeft();
470 | textRect.top = 0;
471 | textRect.right = width - getPaddingRight();
472 | textRect.bottom = height;
473 | Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
474 | final int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
475 | //这是测量文字的长度。
476 | int myTotal = (int) (textPaint.measureText(currentString) + getPaddingRight() + getPaddingLeft());
477 | if (myTotal > getWidth()) {
478 | if (ellipsize == 1) {
479 | textPaint.setTextAlign(Paint.Align.LEFT);
480 | if (animator_text_scroll == null && !isLoading) {
481 | //此时文字长度已经超过一行,进行文字滚动
482 | animator_text_scroll = ValueAnimator.ofInt(textRect.left, (int) (textRect.left - textPaint.measureText(currentString) + (getWidth() - getPaddingLeft() - getPaddingRight())));
483 | animator_text_scroll.setDuration(currentString.length() * ellipsize_speed);
484 | animator_text_scroll.setRepeatMode(ValueAnimator.REVERSE);
485 | animator_text_scroll.setRepeatCount(-1);
486 | animator_text_scroll.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
487 | @Override
488 | public void onAnimationUpdate(ValueAnimator animation) {
489 | drawTextStart = (int) animation.getAnimatedValue();
490 | postInvalidate();
491 | }
492 | });
493 | animator_text_scroll.start();
494 | }
495 | canvas.drawText(currentString, drawTextStart, baseline, textPaint);
496 | } else {
497 | textPaint.setTextAlign(Paint.Align.LEFT);
498 | if (animator_text_scroll == null && !isLoading) {
499 | //此时文字长度已经超过一行,进行文字滚动
500 | animator_text_scroll = ValueAnimator.ofInt(textRect.left, (int) (textRect.left - textPaint.measureText(currentString)));
501 | animator_text_scroll.setDuration(currentString.length() * ellipsize_speed);
502 | animator_text_scroll.setInterpolator(new LinearInterpolator());
503 | animator_text_scroll.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
504 | @Override
505 | public void onAnimationUpdate(ValueAnimator animation) {
506 |
507 | drawTextStart = (int) animation.getAnimatedValue();
508 | postInvalidate();
509 | if (drawTextStart == textRect.left) {
510 | if (animator_marque != null) {
511 | animator_marque.cancel();
512 | animator_marque = null;
513 | }
514 | }
515 | if (animator_marque == null && !isLoading && drawTextStart <= (int) (textRect.left - textPaint.measureText(currentString) + (getWidth() - getPaddingLeft() - getPaddingRight()) - (getWidth() - getPaddingLeft() - getPaddingRight()) / 3)) {
516 | int duration = (int) (((currentString.length() * ellipsize_speed) * (textRect.right - textRect.left)) / textPaint.measureText(currentString));
517 | animator_marque = ValueAnimator.ofInt(textRect.right, textRect.left);
518 | animator_marque.setDuration(duration);
519 | animator_marque.setInterpolator(new LinearInterpolator());
520 | animator_marque.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
521 | @Override
522 | public void onAnimationUpdate(ValueAnimator animation) {
523 | drawMarqueTextStart = (int) animation.getAnimatedValue();
524 | if (drawMarqueTextStart == textRect.left) {
525 | SmartLoadingView.this.postDelayed(new Runnable() {
526 | @Override
527 | public void run() {
528 | if (animator_text_scroll != null) {
529 | animator_text_scroll.cancel();
530 | animator_text_scroll = null;
531 | postInvalidate();
532 | }
533 | }
534 | }, 1500);
535 | }
536 | postInvalidate();
537 | }
538 | });
539 | animator_marque.start();
540 | }
541 | }
542 | });
543 | animator_text_scroll.start();
544 | }
545 | if (animator_marque != null) {
546 | canvas.drawText(currentString, drawMarqueTextStart, baseline, textPaint);
547 | }
548 | canvas.drawText(currentString, drawTextStart, baseline, textPaint);
549 | }
550 |
551 | } else {
552 | cancleScroll();
553 | textPaint.setTextAlign(Paint.Align.CENTER);
554 | drawTextStart = textRect.left;
555 | canvas.drawText(currentString, textRect.centerX(), baseline, textPaint);
556 | }
557 |
558 | // 还原混合模式
559 | textPaint.setXfermode(null);
560 | // 还原画布
561 | canvas.restoreToCount(sc);
562 | }
563 |
564 | @Override
565 | protected void onDraw(Canvas canvas) {
566 | draw_oval_to_circle(canvas);
567 | drawText(canvas);
568 |
569 | //绘制加载进度
570 | if (isDrawLoading) {
571 | canvas.drawArc(new RectF(width / 2 - height / 2 + height / 4, height / 4, width / 2 + height / 2 - height / 4, height / 2 + height / 2 - height / 4), startAngle, progAngle, false, okPaint);
572 | startAngle += 6;
573 | if (progAngle >= 270) {
574 | progAngle -= 2;
575 | isAdd = false;
576 | } else if (progAngle <= 45) {
577 | progAngle += 6;
578 | isAdd = true;
579 | } else {
580 | if (isAdd) {
581 | progAngle += 6;
582 | } else {
583 | progAngle -= 2;
584 | }
585 | }
586 | postInvalidate();
587 | }
588 |
589 | //绘制打勾
590 | if (startDrawOk) {
591 | canvas.drawPath(path, okPaint);
592 | }
593 |
594 | }
595 |
596 |
597 | @Override
598 | protected void onDetachedFromWindow() {
599 | super.onDetachedFromWindow();
600 | cancleScroll();
601 | }
602 |
603 |
604 | //给TextView字体设置透明度。
605 | private int addAlpha(int color, int alpha) {
606 | int red = Color.red(color);
607 | int green = Color.green(color);
608 | int blue = Color.blue(color);
609 | return Color.argb(alpha, red, green, blue);
610 | }
611 |
612 | //取消所有动画
613 | private void cancleScroll() {
614 | if (animator_text_scroll != null) {
615 | animator_text_scroll.cancel();
616 | animator_text_scroll = null;
617 | }
618 |
619 | if (animator_marque != null) {
620 | animator_marque.cancel();
621 | animator_marque = null;
622 | }
623 | }
624 |
625 |
626 | /**
627 | * Function
628 | * *********************************************************************************************
629 | */
630 |
631 | @Override
632 | public void setText(CharSequence text, BufferType type) {
633 | super.setText(text, type);
634 | normalString = (String) text;
635 | currentString = (String) text;
636 | postInvalidate();
637 | }
638 |
639 | public void setAnimaledText(CharSequence text) {
640 | mAnimaledText = (String) text;
641 | }
642 |
643 | @Override
644 | public void setEnabled(boolean enabled) {
645 | super.setEnabled(enabled);
646 | if (enabled) {
647 | if (paint != null) paint.setColor(backgroundColor);
648 | postInvalidate();
649 | } else {
650 | if (paint != null) paint.setColor(unEnabled_backgroundColor);
651 | postInvalidate();
652 | }
653 | }
654 |
655 | //快速点击解决
656 | private long startLoading_click_millons = 0L;
657 |
658 |
659 | /**
660 | * SmartLoadingView -- startLoading:开启加载动画
661 | */
662 | public void startLoading() {
663 | long currentMillons = System.currentTimeMillis();
664 | if ((currentMillons - startLoading_click_millons) < 500L) {
665 | return;
666 | }
667 | startLoading_click_millons = currentMillons;
668 | //没有在loading的情况下才能点击(没有在请求网络的情况下)
669 | if (!isLoading) {
670 | textColor = textColorOriginal;
671 | cancleScroll();
672 | startDrawOk = false;
673 | currentString = normalString;
674 | this.setClickable(false);
675 | paint.setColor(backgroundColor);
676 | isLoading = true;
677 | animatorSet.start();
678 | }
679 | }
680 |
681 | private boolean isFinished;
682 |
683 | public boolean isFinished() {
684 | return isFinished;
685 | }
686 |
687 | private long finished_click_millons = 0L;
688 |
689 | /**
690 | * SmartLoadingView -- setFinished:设置SmartLoadingView的finished状态(无动画)
691 | */
692 | public void setFinished(boolean success) {
693 | long currentMillons = System.currentTimeMillis();
694 | if ((currentMillons - finished_click_millons) < 500L) {
695 | return;
696 | }
697 | finished_click_millons = currentMillons;
698 |
699 | if (mButtonType == SMART_FULL_SCREEN) {
700 | //全屏
701 | throw new IllegalArgumentException("hl_button_type = \"smart_full_screen\",不属于关注模式");
702 | } else if (mButtonType == SMART_BUTTON) {
703 | //正常方式
704 | setFinishedReal(success, SMART_BUTTON);
705 | } else if (mButtonType == SMART_TICK) {
706 | //打勾方式
707 | setFinishedReal(success, SMART_TICK);
708 | } else if (mButtonType == SMART_TICK_HIDE) {
709 | //打勾--隐藏方式
710 | setFinishedReal(success, SMART_TICK_HIDE);
711 | } else if (mButtonType == SMART_TICK_CENTER_HIDE) {
712 | //打勾--移至中间--隐藏方式
713 | setFinishedReal(success, SMART_TICK_CENTER_HIDE);
714 | }
715 | }
716 |
717 | private void setFinishedReal(final boolean success, final int buttonType) {
718 | //兼容控件还未渲染到页面上
719 | if (getWidth() == 0 && getHeight() == 0) {
720 | SmartLoadingView.this.addOnLayoutChangeListener(new OnLayoutChangeListener() {
721 | @Override
722 | public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
723 | changeButtonStatus(success, buttonType);
724 | removeOnLayoutChangeListener(this);
725 | }
726 | });
727 | } else {
728 | changeButtonStatus(success, buttonType);
729 | }
730 |
731 | }
732 |
733 | public void finishLoading() {
734 | finishLoading(true, null);
735 | }
736 |
737 | public void finishLoading(boolean success) {
738 | finishLoading(success, null);
739 | }
740 |
741 | public void finishLoading(LoadingListener listener) {
742 | finishLoading(true, listener);
743 | }
744 |
745 |
746 | /**
747 | * SmartLoadingView -- finishLoading:结束SmartLoadingView的finished状态(动画过渡)
748 | */
749 | private long finishLoading_click_millons = 0L;
750 |
751 | public void finishLoading(boolean success, LoadingListener listener) {
752 | long currentMillons = System.currentTimeMillis();
753 | if ((currentMillons - finishLoading_click_millons) < 500L) {
754 | return;
755 | }
756 | finishLoading_click_millons = currentMillons;
757 |
758 | if (mButtonType == SMART_FULL_SCREEN) {
759 | if (success) {
760 | fullScreen(listener);
761 | } else {
762 | backToEnd(listener, false);
763 | }
764 | } else if (mButtonType == SMART_BUTTON) {
765 | if (success) {
766 | isFinished = true;
767 | backToEnd(listener, true);
768 | } else {
769 | backToStart(listener, false);
770 | }
771 | } else if (mButtonType == SMART_TICK) {
772 | if (success) {
773 | isFinished = true;
774 | startTick(listener);
775 | } else {
776 | backToStart(listener, false);
777 | }
778 | } else if (mButtonType == SMART_TICK_HIDE) {
779 | if (success) {
780 | isFinished = true;
781 | startTickHide(listener);
782 | } else {
783 | backToStart(listener, false);
784 | }
785 | } else if (mButtonType == SMART_TICK_CENTER_HIDE) {
786 | if (success) {
787 | isFinished = true;
788 | startTickCenterHide(listener);
789 | } else {
790 | backToStart(listener, false);
791 | }
792 | }
793 | }
794 |
795 | /**
796 | * ================================= fullScreen start =======================================
797 | */
798 |
799 | /**
800 | * SmartLoadingView -- finishLoadingWithFullScreen:全屏api额外封装,为快速跳转(动画过度,只支持smart_full_screen模式)
801 | */
802 | public void finishLoadingWithFullScreen(Activity activity, Class clazz) {
803 | if (mButtonType == SMART_FULL_SCREEN) {
804 | fullScreen(activity, clazz);
805 | } else {
806 | throw new IllegalArgumentException("此api只支持,hl_button_type = \"smart_full_screen\"");
807 | }
808 | }
809 |
810 | private void backToEnd(final LoadingListener listener, final boolean success) {
811 | if (isLoading) {
812 | if (!animatorSet.isRunning()) {
813 | backToEndReal(listener, success);
814 | } else {
815 | this.postDelayed(new Runnable() {
816 | @Override
817 | public void run() {
818 | backToEndReal(listener, success);
819 | }
820 | }, 1000);
821 | }
822 | }
823 | }
824 |
825 | private void backToEndReal(final LoadingListener listener, final boolean success) {
826 | if (animatorNetfail.isRunning()) {
827 | //防止重复播放动画
828 | return;
829 | }
830 | currentString = mAnimaledText;
831 | paint.setColor(animaled_backgroundColor);
832 | textColor = animaled_textColor;
833 | animatorNetfail.addListener(new Animator.AnimatorListener() {
834 | @Override
835 | public void onAnimationStart(Animator animator) {
836 |
837 | }
838 |
839 | @Override
840 | public void onAnimationEnd(Animator animator) {
841 | if (listener != null) {
842 | listener.loadingFinish(success);
843 | }
844 | }
845 |
846 | @Override
847 | public void onAnimationCancel(Animator animator) {
848 |
849 | }
850 |
851 | @Override
852 | public void onAnimationRepeat(Animator animator) {
853 |
854 | }
855 | });
856 | animatorNetfail.start();
857 | }
858 |
859 | //开启全屏动画,并监听动画进度
860 | private void fullScreen(final LoadingListener listener) {
861 | //必须,点击了最开始的动画处于,加载状态,才能获得回调
862 | if (isLoading) {
863 | if (!animatorSet.isRunning()) {
864 | toBigCircle(listener);
865 | } else {
866 | //当点击按钮的时候请求网络,假如动画执行时间大于网络请求时间,
867 | //那么咱们默认,执行完加载动画后,立即执行加载成功动画
868 | this.postDelayed(new Runnable() {
869 | @Override
870 | public void run() {
871 | toBigCircle(listener);
872 | }
873 | }, 1000);
874 | }
875 | }
876 | }
877 |
878 | //开启全屏动画,并简化跳转进度
879 | private void fullScreen(final Activity activity, final Class clazz) {
880 | //必须,点击了最开始的动画处于,加载状态,才能获得回调
881 | if (isLoading) {
882 | if (!animatorSet.isRunning()) {
883 | toBigCircle(activity, clazz);
884 | } else {
885 | //当点击按钮的时候请求网络,加入动画执行时间大于网络请求时间,
886 | //那么咱们默认,执行完加载动画后,立即执行加载成功动画
887 | this.postDelayed(new Runnable() {
888 | @Override
889 | public void run() {
890 | toBigCircle(activity, clazz);
891 | }
892 | }, 1000);
893 | }
894 | }
895 | }
896 |
897 | /**
898 | * ================================= fullScreen end =========================================
899 | */
900 |
901 |
902 | /**
903 | * ================================= smartButton start =======================================
904 | */
905 | private void backToStart(final LoadingListener listener, final boolean success) {
906 | if (isLoading) {
907 | if (!animatorSet.isRunning()) {
908 | backToStartReal(listener, success);
909 | } else {
910 | this.postDelayed(new Runnable() {
911 | @Override
912 | public void run() {
913 | backToStartReal(listener, success);
914 | }
915 | }, 1000);
916 | }
917 | }
918 | }
919 |
920 | private void backToStartReal(final LoadingListener listener, final boolean success) {
921 | if (animatorNetfail.isRunning()) {
922 | //防止重复播放动画
923 | return;
924 | }
925 | currentString = normalString;
926 | paint.setColor(backgroundColor);
927 | textColor = textColorOriginal;
928 | animatorNetfail.addListener(new Animator.AnimatorListener() {
929 | @Override
930 | public void onAnimationStart(Animator animator) {
931 |
932 | }
933 |
934 | @Override
935 | public void onAnimationEnd(Animator animator) {
936 | if (listener != null) {
937 | listener.loadingFinish(success);
938 | }
939 | }
940 |
941 | @Override
942 | public void onAnimationCancel(Animator animator) {
943 |
944 | }
945 |
946 | @Override
947 | public void onAnimationRepeat(Animator animator) {
948 |
949 | }
950 | });
951 | animatorNetfail.start();
952 | }
953 |
954 | private void changeButtonStatus(boolean success, int buttonType) {
955 | reset();
956 | isFinished = success;
957 | if (success) {
958 | if (buttonType == SMART_BUTTON) {
959 | paint.setColor(animaled_backgroundColor);
960 | currentString = mAnimaledText;
961 | textColor = animaled_textColor;
962 | textPaint.setColor(textColor);
963 | postInvalidate();
964 | } else {
965 | current_left = default_all_distance;
966 | int nowAlpha = textAlpha / 2 - (current_left * textAlpha / default_all_distance) < 0 ? 0 : textAlpha / 2 - (current_left * textAlpha / default_all_distance);
967 | textPaint.setColor(addAlpha(textColor, nowAlpha));
968 | if (current_left == default_all_distance) {
969 | startDrawOk = true;
970 | }
971 | effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, 0 * pathMeasure.getLength());
972 | okPaint.setPathEffect(effect);
973 | postInvalidate();
974 | //SMART_TICK | SMART_TICK_HIDE | SMART_TICK_CENTER_HIDE
975 | if (buttonType == SMART_TICK_HIDE || buttonType == SMART_TICK_CENTER_HIDE) {
976 | setVisibility(View.INVISIBLE);
977 | }
978 | }
979 | }
980 |
981 | }
982 |
983 | /**
984 | * ================================= smartButton end =========================================
985 | */
986 |
987 |
988 | /**
989 | * ================================= smartTick start =========================================
990 | */
991 |
992 | private void startTick(final LoadingListener listener) {
993 | if (isLoading) {
994 | if (!animatorSet.isRunning()) {
995 | startTickAnimal(listener, false);
996 | } else {
997 | this.postDelayed(new Runnable() {
998 | @Override
999 | public void run() {
1000 | startTickAnimal(listener, false);
1001 | }
1002 | }, 1000);
1003 | }
1004 | }
1005 | }
1006 |
1007 | private void startTickHide(final LoadingListener listener) {
1008 | if (isLoading) {
1009 | if (!animatorSet.isRunning()) {
1010 | startTickAnimal(listener, true);
1011 | } else {
1012 | this.postDelayed(new Runnable() {
1013 | @Override
1014 | public void run() {
1015 | startTickAnimal(listener, true);
1016 | }
1017 | }, 1000);
1018 | }
1019 | }
1020 | }
1021 |
1022 | private void startTickAnimal(final LoadingListener listener, final boolean hide) {
1023 | set_draw_ok_animation();
1024 | animator_draw_ok.start();
1025 | animator_draw_ok.addListener(new Animator.AnimatorListener() {
1026 | @Override
1027 | public void onAnimationStart(Animator animation) {
1028 |
1029 | }
1030 |
1031 | @Override
1032 | public void onAnimationEnd(Animator animation) {
1033 | if (listener != null) {
1034 | listener.loadingFinish(true);
1035 | }
1036 | isLoading = false;
1037 | isFinished = true;
1038 | setClickable(true);
1039 | if (hide) {
1040 | //是否隐藏
1041 | Animation animations = AnimationUtils.loadAnimation(getContext(), R.anim.alpha_hide);
1042 | setAnimation(animations);
1043 | animations.start();
1044 | setVisibility(View.INVISIBLE);
1045 | }
1046 | }
1047 |
1048 | @Override
1049 | public void onAnimationCancel(Animator animation) {
1050 |
1051 | }
1052 |
1053 | @Override
1054 | public void onAnimationRepeat(Animator animation) {
1055 |
1056 | }
1057 | });
1058 | }
1059 |
1060 |
1061 | /**
1062 | * ================================= smartTick end ===========================================
1063 | */
1064 |
1065 |
1066 | /**
1067 | * ================================= smartTickCenterHide start ===============================
1068 | */
1069 |
1070 | private void startTickCenterHide(final LoadingListener listener) {
1071 | if (isLoading) {
1072 | if (!animatorSet.isRunning()) {
1073 | startTickCenterHideAnimal(listener);
1074 | } else {
1075 | this.postDelayed(new Runnable() {
1076 | @Override
1077 | public void run() {
1078 | startTickCenterHideAnimal(listener);
1079 | }
1080 | }, 1000);
1081 | }
1082 | }
1083 | }
1084 |
1085 | private void startTickCenterHideAnimal(final LoadingListener listener) {
1086 | //如果是要移动到中间的模式的话
1087 | int[] location = new int[2];
1088 | SmartLoadingView.this.getLocationOnScreen(location);
1089 | FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(height, height);
1090 | layoutParams.leftMargin = location[0] + (width / 2 - height / 2);
1091 | layoutParams.topMargin = location[1];
1092 |
1093 | final OkView okView = new OkView(getContext());
1094 | okView.setLayoutParams(layoutParams);
1095 | okView.setCircleColor(backgroundColor);
1096 | okView.setOkColor(textColor);
1097 | okView.setRadius(height / 2);
1098 |
1099 | final ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
1100 | activityDecorView.addView(okView);
1101 | okView.start(duration);
1102 | //初始真正的那个View
1103 | setVisibility(View.INVISIBLE);
1104 | //reset();
1105 |
1106 | //当前屏幕中心位置
1107 | int window_center_x = UIUtil.getWidth(getContext()) / 2;
1108 | int window_center_y = UIUtil.getHeight(getContext()) / 2;
1109 |
1110 | //okView当前的中心点
1111 | int okView_center_x = location[0] + width / 2;
1112 | int okView_center_y = location[1] + height / 2;
1113 |
1114 | ObjectAnimator translationY = ObjectAnimator.ofFloat(okView, "translationY", 0f, window_center_y - okView_center_y).setDuration(duration);
1115 | ObjectAnimator translationX = ObjectAnimator.ofFloat(okView, "translationX", 0f, window_center_x - okView_center_x).setDuration(duration);
1116 |
1117 | ObjectAnimator toViewAnimatorX = ObjectAnimator.ofFloat(okView, "scaleX", 1f, 1.3f).setDuration(duration / 2);
1118 | toViewAnimatorX.setRepeatMode(ValueAnimator.REVERSE);
1119 | toViewAnimatorX.setRepeatCount(1);
1120 | toViewAnimatorX.setInterpolator(new AnticipateInterpolator());
1121 | ObjectAnimator toViewAnimatorY = ObjectAnimator.ofFloat(okView, "scaleY", 1f, 1.3f).setDuration(duration / 2);
1122 | toViewAnimatorY.setRepeatMode(ValueAnimator.REVERSE);
1123 | toViewAnimatorY.setRepeatCount(1);
1124 | toViewAnimatorY.setInterpolator(new AnticipateInterpolator());
1125 | AnimatorSet animatorScale = new AnimatorSet();
1126 | animatorScale.playTogether(toViewAnimatorX, toViewAnimatorY);
1127 |
1128 | ObjectAnimator toViewAnimatorAlpha = ObjectAnimator.ofFloat(okView, "alpha", 1f, 0f).setDuration(duration);
1129 | //这里就用代码实现把
1130 | AnimatorSet animatorSet = new AnimatorSet();
1131 | animatorSet.play(translationY).with(translationX).before(animatorScale).before(toViewAnimatorAlpha);
1132 | animatorSet.start();
1133 | animatorSet.addListener(new Animator.AnimatorListener() {
1134 | @Override
1135 | public void onAnimationStart(Animator animation) {
1136 |
1137 | }
1138 |
1139 | @Override
1140 | public void onAnimationEnd(Animator animation) {
1141 | if (listener != null) {
1142 | listener.loadingFinish(true);
1143 | }
1144 | isLoading = false;
1145 | setClickable(true);
1146 | activityDecorView.removeView(okView);
1147 | }
1148 |
1149 | @Override
1150 | public void onAnimationCancel(Animator animation) {
1151 |
1152 | }
1153 |
1154 | @Override
1155 | public void onAnimationRepeat(Animator animation) {
1156 |
1157 | }
1158 |
1159 | });
1160 | }
1161 |
1162 |
1163 | /**
1164 | * ================================= smartTickCenterHide end =================================
1165 | */
1166 |
1167 |
1168 | public interface LoadingListener {
1169 | void loadingFinish(boolean success);
1170 |
1171 | }
1172 |
1173 | /**
1174 | * ---------------------------------------------------------------------------------------------
1175 | */
1176 |
1177 | //立即重置状态
1178 | private void reset() {
1179 | isFinished = false;
1180 | //画笔颜色重置
1181 | textColor = textColorOriginal;
1182 | textPaint.setColor(textColor);
1183 | setClickable(true);
1184 | currentString = normalString;
1185 | textPaint.setColor(textColor);
1186 | circleAngle = corners_radius;
1187 | paint.setColor(backgroundColor);
1188 | current_left = 0;
1189 | isDrawLoading = false;
1190 | startDrawOk = false;
1191 | isLoading = false;
1192 | invalidate();
1193 |
1194 | animator_draw_ok.cancel();
1195 | animatorSet.cancel();
1196 | animatorNetfail.cancel();
1197 | if (circlBigView != null) {
1198 | circlBigView.setCircleR(0);
1199 | }
1200 | setVisibility(View.VISIBLE);
1201 | }
1202 |
1203 | private void toBigCircle(LoadingListener listener) {
1204 | circlBigView.setRadius(this.getMeasuredHeight() / 2);
1205 | circlBigView.setColorBg(backgroundColor);
1206 | int[] location = new int[2];
1207 | this.getLocationOnScreen(location);
1208 | circlBigView.setXY(location[0] + this.getMeasuredWidth() / 2, location[1]);
1209 | ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
1210 | ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
1211 | activityDecorView.removeView(circlBigView);
1212 | activityDecorView.addView(circlBigView, layoutParams);
1213 | circlBigView.startShowAni(listener, this);
1214 | }
1215 |
1216 |
1217 | private void toBigCircle(Activity activity, Class clazz) {
1218 | circlBigView.setRadius(this.getMeasuredHeight() / 2);
1219 | circlBigView.setColorBg(backgroundColor);
1220 | int[] location = new int[2];
1221 | this.getLocationOnScreen(location);
1222 | circlBigView.setXY(location[0] + this.getMeasuredWidth() / 2, location[1]);
1223 | ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
1224 | ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
1225 | activityDecorView.removeView(circlBigView);
1226 | activityDecorView.addView(circlBigView, layoutParams);
1227 | circlBigView.startShowAni(activity, clazz);
1228 | }
1229 |
1230 | }
1231 |
--------------------------------------------------------------------------------
/smartloadview/src/main/java/com/lihang/help/CirclBigView.java:
--------------------------------------------------------------------------------
1 | package com.lihang.help;
2 |
3 | import android.animation.Animator;
4 | import android.animation.ValueAnimator;
5 | import android.app.Activity;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.graphics.Canvas;
9 | import android.graphics.Paint;
10 | import android.support.annotation.Nullable;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 |
14 | import com.lihang.SmartLoadingView;
15 | import com.lihang.smartloadview.R;
16 |
17 |
18 | /**
19 | * 圆圈扩散自定义View
20 | * By leo
21 | * 2019.5.23
22 | */
23 |
24 | public class CirclBigView extends View {
25 |
26 | //圆圈扩散动画
27 | private ValueAnimator animator_big;
28 | private int myRadius;
29 | //父类控件半径(父类最短一边,长度的一半)
30 | private int fatherRadius;
31 | private Paint showPaint;
32 | private int y;//当前Y轴位置
33 | private int x;//当前X轴位置
34 |
35 | //最大能扩散到的半径
36 | private int maxRadius;
37 |
38 |
39 | public CirclBigView(Context context) {
40 | this(context, null);
41 | }
42 |
43 | public CirclBigView(Context context, @Nullable AttributeSet attrs) {
44 | this(context, attrs, 0);
45 | }
46 |
47 | public CirclBigView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
48 | super(context, attrs, defStyleAttr);
49 | showPaint = new Paint();
50 | showPaint.setAntiAlias(true);
51 | showPaint.setStyle(Paint.Style.FILL);
52 | showPaint.setColor(getResources().getColor(R.color.guide_anim));
53 | }
54 |
55 |
56 | public void setXY(int x, int y) {
57 | this.x = x;
58 | this.y = y;
59 | int y_true = y + fatherRadius;
60 | //知道了view圆心,计算出到手机4个角的距离,以最大距离为准
61 | int left_top = (int) Math.sqrt((x * x) + (y_true * y_true));
62 | int left_bottom = (int) Math.sqrt((x * x) + ((UIUtil.getHeight(getContext()) - y_true) * (UIUtil.getHeight(getContext()) - y_true)));
63 |
64 | int right_top = (int) Math.sqrt(((UIUtil.getWidth(getContext()) - x) * (UIUtil.getWidth(getContext()) - x)) + (y_true * y_true));
65 | int right_bottom = (int) Math.sqrt(((UIUtil.getWidth(getContext()) - x) * (UIUtil.getWidth(getContext()) - x)) + ((UIUtil.getHeight(getContext()) - y_true) * (UIUtil.getHeight(getContext()) - y_true)));
66 |
67 | int left_big = left_top >= left_bottom ? left_top : left_bottom;
68 | int right_big = right_top >= right_bottom ? right_top : right_bottom;
69 | maxRadius = left_big >= right_big ? left_big : right_big;
70 | //这里虚拟键有个bug,我们把半径大小稍加长
71 | maxRadius = maxRadius + UIUtil.getWidth(getContext()) / 6;
72 |
73 |
74 | animator_big = ValueAnimator.ofInt(myRadius, maxRadius);
75 | animator_big.setDuration(200);
76 | animator_big.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
77 | @Override
78 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
79 | myRadius = (int) valueAnimator.getAnimatedValue();
80 | postInvalidate();
81 | }
82 | });
83 |
84 | invalidate();
85 | }
86 |
87 | public void setRadius(int radius) {
88 | myRadius = radius;
89 | fatherRadius = radius;
90 | }
91 |
92 | public void setCircleR(int currentR) {
93 | myRadius = currentR;
94 | postInvalidate();
95 | }
96 |
97 |
98 | public void setColorBg(int colorBg) {
99 | showPaint.setColor(colorBg);
100 | }
101 |
102 |
103 | @Override
104 | protected void onDraw(Canvas canvas) {
105 | super.onDraw(canvas);
106 | canvas.drawCircle(x, y + fatherRadius, myRadius, showPaint);
107 | }
108 |
109 |
110 | public void startShowAni(final SmartLoadingView.LoadingListener listener, final SmartLoadingView smartLoadingView) {
111 |
112 | if (listener != null) {
113 | if (!animator_big.isRunning()) {
114 | animator_big.start();
115 | }
116 | if (listener != null) {
117 | animator_big.addListener(new Animator.AnimatorListener() {
118 | @Override
119 | public void onAnimationStart(Animator animation) {
120 |
121 | }
122 |
123 | @Override
124 | public void onAnimationEnd(Animator animation) {
125 | listener.loadingFinish(true);
126 | }
127 |
128 | @Override
129 | public void onAnimationCancel(Animator animation) {
130 |
131 | }
132 |
133 | @Override
134 | public void onAnimationRepeat(Animator animation) {
135 |
136 | }
137 | });
138 | }
139 |
140 | }
141 | }
142 |
143 |
144 | public void startShowAni(final Activity activity, final Class clazz) {
145 |
146 | if (!animator_big.isRunning()) {
147 | animator_big.start();
148 | animator_big.addListener(new Animator.AnimatorListener() {
149 | @Override
150 | public void onAnimationStart(Animator animation) {
151 |
152 | }
153 |
154 | @Override
155 | public void onAnimationEnd(Animator animation) {
156 | activity.startActivity(new Intent(activity, clazz));
157 | activity.finish();
158 | activity.overridePendingTransition(R.anim.scale_test_home, R.anim.scale_test2);
159 | }
160 |
161 | @Override
162 | public void onAnimationCancel(Animator animation) {
163 |
164 | }
165 |
166 | @Override
167 | public void onAnimationRepeat(Animator animation) {
168 |
169 | }
170 | });
171 | }
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/smartloadview/src/main/java/com/lihang/help/OkView.java:
--------------------------------------------------------------------------------
1 | package com.lihang.help;
2 |
3 | import android.animation.ValueAnimator;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.DashPathEffect;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.graphics.PathEffect;
10 | import android.graphics.PathMeasure;
11 | import android.support.annotation.Nullable;
12 | import android.util.AttributeSet;
13 | import android.view.View;
14 |
15 | /**
16 | * Created by leo
17 | * on 2019/11/21.
18 | */
19 | public class OkView extends View {
20 | //绘制一个小圆圈
21 | private Paint paint;
22 | //绘制打勾paint
23 | private Paint okPaint;
24 |
25 | //背景圆圈的半径
26 | private int myRadius;
27 | //绘制打勾的路径
28 | private Path path = new Path();
29 | //绘制路径的长度,也可以理解为完成度
30 | private PathMeasure pathMeasure;
31 |
32 | //绘制对勾(√)的动画
33 | private ValueAnimator animator_draw_ok;
34 |
35 | //对路径处理实现绘制动画效果
36 | private PathEffect effect;
37 |
38 | private boolean startDrawOk;
39 |
40 | public OkView(Context context) {
41 | this(context, null);
42 | }
43 |
44 | public OkView(Context context, @Nullable AttributeSet attrs) {
45 | this(context, attrs, 0);
46 | }
47 |
48 | public OkView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
49 | super(context, attrs, defStyleAttr);
50 | paint = new Paint();
51 | paint.setAntiAlias(true);
52 | paint.setStyle(Paint.Style.FILL);
53 |
54 |
55 | okPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
56 | okPaint.setStrokeWidth(5);
57 | okPaint.setStyle(Paint.Style.STROKE);
58 | okPaint.setStrokeCap(Paint.Cap.ROUND);
59 |
60 |
61 | }
62 |
63 |
64 | @Override
65 | protected void onDraw(Canvas canvas) {
66 | super.onDraw(canvas);
67 | canvas.drawCircle(getWidth() / 2, getHeight() / 2, myRadius, paint);
68 |
69 | if (startDrawOk) {
70 | canvas.drawPath(path, okPaint);
71 | }
72 | }
73 |
74 | public void setOkColor(int color) {
75 | okPaint.setColor(color);
76 | }
77 |
78 | public void setCircleColor(int color) {
79 | paint.setColor(color);
80 | }
81 |
82 |
83 | public void setRadius(int radius) {
84 | myRadius = radius;
85 | //对勾的路径
86 | int cHeight = radius * 2;
87 | path.moveTo(+cHeight / 8 * 3, cHeight / 2);
88 | path.lineTo(+cHeight / 2, cHeight / 5 * 3);
89 | path.lineTo(+cHeight / 3 * 2, cHeight / 5 * 2);
90 | pathMeasure = new PathMeasure(path, true);
91 |
92 | invalidate();
93 | }
94 |
95 |
96 | public void start(int duration) {
97 | animator_draw_ok = ValueAnimator.ofFloat(1, 0);
98 | animator_draw_ok.setDuration(duration);
99 | animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
100 | @Override
101 | public void onAnimationUpdate(ValueAnimator animation) {
102 | startDrawOk = true;
103 | float value = (Float) animation.getAnimatedValue();
104 | effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
105 | okPaint.setPathEffect(effect);
106 | invalidate();
107 | }
108 | });
109 | animator_draw_ok.start();
110 | }
111 |
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/smartloadview/src/main/java/com/lihang/help/UIUtil.java:
--------------------------------------------------------------------------------
1 | package com.lihang.help;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | import android.util.TypedValue;
8 | import android.view.WindowManager;
9 |
10 | import java.io.UnsupportedEncodingException;
11 | import java.lang.reflect.Field;
12 | import java.security.MessageDigest;
13 | import java.security.NoSuchAlgorithmException;
14 | import java.util.Locale;
15 | import java.util.UUID;
16 | import java.util.regex.Matcher;
17 | import java.util.regex.Pattern;
18 |
19 |
20 | /**
21 | * Created by lihang Leo on 2016/12/10.
22 | */
23 | public class UIUtil {
24 |
25 | private static final String TAG = UIUtil.class.getName();
26 |
27 | /**
28 | * Dip to Px
29 | *
30 | * @param context
31 | * @param dipValue
32 | * @return
33 | */
34 | public static int dip2px(Context context, float dipValue) {
35 | float scale = context.getResources().getDisplayMetrics().density;
36 | return (int) (dipValue * scale + 0.5f);
37 | }
38 |
39 | /**
40 | * Px To Dip
41 | *
42 | * @param context
43 | * @param pxValue
44 | * @return
45 | */
46 | public static int px2dip(Context context, float pxValue) {
47 | float scale = context.getResources().getDisplayMetrics().density;
48 | return (int) (pxValue / scale + 0.5f);
49 | }
50 |
51 | //sp转px
52 | public static int Sp2Px(Context context, int sp) {
53 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
54 | }
55 |
56 |
57 | public static int getWidth(Context context) {
58 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
59 | int width = wm.getDefaultDisplay().getWidth();
60 | return width;
61 | }
62 |
63 | public static int getHeight(Context context) {
64 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
65 | int height = wm.getDefaultDisplay().getHeight();
66 | return height;
67 | }
68 |
69 | //获取手机状态栏高度
70 | public static int getStatusBarHeight(Context context) {
71 | Class> c = null;
72 | Object obj = null;
73 | Field field = null;
74 | int x = 0, statusBarHeight = 0;
75 | try {
76 | c = Class.forName("com.android.internal.R$dimen");
77 | obj = c.newInstance();
78 | field = c.getField("status_bar_height");
79 | x = Integer.parseInt(field.get(obj).toString());
80 | statusBarHeight = context.getResources().getDimensionPixelSize(x);
81 | } catch (Exception e1) {
82 | e1.printStackTrace();
83 | }
84 | return statusBarHeight;
85 | }
86 |
87 |
88 |
89 | //获得独一无二的Psuedo ID
90 | public static String getDeviceId() {
91 | String serial = null;
92 | String m_szDevIDShort = "35" +
93 | Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
94 |
95 | Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
96 |
97 | Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
98 |
99 | Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
100 |
101 | Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
102 |
103 | Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
104 |
105 | Build.USER.length() % 10; //13 位
106 |
107 | try {
108 | serial = Build.class.getField("SERIAL").get(null).toString();
109 | //API>=9 使用serial号
110 | return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
111 | } catch (Exception exception) {
112 | //serial需要一个初始化
113 | serial = "serial"; // 随便一个初始化
114 | }
115 | //使用硬件信息拼凑出来的15位号码
116 | return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
117 | }
118 |
119 |
120 | /**
121 | * 获取当前手机系统语言。
122 | *
123 | * @return 返回当前系统语言。例如:当前设置的是“中文-中国”,则返回“zh-CN”
124 | */
125 | public static String getSystemLanguage() {
126 | return Locale.getDefault().getLanguage();
127 | }
128 |
129 | /**
130 | * 获取当前系统上的语言列表(Locale列表)
131 | *
132 | * @return 语言列表
133 | */
134 | public static Locale[] getSystemLanguageList() {
135 | return Locale.getAvailableLocales();
136 | }
137 |
138 | /**
139 | * 获取当前手机系统版本号
140 | *
141 | * @return 系统版本号
142 | */
143 | public static String getSystemVersion() {
144 | return Build.VERSION.RELEASE;
145 | }
146 |
147 | /**
148 | * 获取手机型号
149 | *
150 | * @return 手机型号
151 | */
152 | public static String getSystemModel() {
153 | return Build.MODEL;
154 | }
155 |
156 | /**
157 | * 获取手机厂商
158 | *
159 | * @return 手机厂商
160 | */
161 | public static String getDeviceBrand() {
162 | return Build.BRAND;
163 | }
164 |
165 |
166 | /**
167 | * get App versionCode
168 | *
169 | * @param context
170 | * @return
171 | */
172 | public static int getVersionCode(Context context) {
173 | PackageManager packageManager = context.getPackageManager();
174 | PackageInfo packageInfo;
175 | int versionCode = 1;
176 | try {
177 | packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
178 | versionCode = packageInfo.versionCode ;
179 | } catch (PackageManager.NameNotFoundException e) {
180 | e.printStackTrace();
181 | }
182 | return versionCode;
183 | }
184 |
185 | /**
186 | * get App versionName
187 | *
188 | * @param context
189 | * @return
190 | */
191 | public static String getVersionName(Context context) {
192 | PackageManager packageManager = context.getPackageManager();
193 | PackageInfo packageInfo;
194 | String versionName = "";
195 | try {
196 | packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
197 | versionName = packageInfo.versionName;
198 | } catch (PackageManager.NameNotFoundException e) {
199 | e.printStackTrace();
200 | }
201 | return versionName;
202 | }
203 |
204 |
205 | public static String md5(String content) {//MD5加密
206 | byte[] hash;
207 | try {
208 | hash = MessageDigest.getInstance("MD5").digest(content.getBytes("UTF-8"));
209 | } catch (NoSuchAlgorithmException e) {
210 | throw new RuntimeException("NoSuchAlgorithmException",e);
211 | } catch (UnsupportedEncodingException e) {
212 | throw new RuntimeException("UnsupportedEncodingException", e);
213 | }
214 |
215 | StringBuilder hex = new StringBuilder(hash.length * 2);
216 | for (byte b : hash) {
217 | if ((b & 0xFF) < 0x10){
218 | hex.append("0");
219 | }
220 | hex.append(Integer.toHexString(b & 0xFF));
221 | }
222 | return hex.toString();
223 | }
224 |
225 |
226 |
227 | /**
228 | * 判断手机格式是否正确
229 | *
230 | * @param mobiles
231 | * @return true为格式正确
232 | */
233 | public static boolean isMobileNO(String mobiles) {
234 | Pattern p = Pattern
235 | .compile("^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\\d{8}$");
236 | Matcher m = p.matcher(mobiles);
237 | return m.matches();
238 | }
239 |
240 |
241 |
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/smartloadview/src/main/res/anim/alpha_hide.xml:
--------------------------------------------------------------------------------
1 |
2 |