├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── hitomi
│ │ │ │ └── activityswitcher
│ │ │ │ ├── MyApplication.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── hitomi
│ │ │ └── activityswitcher
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── hitomi
│ │ └── activityswitcher
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── aslibrary
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── hitomi
│ │ └── aslibrary
│ │ ├── ActivitySwitcher.java
│ │ ├── ActivityContainer.java
│ │ ├── ActivityManager.java
│ │ ├── FastBlur.java
│ │ ├── RoundRectDrawableWithShadow.java
│ │ ├── ActivitySwitcherHelper.java
│ │ └── ActivityControllerLayout.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── sample
└── app.apk
├── preview
└── activity_swither.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/aslibrary/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':aslibrary'
2 |
--------------------------------------------------------------------------------
/sample/app.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/sample/app.apk
--------------------------------------------------------------------------------
/preview/activity_swither.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/preview/activity_swither.gif
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ActivitySwitcher
3 |
4 |
--------------------------------------------------------------------------------
/aslibrary/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | asLibrary
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hitomis/ActivitySwitcher/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
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-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/aslibrary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/com/hitomi/activityswitcher/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.activityswitcher;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/hitomi/activityswitcher/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.activityswitcher;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hitomi/activityswitcher/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.activityswitcher;
2 |
3 | import android.app.Application;
4 |
5 | import com.hitomi.aslibrary.ActivitySwitcher;
6 |
7 | /**
8 | * Created by hitomi on 2016/10/11.
9 | */
10 | public class MyApplication extends Application {
11 |
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 | ActivitySwitcher.getInstance().init(this);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/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 E:\androidstuido_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 |
--------------------------------------------------------------------------------
/aslibrary/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 E:\androidstuido_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 |
--------------------------------------------------------------------------------
/aslibrary/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 14
9 | targetSdkVersion 24
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | testCompile 'junit:junit:4.12'
24 | compile 'com.android.support:appcompat-v7:24.1.1'
25 | compile 'com.android.support:cardview-v7:24.1.1'
26 | }
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "24.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.hitomi.activityswitcher"
9 | minSdkVersion 14
10 | targetSdkVersion 24
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:24.1.1'
26 | compile project(':aslibrary')
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/ActivitySwitcher.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.view.MotionEvent;
6 | import android.view.ViewConfiguration;
7 |
8 | /**
9 | *
10 | * Activity 切换器。支持:
11 | *
12 | * - Activity 之间任意跳转
13 | * - 关闭任意N个 Activity
14 | * - 结束应用程序
15 | *
16 | *
17 | *
18 | * email : 196425254@qq.com
19 | *
20 | * github : https://github.com/Hitomis
21 | *
22 | * Created by hitomi on 2016/10/11.
23 | */
24 | public class ActivitySwitcher {
25 |
26 | private ActivitySwitcherHelper switcherHelper;
27 |
28 | private boolean switching;
29 |
30 | private float preX, touchSlop;
31 |
32 | private static class SingletonHolder {
33 | public final static ActivitySwitcher instance = new ActivitySwitcher();
34 | }
35 |
36 | public static ActivitySwitcher getInstance() {
37 | return SingletonHolder.instance;
38 | }
39 |
40 | public void init(Application application) {
41 | switcherHelper = new ActivitySwitcherHelper(this, application);
42 | ViewConfiguration conf = ViewConfiguration.get(application);
43 | touchSlop = conf.getScaledEdgeSlop();
44 | }
45 |
46 | public void processTouchEvent(MotionEvent event) {
47 | switch (event.getAction()) {
48 | case MotionEvent.ACTION_DOWN:
49 | preX = event.getX();
50 | break;
51 | case MotionEvent.ACTION_MOVE:
52 | float slideX = event.getX() - preX;
53 | if (preX <= touchSlop && slideX > 50 && !switching) {
54 | showSwitch();
55 | }
56 | break;
57 | }
58 | }
59 |
60 | public void exit() {
61 | switcherHelper.exit();
62 | }
63 |
64 | /**
65 | * 关闭 ActivitySwitcher 切换视图
66 | * @param activity
67 | */
68 | public void finishSwitch(Activity activity) {
69 | if (switcherHelper.isActivityControllerDisplayed()) {
70 | switcherHelper.endSwitch();
71 | } else if (switcherHelper.isActivityControllerClosed()) {
72 | activity.finish();
73 | }
74 | }
75 |
76 | /**
77 | * 显示 ActivitySwitcher 切换视图
78 | */
79 | public void showSwitch() {
80 | switching = true;
81 | switcherHelper.startSwitch();
82 | }
83 |
84 | void setSwitching(boolean switching) {
85 | this.switching = switching;
86 | }
87 |
88 | public void setOnActivitySwitchListener(OnActivitySwitchListener listener) {
89 | switcherHelper.setOnActivitySwitchListener(listener);
90 | }
91 |
92 | public interface OnActivitySwitchListener {
93 |
94 | void onSwitchStarted();
95 |
96 | void onSwitchFinished(Activity activity);
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hitomi/activityswitcher/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.activityswitcher;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.graphics.Color;
6 | import android.os.Bundle;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.view.MotionEvent;
9 | import android.view.View;
10 | import android.widget.Button;
11 | import android.widget.RelativeLayout;
12 | import android.widget.TextView;
13 |
14 | import com.hitomi.aslibrary.ActivitySwitcher;
15 |
16 | public class MainActivity extends AppCompatActivity {
17 |
18 | static int index;
19 | static int totalCount = 8;
20 | static int[] bgColors = new int[] {
21 | Color.parseColor("#92c8d0"),
22 | Color.parseColor("#c4dcce"),
23 | Color.parseColor("#cd7b91"),
24 | Color.parseColor("#e5c5dc"),
25 | Color.parseColor("#742a8d"),
26 | Color.parseColor("#2eb2d8"),
27 | Color.parseColor("#b9d84e"),
28 | Color.parseColor("#35fe62")
29 | };
30 |
31 | private RelativeLayout relativeLayout;
32 | private Button btnNext;
33 | private TextView tvPage;
34 |
35 | private ActivitySwitcher activitySwitcher;
36 | private int tag;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_main);
42 | activitySwitcher = ActivitySwitcher.getInstance();
43 |
44 | relativeLayout = (RelativeLayout) findViewById(R.id.relayout);
45 | btnNext = (Button) findViewById(R.id.btn_next);
46 | tvPage = (TextView) findViewById(R.id.tv_page);
47 |
48 | relativeLayout.setBackgroundColor(bgColors[index]);
49 | tag = index;
50 | tvPage.setText("当前第" + (tag + 1) + "页" );
51 |
52 | if (index == totalCount - 1) {
53 | btnNext.setText("退出程序");
54 | }
55 | btnNext.setOnClickListener(new View.OnClickListener() {
56 | @Override
57 | public void onClick(View view) {
58 | if (++index > totalCount - 1) {
59 | activitySwitcher.exit();
60 | } else {
61 | startActivity(new Intent(MainActivity.this, MainActivity.class));
62 | }
63 | }
64 | });
65 |
66 | activitySwitcher.setOnActivitySwitchListener(new ActivitySwitcher.OnActivitySwitchListener() {
67 | @Override
68 | public void onSwitchStarted() {}
69 |
70 | @Override
71 | public void onSwitchFinished(Activity activity) {
72 | if (activity instanceof MainActivity) {
73 | MainActivity mainActivity = (MainActivity) activity;
74 | MainActivity.index = mainActivity.getTag();
75 | }
76 | }
77 | });
78 |
79 | }
80 |
81 | @Override
82 | public boolean dispatchTouchEvent(MotionEvent ev) {
83 | activitySwitcher.processTouchEvent(ev);
84 | return super.dispatchTouchEvent(ev);
85 | }
86 |
87 | @Override
88 | public void onBackPressed() {
89 | MainActivity.index = tag - 1 <= 0 ? 0 : tag - 1;
90 | activitySwitcher.finishSwitch(this);
91 | }
92 |
93 | public int getTag() {
94 | return tag;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ActivitySwitcher
2 |
3 | [](http://android-arsenal.com/details/1/4635)
4 |
5 | ActivitySwitcher 是一个基于 Activity 视图操作管理库,可以实现 Activity 之间任意跳转、关闭任意一个 Activity
6 | 以及结束应用程序等功能。
7 |
8 | 本库中的展现 Activity 视图时,附带阴影的卡片效果抽取自 [CrazyShadow](https://github.com/Hitomis/CrazyShadow) 有兴趣的朋友可以移步看看。
9 |
10 | 欢迎大家给 ActivitySwitcher 提 Issues,有问题我会尽快修复
11 |
12 | # Preview
13 |
14 | 录制图像有丢帧的情况,所以预览图效果不够流畅,背景图显示的也有问题。
15 |
16 |
17 |
18 | # Sample
19 |
20 | [demo.apk](https://github.com/Hitomis/ActivitySwitcher/tree/master/sample/app.apk)
21 |
22 | # Import
23 |
24 | 导入 aslibrary Module 作为依赖库, 或者直接复制 com.hitomi.aslibrary 中所有类文件到自己的项目中即可
25 |
26 | # Usage
27 |
28 | #### 1、Application 中 初始化
29 |
30 | ActivitySwitcher.getInstance().init(this);
31 |
32 | #### 2、在 Activity 中重写 dispatchTouchEvent 处理事件分发。最好直接在 BaseActivity 中处理。万事大吉
33 |
34 | @Override
35 | public boolean dispatchTouchEvent(MotionEvent ev) {
36 | activitySwitcher.processTouchEvent(ev);
37 | return super.dispatchTouchEvent(ev);
38 | }
39 |
40 | 如果不想通过手势打开 ActivitySwitcher,可以通过以下方式手动打开
41 |
42 | activitySwitcher.showSwitcher();
43 |
44 | #### 3、Android 手机默认按下返回键就回 finish 掉当前 Activity,这与本库冲突,所以需要重写 onBackPressed 方法,同样最好在 BaseActivity 中去重写
45 |
46 | @Override
47 | public void onBackPressed() {
48 | activitySwitcher.finishSwitch(this);
49 | }
50 |
51 | #### 4、如果希望监听 ActivitySwitcher 当前的行为状态,可以添加以下代码
52 |
53 | activitySwitcher.setOnActivitySwitchListener(new ActivitySwitcher.OnActivitySwitchListener() {
54 | @Override
55 | public void onSwitchStarted() {}
56 |
57 | @Override
58 | public void onSwitchFinished(Activity activity) {}
59 | });
60 |
61 | onSwitchStarted :在 ActivitySwitcher 打开后被回调
62 | onSwitchFinished 在 ActivitySwitcher 关闭后被回调
63 |
64 | 全部示例代码详情请前往 [MainActivity](https://github.com/Hitomis/ActivitySwitcher/blob/master/app/src/main/java/com/hitomi/activityswitcher/MainActivity.java) 查看
65 |
66 | #Method
67 |
68 | | 方法 | 说明 |
69 | | :--: | :--: |
70 | | getInstance | 获取 ActivitySwitcher 实例 (ActivitySwitcher 为单例) |
71 | | init | 全局初始化 ActivitySwitcher, 一般在 Application 的 onCreate 方法中调用 |
72 | | processTouchEvent | 用于需要手势打开 ActivitySwitcher 的场景, 一般在 BaseActivity 的 dispatchTouchEvent 方法中调用 |
73 | | showSwitch | 打开 ActivitySwitcher,切换到 Activity 卡片式管理界面 |
74 | | finishSwitch | 关闭 ActivitySwitcher, 退出 Activity 卡片式管理界面,回到选中或者默认的 Activity 界面 |
75 | | exit | 退出当前应用程序 |
76 | | setOnActivitySwitchListener | 设置监听器,监听 ActivitySwitcher 的打开和关闭 |
77 |
78 |
79 | #Licence
80 |
81 | Copyright 2016 Hitomis, Inc.
82 |
83 | Licensed under the Apache License, Version 2.0 (the "License");
84 | you may not use this file except in compliance with the License.
85 | You may obtain a copy of the License at
86 |
87 | http://www.apache.org/licenses/LICENSE-2.0
88 |
89 | Unless required by applicable law or agreed to in writing, software
90 | distributed under the License is distributed on an "AS IS" BASIS,
91 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
92 | See the License for the specific language governing permissions and
93 | limitations under the License.
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/ActivityContainer.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.content.Context;
4 | import android.graphics.RectF;
5 | import android.util.AttributeSet;
6 | import android.view.MotionEvent;
7 | import android.view.ViewGroup;
8 | import android.widget.FrameLayout;
9 |
10 | /**
11 | * 用于 Activity 实际大小、位置以及事件分发处理
12 | *
13 | * email : 196425254@qq.com
14 | *
15 | * github : https://github.com/Hitomis
16 | *
17 | * Created by hitomi on 2016/11/2.
18 | */
19 | public class ActivityContainer extends FrameLayout {
20 |
21 | private RectF bounds;
22 |
23 | private float offsetX, offsetY;
24 | private float tranX, tranY;
25 |
26 | private boolean intercept;
27 |
28 | public ActivityContainer(Context context) {
29 | this(context, null);
30 | }
31 |
32 | public ActivityContainer(Context context, AttributeSet attrs) {
33 | this(context, attrs, 0);
34 | }
35 |
36 | public ActivityContainer(Context context, AttributeSet attrs, int defStyleAttr) {
37 | super(context, attrs, defStyleAttr);
38 | bounds = new RectF();
39 | }
40 |
41 | @Override
42 | public boolean onInterceptTouchEvent(MotionEvent ev) {
43 | return intercept;
44 | }
45 |
46 | @Override
47 | public void setLayoutParams(ViewGroup.LayoutParams params) {
48 | super.setLayoutParams(params);
49 | bounds.left = 0;
50 | bounds.top = 0;
51 | bounds.right = params.width;
52 | bounds.bottom = params.height;
53 | }
54 |
55 | @Override
56 | public void setScaleX(float scaleX) {
57 | super.setScaleX(scaleX);
58 | int width = getLayoutParams().width;
59 | float halfScaleX = width * (1 - scaleX) * .5f;
60 | bounds.left += halfScaleX - offsetX;
61 | bounds.right -= halfScaleX - offsetX;
62 | offsetX = halfScaleX;
63 | }
64 |
65 | @Override
66 | public void setScaleY(float scaleY) {
67 | super.setScaleY(scaleY);
68 | int height = getLayoutParams().height;
69 | float halfScaleY = height * (1 - scaleY) * .5f;
70 | bounds.top += halfScaleY - offsetY;
71 | bounds.bottom -= halfScaleY - offsetY;
72 | offsetY = halfScaleY;
73 | }
74 |
75 | @Override
76 | public void setTranslationX(float translationX) {
77 | super.setTranslationX(translationX);
78 | bounds.left += translationX - tranX;
79 | bounds.right += translationX - tranX;
80 | tranX = translationX;
81 | }
82 |
83 | @Override
84 | public void setTranslationY(float translationY) {
85 | super.setTranslationY(translationY);
86 | bounds.top += translationY - tranY;
87 | bounds.bottom += translationY - tranY;
88 | tranY = translationY;
89 | }
90 |
91 | @Override
92 | public void setX(float x) {
93 | super.setX(x);
94 | bounds.left += x - tranX;
95 | bounds.right += x - tranX;
96 | tranX = x;
97 | }
98 |
99 | @Override
100 | public void setY(float y) {
101 | super.setY(y);
102 | bounds.top += y - tranY;
103 | bounds.bottom += y - tranY;
104 | tranY = y;
105 | }
106 |
107 | public void setIntercept(boolean intercept) {
108 | this.intercept = intercept;
109 | }
110 |
111 | public RectF getBounds() {
112 | return bounds;
113 | }
114 |
115 | public float getIntrinsicHeight() {
116 | return bounds.bottom - bounds.top;
117 | }
118 |
119 | public float getIntrinsicWidth() {
120 | return bounds.right - bounds.left;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/ActivityManager.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.os.Bundle;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Stack;
10 |
11 | /**
12 | * Activity 管理工具类
13 | *
14 | * email : 196425254@qq.com
15 | *
16 | * github : https://github.com/Hitomis
17 | *
18 | * Created by hitomi on 2016/10/11.
19 | */
20 | class ActivityManager implements Application.ActivityLifecycleCallbacks {
21 |
22 | private static Stack activityStack;
23 |
24 | private ActivityManager() {}
25 |
26 | private static class SingletonHolder {
27 | public final static ActivityManager instance = new ActivityManager();
28 | }
29 |
30 | public static ActivityManager getInstance() {
31 | if (activityStack == null) {
32 | activityStack = new Stack<>();
33 | }
34 | return SingletonHolder.instance;
35 | }
36 |
37 | @Override
38 | public void onActivityCreated(Activity activity, Bundle bundle) {
39 | addActivity(activity);
40 | }
41 |
42 | @Override
43 | public void onActivityStarted(Activity activity) {
44 | }
45 |
46 | @Override
47 | public void onActivityResumed(Activity activity) {
48 | }
49 |
50 | @Override
51 | public void onActivityPaused(Activity activity) {
52 |
53 | }
54 |
55 | @Override
56 | public void onActivityStopped(Activity activity) {
57 |
58 | }
59 |
60 | @Override
61 | public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
62 |
63 | }
64 |
65 | @Override
66 | public void onActivityDestroyed(Activity activity) {
67 | removeActivity(activity);
68 | }
69 |
70 | /**
71 | * 添加Activity到堆栈
72 | */
73 | public void addActivity(Activity activity) {
74 | if (activityStack == null) {
75 | activityStack = new Stack<>();
76 | }
77 | activityStack.add(activity);
78 | }
79 |
80 | /**
81 | * 获取当前Activity(堆栈中最后一个压入的)
82 | */
83 | public Activity getCurrentActivity() {
84 | Activity activity = activityStack.get(activityStack.size() - 1);
85 | return activity;
86 | }
87 |
88 | public Activity getPreActivity() {
89 | int size = activityStack.size();
90 | if(size < 2)return null;
91 | return activityStack.get(size - 2);
92 | }
93 |
94 | /**
95 | * 获取当前 Activity 之前所有 Activity
96 | * @return
97 | */
98 | public List getPreActivies() {
99 | List preActivities = new ArrayList<>();
100 | for (int i = 0, size = activityStack.size(); i < size; i++) {
101 | if (activityStack.get(i) == getCurrentActivity()) {
102 | break;
103 | }
104 | preActivities.add(activityStack.get(i));
105 | }
106 | // activityStack.subList(from, to); 这个方法有毒,巨坑爹
107 | return preActivities;
108 | }
109 |
110 | /**
111 | * 结束当前Activity(堆栈中最后一个压入的)
112 | */
113 | public void finishActivity() {
114 | Activity activity = activityStack.get(activityStack.size() - 1);
115 | finishActivity(activity);
116 | }
117 |
118 | /**
119 | * 结束指定的Activity
120 | */
121 | public void finishActivity(Activity activity) {
122 | if (activity != null) {
123 | activityStack.remove(activity);
124 | activity.finish();
125 | }
126 | }
127 |
128 | /**
129 | * 结束所有Activity
130 | */
131 | public void finishAllActivity() {
132 | for (int i = 0, size = activityStack.size(); i < size; i++) {
133 | if (null != activityStack.get(i)) {
134 | Activity activity = activityStack.get(i);
135 | if (!activity.isFinishing()) {
136 | activity.finish();
137 | }
138 | }
139 | }
140 | activityStack.clear();
141 | }
142 |
143 | public void removeActivity(Activity activity) {
144 | if (activity != null) {
145 | activityStack.remove(activity);
146 | }
147 | }
148 |
149 | public void removeAllWithoutItself(Activity activity) {
150 | activityStack.clear();
151 | addActivity(activity);
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/FastBlur.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | /**
6 | * 高斯模糊工具类
7 | */
8 | public class FastBlur {
9 |
10 | public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
11 | Bitmap bitmap;
12 | if (canReuseInBitmap) {
13 | bitmap = sentBitmap;
14 | } else {
15 | bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
16 | }
17 |
18 | if (radius < 1) {
19 | return (null);
20 | }
21 |
22 | int w = bitmap.getWidth();
23 | int h = bitmap.getHeight();
24 |
25 | int[] pix = new int[w * h];
26 | bitmap.getPixels(pix, 0, w, 0, 0, w, h);
27 |
28 | int wm = w - 1;
29 | int hm = h - 1;
30 | int wh = w * h;
31 | int div = radius + radius + 1;
32 |
33 | int r[] = new int[wh];
34 | int g[] = new int[wh];
35 | int b[] = new int[wh];
36 | int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
37 | int vmin[] = new int[Math.max(w, h)];
38 |
39 | int divsum = (div + 1) >> 1;
40 | divsum *= divsum;
41 | int dv[] = new int[256 * divsum];
42 | for (i = 0; i < 256 * divsum; i++) {
43 | dv[i] = (i / divsum);
44 | }
45 |
46 | yw = yi = 0;
47 |
48 | int[][] stack = new int[div][3];
49 | int stackpointer;
50 | int stackstart;
51 | int[] sir;
52 | int rbs;
53 | int r1 = radius + 1;
54 | int routsum, goutsum, boutsum;
55 | int rinsum, ginsum, binsum;
56 |
57 | for (y = 0; y < h; y++) {
58 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
59 | for (i = -radius; i <= radius; i++) {
60 | p = pix[yi + Math.min(wm, Math.max(i, 0))];
61 | sir = stack[i + radius];
62 | sir[0] = (p & 0xff0000) >> 16;
63 | sir[1] = (p & 0x00ff00) >> 8;
64 | sir[2] = (p & 0x0000ff);
65 | rbs = r1 - Math.abs(i);
66 | rsum += sir[0] * rbs;
67 | gsum += sir[1] * rbs;
68 | bsum += sir[2] * rbs;
69 | if (i > 0) {
70 | rinsum += sir[0];
71 | ginsum += sir[1];
72 | binsum += sir[2];
73 | } else {
74 | routsum += sir[0];
75 | goutsum += sir[1];
76 | boutsum += sir[2];
77 | }
78 | }
79 | stackpointer = radius;
80 |
81 | for (x = 0; x < w; x++) {
82 |
83 | r[yi] = dv[rsum];
84 | g[yi] = dv[gsum];
85 | b[yi] = dv[bsum];
86 |
87 | rsum -= routsum;
88 | gsum -= goutsum;
89 | bsum -= boutsum;
90 |
91 | stackstart = stackpointer - radius + div;
92 | sir = stack[stackstart % div];
93 |
94 | routsum -= sir[0];
95 | goutsum -= sir[1];
96 | boutsum -= sir[2];
97 |
98 | if (y == 0) {
99 | vmin[x] = Math.min(x + radius + 1, wm);
100 | }
101 | p = pix[yw + vmin[x]];
102 |
103 | sir[0] = (p & 0xff0000) >> 16;
104 | sir[1] = (p & 0x00ff00) >> 8;
105 | sir[2] = (p & 0x0000ff);
106 |
107 | rinsum += sir[0];
108 | ginsum += sir[1];
109 | binsum += sir[2];
110 |
111 | rsum += rinsum;
112 | gsum += ginsum;
113 | bsum += binsum;
114 |
115 | stackpointer = (stackpointer + 1) % div;
116 | sir = stack[(stackpointer) % div];
117 |
118 | routsum += sir[0];
119 | goutsum += sir[1];
120 | boutsum += sir[2];
121 |
122 | rinsum -= sir[0];
123 | ginsum -= sir[1];
124 | binsum -= sir[2];
125 |
126 | yi++;
127 | }
128 | yw += w;
129 | }
130 | for (x = 0; x < w; x++) {
131 | rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
132 | yp = -radius * w;
133 | for (i = -radius; i <= radius; i++) {
134 | yi = Math.max(0, yp) + x;
135 |
136 | sir = stack[i + radius];
137 |
138 | sir[0] = r[yi];
139 | sir[1] = g[yi];
140 | sir[2] = b[yi];
141 |
142 | rbs = r1 - Math.abs(i);
143 |
144 | rsum += r[yi] * rbs;
145 | gsum += g[yi] * rbs;
146 | bsum += b[yi] * rbs;
147 |
148 | if (i > 0) {
149 | rinsum += sir[0];
150 | ginsum += sir[1];
151 | binsum += sir[2];
152 | } else {
153 | routsum += sir[0];
154 | goutsum += sir[1];
155 | boutsum += sir[2];
156 | }
157 |
158 | if (i < hm) {
159 | yp += w;
160 | }
161 | }
162 | yi = x;
163 | stackpointer = radius;
164 | for (y = 0; y < h; y++) {
165 | // Preserve alpha channel: ( 0xff000000 & pix[yi] )
166 | pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
167 |
168 | rsum -= routsum;
169 | gsum -= goutsum;
170 | bsum -= boutsum;
171 |
172 | stackstart = stackpointer - radius + div;
173 | sir = stack[stackstart % div];
174 |
175 | routsum -= sir[0];
176 | goutsum -= sir[1];
177 | boutsum -= sir[2];
178 |
179 | if (x == 0) {
180 | vmin[y] = Math.min(y + r1, hm) * w;
181 | }
182 | p = x + vmin[y];
183 |
184 | sir[0] = r[p];
185 | sir[1] = g[p];
186 | sir[2] = b[p];
187 |
188 | rinsum += sir[0];
189 | ginsum += sir[1];
190 | binsum += sir[2];
191 |
192 | rsum += rinsum;
193 | gsum += ginsum;
194 | bsum += binsum;
195 |
196 | stackpointer = (stackpointer + 1) % div;
197 | sir = stack[stackpointer];
198 |
199 | routsum += sir[0];
200 | goutsum += sir[1];
201 | boutsum += sir[2];
202 |
203 | rinsum -= sir[0];
204 | ginsum -= sir[1];
205 | binsum -= sir[2];
206 |
207 | yi += w;
208 | }
209 | }
210 |
211 | bitmap.setPixels(pix, 0, w, 0, 0, w, h);
212 |
213 | return (bitmap);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/RoundRectDrawableWithShadow.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.ColorFilter;
5 | import android.graphics.LinearGradient;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.graphics.PixelFormat;
9 | import android.graphics.RadialGradient;
10 | import android.graphics.Rect;
11 | import android.graphics.RectF;
12 | import android.graphics.Shader;
13 | import android.graphics.drawable.Drawable;
14 |
15 | /**
16 | * 带阴影效果圆角矩形 Drawable 抽取自本人 CrazyShadow 项目中
17 | *
18 | * email : 196425254@qq.com
19 | *
20 | * github : https://github.com/Hitomis
21 | *
22 | * Created by hitomi on 2016/10/17.
23 | */
24 | public class RoundRectDrawableWithShadow extends Drawable {
25 | // used to calculate content padding
26 | final static double COS_45 = Math.cos(Math.toRadians(45));
27 |
28 | final static float SHADOW_MULTIPLIER = 1.5f;
29 |
30 | final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
31 | final RectF mCardBounds;
32 | private final int mShadowStartColor;
33 | private final int mShadowCentertColor;
34 | private final int mShadowEndColor;
35 | Paint mPaint;
36 | Paint mCornerShadowPaint;
37 | Paint mEdgeShadowPaint;
38 | float mCornerRadius;
39 | Path mCornerShadowPath;
40 | // updated value with inset
41 | float mMaxShadowSize;
42 | // actual value set by developer
43 | float mRawMaxShadowSize;
44 | // multiplied value to account for shadow offset
45 | float mShadowSize;
46 | // actual value set by developer
47 | float mRawShadowSize;
48 | private boolean mDirty = true;
49 | private boolean mAddPaddingForCorners = true;
50 | private int backgroundColor;
51 |
52 | public RoundRectDrawableWithShadow(int background, float radius, float shadowSize, float maxShadowSize) {
53 | mShadowStartColor = 0x43000000;
54 | mShadowCentertColor = 0x43000000;
55 | mShadowEndColor = 0x00000000;
56 | mInsetShadow = 10;
57 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
58 | backgroundColor = background;
59 | mPaint.setColor(background);
60 | mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
61 | mCornerShadowPaint.setStyle(Paint.Style.FILL);
62 | mCornerRadius = (int) (radius + .5f);
63 | mCardBounds = new RectF();
64 | mEdgeShadowPaint = new Paint(mCornerShadowPaint);
65 | mEdgeShadowPaint.setAntiAlias(false);
66 | setShadowSize(shadowSize, maxShadowSize);
67 | }
68 |
69 | /**
70 | * Casts the value to an even integer.
71 | */
72 | private int toEven(float value) {
73 | int i = (int) (value + .5f);
74 | if (i % 2 == 1) {
75 | return i - 1;
76 | }
77 | return i;
78 | }
79 |
80 | public void setAddPaddingForCorners(boolean addPaddingForCorners) {
81 | mAddPaddingForCorners = addPaddingForCorners;
82 | invalidateSelf();
83 | }
84 |
85 | @Override
86 | public void setAlpha(int alpha) {
87 | mPaint.setAlpha(alpha);
88 | mCornerShadowPaint.setAlpha(alpha);
89 | mEdgeShadowPaint.setAlpha(alpha);
90 | }
91 |
92 | @Override
93 | protected void onBoundsChange(Rect bounds) {
94 | super.onBoundsChange(bounds);
95 | mDirty = true;
96 | }
97 |
98 | void setShadowSize(float shadowSize, float maxShadowSize) {
99 | if (shadowSize < 0f) {
100 | throw new IllegalArgumentException("Invalid shadow size " + shadowSize +
101 | ". Must be >= 0");
102 | }
103 | if (maxShadowSize < 0f) {
104 | throw new IllegalArgumentException("Invalid max shadow size " + maxShadowSize +
105 | ". Must be >= 0");
106 | }
107 | shadowSize = toEven(shadowSize);
108 | maxShadowSize = toEven(maxShadowSize);
109 | if (shadowSize > maxShadowSize) {
110 | shadowSize = maxShadowSize;
111 | }
112 | if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
113 | return;
114 | }
115 | mRawShadowSize = shadowSize;
116 | mRawMaxShadowSize = maxShadowSize;
117 | mShadowSize = (int) (shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
118 | mMaxShadowSize = maxShadowSize + mInsetShadow;
119 | mDirty = true;
120 | invalidateSelf();
121 | }
122 |
123 | @Override
124 | public boolean getPadding(Rect padding) {
125 | int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
126 | mAddPaddingForCorners));
127 | int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
128 | mAddPaddingForCorners));
129 | padding.set(hOffset, vOffset, hOffset, vOffset);
130 | return true;
131 | }
132 |
133 | float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
134 | boolean addPaddingForCorners) {
135 | if (addPaddingForCorners) {
136 | return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
137 | } else {
138 | return maxShadowSize * SHADOW_MULTIPLIER;
139 | }
140 | }
141 |
142 | float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
143 | boolean addPaddingForCorners) {
144 | if (addPaddingForCorners) {
145 | return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
146 | } else {
147 | return maxShadowSize;
148 | }
149 | }
150 |
151 | @Override
152 | public void setColorFilter(ColorFilter cf) {
153 | mPaint.setColorFilter(cf);
154 | }
155 |
156 | @Override
157 | public int getOpacity() {
158 | return PixelFormat.TRANSLUCENT;
159 | }
160 |
161 | @Override
162 | public void draw(Canvas canvas) {
163 | if (mDirty) {
164 | buildComponents(getBounds());
165 | mDirty = false;
166 | }
167 | canvas.translate(0, mRawShadowSize / 2);
168 | drawShadow(canvas);
169 | canvas.translate(0, -mRawShadowSize / 2);
170 | canvas.drawRoundRect(mCardBounds, mCornerRadius, mCornerRadius, mPaint);
171 | }
172 |
173 | private void drawShadow(Canvas canvas) {
174 | final float edgeShadowTop = -mCornerRadius - mShadowSize;
175 | final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
176 | final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
177 | final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
178 | // LT
179 | int saved = canvas.save();
180 | canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
181 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
182 | if (drawHorizontalEdges) {
183 | canvas.drawRect(0, edgeShadowTop,
184 | mCardBounds.width() - 2 * inset, -mCornerRadius,
185 | mEdgeShadowPaint);
186 | }
187 | canvas.restoreToCount(saved);
188 | // RB
189 | saved = canvas.save();
190 | canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
191 | canvas.rotate(180f);
192 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
193 | if (drawHorizontalEdges) {
194 | canvas.drawRect(0, edgeShadowTop,
195 | mCardBounds.width() - 2 * inset, -mCornerRadius,
196 | mEdgeShadowPaint);
197 | }
198 | canvas.restoreToCount(saved);
199 | // LB
200 | saved = canvas.save();
201 | canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
202 | canvas.rotate(270f);
203 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
204 | if (drawVerticalEdges) {
205 | canvas.drawRect(0, edgeShadowTop,
206 | mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
207 | }
208 | canvas.restoreToCount(saved);
209 | // RT
210 | saved = canvas.save();
211 | canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
212 | canvas.rotate(90f);
213 | canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
214 | if (drawVerticalEdges) {
215 | canvas.drawRect(0, edgeShadowTop,
216 | mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
217 | }
218 | canvas.restoreToCount(saved);
219 | }
220 |
221 | private void buildShadowCorners() {
222 | RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
223 | RectF outerBounds = new RectF(innerBounds);
224 | outerBounds.inset(-mShadowSize, -mShadowSize);
225 |
226 | if (mCornerShadowPath == null) {
227 | mCornerShadowPath = new Path();
228 | } else {
229 | mCornerShadowPath.reset();
230 | }
231 | mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
232 | mCornerShadowPath.moveTo(-mCornerRadius, 0);
233 | mCornerShadowPath.rLineTo(-mShadowSize, 0);
234 | // outer arc
235 | mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
236 | // inner arc
237 | mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
238 | mCornerShadowPath.close();
239 | float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
240 | mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
241 | new int[]{mShadowStartColor, mShadowCentertColor, mShadowEndColor},
242 | new float[]{0f, startRatio, 1f}
243 | , Shader.TileMode.CLAMP));
244 |
245 | // we offset the content shadowSize/2 pixels up to make it more realistic.
246 | // this is why edge shadow shader has some extra space
247 | // When drawing bottom edge shadow, we use that extra space.
248 | mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
249 | -mCornerRadius - mShadowSize,
250 | new int[]{mShadowStartColor, mShadowCentertColor, mShadowEndColor},
251 | new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
252 | mEdgeShadowPaint.setAntiAlias(false);
253 | }
254 |
255 | private void buildComponents(Rect bounds) {
256 | // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
257 | // We could have different top-bottom offsets to avoid extra gap above but in that case
258 | // center aligning Views inside the CardView would be problematic.
259 | final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
260 | mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
261 | bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
262 | buildShadowCorners();
263 | }
264 |
265 | float getCornerRadius() {
266 | return mCornerRadius;
267 | }
268 |
269 | void setCornerRadius(float radius) {
270 | if (radius < 0f) {
271 | throw new IllegalArgumentException("Invalid radius " + radius + ". Must be >= 0");
272 | }
273 | radius = (int) (radius + .5f);
274 | if (mCornerRadius == radius) {
275 | return;
276 | }
277 | mCornerRadius = radius;
278 | mDirty = true;
279 | invalidateSelf();
280 | }
281 |
282 | void getMaxShadowAndCornerPadding(Rect into) {
283 | getPadding(into);
284 | }
285 |
286 | float getShadowSize() {
287 | return mRawShadowSize;
288 | }
289 |
290 | void setShadowSize(float size) {
291 | setShadowSize(size, mRawMaxShadowSize);
292 | }
293 |
294 | float getMaxShadowSize() {
295 | return mRawMaxShadowSize;
296 | }
297 |
298 | void setMaxShadowSize(float size) {
299 | setShadowSize(mRawShadowSize, size);
300 | }
301 |
302 | int getBackgroundColor() {
303 | return backgroundColor;
304 | }
305 |
306 | float getMinWidth() {
307 | final float content = 2 *
308 | Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
309 | return content + (mRawMaxShadowSize + mInsetShadow) * 2;
310 | }
311 |
312 | float getMinHeight() {
313 | final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
314 | + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
315 | return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/ActivitySwitcherHelper.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.app.WallpaperManager;
6 | import android.content.Context;
7 | import android.graphics.Bitmap;
8 | import android.graphics.drawable.BitmapDrawable;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.os.AsyncTask;
12 | import android.support.annotation.NonNull;
13 | import android.util.DisplayMetrics;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.view.Window;
17 | import android.widget.FrameLayout;
18 | import android.widget.ImageView;
19 |
20 | import java.lang.reflect.Field;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | /**
25 | * ActivitySwitch 帮助类。负责:
26 | *
27 | * - 打开/关闭 ActivitySwitcher
28 | * - ActivitySwitcher 背景处理
29 | * - Activity关闭,程序结束进程处理
30 | *
31 | *
32 | * email : 196425254@qq.com
33 | *
34 | * github : https://github.com/Hitomis
35 | *
36 | * Created by hitomi on 2016/10/11.
37 | */
38 | class ActivitySwitcherHelper {
39 |
40 | private ActivitySwitcher actSwitcher;
41 |
42 | private Context appContext;
43 |
44 | private ActivityManager actManager;
45 |
46 | private ActivityControllerLayout actControllerLayout;
47 |
48 | private List preActivities;
49 | private List flingActivities;
50 |
51 | private ActivitySwitcher.OnActivitySwitchListener onActivitySwitchListener;
52 |
53 | private ActivityControllerLayout.OnControlCallback callback = new ActivityControllerLayout.OnControlCallback() {
54 |
55 | @Override
56 | public void onDisplayed() {
57 | if (onActivitySwitchListener != null)
58 | onActivitySwitchListener.onSwitchStarted();
59 | }
60 |
61 | @Override
62 | public void onSelected(ActivityContainer selectedContainer) {
63 | actSwitcher.setSwitching(false);
64 | endSwitch(actControllerLayout.indexOfChild(selectedContainer));
65 | }
66 |
67 | @Override
68 | public void onFling(ActivityContainer flingContainer) {
69 | int index = actControllerLayout.indexOfChild(flingContainer);
70 | Activity flingActivity = preActivities.get(index);
71 | actControllerLayout.removeView(flingContainer);
72 | preActivities.remove(flingActivity);
73 | flingActivities.add(flingActivity);
74 |
75 | if (preActivities.isEmpty()) {
76 | for (Activity flingAct : flingActivities) {
77 | finishActivityByNoAnimation(flingAct);
78 | }
79 | // 必须先关闭所有 Activity 才能结束进程
80 | android.os.Process.killProcess(android.os.Process.myPid());
81 | }
82 | }
83 | };
84 |
85 | public ActivitySwitcherHelper(ActivitySwitcher switcher, @NonNull Application application) {
86 | actSwitcher = switcher;
87 | appContext = application;
88 | actManager = ActivityManager.getInstance();
89 | application.registerActivityLifecycleCallbacks(actManager);
90 | actControllerLayout = new ActivityControllerLayout(application);
91 | flingActivities = new ArrayList<>();
92 | attachBlurBackground();
93 | }
94 |
95 | /**
96 | * Start the Activity switch
97 | */
98 | public void startSwitch() {
99 | // 获取当前 Activity 以及当前 Activity 之前所有 Activity
100 | preActivities = actManager.getPreActivies();
101 | Activity currAct = actManager.getCurrentActivity();
102 | preActivities.add(currAct);
103 |
104 | ViewGroup contentViewGroup, contentView;
105 | final int radius = 8;
106 | final int shadowSize = 12;
107 | int[] actSize = getActivitySize();
108 | Drawable background;
109 | ActivityContainer container;
110 | for (Activity activity : preActivities) {
111 | if (activity.getWindow() == null) continue;
112 | contentViewGroup = getContentView(activity.getWindow());
113 | contentView = (ViewGroup) contentViewGroup.getChildAt(0);
114 | contentViewGroup.removeView(contentView);
115 | container = new ActivityContainer(appContext);
116 | container.addView(contentView);
117 | // 如果 Activity 没有背景则使用 window 的背景
118 | background = contentView.getBackground() != null
119 | ? contentView.getBackground()
120 | : activity.getWindow().getDecorView().getBackground();
121 | if (background instanceof ColorDrawable) {
122 | ColorDrawable colorDrawable = (ColorDrawable) background;
123 | RoundRectDrawableWithShadow roundDrawable = new RoundRectDrawableWithShadow(
124 | colorDrawable.getColor(), radius, shadowSize, shadowSize);
125 | // 设置背景
126 | container.setBackgroundDrawable(roundDrawable);
127 | }
128 | FrameLayout.LayoutParams contentViewLp = new FrameLayout.LayoutParams(actSize[0], actSize[1]);
129 | container.setLayoutParams(contentViewLp);
130 | // 将修改好背景/尺寸/布局的 Activity 中的 contentView 添加到 ActivityControllerLayout 中
131 | actControllerLayout.addView(container);
132 | }
133 |
134 | FrameLayout currContentView = getContentView(currAct.getWindow());
135 | currContentView.addView(actControllerLayout);
136 | actControllerLayout.display(callback);
137 | }
138 |
139 | public void endSwitch() {
140 | actControllerLayout.closure(true);
141 | }
142 |
143 | public boolean isActivityControllerClosed() {
144 | return actControllerLayout.getFlag() == ActivityControllerLayout.FLAG_CLOSED;
145 | }
146 |
147 | public boolean isActivityControllerDisplayed() {
148 | return actControllerLayout.getFlag() == ActivityControllerLayout.FLAG_DISPLAYED;
149 | }
150 |
151 | public void exit() {
152 | if (preActivities == null) {
153 | preActivities = actManager.getPreActivies();
154 | } else {
155 | preActivities.addAll(flingActivities);
156 | }
157 | for (Activity activity : preActivities) {
158 | finishActivityByNoAnimation(activity);
159 | }
160 | android.os.Process.killProcess(android.os.Process.myPid());
161 | }
162 |
163 | /**
164 | * End of the Activity switch
165 | * @param selectedIndex
166 | */
167 | private void endSwitch(int selectedIndex) {
168 | // 从栈顶 Activity 的 ContentView 中移除 ActivityControllerLayout
169 | FrameLayout topContentViewGroup = getContentView(actManager.getCurrentActivity().getWindow());
170 | topContentViewGroup.removeView(actControllerLayout);
171 |
172 | // 关闭当前选中的 Activity 之后的 Activity 和被 fling 掉的 Activity
173 | Activity activity;
174 | View contentView;
175 | for (int i = preActivities.size() - 1; i > selectedIndex; i--) {
176 | activity = preActivities.get(i);
177 | finishActivityByNoAnimation(activity);
178 | }
179 | for (Activity act : flingActivities) {
180 | finishActivityByNoAnimation(act);
181 | }
182 |
183 | // 将 ActivityControllerLayout 中的每个 ContentView 还原给 Activity
184 | FrameLayout contentViewGroup;
185 | FrameLayout.LayoutParams contentViewLp;
186 | ActivityContainer activityContainer;
187 | for (int i = selectedIndex; i >= 0; i--) {
188 | activityContainer = (ActivityContainer) actControllerLayout.getChildAt(i);
189 | contentView = activityContainer.getChildAt(0);
190 | activityContainer.removeView(contentView);
191 | actControllerLayout.removeView(activityContainer);
192 |
193 | activity = preActivities.get(i);
194 | contentViewGroup = getContentView(activity.getWindow());
195 | contentViewLp = new FrameLayout.LayoutParams(
196 | ViewGroup.LayoutParams.MATCH_PARENT,
197 | ViewGroup.LayoutParams.MATCH_PARENT);
198 | contentViewGroup.addView(contentView, contentViewLp);
199 | }
200 | actControllerLayout.removeAllViews();
201 |
202 | if (onActivitySwitchListener != null)
203 | onActivitySwitchListener.onSwitchFinished(preActivities.get(selectedIndex));
204 | }
205 |
206 | /**
207 | * 无任何痕迹实现 Activity 关闭
208 | * @param activity
209 | */
210 | private void finishActivityByNoAnimation(Activity activity) {
211 | Window window = activity.getWindow();
212 | window.getDecorView().setAlpha(0);
213 | activity.finish();
214 | activity.overridePendingTransition(0, 0);
215 | }
216 |
217 | /**
218 | * 附加高斯模糊图片背景
219 | */
220 | private void attachBlurBackground() {
221 | if (actControllerLayout.getBackground() != null) return;
222 | setContainerBackground();
223 | }
224 |
225 | /**
226 | * 抽取系统桌面背景图, 设置为高斯模糊效果
227 | *
228 | * 高斯模糊性能优化,参考文章 :[here]
229 | */
230 | private void setContainerBackground() {
231 | new AsyncTask() {
232 |
233 | @Override
234 | protected Bitmap doInBackground(Void... voids) {
235 | // 获取系统桌面背景图片
236 | WallpaperManager wallpaperManager = WallpaperManager.getInstance(appContext);
237 | Drawable wallpaperDrawable = wallpaperManager.getDrawable();
238 | Bitmap wallpaperBitmap = ((BitmapDrawable) wallpaperDrawable).getBitmap();
239 | // 以当前 Activity 尺寸为参照物,居中裁剪
240 | Bitmap centerBitmap = cropCenterBitmap(wallpaperBitmap);
241 | final int scaleRatio = 20;
242 | final int blurRadius = 8;
243 | // filter是指缩放的效果,filter为true则会得到一个边缘平滑的bitmap,反之,则会得到边缘锯齿、pixelrelated的bitmap。
244 | // 这里我们要对缩放的图片进行虚化,所以无所谓边缘效果,filter=false。
245 | Bitmap scaledBitmap = Bitmap.createScaledBitmap(centerBitmap,
246 | centerBitmap.getWidth() / scaleRatio,
247 | centerBitmap.getHeight() / scaleRatio,
248 | false);
249 | // 返回高斯模糊渲染后的图片
250 | return FastBlur.doBlur(scaledBitmap, blurRadius, true);
251 | }
252 |
253 | @Override
254 | protected void onPostExecute(Bitmap bitmap) {
255 | ImageView imageView = new ImageView(appContext);
256 | imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
257 | imageView.setImageBitmap(bitmap);
258 | actControllerLayout.setBackgroundDrawable(imageView.getDrawable());
259 | }
260 | }.execute();
261 | }
262 |
263 | /**
264 | * 以当前 Activity 尺寸为参照物,居中裁剪 Bitmap
265 | * @param bitmap
266 | * @return
267 | */
268 | private Bitmap cropCenterBitmap(Bitmap bitmap) {
269 | Bitmap resultBitmap = bitmap;
270 | int srcWidth = bitmap.getWidth();
271 | int srcHeight = bitmap.getHeight();
272 | int[] actSize = getActivitySize();
273 | int x = 0, y = 0;
274 | if (srcWidth <= actSize[0] && srcHeight <= actSize[1]){
275 | // 背景图的宽高都小于当前 Activity 宽高 —> 放大到 Activity 宽高
276 | resultBitmap = Bitmap.createScaledBitmap(bitmap, actSize[0], actSize[1], true);
277 | } else if (srcWidth <= actSize[0] && srcHeight > actSize[1]) {
278 | // 背景图的宽小于当前 Activity 宽, 高大于当前 Activity 的高 -> 截取高
279 | y = (srcHeight - actSize[1]) / 2;
280 | resultBitmap = Bitmap.createBitmap(bitmap, x, y, srcWidth, actSize[1]);
281 | } else if (srcHeight <= actSize[1] && srcWidth > actSize[0]) {
282 | // 背景图的高小于当前 Activity 高, 宽大于当前 Activity 的宽 -> 截取宽
283 | x = (srcWidth - actSize[0]) / 2;
284 | resultBitmap = Bitmap.createBitmap(bitmap, x, y, actSize[0], srcHeight);
285 | } else if (srcWidth > actSize[0] && srcHeight > actSize[1]) {
286 | // 背景图的宽高均大于当前 Activity 宽高 -> 截取宽高
287 | x = (srcWidth - actSize[0]) / 2;
288 | y = (srcHeight - actSize[1]) / 2;
289 | resultBitmap = Bitmap.createBitmap(bitmap, x, y, actSize[0], actSize[1]);
290 | }
291 | return resultBitmap;
292 | }
293 |
294 | /**
295 | * 获取当前 Activity 在窗口中显示的尺寸大小
296 | * @return
297 | */
298 | private int[] getActivitySize() {
299 | int[] actSize = new int[2];
300 | DisplayMetrics displayMetrics = appContext.getResources().getDisplayMetrics();
301 | actSize[0] = displayMetrics.widthPixels;
302 | actSize[1] = displayMetrics.heightPixels - getStatusHeight();
303 | return actSize;
304 | }
305 |
306 | /**
307 | * 获取状态栏高度
308 | * @return
309 | */
310 | private int getStatusHeight() {
311 | try {
312 | Class> c = Class.forName("com.android.internal.R$dimen");
313 | Object object = c.newInstance();
314 | Field field = c.getField("status_bar_height");
315 | int x = (Integer) field.get(object);
316 | return appContext.getResources().getDimensionPixelSize(x);
317 | } catch (Exception e) {
318 | return 0;
319 | }
320 | }
321 |
322 | private final FrameLayout getContentView(Window window) {
323 | if(window == null) return null;
324 | return (FrameLayout) window.findViewById(Window.ID_ANDROID_CONTENT);
325 | }
326 |
327 | public void setOnActivitySwitchListener(ActivitySwitcher.OnActivitySwitchListener listener) {
328 | this.onActivitySwitchListener = listener;
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/aslibrary/src/main/java/com/hitomi/aslibrary/ActivityControllerLayout.java:
--------------------------------------------------------------------------------
1 | package com.hitomi.aslibrary;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.AnimatorSet;
6 | import android.animation.ObjectAnimator;
7 | import android.animation.ValueAnimator;
8 | import android.content.Context;
9 | import android.support.annotation.NonNull;
10 | import android.util.AttributeSet;
11 | import android.util.Log;
12 | import android.view.MotionEvent;
13 | import android.view.VelocityTracker;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.view.animation.DecelerateInterpolator;
17 | import android.widget.FrameLayout;
18 |
19 | /**
20 | * 排列、展示、拖动 Activity 容器类
21 | *
22 | * email : 196425254@qq.com
23 | *
24 | * github : https://github.com/Hitomis
25 | *
26 | * Created by hitomi on 2016/10/11.
27 | */
28 | class ActivityControllerLayout extends FrameLayout implements View.OnClickListener{
29 | public static final String TAG = "ActivitySwitcher";
30 |
31 | public static final int FLAG_DISPLAYING = 100;
32 | public static final int FLAG_DISPLAYED = 200;
33 | public static final int FLAG_CLOSING = -100;
34 | public static final int FLAG_CLOSED = -200;
35 | public static final int FLAG_SLIDING = 0;
36 |
37 | private static final int STYLE_SINGLE = 1;
38 | private static final int STYLE_DOUBLE = 1 << 1;
39 |
40 | private static final int STYLE_MULTIPLE = 1 << 2;
41 |
42 | private static final float CENTER_SCALE_RATE = .65f;
43 | private static final float OFFSET_SCALE_RATE = .02f;
44 |
45 | private static final int MIN_OFFSET_SIZE = 80;
46 | private static final int MAX_OFFSET_SIZE = 180;
47 |
48 | private int maxVelocity = 2500;
49 | private int touchSlop = 8;
50 |
51 | private int flag;
52 | private int width;
53 |
54 | private float pageOffsetSize;
55 | private float preY, diffY;
56 | private float controlViewBottom = 0.f;
57 |
58 | private float[] originalContainerX;
59 | private float[] originalContainerScale;
60 |
61 | private boolean resetBackground;
62 |
63 | private VelocityTracker velocityTracker;
64 |
65 | private OnControlCallback onControlCallback;
66 | private ActivityContainer controlView;
67 |
68 | public ActivityControllerLayout(Context context) {
69 | this(context, null);
70 | }
71 |
72 | public ActivityControllerLayout(Context context, AttributeSet attrs) {
73 | this(context, attrs, 0);
74 | }
75 |
76 | public ActivityControllerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
77 | super(context, attrs, defStyleAttr);
78 | flag = FLAG_CLOSED;
79 | width = getResources().getDisplayMetrics().widthPixels;
80 | }
81 |
82 | @Override
83 | public void addView(View child) {
84 | child.setOnClickListener(this);
85 | super.addView(child);
86 | }
87 |
88 | @Override
89 | public boolean dispatchTouchEvent(MotionEvent ev) {
90 | if (flag == FLAG_DISPLAYED || flag == FLAG_SLIDING)
91 | switch (ev.getAction()) {
92 | case MotionEvent.ACTION_DOWN:
93 | if (null == velocityTracker) {
94 | velocityTracker = VelocityTracker.obtain();
95 | } else {
96 | velocityTracker.clear();
97 | }
98 | velocityTracker.addMovement(ev);
99 |
100 | if (findControlView(ev) == null || flag == FLAG_SLIDING) return false;
101 | cacheOrginalContainerParamter(controlView = findControlView(ev));
102 |
103 | preY = ev.getY();
104 | break;
105 | case MotionEvent.ACTION_MOVE:
106 | velocityTracker.addMovement(ev);
107 |
108 | diffY = ev.getY() - preY;
109 | float newDiffY = diffY;
110 | if (controlView.getY() <= 0) { // 在中线之上
111 | moveToLastContainerPos();
112 | } else if (controlView.getY() > 0 && diffY > 0) { // 在中线之下
113 | float slopRate = 1.f - 1.65f * controlView.getY() / (controlView.getIntrinsicHeight());
114 | newDiffY *= slopRate;
115 | }
116 | controlView.setY(controlView.getY() + newDiffY);
117 |
118 | preY = ev.getY();
119 | break;
120 | case MotionEvent.ACTION_UP:
121 | velocityTracker.addMovement(ev);
122 | velocityTracker.computeCurrentVelocity(1000);
123 | float velocityY = velocityTracker.getYVelocity();
124 |
125 | boolean over = Math.abs(controlView.getY()) >= controlView.getIntrinsicHeight() * .618;
126 | if (diffY < touchSlop * .4f && controlView.getY() < 0 && (over || Math.abs(velocityY) >= maxVelocity)) {
127 | // 上移且超出阈值 或者 上移速度超过阈值 -> 移除到窗外
128 | slideOutAnimation();
129 | } else if (flag != FLAG_CLOSING && Math.abs(controlView.getY()) >= touchSlop) {
130 | // 下移或者上移没有超出阈值- > 回落到原始位置
131 | slideOrignalPosAnimation();
132 | }
133 |
134 | diffY = 0;
135 |
136 | if (null != velocityTracker) {
137 | velocityTracker.recycle();
138 | velocityTracker = null;
139 | }
140 | break;
141 | case MotionEvent.ACTION_CANCEL:
142 | if (null != velocityTracker) {
143 | velocityTracker.recycle();
144 | velocityTracker = null;
145 | }
146 | break;
147 | }
148 | return super.dispatchTouchEvent(ev);
149 | }
150 |
151 | @Override
152 | public void onClick(final View view) {
153 | if (flag == FLAG_DISPLAYED
154 | && Math.abs(diffY) < touchSlop
155 | && Math.abs(view.getY()) < touchSlop) {
156 | closure(false);
157 | }
158 | }
159 |
160 | private void cacheOrginalContainerParamter(ActivityContainer controlContainer) {
161 | controlViewBottom = controlContainer.getBounds().bottom;
162 | int controlIndex = indexOfChild(controlContainer);
163 | if (getChildCount() != 3) {
164 | originalContainerX = new float[getChildCount()];
165 | originalContainerScale = new float[getChildCount()];
166 | View child;
167 | for (int i = 0; i < getChildCount(); i++) {
168 | child = getChildAt(i);
169 | originalContainerX[i] = child.getX();
170 | originalContainerScale[i] = child.getScaleX();
171 | }
172 | } else {
173 | originalContainerX = new float[2];
174 | originalContainerScale = new float[2];
175 | if (controlIndex == 0) {
176 | originalContainerX[0] = getChildAt(1).getX();
177 | originalContainerX[1] = getChildAt(2).getX();
178 |
179 | originalContainerScale[0] = getChildAt(1).getScaleX();
180 | originalContainerScale[1] = getChildAt(2).getScaleX();
181 | } else if (controlIndex == 1) {
182 | originalContainerX[0] = getChildAt(0).getX();
183 | originalContainerX[1] = getChildAt(2).getX();
184 |
185 | originalContainerScale[0] = getChildAt(0).getScaleX();
186 | originalContainerScale[1] = getChildAt(2).getScaleX();
187 | } else {
188 | originalContainerX[0] = getChildAt(0).getX();
189 | originalContainerX[1] = getChildAt(1).getX();
190 |
191 | originalContainerScale[0] = getChildAt(0).getScaleX();
192 | originalContainerScale[1] = getChildAt(1).getScaleX();
193 | }
194 |
195 | }
196 | }
197 |
198 | private void slideOrignalPosAnimation() {
199 | ObjectAnimator tranYAnima = ObjectAnimator.ofFloat(controlView, "Y", controlView.getY(), 0);
200 | tranYAnima.setDuration(350);
201 | tranYAnima.setInterpolator(new DecelerateInterpolator());
202 | tranYAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
203 | @Override
204 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
205 | if (controlView.getY() < 0)
206 | moveToLastContainerPos();
207 | }
208 | });
209 | tranYAnima.start();
210 | }
211 |
212 | private void slideOutAnimation() {
213 | flag = FLAG_SLIDING;
214 | float endTranY = controlView.getY() - controlView.getBounds().bottom;
215 | ObjectAnimator tranYAnima = ObjectAnimator.ofFloat(controlView, "Y", controlView.getY(), endTranY);
216 | tranYAnima.setDuration(150);
217 | tranYAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
218 | @Override
219 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
220 | moveToLastContainerPos();
221 | }
222 | });
223 | tranYAnima.addListener(new AnimatorListenerAdapter() {
224 | @Override
225 | public void onAnimationEnd(Animator animation) {
226 | if (onControlCallback != null) {
227 | onControlCallback.onFling(controlView);
228 | }
229 | flag = FLAG_DISPLAYED;
230 | }
231 | });
232 | tranYAnima.start();
233 | }
234 |
235 | private void moveToLastContainerPos() {
236 | int controlIndex = indexOfChild(controlView);
237 | if (getChildCount() == 3) {
238 | View belowChild, aboveChild;
239 | float totalOffset, currOffset;
240 | if (controlIndex == 0) {
241 | belowChild = getChildAt(1);
242 | aboveChild = getChildAt(2);
243 | } else if (controlIndex == 1) {
244 | belowChild = getChildAt(0);
245 | aboveChild = getChildAt(2);
246 | } else {
247 | belowChild = getChildAt(0);
248 | aboveChild = getChildAt(1);
249 | }
250 |
251 | totalOffset = originalContainerX[0];
252 | currOffset = calcOffsetSize(totalOffset);
253 | belowChild.setX(originalContainerX[0] + currOffset);
254 |
255 | totalOffset = CENTER_SCALE_RATE - originalContainerScale[0];
256 | currOffset = calcOffsetSize(totalOffset);
257 | belowChild.setScaleX(originalContainerScale[0] - currOffset);
258 | belowChild.setScaleY(originalContainerScale[0] - currOffset);
259 |
260 | totalOffset = width * (CENTER_SCALE_RATE + OFFSET_SCALE_RATE) / 2 - originalContainerX[1];
261 | currOffset = calcOffsetSize(totalOffset);
262 | aboveChild.setX(originalContainerX[1] - currOffset);
263 |
264 | totalOffset = CENTER_SCALE_RATE + OFFSET_SCALE_RATE - originalContainerScale[1];
265 | currOffset = calcOffsetSize(totalOffset);
266 | aboveChild.setScaleX(originalContainerScale[1] - currOffset);
267 | aboveChild.setScaleY(originalContainerScale[1] - currOffset);
268 | } else if (getChildCount() != 1 && getChildCount() != controlIndex + 1) {
269 | float currOffsetX, currScaleSize;
270 | float totalOffsetX = originalContainerX[controlIndex + 1] - originalContainerX[controlIndex];
271 | float totalScaleSize = getLayoutStyle() == STYLE_DOUBLE
272 | ? OFFSET_SCALE_RATE : 3 * OFFSET_SCALE_RATE;
273 |
274 | View child;
275 | for (int i = controlIndex + 1; i < getChildCount(); i++) {
276 | if (controlViewBottom == 0.f) continue;
277 | child = getChildAt(i);
278 |
279 | currOffsetX = calcOffsetSize(totalOffsetX);
280 | child.setX(originalContainerX[i] + currOffsetX);
281 |
282 | currScaleSize = calcOffsetSize(totalScaleSize);
283 | child.setScaleX(originalContainerScale[i] + currScaleSize);
284 | child.setScaleY(originalContainerScale[i] + currScaleSize);
285 | }
286 | }
287 | }
288 |
289 | private float calcOffsetSize(float totalSize) {
290 | return controlView.getY() * totalSize / controlViewBottom;
291 | }
292 |
293 |
294 | private ActivityContainer findControlView(MotionEvent ev) {
295 | int childCount = getChildCount();
296 | ActivityContainer controlView = null;
297 | ActivityContainer container;
298 | for (int i = childCount - 1; i >= 0; i--) {
299 | container = (ActivityContainer) getChildAt(i);
300 | if (container.getBounds().contains(ev.getX(), ev.getY())) {
301 | controlView = container;
302 | break;
303 | }
304 | }
305 | return controlView;
306 | }
307 |
308 | @NonNull
309 | private ObjectAnimator getCheckedScaleXAnima(View view) {
310 | ObjectAnimator chooseScaleXAnima = ObjectAnimator.ofFloat(view, "scaleX", view.getScaleX(), 1.0f);
311 | chooseScaleXAnima.setDuration(200);
312 | return chooseScaleXAnima;
313 | }
314 |
315 | @NonNull
316 | private ObjectAnimator getCheckedScaleYAnima(View view) {
317 | ObjectAnimator chooseScaleYAnima = ObjectAnimator.ofFloat(view, "scaleY", view.getScaleY(), 1.0f);
318 | chooseScaleYAnima.setDuration(200);
319 | chooseScaleYAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
320 | @Override
321 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
322 | removeShadow(valueAnimator);
323 | }
324 | });
325 | return chooseScaleYAnima;
326 | }
327 |
328 | @NonNull
329 | private AnimatorSet singleStyleAnimator(View view) {
330 | AnimatorSet animatorSet = new AnimatorSet();
331 | animatorSet.play(getCheckedScaleXAnima(view))
332 | .with(getCheckedScaleYAnima(view));
333 | return animatorSet;
334 | }
335 |
336 | private AnimatorSet doubleStyleAnimator(View view) {
337 | ObjectAnimator preObjAnima;
338 | if (indexOfChild(view) == 0) {
339 | float afterEndTranX = width * .5f;
340 | View afterChild = getChildAt(1);
341 | preObjAnima = ObjectAnimator.ofFloat(afterChild, "X",
342 | afterChild.getX(), afterChild.getX() + afterEndTranX);
343 | preObjAnima.setDuration(300);
344 | } else {
345 | preObjAnima = ObjectAnimator.ofFloat(view, "X", view.getX(), 0);
346 | preObjAnima.setDuration(200);
347 | }
348 | AnimatorSet animatorSet = new AnimatorSet();
349 | animatorSet.play(preObjAnima)
350 | .before(getCheckedScaleXAnima(view))
351 | .before(getCheckedScaleYAnima(view));
352 | return animatorSet;
353 | }
354 |
355 | private AnimatorSet multipleStyleAnimator(final View view) {
356 | final int chooseIndex = indexOfChild(view);
357 | ValueAnimator afterTranXAnima = null;
358 | if (chooseIndex < getChildCount() - 1) {
359 | float afterEndTranX = width - (chooseIndex + 2) * pageOffsetSize;
360 | final float[] currX = new float[getChildCount() - chooseIndex -1];
361 | for (int i = chooseIndex + 1; i < getChildCount(); i++) {
362 | currX[i - chooseIndex - 1] = getChildAt(i).getX();
363 | }
364 | afterTranXAnima = ValueAnimator.ofFloat(0, afterEndTranX);
365 | afterTranXAnima.setDuration(300);
366 | afterTranXAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
367 | @Override
368 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
369 | float valueX = Float.parseFloat(valueAnimator.getAnimatedValue().toString());
370 | View afterChild;
371 | for (int i = chooseIndex + 1; i < getChildCount(); i++) {
372 | afterChild = getChildAt(i);
373 | afterChild.setX(currX[i - chooseIndex - 1] + valueX);
374 | }
375 | }
376 | });
377 | }
378 | ObjectAnimator chooseTranXAnima = ObjectAnimator.ofFloat(view, "X", view.getX(), 0);
379 | chooseTranXAnima.setDuration(200);
380 | AnimatorSet animatorSet = new AnimatorSet();
381 | AnimatorSet.Builder animaBuilder = animatorSet
382 | .play(chooseTranXAnima)
383 | .before(getCheckedScaleXAnima(view))
384 | .before(getCheckedScaleYAnima(view));
385 | if (afterTranXAnima != null) {
386 | animaBuilder.after(afterTranXAnima);
387 | }
388 | return animatorSet;
389 | }
390 |
391 | private Animator displayBySingleStyle() {
392 | final View singleChild = getChildAt(0);
393 | ValueAnimator scaleAnima = ValueAnimator.ofFloat(1, 100);
394 | scaleAnima.setDuration(200);
395 | scaleAnima.setInterpolator(new DecelerateInterpolator());
396 | scaleAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
397 | @Override
398 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
399 | float fraction = valueAnimator.getAnimatedFraction();
400 | float scaleValue = 1 - (1 - CENTER_SCALE_RATE) * fraction;
401 | singleChild.setScaleX(scaleValue);
402 | singleChild.setScaleY(scaleValue);
403 | }
404 | });
405 | return scaleAnima;
406 | }
407 |
408 | private Animator displayByDoubleStyle() {
409 | final View belowChild = getChildAt(0);
410 | final View aboveChild = getChildAt(1);
411 | ValueAnimator scaleAnima = ValueAnimator.ofFloat(1, 100);
412 | scaleAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
413 | @Override
414 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
415 | float fraction = valueAnimator.getAnimatedFraction();
416 | float scaleValue = 1 - (1 - CENTER_SCALE_RATE) * fraction;
417 | belowChild.setScaleX(scaleValue);
418 | belowChild.setScaleY(scaleValue);
419 |
420 | scaleValue = 1 - (1 - (CENTER_SCALE_RATE + OFFSET_SCALE_RATE)) * fraction;
421 | aboveChild.setScaleX(scaleValue);
422 | aboveChild.setScaleY(scaleValue);
423 | }
424 | });
425 |
426 | float endTranX = width * (CENTER_SCALE_RATE + OFFSET_SCALE_RATE) / 2;
427 | ObjectAnimator tranXAnima = ObjectAnimator.ofFloat(aboveChild, "X", aboveChild.getX(), endTranX);
428 |
429 | AnimatorSet animatorSet = new AnimatorSet();
430 | animatorSet.setDuration(200);
431 | animatorSet.setInterpolator(new DecelerateInterpolator());
432 | animatorSet.play(scaleAnima).with(tranXAnima);
433 | return animatorSet;
434 | }
435 |
436 | private Animator displayByMultipleStyle() {
437 | ValueAnimator scaleAnima = ValueAnimator.ofFloat(1, 100);
438 | scaleAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
439 | @Override
440 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
441 | float fraction = valueAnimator.getAnimatedFraction();
442 | float scaleValue;
443 | int childCount = getChildCount();
444 | View child;
445 | for (int i = 0; i < childCount; i++) {
446 | child = getChildAt(i);
447 | scaleValue = CENTER_SCALE_RATE + 3 * OFFSET_SCALE_RATE * (i - 1);
448 | scaleValue = 1 - (1 - scaleValue) * fraction;
449 | child.setScaleX(scaleValue);
450 | child.setScaleY(scaleValue);
451 | }
452 | }
453 | });
454 |
455 |
456 | ValueAnimator tranXAnima = ValueAnimator.ofFloat(1, 100);
457 | tranXAnima.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
458 | @Override
459 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
460 | float fraction = valueAnimator.getAnimatedFraction();
461 | int childCount = getChildCount();
462 | float tranX;
463 | View child;
464 | float initTranX;
465 | for (int i = 0; i < childCount; i++) {
466 | child = getChildAt(i);
467 | initTranX = (width - width * (CENTER_SCALE_RATE + 3 * OFFSET_SCALE_RATE * (i - 1))) * .5f;
468 | tranX = pageOffsetSize * i;
469 | tranX = fraction * tranX - initTranX + pageOffsetSize;
470 | child.setX(tranX);
471 | }
472 | }
473 | });
474 | AnimatorSet animatorSet = new AnimatorSet();
475 | animatorSet.setDuration(200);
476 | animatorSet.setInterpolator(new DecelerateInterpolator());
477 | animatorSet.play(scaleAnima).with(tranXAnima);
478 | return animatorSet;
479 | }
480 |
481 | private void removeShadow(ValueAnimator valueAnimator) {
482 | ViewGroup vgChild;
483 | RoundRectDrawableWithShadow drawable;
484 | if (valueAnimator.getAnimatedFraction() > .3f && !resetBackground) {
485 | for (int i = 0; i < getChildCount(); i++) {
486 | vgChild = (ViewGroup) getChildAt(i);
487 | if (vgChild.getBackground() instanceof RoundRectDrawableWithShadow) {
488 | drawable = (RoundRectDrawableWithShadow) vgChild.getBackground();
489 | vgChild.setBackgroundColor(drawable.getBackgroundColor());
490 | }
491 | }
492 | resetBackground = true;
493 | }
494 | }
495 |
496 | private int getLayoutStyle() {
497 | int style = 0;
498 | int childCount = getChildCount();
499 | if (childCount == 1) {
500 | style = STYLE_SINGLE;
501 | } else if (childCount == 2) {
502 | style = STYLE_DOUBLE;
503 | } else if (childCount >=3) {
504 | style = STYLE_MULTIPLE;
505 | }
506 | return style;
507 | }
508 |
509 | private void updateContainerIntercept(boolean interceptEvent) {
510 | ActivityContainer container;
511 | for (int i = 0; i < getChildCount(); i++) {
512 | container = (ActivityContainer) getChildAt(i);
513 | container.setIntercept(interceptEvent);
514 | }
515 | }
516 |
517 | public void display(@NonNull OnControlCallback callback) {
518 | onControlCallback = callback;
519 | flag = FLAG_DISPLAYING;
520 | Animator animator;
521 | int childCount = getChildCount();
522 | if (childCount <=0) return ;
523 | if (childCount == 1) {
524 | animator = displayBySingleStyle();
525 | } else if (childCount == 2) {
526 | animator = displayByDoubleStyle();
527 | } else {
528 | pageOffsetSize = width * 1.f / (childCount + 1);
529 | pageOffsetSize = pageOffsetSize < MIN_OFFSET_SIZE ? MIN_OFFSET_SIZE : pageOffsetSize;
530 | pageOffsetSize = pageOffsetSize > MAX_OFFSET_SIZE ? MAX_OFFSET_SIZE : pageOffsetSize;
531 | animator = displayByMultipleStyle();
532 | }
533 | animator.addListener(new AnimatorListenerAdapter() {
534 | @Override
535 | public void onAnimationStart(Animator animation) {
536 | updateContainerIntercept(true);
537 | }
538 |
539 | @Override
540 | public void onAnimationEnd(Animator animation) {
541 | flag = FLAG_DISPLAYED;
542 | if (onControlCallback != null)
543 | onControlCallback.onDisplayed();
544 | }
545 | });
546 | animator.start();
547 | }
548 |
549 | public void closure(boolean back) {
550 | controlView = controlView == null || indexOfChild(controlView) == -1 || back
551 | ? (ActivityContainer) getChildAt(getChildCount() - 1)
552 | : controlView;
553 | flag = FLAG_CLOSING;
554 | AnimatorSet animatorSet = null;
555 | resetBackground = false;
556 | switch (getLayoutStyle()) {
557 | case STYLE_SINGLE:
558 | animatorSet = singleStyleAnimator(controlView);
559 | break;
560 | case STYLE_DOUBLE:
561 | animatorSet = doubleStyleAnimator(controlView);
562 | break;
563 | case STYLE_MULTIPLE:
564 | animatorSet = multipleStyleAnimator(controlView);
565 | break;
566 | }
567 | if (animatorSet == null) return ;
568 | animatorSet.addListener(new AnimatorListenerAdapter() {
569 | @Override
570 | public void onAnimationEnd(Animator animation) {
571 | if (onControlCallback != null)
572 | onControlCallback.onSelected(controlView);
573 |
574 | updateContainerIntercept(false);
575 | controlView.setOnClickListener(null);
576 | controlView = null;
577 | flag = FLAG_CLOSED;
578 | }
579 | });
580 | animatorSet.start();
581 | }
582 |
583 | public int getFlag() {
584 | return flag;
585 | }
586 |
587 | public void log(String text) {
588 | Log.d(TAG, text);
589 | }
590 |
591 | public interface OnControlCallback {
592 |
593 | void onDisplayed();
594 |
595 | void onSelected(ActivityContainer selectedContainer);
596 |
597 | void onFling(ActivityContainer flingContainer);
598 |
599 | }
600 | }
601 |
--------------------------------------------------------------------------------