├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── myapplication │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── myapplication │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.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 │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── myapplication │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── image1.jpeg ├── image2.gif ├── image3.gif ├── image4.gif ├── readme.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | My Application -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 'android-R' 7 | buildToolsVersion "29.0.2" 8 | 9 | defaultConfig { 10 | applicationId "com.example.myapplication" 11 | minSdkVersion 'R' 12 | targetSdkVersion 'R' 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility = 1.8 27 | targetCompatibility = 1.8 28 | } 29 | 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | implementation 'androidx.appcompat:appcompat:1.1.0' 36 | implementation 'androidx.core:core-ktx:1.2.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 38 | testImplementation 'junit:junit:4.12' 39 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 41 | } 42 | -------------------------------------------------------------------------------- /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/example/myapplication/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.example.myapplication", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/myapplication/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import android.os.Bundle 4 | import android.os.Looper 5 | import android.util.Log 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.WindowInsets 9 | import android.view.WindowInsetsAnimation 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.core.view.updateLayoutParams 12 | import androidx.core.view.updateMargins 13 | import kotlinx.android.synthetic.main.activity_main.* 14 | 15 | 16 | class MainActivity : AppCompatActivity() { 17 | private val TAG = "MainActivity" 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | window.setDecorFitsSystemWindows(false) 21 | setContentView(R.layout.activity_main) 22 | content.postDelayed({ 23 | content.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) 24 | },5000) 25 | content.setOnApplyWindowInsetsListener { view, windowInsets -> 26 | val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars()) 27 | // It's also possible to use multiple types 28 | val insets = windowInsets.getInsets(WindowInsets.Type.navigationBars()) 29 | val insets2 = windowInsets.getInsets(WindowInsets.Type.ime()) 30 | Log.e( 31 | TAG, "navigationBars:" + insets.top + " " + insets.bottom + 32 | " " + windowInsets.isVisible(WindowInsets.Type.navigationBars()) 33 | ) 34 | Log.e(TAG, "statusBars:" + statusBars.top + " " + statusBars.bottom) 35 | Log.e(TAG, "insets2:" + insets2.top + " " + insets2.bottom) 36 | findViewById(android.R.id.content).setPadding(0, 0, 0, insets.bottom) 37 | windowInsets 38 | } 39 | val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { 40 | override fun onProgress( 41 | insets: WindowInsets, 42 | animations: MutableList 43 | ): WindowInsets { 44 | val navigationBars = insets.getInsets(WindowInsets.Type.navigationBars()) 45 | val ime = insets.getInsets(WindowInsets.Type.ime()) 46 | Log.e( 47 | TAG, "ime:" + ime.top + 48 | " " + ime.bottom 49 | ) 50 | val parmas = (content.layoutParams as ViewGroup.MarginLayoutParams) 51 | parmas.bottomMargin = ime.bottom - navigationBars.bottom 52 | content.layoutParams = parmas 53 | return insets 54 | } 55 | 56 | override fun onStart( 57 | animation: WindowInsetsAnimation, 58 | bounds: WindowInsetsAnimation.Bounds 59 | ): WindowInsetsAnimation.Bounds { 60 | Log.e( 61 | TAG, 62 | "start lowerBound:" + bounds.lowerBound.top + " " + bounds.lowerBound.bottom 63 | ) 64 | Log.e( 65 | TAG, 66 | "start upperBound:" + bounds.upperBound.top + " " + bounds.upperBound.bottom 67 | ) 68 | Log.e(TAG, "start time:" + animation.durationMillis) 69 | return super.onStart(animation, bounds) 70 | } 71 | 72 | override fun onEnd(animation: WindowInsetsAnimation) { 73 | Log.e(TAG, "end:" + animation.durationMillis) 74 | super.onEnd(animation) 75 | } 76 | 77 | override fun onPrepare(animation: WindowInsetsAnimation) { 78 | Log.e(TAG, "onPrepare:" + animation.durationMillis) 79 | super.onPrepare(animation) 80 | } 81 | 82 | } 83 | content.setWindowInsetsAnimationCallback(callback) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/myapplication/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.61' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.6.1' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 30 14:38:52 CST 2020 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-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /image1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/image1.jpeg -------------------------------------------------------------------------------- /image2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/image2.gif -------------------------------------------------------------------------------- /image3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/image3.gif -------------------------------------------------------------------------------- /image4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siyehua/WindowInsetsAnimation/1f36cc229d3cd4e7f39f1ebf2d006beb5e96dca2/image4.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WindowInsets - 获取导航栏,状态栏,键盘的高度和状态 2 | ## 背景 3 | 最新的 Android Q(11) 推出了许多功能,有一个比较重要的功能(需梯子): 4 | [Synchronized IME transitions](https://android-developers.googleblog.com/2020/03/android-11-developer-preview-2.html) 5 | 6 | > A new set of APIs let you synchronize your app’s content with the IME (input method editor, aka soft keyboard) and system bars as they animate on and offscreen, making it much easier to create natural, intuitive and jank-free IME transitions. For frame-perfect transitions, a new insets animation listener notifies apps of per-frame changes to insets while the system bars or the IME animate. Additionally, apps can take control of the IME and system bar transitions through the WindowInsetsAnimationController API. For example, app-driven IME experiences let apps control the IME in response to overscrolling the app UI. Give these new IME transitions a try and let us know what other transitions are important to you. 7 | 8 | 按照文章的意思,可以监听键盘的高度变化,光介绍就非常让人激动人心. 9 |
凡是搞过键盘的同学都知道,监听 Android 键盘的高度非常复杂,网上的一些黑科技也只对某些场景,有些场景就是无法处理。 10 |
而且有一个非常关键的点:键盘只有完全弹出来了才知道高度,当我们想根据键盘的上升做一个动画时,就很难做到-无法知道键盘动画的时间和最终高度 11 | 12 | 我们来看看官方给的效果图: 13 | 14 | ![Synchronized IME transition through insets animation listener.](./image2.gif)![App-driven IME experience through WindowInsetsAnimationController.](./image4.gif) 15 | 16 | 话不多说,我们来测试一下 17 | 18 | ## 测试 19 | ### 引入 20 | 1. 前期准备 21 |
先将 Android Studio 和 Gradle, Android SDK 更新到最新, 我的版本分别是: 22 | Android Studio 3.6.1, Gradle 5.6.4, Android SDK R 23 | 24 |
有 Pixel 手机的直接更新到最新版本 Q ,没有的可以下载最新的 Q 镜像 25 | 26 | 2. 更新 gradle 配置 27 | 28 | ```groovy 29 | android { 30 | compileSdkVersion 'android-R' 31 | buildToolsVersion "29.0.2" 32 | 33 | defaultConfig { 34 | applicationId "com.example.myapplication" 35 | minSdkVersion 'R' 36 | targetSdkVersion 'R' 37 | versionCode 1 38 | versionName "1.0" 39 | 40 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 41 | } 42 | 43 | //.... 44 | 45 | } 46 | ``` 47 | 48 | ### 使用 49 | 1. 先设置 FitSystemWindows 为 false: 50 | 51 | ```kotlin 52 | //非常重要,没有这句话监听无法生效 53 | window.setDecorFitsSystemWindows(false) 54 | ``` 55 | 56 | 2. 再对 view 设置监听: 57 | 58 | ```kotlin 59 | class MainActivity : AppCompatActivity() { 60 | 61 | override fun onCreate(savedInstanceState: Bundle?) { 62 | super.onCreate(savedInstanceState) 63 | //非常重要,没有这句话监听无法生效 64 | window.setDecorFitsSystemWindows(false) 65 | setContentView(R.layout.activity_main) 66 | val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { 67 | override fun onProgress( 68 | insets: WindowInsets, 69 | animations: MutableList 70 | ): WindowInsets { 71 | Log.e("MainActivity", "ime:" + insets.getInsets(WindowInsets.Type.ime()).top + 72 | " " + insets.getInsets(WindowInsets.Type.ime()).bottom) 73 | return insets 74 | } 75 | } 76 | content.setWindowInsetsAnimationCallback(callback) 77 | } 78 | } 79 | ``` 80 | 81 | 运行结果: 82 | 83 | ``` 84 | MainActivity: ime:0 0 85 | MainActivity: ime:0 0 86 | MainActivity: ime:0 9 87 | MainActivity: ime:0 37 88 | MainActivity: ime:0 98 89 | MainActivity: ime:0 207 90 | MainActivity: ime:0 351 91 | MainActivity: ime:0 526 92 | MainActivity: ime:0 684 93 | MainActivity: ime:0 799 94 | MainActivity: ime:0 895 95 | MainActivity: ime:0 1020 96 | MainActivity: ime:0 1062 97 | MainActivity: ime:0 1095 98 | MainActivity: ime:0 1117 99 | MainActivity: ime:0 1134 100 | MainActivity: ime:0 1146 101 | MainActivity: ime:0 1152 102 | MainActivity: ime:0 1155 103 | ``` 104 | 105 | 可以看到很清晰的打印出了键盘的每一帧高度,这样我们就可以根据高度回调,实现文章开头的效果 106 | 107 | 对比微信键盘弹出和 Android Q 的键盘弹出,可以看到微信的上升和下降,都不是完全吻合,但是 Android Q 一直是稳稳的贴着: 108 | 109 | ![微信](./image1.jpeg)![Android Q](./image3.gif) 110 | 111 | ### 详细代码 112 | 113 | 114 | ```kotlin 115 | class MainActivity : AppCompatActivity() { 116 | override fun onCreate(savedInstanceState: Bundle?) { 117 | super.onCreate(savedInstanceState) 118 | //非常重要,没有这句话监听无法生效 119 | window.setDecorFitsSystemWindows(false) 120 | setContentView(R.layout.activity_main) 121 | val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { 122 | override fun onProgress( 123 | insets: WindowInsets, 124 | animations: MutableList 125 | ): WindowInsets { 126 | val navigationBars = insets.getInsets(WindowInsets.Type.navigationBars()) 127 | val ime = insets.getInsets(WindowInsets.Type.ime()) 128 | Log.e( 129 | TAG, "ime:" + ime.top + 130 | " " + ime.bottom 131 | ) 132 | val parmas = (content.layoutParams as ViewGroup.MarginLayoutParams) 133 | parmas.bottomMargin = ime.bottom - navigationBars.bottom 134 | content.layoutParams = parmas 135 | return insets 136 | return insets 137 | } 138 | } 139 | content.setWindowInsetsAnimationCallback(callback) 140 | } 141 | } 142 | ``` 143 | 144 | ## 分析 145 | 146 | 除了上面例子用到的 onProgress() 方法,WindowInsetsAnimation.Callback 还有其他的属性和方法值得关注: 147 | 148 | 1. 分发方式 149 | 150 | 构造 WindowInsetsAnimation.Callback(int) 传入一个int 值表示分发方式,目前有两个值: 151 | 152 | * DISPATCH_MODE_CONTINUE_ON_SUBTREE :继续分发动画事件 153 | * DISPATCH_MODE_STOP :不再分发 154 | 155 | 这两个值和 view 的事件分发很类似,这里就不多解释了。 156 | 157 | 2. 键盘弹出的开始和结束 158 | 159 | 假设需求仅仅是想获取键盘的高度,不需要实时获取高度变化,可以重写 start() 方法 160 | 161 | ```kotlin 162 | val callback = object : WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) { 163 | override fun onStart( 164 | animation: WindowInsetsAnimation, 165 | bounds: WindowInsetsAnimation.Bounds 166 | ): WindowInsetsAnimation.Bounds { 167 | Log.e(TAG,"start lowerBound:" + bounds.lowerBound.top + " " + bounds.lowerBound.bottom) 168 | Log.e( TAG,"start upperBound:" + bounds.upperBound.top + " " + bounds.upperBound.bottom) 169 | Log.e(TAG, "start time:" + animation.durationMillis) 170 | return super.onStart(animation, bounds) 171 | } 172 | } 173 | ``` 174 | 175 | 其中 bounds 表示目标对象,从里面可以拿到动画结束后键盘有多高。 176 |
animation 中可以获取动画的执行时间,透明度等等。 177 | 178 | ## 导航栏,状态栏高度和状态 179 | 180 | 获取高度之前先来了解一下 WindowInsets.Type 有什么类型,上面我们用到了 ime() 是键盘,除了键盘,还有其他的类型,包括: 181 | 182 | ``` 183 | android.view.WindowInsets.Type.STATUS_BARS, //状态栏 184 | android.view.WindowInsets.Type.NAVIGATION_BARS, //导航栏 185 | android.view.WindowInsets.Type.CAPTION_BAR, 186 | android.view.WindowInsets.Type.IME, //键盘 187 | android.view.WindowInsets.Type.WINDOW_DECOR, 188 | android.view.WindowInsets.Type.SYSTEM_GESTURES, 189 | android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES, 190 | and android.view.WindowInsets.Type.TAPPABLE_ELEMENT 191 | ``` 192 | 193 | 类型很多,我们通常关心键盘,状态栏和导航栏 194 | 195 | ### 获取高度和状态 196 | 197 | 在 Android Q 之前,获取状态栏高度通常是通过反射获取。但是有了 WindowInsets 就不用这么麻烦了: 198 | 199 | ```kotlin 200 | content.setOnApplyWindowInsetsListener { view, windowInsets -> 201 | //状态栏 202 | val statusBars = windowInsets.getInsets(WindowInsets.Type.statusBars()) 203 | //导航栏 204 | val navigationBars = windowInsets.getInsets(WindowInsets.Type.navigationBars()) 205 | //键盘 206 | val ime = windowInsets.getInsets(WindowInsets.Type.ime()) 207 | windowInsets 208 | } 209 | ``` 210 | 211 | 上面代码可以获取导航栏和状态栏的高度,假设要获取隐藏和显示,可以通过: 212 | 213 | ```kotlin 214 | //注意:setOnApplyWindowInsetsListener 一设置监听就会回调,此时获取的 navigationBars 是否可见是 false 215 | //等绘制完成再去获取就是 true,这个稍微比较坑一点 216 | windowInsets.isVisible(WindowInsets.Type.navigationBars()) 217 | ``` 218 | 219 | ### 控制各种状态栏的显示和隐藏 220 | 221 | 在 Q 之前,控制导航栏和状态,需要用上各种谜之属性: 222 | 223 | ```kotlin 224 | view.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or 225 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or 226 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 227 | ``` 228 | 229 | 新版是这样的: 230 | 231 | ```kotlin 232 | //对状态栏和键盘也可以同样控制 233 | content.windowInsetsController?.show(WindowInsets.Type.navigationBars()) 234 | content.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) 235 | ``` 236 | 237 | ## 其他 238 | 239 | 一番测试下来,新版的 API 对于之前来说,可以说是非常好用了。 240 |
目前存在以下几个问题: 241 | 242 | 1. 不支持旧版 243 |
如果仅是在 Android Q 上使用,那这个工具就没这么香了,希望能通过 androidx 或 support 方式支持 244 | 245 | 2. FitSystemWindows 246 |
在有虚拟导航栏的手机上,FitSystemWindows 设置为 false,会强制改变 Activity 与导航栏的关系。 247 |
默认情况下, Activity 在导航栏的上面,它们处于同一层,但是设置为 false 之后,导航栏会直接覆盖在 Activity 的上面。 248 | 不过这可以通过给 Activity 的 parent 设置一个 padding 来解决。 249 | 250 | 3. 等 Q 发布后,还需要测试国产 ROM,以及第三方键盘的兼容性 251 | 252 | ## 总结 253 | 254 | 总的来说这个工具的出现,使获取,管理键盘等 APP 以外的装饰都变得非常友好。 255 |
由于 Android Q 现在还是预览版,还没有公布源码,暂时不知道其内部实现原理,等后期公布了源码再分析一下原理 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='My Application' 2 | include ':app' 3 | --------------------------------------------------------------------------------