├── .gitignore ├── README.md ├── build.gradle ├── gradle-mvn-push.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── me │ └── zhanghai │ └── android │ └── materialedittext │ ├── BaseDrawable.java │ ├── BasePaintDrawable.java │ ├── LinearRipple.java │ ├── MaterialEditText.java │ ├── MaterialEditTextBackgroundDrawable.java │ ├── MaterialTextInputLayout.java │ ├── TintableDrawable.java │ └── internal │ ├── FloatProperty.java │ ├── MathUtils.java │ ├── ThemeUtils.java │ └── ViewCompat.java ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── me │ │ └── zhanghai │ │ └── android │ │ └── materialedittext │ │ └── sample │ │ ├── AboutActivity.java │ │ ├── AppUtils.java │ │ └── MainActivity.java │ ├── launcher_icon-web.png │ └── res │ ├── layout │ ├── about_activity.xml │ └── main_activity.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ └── launcher_icon.png │ ├── mipmap-mdpi │ └── launcher_icon.png │ ├── mipmap-xhdpi │ └── launcher_icon.png │ ├── mipmap-xxhdpi │ └── launcher_icon.png │ ├── mipmap-xxxhdpi │ └── launcher_icon.png │ ├── values-sw600dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── screenshot ├── materialedittext.gif ├── materialedittext.mp4 ├── native-edittext.gif ├── native-edittext.mp4 ├── screenshot-raw.png └── screenshot.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle/ 2 | /.idea/ 3 | /build/ 4 | /captures/ 5 | /local.properties 6 | .DS_Store 7 | *.iml 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaterialEditText 2 | 3 | Material Design `EditText` with a delightful linear ripple in background, compatible with Android 4.0+. 4 | 5 | ## Preview 6 | 7 | Native `EditText` (on Samsung S4 with Android 5.0.1): 8 | 9 | ![NativeEditText](screenshot/native-edittext.gif) 10 | 11 | `MaterialEditText`: 12 | 13 | ![MaterialEditText](screenshot/materialedittext.gif) 14 | 15 | Sample screenshot: 16 | 17 | ![Screenshot](screenshot/screenshot.png) 18 | 19 | [Sample APK](//github.com/zhanghai/MaterialEditText/releases/download/v1.0.5/sample-release.apk) 20 | 21 | ## Design 22 | 23 | This library took the framework implementation of `RippleDrawable` as reference to implement an `EditText` background with a ripple on the line. The `MaterialEditTextBackgroundDrawable` is made to behave just the same as the framework XML implementation of `EditText` background, so that a drop-in replacement should not affect your layout and alignment. 24 | 25 | For simplicity of usage, a `MaterialEditText` is created to set the background automatically, and set the hotspot of ripple on platforms prior to Lollipop. 26 | 27 | The `InputTextLayout` from Design Support Library can automatically set the red tint on background when an error is set. An `MaterialInputTextLayout` is introduced to make the background drawable show a bold red line consistent with Material Design Guideline when an error is set. 28 | 29 | ## Integration 30 | 31 | Gradle: 32 | 33 | ```gradle 34 | compile 'me.zhanghai.android.materialedittext:library:1.0.5' 35 | ``` 36 | 37 | ## Usage 38 | 39 | Simply replace your `EditText` with `MaterialEditText` and `TextInputLayout` with `MaterialTextInputLayout`, then you can enjoy the delightful detail now. 40 | 41 | For example: 42 | 43 | ```xml 44 | 47 | 48 | 51 | 52 | ``` 53 | 54 | ## ProGuard 55 | 56 | The AAR of this library has already included a ProGuard configuration file to make `ObjectAnimator` work properly. 57 | 58 | ## Older versions 59 | 60 | Neither Support v4 nor AppCompat v7 backported animation API to versions prior to ICS, and the [NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids/) library has already been deprecated since people should all be using `minSdkVersion="14"` now, so versions older than ICS are not supported. 61 | 62 | ## License 63 | 64 | Copyright 2015 Zhang Hai 65 | 66 | Licensed under the Apache License, Version 2.0 (the "License"); 67 | you may not use this file except in compliance with the License. 68 | You may obtain a copy of the License at 69 | 70 | http://www.apache.org/licenses/LICENSE-2.0 71 | 72 | Unless required by applicable law or agreed to in writing, software 73 | distributed under the License is distributed on an "AS IS" BASIS, 74 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 75 | See the License for the specific language governing permissions and 76 | limitations under the License. 77 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | version = VERSION_NAME 18 | group = GROUP 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : 40 | System.console() != null ? System.console().readLine("\nNexus password: ") : "" 41 | } 42 | 43 | afterEvaluate { project -> 44 | uploadArchives { 45 | repositories { 46 | mavenDeployer { 47 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 48 | 49 | pom.groupId = GROUP 50 | pom.artifactId = POM_ARTIFACT_ID 51 | pom.version = VERSION_NAME 52 | 53 | repository(url: getReleaseRepositoryUrl()) { 54 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 55 | } 56 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 57 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 58 | } 59 | 60 | pom.project { 61 | name POM_NAME 62 | packaging POM_PACKAGING 63 | description POM_DESCRIPTION 64 | url POM_URL 65 | 66 | scm { 67 | url POM_SCM_URL 68 | connection POM_SCM_CONNECTION 69 | developerConnection POM_SCM_DEV_CONNECTION 70 | } 71 | 72 | licenses { 73 | license { 74 | name POM_LICENCE_NAME 75 | url POM_LICENCE_URL 76 | distribution POM_LICENCE_DIST 77 | } 78 | } 79 | 80 | developers { 81 | developer { 82 | id POM_DEVELOPER_ID 83 | name POM_DEVELOPER_NAME 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | signing { 92 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 93 | sign configurations.archives 94 | } 95 | 96 | gradle.taskGraph.whenReady { taskGraph -> 97 | if (taskGraph.allTasks.any { it instanceof Sign }) { 98 | allprojects { 99 | ext."signing.password" = System.console() != null ? System.console().readLine("\nPGP private key password: ") : "" 100 | } 101 | } 102 | } 103 | 104 | task androidJavadocs(type: Javadoc) { 105 | source = android.sourceSets.main.java.srcDirs 106 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 107 | classpath += configurations.compile 108 | } 109 | 110 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 111 | classifier = 'javadoc' 112 | from androidJavadocs.destinationDir 113 | } 114 | 115 | task androidSourcesJar(type: Jar) { 116 | classifier = 'sources' 117 | from android.sourceSets.main.java.sourceFiles 118 | } 119 | 120 | artifacts { 121 | archives androidSourcesJar 122 | archives androidJavadocsJar 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /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 19 | 20 | VERSION_NAME=1.0.5 21 | VERSION_CODE=6 22 | GROUP=me.zhanghai.android.materialedittext 23 | 24 | POM_DESCRIPTION=A Material Design EditTextBar with consistent appearance 25 | POM_URL=https://github.com/zhanghai/MaterialEditText 26 | POM_SCM_URL=https://github.com/zhanghai/MaterialEditText 27 | POM_SCM_CONNECTION=scm:git@github.com:zhanghai/MaterialEditText.git 28 | POM_SCM_DEV_CONNECTION=scm:git@github.com:zhanghai/MaterialEditText.git 29 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 31 | POM_LICENCE_DIST=repo 32 | POM_DEVELOPER_ID=zhanghai 33 | POM_DEVELOPER_NAME=Zhang Hai 34 | 35 | ANDROID_MIN_SDK_VERSION=14 36 | ANDROID_COMPILE_SDK_VERSION=28 37 | ANDROID_BUILD_TOOLS_VERSION=28.0.3 38 | ANDROID_TARGET_SDK_VERSION=28 39 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 07 22:22:29 PDT 2019 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.4.1-all.zip 7 | -------------------------------------------------------------------------------- /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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | 8 | android { 9 | 10 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 11 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 12 | 13 | defaultConfig { 14 | minSdkVersion Integer.parseInt(project.ANDROID_MIN_SDK_VERSION) 15 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 16 | versionCode Integer.parseInt(project.VERSION_CODE) 17 | versionName project.VERSION_NAME 18 | consumerProguardFiles 'proguard-rules.pro' 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | } 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation 'com.android.support:appcompat-v7:28.0.0' 36 | implementation 'com.android.support:design:28.0.0' 37 | implementation 'com.android.support:support-annotations:28.0.0' 38 | } 39 | 40 | apply from: '../gradle-mvn-push.gradle' 41 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=MaterialEditText Library 2 | POM_ARTIFACT_ID=library 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /library/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 /opt/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | -keep class me.zhanghai.android.materialedittext.** { *; } 20 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/BaseDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.content.res.ColorStateList; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.ColorFilter; 12 | import android.graphics.PixelFormat; 13 | import android.graphics.PorterDuff; 14 | import android.graphics.PorterDuffColorFilter; 15 | import android.graphics.Rect; 16 | import android.graphics.drawable.Drawable; 17 | import android.support.annotation.ColorInt; 18 | import android.support.annotation.NonNull; 19 | import android.support.annotation.Nullable; 20 | 21 | abstract class BaseDrawable extends Drawable implements TintableDrawable { 22 | 23 | protected int mAlpha = 0xFF; 24 | protected ColorFilter mColorFilter; 25 | protected ColorStateList mTintList; 26 | protected PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN; 27 | protected PorterDuffColorFilter mTintFilter; 28 | 29 | private DummyConstantState mConstantState = new DummyConstantState(); 30 | 31 | @Override 32 | public int getAlpha() { 33 | return mAlpha; 34 | } 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | @Override 40 | public void setAlpha(int alpha) { 41 | if (mAlpha != alpha) { 42 | mAlpha = alpha; 43 | invalidateSelf(); 44 | } 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | @Override 51 | public ColorFilter getColorFilter() { 52 | return mColorFilter; 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | @Override 59 | public void setColorFilter(@Nullable ColorFilter colorFilter) { 60 | mColorFilter = colorFilter; 61 | invalidateSelf(); 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | */ 67 | @Override 68 | public void setTint(@ColorInt int tintColor) { 69 | setTintList(ColorStateList.valueOf(tintColor)); 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | */ 75 | @Override 76 | public void setTintList(@Nullable ColorStateList tint) { 77 | mTintList = tint; 78 | if (updateTintFilter()) { 79 | invalidateSelf(); 80 | } 81 | } 82 | 83 | /** 84 | * {@inheritDoc} 85 | */ 86 | @Override 87 | public void setTintMode(@NonNull PorterDuff.Mode tintMode) { 88 | mTintMode = tintMode; 89 | if (updateTintFilter()) { 90 | invalidateSelf(); 91 | } 92 | } 93 | 94 | @Override 95 | public boolean isStateful() { 96 | return mTintList != null && mTintList.isStateful(); 97 | } 98 | 99 | @Override 100 | protected boolean onStateChange(int[] state) { 101 | return updateTintFilter(); 102 | } 103 | 104 | private boolean updateTintFilter() { 105 | 106 | if (mTintList == null || mTintMode == null) { 107 | boolean hadTintFilter = mTintFilter != null; 108 | mTintFilter = null; 109 | return hadTintFilter; 110 | } 111 | 112 | int tintColor = mTintList.getColorForState(getState(), Color.TRANSPARENT); 113 | // They made PorterDuffColorFilter.setColor() and setMode() @hide. 114 | mTintFilter = new PorterDuffColorFilter(tintColor, mTintMode); 115 | return true; 116 | } 117 | 118 | /** 119 | * {@inheritDoc} 120 | */ 121 | @Override 122 | public int getOpacity() { 123 | // Be safe. 124 | return PixelFormat.TRANSLUCENT; 125 | } 126 | 127 | /** 128 | * {@inheritDoc} 129 | */ 130 | @Override 131 | public void draw(Canvas canvas) { 132 | 133 | Rect bounds = getBounds(); 134 | if (bounds.width() == 0 || bounds.height() == 0) { 135 | return; 136 | } 137 | 138 | int saveCount = canvas.save(); 139 | canvas.translate(bounds.left, bounds.top); 140 | onDraw(canvas, bounds.width(), bounds.height()); 141 | canvas.restoreToCount(saveCount); 142 | } 143 | 144 | protected ColorFilter getColorFilterForDrawing() { 145 | return mColorFilter != null ? mColorFilter : mTintFilter; 146 | } 147 | 148 | protected abstract void onDraw(Canvas canvas, int width, int height); 149 | 150 | // Workaround TextInputLayout's workaround which calls bg.getConstantState().newDrawable() 151 | // without checking for null. 152 | // We are never inflated from XML so the protocol of ConstantState does not apply to us. In 153 | // order to make TextInputLayout happy, we return ourselves from 154 | // DummyConstantState.newDrawable(). 155 | 156 | @Override 157 | public ConstantState getConstantState() { 158 | return mConstantState; 159 | } 160 | 161 | private class DummyConstantState extends ConstantState { 162 | 163 | @Override 164 | public int getChangingConfigurations() { 165 | return 0; 166 | } 167 | 168 | @NonNull 169 | @Override 170 | public Drawable newDrawable() { 171 | return BaseDrawable.this; 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/BasePaintDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | 12 | public abstract class BasePaintDrawable extends BaseDrawable { 13 | 14 | private Paint mPaint; 15 | 16 | @Override 17 | protected final void onDraw(Canvas canvas, int width, int height) { 18 | 19 | if (mPaint == null) { 20 | mPaint = new Paint(); 21 | mPaint.setAntiAlias(true); 22 | mPaint.setColor(Color.BLACK); 23 | onPreparePaint(mPaint); 24 | } 25 | mPaint.setAlpha(mAlpha); 26 | mPaint.setColorFilter(getColorFilterForDrawing()); 27 | 28 | onDraw(canvas, width, height, mPaint); 29 | } 30 | 31 | protected abstract void onPreparePaint(Paint paint); 32 | 33 | protected abstract void onDraw(Canvas canvas, int width, int height, Paint paint); 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/LinearRipple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.animation.Animator; 9 | import android.animation.AnimatorSet; 10 | import android.animation.ObjectAnimator; 11 | import android.animation.TimeInterpolator; 12 | import android.graphics.Canvas; 13 | import android.graphics.Paint; 14 | import android.graphics.Rect; 15 | import android.graphics.drawable.Drawable; 16 | import android.view.animation.LinearInterpolator; 17 | 18 | import me.zhanghai.android.materialedittext.internal.FloatProperty; 19 | import me.zhanghai.android.materialedittext.internal.MathUtils; 20 | 21 | /** 22 | * A linear ripple implemented according to the framework implementation of 23 | * {@code RippleForeground}. 24 | * 25 | * @see RippleForeground@f872ee 26 | */ 27 | class LinearRipple { 28 | 29 | private static final float WAVE_TOUCH_DOWN_ACCELERATION_DP = 1024; 30 | private static final float WAVE_TOUCH_UP_ACCELERATION_DP = 3400; 31 | private static final float WAVE_OPACITY_DECAY_VELOCITY = 3; 32 | 33 | private static final int RIPPLE_ENTER_DELAY = 80; 34 | private static final int OPACITY_ENTER_DURATION = 120; 35 | 36 | private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 37 | private static final TimeInterpolator DECELERATE_INTERPOLATOR = new LogDecelerateInterpolator( 38 | 400f, 1.4f, 0); 39 | 40 | private final float mWaveTouchDownAcceleration; 41 | private final float mWaveTouchUpAcceleration; 42 | 43 | private Drawable mOwner; 44 | private Rect mBounds; 45 | private float mStartingPosition; 46 | 47 | private float mTargetPosition; 48 | private float mTargetRadius; 49 | 50 | private float mTweenRipple; 51 | private float mOpacity = 1; 52 | 53 | private Animator mAnimator; 54 | 55 | public LinearRipple(Drawable owner, Rect bounds, float position, float density) { 56 | 57 | mWaveTouchDownAcceleration = WAVE_TOUCH_DOWN_ACCELERATION_DP * density; 58 | mWaveTouchUpAcceleration = WAVE_TOUCH_UP_ACCELERATION_DP * density; 59 | 60 | mOwner = owner; 61 | onBoundsChange(bounds); 62 | mStartingPosition = position; 63 | } 64 | 65 | public void onBoundsChange(Rect bounds) { 66 | mBounds = bounds; 67 | mTargetRadius = mBounds.width() / 2f; 68 | mTargetPosition = mBounds.left + mTargetRadius; 69 | } 70 | 71 | public void enter() { 72 | mAnimator = createEnterAnimation(); 73 | mAnimator.start(); 74 | } 75 | 76 | private int getRippleEnterDuration() { 77 | return (int) (1000 * Math.sqrt(mTargetRadius / mWaveTouchDownAcceleration) + 0.5); 78 | } 79 | 80 | private Animator createEnterAnimation() { 81 | 82 | ObjectAnimator tweenRipple = ObjectAnimator.ofFloat(this, TWEEN_RIPPLE, 1); 83 | int duration = getRippleEnterDuration(); 84 | //tweenRipple.setAutoCancel(true); 85 | tweenRipple.setDuration(duration); 86 | tweenRipple.setInterpolator(LINEAR_INTERPOLATOR); 87 | tweenRipple.setStartDelay(RIPPLE_ENTER_DELAY); 88 | 89 | ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1); 90 | //opacity.setAutoCancel(true); 91 | opacity.setDuration(OPACITY_ENTER_DURATION); 92 | opacity.setInterpolator(LINEAR_INTERPOLATOR); 93 | 94 | AnimatorSet set = new AnimatorSet(); 95 | set.play(tweenRipple).with(opacity); 96 | 97 | return set; 98 | } 99 | 100 | public void moveTo(float position) { 101 | mStartingPosition = position; 102 | } 103 | 104 | public void fill() { 105 | 106 | cancelAnimation(); 107 | 108 | if (hasFilled()) { 109 | return; 110 | } 111 | 112 | mAnimator = createFillAnimation(); 113 | mAnimator.start(); 114 | } 115 | 116 | public boolean hasFilled() { 117 | return mTweenRipple == 1 && mOpacity == 1; 118 | } 119 | 120 | private float getCurrentRadius() { 121 | return MathUtils.lerp(0, mTargetRadius, mTweenRipple); 122 | } 123 | 124 | private int getRippleFillOrExitDuration() { 125 | float radius = getCurrentRadius(); 126 | float remaining = mTargetRadius - radius; 127 | return (int) (1000 * Math.sqrt(2 * remaining / 128 | (mWaveTouchUpAcceleration + mWaveTouchDownAcceleration)) + 0.5); 129 | } 130 | 131 | private Animator createFillAnimation() { 132 | ObjectAnimator tweenRipple = ObjectAnimator.ofFloat(this, TWEEN_RIPPLE, 1); 133 | //tweenRipple.setAutoCancel(true); 134 | tweenRipple.setDuration(getRippleFillOrExitDuration()); 135 | tweenRipple.setInterpolator(DECELERATE_INTERPOLATOR); 136 | return tweenRipple; 137 | } 138 | 139 | public void makeFilled() { 140 | 141 | mTweenRipple = 1; 142 | mOpacity = 1; 143 | 144 | invalidateSelf(); 145 | } 146 | 147 | public void exit() { 148 | 149 | cancelAnimation(); 150 | 151 | if (hasExited()) { 152 | return; 153 | } 154 | 155 | mAnimator = createExitAnimation(); 156 | mAnimator.start(); 157 | } 158 | 159 | public boolean hasExited() { 160 | return mOpacity == 0; 161 | } 162 | 163 | private int getOpacityExitDuration() { 164 | return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f); 165 | } 166 | 167 | private Animator createExitAnimation() { 168 | 169 | ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 0); 170 | //opacity.setAutoCancel(true); 171 | opacity.setDuration(getOpacityExitDuration()); 172 | opacity.setInterpolator(LINEAR_INTERPOLATOR); 173 | 174 | if (hasFilled()) { 175 | return opacity; 176 | } else { 177 | 178 | ObjectAnimator tweenRipple = ObjectAnimator.ofFloat(this, TWEEN_RIPPLE, 1); 179 | //tweenRipple.setAutoCancel(true); 180 | tweenRipple.setDuration(getRippleFillOrExitDuration()); 181 | tweenRipple.setInterpolator(DECELERATE_INTERPOLATOR); 182 | 183 | AnimatorSet set = new AnimatorSet(); 184 | set.play(tweenRipple).with(opacity); 185 | return set; 186 | } 187 | } 188 | 189 | public void cancelAnimation() { 190 | stopAnimation(true); 191 | } 192 | 193 | public void endAnimation() { 194 | stopAnimation(false); 195 | } 196 | 197 | private void stopAnimation(boolean cancel) { 198 | if (mAnimator == null) { 199 | return; 200 | } 201 | 202 | if (cancel) { 203 | mAnimator.cancel(); 204 | } else { 205 | mAnimator.end(); 206 | } 207 | mAnimator = null; 208 | } 209 | 210 | public void draw(Canvas canvas, Paint paint) { 211 | 212 | int origAlpha = paint.getAlpha(); 213 | int alpha = (int) (origAlpha * mOpacity + 0.5f); 214 | float radius = getCurrentRadius(); 215 | if (alpha <= 0 || radius <= 0) { 216 | return; 217 | } 218 | 219 | float position = MathUtils.lerp(mStartingPosition, mTargetPosition, mTweenRipple); 220 | float left = MathUtils.constrain(position - radius, mBounds.left, mBounds.right); 221 | float right = MathUtils.constrain(position + radius, mBounds.left, mBounds.right); 222 | paint.setAlpha(alpha); 223 | canvas.drawRect(left, mBounds.top, right, mBounds.bottom, paint); 224 | paint.setAlpha(origAlpha); 225 | } 226 | 227 | private void invalidateSelf() { 228 | mOwner.invalidateSelf(); 229 | } 230 | 231 | /** 232 | * From {@code android.graphics.drawable.RippleForeground.LogDecelerateInterpolator}. 233 | * 234 | * Interpolator with a smooth log deceleration. 235 | */ 236 | private static class LogDecelerateInterpolator implements TimeInterpolator { 237 | 238 | private float mBase; 239 | private float mDrift; 240 | private float mTimeScale; 241 | private float mOutputScale; 242 | 243 | public LogDecelerateInterpolator(float base, float timeScale, float drift) { 244 | mBase = base; 245 | mDrift = drift; 246 | mTimeScale = 1f / timeScale; 247 | mOutputScale = 1f / computeLog(1f); 248 | } 249 | 250 | private float computeLog(float input) { 251 | return 1f - MathUtils.pow(mBase, -input * mTimeScale) + (mDrift * input); 252 | } 253 | 254 | @Override 255 | public float getInterpolation(float input) { 256 | return computeLog(input) * mOutputScale; 257 | } 258 | } 259 | 260 | /** 261 | * Property for animating position and radius between its initial and target values. 262 | */ 263 | private static final FloatProperty TWEEN_RIPPLE = 264 | new FloatProperty("tweenRipple") { 265 | 266 | @Override 267 | public Float get(LinearRipple object) { 268 | return object.mTweenRipple; 269 | } 270 | 271 | @Override 272 | public void setValue(LinearRipple object, float value) { 273 | object.mTweenRipple = value; 274 | object.invalidateSelf(); 275 | } 276 | }; 277 | 278 | /** 279 | * Property for animating opacity between 0 and its target value. 280 | */ 281 | private static final FloatProperty OPACITY = 282 | new FloatProperty("opacity") { 283 | 284 | @Override 285 | public Float get(LinearRipple object) { 286 | return object.mOpacity; 287 | } 288 | 289 | @Override 290 | public void setValue(LinearRipple object, float value) { 291 | object.mOpacity = value; 292 | object.invalidateSelf(); 293 | } 294 | }; 295 | } 296 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/MaterialEditText.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.annotation.SuppressLint; 9 | import android.content.Context; 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Build; 12 | import android.support.design.widget.TextInputEditText; 13 | import android.util.AttributeSet; 14 | import android.view.MotionEvent; 15 | 16 | import me.zhanghai.android.materialedittext.internal.ViewCompat; 17 | 18 | /** 19 | * A {@code TextInputEditText} that automatically sets its background to 20 | * {@link MaterialEditTextBackgroundDrawable} and calls {@link Drawable#setHotspot} for it on 21 | * platforms prior to {@link Build.VERSION_CODES#LOLLIPOP}. 22 | */ 23 | public class MaterialEditText extends TextInputEditText { 24 | 25 | private MaterialEditTextBackgroundDrawable mBackground; 26 | 27 | public MaterialEditText(Context context) { 28 | super(context); 29 | 30 | init(); 31 | } 32 | 33 | public MaterialEditText(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | 36 | init(); 37 | } 38 | 39 | public MaterialEditText(Context context, AttributeSet attrs, int defStyleAttr) { 40 | super(context, attrs, defStyleAttr); 41 | 42 | init(); 43 | } 44 | 45 | private void init() { 46 | mBackground = new MaterialEditTextBackgroundDrawable(getContext()); 47 | ViewCompat.setBackground(this, mBackground); 48 | } 49 | 50 | @Override 51 | @SuppressLint("NewApi") 52 | public boolean onTouchEvent(MotionEvent event) { 53 | boolean consumed = super.onTouchEvent(event); 54 | 55 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 56 | int action = event.getAction(); 57 | if (isEnabled() && (isClickable() || isLongClickable()) 58 | && (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE)) { 59 | mBackground.setHotspot(event.getX(), event.getY()); 60 | } 61 | } 62 | 63 | return consumed; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/MaterialEditTextBackgroundDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.annotation.SuppressLint; 9 | import android.annotation.TargetApi; 10 | import android.content.Context; 11 | import android.content.res.ColorStateList; 12 | import android.content.res.Resources; 13 | import android.graphics.Canvas; 14 | import android.graphics.Color; 15 | import android.graphics.Outline; 16 | import android.graphics.Paint; 17 | import android.graphics.Rect; 18 | import android.graphics.drawable.Drawable; 19 | import android.os.Build; 20 | import android.support.annotation.NonNull; 21 | import android.util.Log; 22 | import android.widget.EditText; 23 | 24 | import me.zhanghai.android.materialedittext.internal.ThemeUtils; 25 | 26 | /** 27 | * A Material Design background {@link Drawable} for {@link EditText}. 28 | */ 29 | public class MaterialEditTextBackgroundDrawable extends BasePaintDrawable { 30 | 31 | private static final String TAG = MaterialEditTextBackgroundDrawable.class.getName(); 32 | 33 | private static final int INTRINSIC_WIDTH_DP = 20; 34 | private static final int INTRINSIC_HEIGHT_DP = 24; 35 | 36 | private static final int INTRINSIC_PADDING_HORIZONTAL = 4; 37 | private static final int INTRINSIC_PADDING_TOP = 10; 38 | private static final int INTRINSIC_PADDING_BOTTOM = 11; 39 | 40 | private static final int DRAWING_RECT_TOP_FROM_BOTTOM_DP = 9; 41 | private static final int DEFAULT_HEIGHT_DP = 1; 42 | private static final int ACTIVATED_HEIGHT_DP = 2; 43 | 44 | // As android.graphics.drawable.RippleDrawable.MAX_RIPPLES. 45 | private static final int MAX_RIPPLES = 10; 46 | 47 | private final Rect mPadding; 48 | 49 | private final int mIntrinsicWidth; 50 | private final int mIntrinsicHeight; 51 | private final int mDrawingRectTopFromBottom; 52 | 53 | private final int mDefaultHeight; 54 | private final int mActivatedHeight; 55 | 56 | private final ColorStateList mHintColorList; 57 | private final float mDisabledAlpha; 58 | 59 | private int mHintColor; 60 | private float mHintColorAlpha; 61 | 62 | private float mDensity; 63 | 64 | private Rect mDefaultRect = new Rect(); 65 | private Rect mActivatedRect = new Rect(); 66 | 67 | private Paint mDefaultPaint; 68 | 69 | private boolean mEnabled; 70 | private boolean mPressed; 71 | private boolean mFocused; 72 | private boolean mError; 73 | 74 | private boolean mHasPendingRipple = false; 75 | private float mPendingRipplePosition; 76 | 77 | private LinearRipple mEnteringRipple; 78 | private LinearRipple[] mFillingRipples = new LinearRipple[MAX_RIPPLES]; 79 | private int mFillingRippleCount; 80 | private LinearRipple mFilledRipple; 81 | private LinearRipple[] mExitingRipples = new LinearRipple[MAX_RIPPLES]; 82 | private int mExitingRippleCount; 83 | 84 | @SuppressLint("NewApi") 85 | public MaterialEditTextBackgroundDrawable(Context context) { 86 | 87 | int colorControlActivated = ThemeUtils.getColorFromAttrRes(R.attr.colorControlActivated, 88 | context); 89 | // setTint() has been overridden for compatibility; DrawableCompat won't work because 90 | // wrapped Drawable won't be Animatable. 91 | setTint(colorControlActivated); 92 | 93 | Resources resources = context.getResources(); 94 | mDensity = resources.getDisplayMetrics().density; 95 | 96 | mIntrinsicWidth = (int) (INTRINSIC_WIDTH_DP * mDensity + 0.5f); 97 | mIntrinsicHeight = (int) (INTRINSIC_HEIGHT_DP * mDensity + 0.5f); 98 | 99 | // As in android.util.TypedValue.complexToDimensionPixelOffset(). 100 | int paddingHorizontal = (int) (INTRINSIC_PADDING_HORIZONTAL * mDensity); 101 | int paddingTop = (int) (INTRINSIC_PADDING_TOP * mDensity); 102 | int paddingBottom = (int) (INTRINSIC_PADDING_BOTTOM * mDensity); 103 | mPadding = new Rect(paddingHorizontal, paddingTop, paddingHorizontal, paddingBottom); 104 | 105 | mDrawingRectTopFromBottom = (int) (DRAWING_RECT_TOP_FROM_BOTTOM_DP * mDensity + 0.5f); 106 | mDefaultHeight = (int) (DEFAULT_HEIGHT_DP * mDensity + 0.5f); 107 | mActivatedHeight = (int) (ACTIVATED_HEIGHT_DP * mDensity + 0.5f); 108 | 109 | mHintColorList = ThemeUtils.getColorStateListFromAttrRes(android.R.attr.textColorHint, 110 | context); 111 | updateHintColor(); 112 | mDisabledAlpha = ThemeUtils.getFloatFromAttrRes(android.R.attr.disabledAlpha, context); 113 | } 114 | 115 | /** 116 | * {@inheritDoc} 117 | */ 118 | @Override 119 | public int getIntrinsicWidth() { 120 | return mIntrinsicWidth; 121 | } 122 | 123 | /** 124 | * {@inheritDoc} 125 | */ 126 | @Override 127 | public int getIntrinsicHeight() { 128 | return mIntrinsicHeight; 129 | } 130 | 131 | /** 132 | * {@inheritDoc} 133 | */ 134 | @Override 135 | public boolean getPadding(@NonNull Rect padding) { 136 | padding.set(mPadding); 137 | return true; 138 | } 139 | 140 | /** 141 | * {@inheritDoc} 142 | */ 143 | @Override 144 | protected void onBoundsChange(Rect bounds) { 145 | 146 | int drawingRectLeft = bounds.left + mPadding.left; 147 | int drawingRectTop = bounds.bottom - mDrawingRectTopFromBottom; 148 | int drawingRectRight = bounds.right - mPadding.right; 149 | mDefaultRect.set(drawingRectLeft, drawingRectTop, drawingRectRight, 150 | drawingRectTop + mDefaultHeight); 151 | mActivatedRect.set(drawingRectLeft, drawingRectTop, drawingRectRight, 152 | drawingRectTop + mActivatedHeight); 153 | 154 | if (mEnteringRipple != null) { 155 | mEnteringRipple.onBoundsChange(mActivatedRect); 156 | } 157 | for (int i = 0; i < mFillingRippleCount; ++i) { 158 | mFillingRipples[i].onBoundsChange(mActivatedRect); 159 | } 160 | if (mFilledRipple != null) { 161 | mFilledRipple.onBoundsChange(mActivatedRect); 162 | } 163 | for (int i = 0; i < mExitingRippleCount; ++i) { 164 | mExitingRipples[i].onBoundsChange(mActivatedRect); 165 | } 166 | 167 | invalidateSelf(); 168 | } 169 | 170 | /** 171 | * {@inheritDoc} 172 | */ 173 | @Override 174 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 175 | public void getOutline(@NonNull Outline outline) { 176 | outline.setRect(hasRipple() ? mActivatedRect : mDefaultRect); 177 | } 178 | 179 | private boolean hasRipple() { 180 | return mEnteringRipple != null || mFillingRippleCount > 0 || mFilledRipple != null 181 | || mExitingRippleCount >= 0; 182 | } 183 | 184 | /** 185 | * {@inheritDoc} 186 | */ 187 | @Override 188 | public void setHotspot(float x, float y) { 189 | x -= mActivatedRect.left; 190 | if (mEnteringRipple == null) { 191 | mPendingRipplePosition = x; 192 | mHasPendingRipple = true; 193 | } else { 194 | mEnteringRipple.moveTo(x); 195 | } 196 | } 197 | 198 | /** 199 | * Get whether this drawable is in error state. The default is {@code false}. 200 | * 201 | * @return Whether this drawable is in error state. 202 | */ 203 | public boolean hasError() { 204 | return mError; 205 | } 206 | 207 | /** 208 | * Set whether this drawable is in error state. The default is {@code false}. 209 | * 210 | * @param error Whether this drawable should be in error state. 211 | */ 212 | public void setError(boolean error) { 213 | if (mError != error) { 214 | mError = error; 215 | onStateChanged(); 216 | } 217 | } 218 | 219 | /** 220 | * {@inheritDoc} 221 | */ 222 | @Override 223 | public boolean isStateful() { 224 | return true; 225 | } 226 | 227 | /** 228 | * {@inheritDoc} 229 | */ 230 | @Override 231 | protected boolean onStateChange(int[] stateSet) { 232 | 233 | updateHintColor(); 234 | 235 | mEnabled = false; 236 | mPressed = false; 237 | mFocused = false; 238 | for (int state : stateSet) { 239 | switch (state) { 240 | case android.R.attr.state_enabled: 241 | mEnabled = true; 242 | break; 243 | case android.R.attr.state_pressed: 244 | mPressed = true; 245 | break; 246 | case android.R.attr.state_focused: 247 | mFocused = true; 248 | break; 249 | } 250 | } 251 | 252 | onStateChanged(); 253 | 254 | // Be safe. 255 | return true; 256 | } 257 | 258 | private void updateHintColor() { 259 | mHintColor = mHintColorList.getColorForState(getState(), Color.TRANSPARENT); 260 | mHintColorAlpha = (float) Color.alpha(mHintColor) / 0xFF; 261 | } 262 | 263 | private void onStateChanged() { 264 | 265 | updateRipples(); 266 | 267 | // Branch into states and operate on each non-exiting group of ripple. 268 | if (!mEnabled || (!mPressed && !mFocused && !mError)) { 269 | // Disabled, or enabled and unpressed and unfocused. 270 | // Exit non-exiting ripples. 271 | exitRipples(); 272 | } else if (mPressed) { 273 | // Enabled and pressed. 274 | // Enter a new ripple if no ripple is entering or filled; leave filling, filled ripples 275 | // unchanged. 276 | if (mEnteringRipple == null && mFilledRipple == null) { 277 | createAndEnterRipple(); 278 | } 279 | } else { 280 | // Enabled, unpressed, focused || error. 281 | // Fill the entering ripple if exists. 282 | if (mEnteringRipple != null) { 283 | fillEnteringRipple(); 284 | } 285 | // Ensure a filling or filled ripple. 286 | if (mFillingRippleCount == 0 && mFilledRipple == null) { 287 | createFillingRipple(); 288 | } 289 | } 290 | 291 | invalidateSelf(); 292 | } 293 | 294 | private void removeEnteringRipple() { 295 | if (mEnteringRipple != null) { 296 | mEnteringRipple.endAnimation(); 297 | mEnteringRipple = null; 298 | } 299 | } 300 | 301 | private void removeFillingRipples() { 302 | for (int i = 0; i < mFillingRippleCount; ++i) { 303 | mFillingRipples[i].endAnimation(); 304 | mFillingRipples[i] = null; 305 | } 306 | mFillingRippleCount = 0; 307 | } 308 | 309 | private void removeFilledRipple() { 310 | mFilledRipple = null; 311 | } 312 | 313 | private void removeExitingRipples() { 314 | for (int i = 0; i < mExitingRippleCount; ++i) { 315 | mExitingRipples[i].endAnimation(); 316 | mExitingRipples[i] = null; 317 | } 318 | mExitingRippleCount = 0; 319 | } 320 | 321 | private void updateRipples() { 322 | 323 | // Update filled ripple. 324 | if (mFilledRipple == null) { 325 | if (mEnteringRipple != null && mEnteringRipple.hasFilled()) { 326 | mFilledRipple = mEnteringRipple; 327 | } 328 | } 329 | if (mFilledRipple == null) { 330 | for (int i = 0; i < mFillingRippleCount; ++i) { 331 | LinearRipple fillingRipple = mFillingRipples[i]; 332 | if (fillingRipple.hasFilled()) { 333 | mFilledRipple = mFillingRipples[i]; 334 | break; 335 | } 336 | } 337 | } 338 | 339 | if (mFilledRipple != null) { 340 | 341 | // Clear all ripples except for the filled one. 342 | removeEnteringRipple(); 343 | removeFillingRipples(); 344 | removeExitingRipples(); 345 | 346 | } else { 347 | 348 | // Remove exited ripples. 349 | int remaining = 0; 350 | for (int i = 0; i < mExitingRippleCount; ++i) { 351 | if (!mExitingRipples[i].hasExited()) { 352 | mExitingRipples[remaining++] = mExitingRipples[i]; 353 | } 354 | } 355 | for (int i = remaining; i < mExitingRippleCount; ++i) { 356 | mExitingRipples[i] = null; 357 | } 358 | mExitingRippleCount = remaining; 359 | } 360 | } 361 | 362 | private void exitRipple(LinearRipple ripple) { 363 | ripple.exit(); 364 | mExitingRipples[mExitingRippleCount++] = ripple; 365 | } 366 | 367 | private void exitRipples() { 368 | 369 | if (mEnteringRipple != null) { 370 | exitRipple(mEnteringRipple); 371 | mEnteringRipple = null; 372 | } 373 | 374 | for (int i = 0; i < mFillingRippleCount; ++i) { 375 | exitRipple(mFillingRipples[i]); 376 | mFillingRipples[i] = null; 377 | } 378 | mFillingRippleCount = 0; 379 | 380 | if (mFilledRipple != null) { 381 | exitRipple(mFilledRipple); 382 | mFilledRipple = null; 383 | } 384 | } 385 | 386 | private LinearRipple createRipple() { 387 | 388 | int rippleCount = 0; 389 | if (mEnteringRipple != null) { 390 | ++rippleCount; 391 | } 392 | rippleCount += mFillingRippleCount; 393 | if (mFilledRipple != null) { 394 | ++rippleCount; 395 | } 396 | rippleCount += mExitingRippleCount; 397 | if (rippleCount >= MAX_RIPPLES) { 398 | Log.w(TAG, "Too many ripples alive, skipping ripple creation"); 399 | return null; 400 | } 401 | 402 | float position; 403 | if (mHasPendingRipple) { 404 | mHasPendingRipple = false; 405 | position = mPendingRipplePosition; 406 | } else { 407 | position = mActivatedRect.exactCenterX(); 408 | } 409 | return new LinearRipple(this, mActivatedRect, position, mDensity); 410 | } 411 | 412 | private void createAndEnterRipple() { 413 | mEnteringRipple = createRipple(); 414 | if (mEnteringRipple != null) { 415 | mEnteringRipple.enter(); 416 | } 417 | } 418 | 419 | private void fillRipple(LinearRipple ripple) { 420 | ripple.fill(); 421 | mFillingRipples[mFillingRippleCount++] = ripple; 422 | } 423 | 424 | private void fillEnteringRipple() { 425 | 426 | // DEBUG: Remove this. 427 | if (mEnteringRipple == null) { 428 | throw new IllegalStateException("fillEnteringRipple() when mEnteringRipple is not null"); 429 | } 430 | 431 | fillRipple(mEnteringRipple); 432 | mEnteringRipple = null; 433 | } 434 | 435 | private void createFillingRipple() { 436 | LinearRipple ripple = createRipple(); 437 | if (ripple != null) { 438 | fillRipple(ripple); 439 | } 440 | } 441 | 442 | /** 443 | * {@inheritDoc} 444 | */ 445 | @Override 446 | public void jumpToCurrentState() { 447 | 448 | updateRipples(); 449 | 450 | removeEnteringRipple(); 451 | removeFillingRipples(); 452 | removeExitingRipples(); 453 | 454 | if (mEnabled && (mPressed || mFocused)) { 455 | if (mFilledRipple == null) { 456 | createFilledRipple(); 457 | } 458 | } else { 459 | removeFilledRipple(); 460 | } 461 | 462 | invalidateSelf(); 463 | } 464 | 465 | private void createFilledRipple() { 466 | LinearRipple ripple = createRipple(); 467 | if (ripple != null) { 468 | ripple.makeFilled(); 469 | mFilledRipple = ripple; 470 | } 471 | } 472 | 473 | /** 474 | * {@inheritDoc} 475 | */ 476 | @Override 477 | public boolean setVisible(boolean visible, boolean restart) { 478 | 479 | updateRipples(); 480 | 481 | boolean changed = super.setVisible(visible, restart); 482 | if (changed && !visible) { 483 | jumpToCurrentState(); 484 | } 485 | return changed; 486 | } 487 | 488 | @Override 489 | protected void onDraw(Canvas canvas, int width, int height, Paint paint) { 490 | drawDefault(canvas); 491 | drawRipples(canvas, paint); 492 | } 493 | 494 | private void drawDefault(Canvas canvas) { 495 | 496 | if (mDefaultPaint == null) { 497 | mDefaultPaint = new Paint(); 498 | mDefaultPaint.setAntiAlias(true); 499 | mDefaultPaint.setColor(mHintColor); 500 | } 501 | int alpha = (int) ((mEnabled ? 1 : mDisabledAlpha) * mHintColorAlpha * mAlpha + 0.5f); 502 | mDefaultPaint.setAlpha(alpha); 503 | 504 | canvas.drawRect(mDefaultRect, mDefaultPaint); 505 | } 506 | 507 | @Override 508 | protected void onPreparePaint(Paint paint) { 509 | paint.setStyle(Paint.Style.FILL); 510 | } 511 | 512 | private void drawRipples(Canvas canvas, Paint paint) { 513 | 514 | updateRipples(); 515 | 516 | if (mFilledRipple != null) { 517 | mFilledRipple.draw(canvas, paint); 518 | } else { 519 | 520 | for (int i = 0; i < mExitingRippleCount; ++i) { 521 | mExitingRipples[i].draw(canvas, paint); 522 | } 523 | 524 | for (int i = 0; i < mFillingRippleCount; ++i) { 525 | mFillingRipples[i].draw(canvas, paint); 526 | } 527 | 528 | if (mEnteringRipple != null) { 529 | mEnteringRipple.draw(canvas, paint); 530 | } 531 | } 532 | } 533 | } 534 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/MaterialTextInputLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.content.Context; 9 | import android.support.design.widget.TextInputLayout; 10 | import android.text.TextUtils; 11 | import android.util.AttributeSet; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.EditText; 15 | 16 | /** 17 | * A {@code TextInputLayout} that automatically calls 18 | * {@link MaterialEditTextBackgroundDrawable#setError(boolean)} in {@link #setError(CharSequence)} 19 | * if its {@link EditText} is an instance of {@link MaterialEditTextBackgroundDrawable}. 20 | */ 21 | public class MaterialTextInputLayout extends TextInputLayout { 22 | 23 | private MaterialEditTextBackgroundDrawable mEditTextBackground; 24 | 25 | public MaterialTextInputLayout(Context context) { 26 | super(context); 27 | } 28 | 29 | public MaterialTextInputLayout(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | } 32 | 33 | public MaterialTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | } 36 | 37 | @Override 38 | public void addView(View child, int index, ViewGroup.LayoutParams params) { 39 | super.addView(child, index, params); 40 | 41 | if (child instanceof MaterialEditText) { 42 | // Just throw a ClassCastException if the background of MaterialEditText is not the one 43 | // automatically set. 44 | mEditTextBackground = (MaterialEditTextBackgroundDrawable) child.getBackground(); 45 | } 46 | } 47 | 48 | @Override 49 | public void setError(CharSequence error) { 50 | super.setError(error); 51 | 52 | if (mEditTextBackground != null) { 53 | mEditTextBackground.setError(!TextUtils.isEmpty(error)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/TintableDrawable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext; 7 | 8 | import android.content.res.ColorStateList; 9 | import android.graphics.ColorFilter; 10 | import android.graphics.PorterDuff; 11 | import android.graphics.drawable.Drawable; 12 | import android.support.annotation.ColorInt; 13 | import android.support.annotation.NonNull; 14 | import android.support.annotation.Nullable; 15 | 16 | /** 17 | * A {@code Drawable} that is tintable. 18 | */ 19 | public interface TintableDrawable { 20 | 21 | /** 22 | * Specifies tint color for this drawable. 23 | *

24 | * A Drawable's drawing content will be blended together with its tint 25 | * before it is drawn to the screen. This functions similarly to 26 | * {@link Drawable#setColorFilter(int, PorterDuff.Mode)}. 27 | *

28 | *

29 | * To clear the tint, pass {@code null} to 30 | * {@link #setTintList(ColorStateList)}. 31 | *

32 | *

Note: Setting a color filter via 33 | * {@link Drawable#setColorFilter(ColorFilter)} or 34 | * {@link Drawable#setColorFilter(int, PorterDuff.Mode)} overrides tint. 35 | *

36 | * 37 | * @param tintColor Color to use for tinting this drawable 38 | * @see #setTintList(ColorStateList) 39 | * @see #setTintMode(PorterDuff.Mode) 40 | */ 41 | void setTint(@ColorInt int tintColor); 42 | 43 | /** 44 | * Specifies tint color for this drawable as a color state list. 45 | *

46 | * A Drawable's drawing content will be blended together with its tint 47 | * before it is drawn to the screen. This functions similarly to 48 | * {@link Drawable#setColorFilter(int, PorterDuff.Mode)}. 49 | *

50 | *

Note: Setting a color filter via 51 | * {@link Drawable#setColorFilter(ColorFilter)} or 52 | * {@link Drawable#setColorFilter(int, PorterDuff.Mode)} overrides tint. 53 | *

54 | * 55 | * @param tint Color state list to use for tinting this drawable, or 56 | * {@code null} to clear the tint 57 | * @see #setTint(int) 58 | * @see #setTintMode(PorterDuff.Mode) 59 | */ 60 | void setTintList(@Nullable ColorStateList tint); 61 | 62 | /** 63 | * Specifies a tint blending mode for this drawable. 64 | *

65 | * Defines how this drawable's tint color should be blended into the drawable 66 | * before it is drawn to screen. Default tint mode is {@link PorterDuff.Mode#SRC_IN}. 67 | *

68 | *

Note: Setting a color filter via 69 | * {@link Drawable#setColorFilter(ColorFilter)} or 70 | * {@link Drawable#setColorFilter(int, PorterDuff.Mode)} overrides tint. 71 | *

72 | * 73 | * @param tintMode A Porter-Duff blending mode 74 | * @see #setTint(int) 75 | * @see #setTintList(ColorStateList) 76 | */ 77 | void setTintMode(@NonNull PorterDuff.Mode tintMode); 78 | } 79 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/internal/FloatProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.internal; 7 | 8 | import android.util.Property; 9 | 10 | /** 11 | * From {@code android.util.FloatProperty}. 12 | * 13 | * An implementation of {@link android.util.Property} to be used specifically with fields of type 14 | * float. This type-specific subclass enables performance benefit by allowing 15 | * calls to a {@link #set(Object, Float) set()} function that takes the primitive 16 | * float type and avoids autoboxing and other overhead associated with the 17 | * Float class. 18 | * 19 | * @param The class on which the Property is declared. 20 | */ 21 | public abstract class FloatProperty extends Property { 22 | 23 | public FloatProperty(String name) { 24 | super(Float.class, name); 25 | } 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | @Override 31 | public final void set(T object, Float value) { 32 | setValue(object, value); 33 | } 34 | 35 | /** 36 | * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing 37 | * with fields of type float. 38 | * 39 | * @param object The target object. 40 | * @param value The float type value. 41 | */ 42 | public abstract void setValue(T object, float value); 43 | } 44 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/internal/MathUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.internal; 7 | 8 | import java.util.Random; 9 | 10 | /** 11 | * From {@code android.util.MathUtils}. 12 | * 13 | * A class that contains utility methods related to numbers. 14 | */ 15 | public final class MathUtils { 16 | 17 | private static final Random sRandom = new Random(); 18 | private static final float DEG_TO_RAD = 3.1415926f / 180.0f; 19 | private static final float RAD_TO_DEG = 180.0f / 3.1415926f; 20 | 21 | private MathUtils() {} 22 | 23 | public static float abs(float v) { 24 | return v > 0 ? v : -v; 25 | } 26 | 27 | public static int constrain(int amount, int low, int high) { 28 | return amount < low ? low : (amount > high ? high : amount); 29 | } 30 | 31 | public static long constrain(long amount, long low, long high) { 32 | return amount < low ? low : (amount > high ? high : amount); 33 | } 34 | 35 | public static float constrain(float amount, float low, float high) { 36 | return amount < low ? low : (amount > high ? high : amount); 37 | } 38 | 39 | public static float log(float a) { 40 | return (float) Math.log(a); 41 | } 42 | 43 | public static float exp(float a) { 44 | return (float) Math.exp(a); 45 | } 46 | 47 | public static float pow(float a, float b) { 48 | return (float) Math.pow(a, b); 49 | } 50 | 51 | public static float max(float a, float b) { 52 | return a > b ? a : b; 53 | } 54 | 55 | public static float max(int a, int b) { 56 | return a > b ? a : b; 57 | } 58 | 59 | public static float max(float a, float b, float c) { 60 | return a > b ? (a > c ? a : c) : (b > c ? b : c); 61 | } 62 | 63 | public static float max(int a, int b, int c) { 64 | return a > b ? (a > c ? a : c) : (b > c ? b : c); 65 | } 66 | 67 | public static float min(float a, float b) { 68 | return a < b ? a : b; 69 | } 70 | 71 | public static float min(int a, int b) { 72 | return a < b ? a : b; 73 | } 74 | 75 | public static float min(float a, float b, float c) { 76 | return a < b ? (a < c ? a : c) : (b < c ? b : c); 77 | } 78 | 79 | public static float min(int a, int b, int c) { 80 | return a < b ? (a < c ? a : c) : (b < c ? b : c); 81 | } 82 | 83 | public static float dist(float x1, float y1, float x2, float y2) { 84 | final float x = (x2 - x1); 85 | final float y = (y2 - y1); 86 | return (float) Math.hypot(x, y); 87 | } 88 | 89 | public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) { 90 | final float x = (x2 - x1); 91 | final float y = (y2 - y1); 92 | final float z = (z2 - z1); 93 | return (float) Math.sqrt(x * x + y * y + z * z); 94 | } 95 | 96 | public static float mag(float a, float b) { 97 | return (float) Math.hypot(a, b); 98 | } 99 | 100 | public static float mag(float a, float b, float c) { 101 | return (float) Math.sqrt(a * a + b * b + c * c); 102 | } 103 | 104 | public static float sq(float v) { 105 | return v * v; 106 | } 107 | 108 | public static float dot(float v1x, float v1y, float v2x, float v2y) { 109 | return v1x * v2x + v1y * v2y; 110 | } 111 | 112 | public static float cross(float v1x, float v1y, float v2x, float v2y) { 113 | return v1x * v2y - v1y * v2x; 114 | } 115 | 116 | public static float radians(float degrees) { 117 | return degrees * DEG_TO_RAD; 118 | } 119 | 120 | public static float degrees(float radians) { 121 | return radians * RAD_TO_DEG; 122 | } 123 | 124 | public static float acos(float value) { 125 | return (float) Math.acos(value); 126 | } 127 | 128 | public static float asin(float value) { 129 | return (float) Math.asin(value); 130 | } 131 | 132 | public static float atan(float value) { 133 | return (float) Math.atan(value); 134 | } 135 | 136 | public static float atan2(float a, float b) { 137 | return (float) Math.atan2(a, b); 138 | } 139 | 140 | public static float tan(float angle) { 141 | return (float) Math.tan(angle); 142 | } 143 | 144 | public static float lerp(float start, float stop, float amount) { 145 | return start + (stop - start) * amount; 146 | } 147 | 148 | public static float norm(float start, float stop, float value) { 149 | return (value - start) / (stop - start); 150 | } 151 | 152 | public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) { 153 | return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart)); 154 | } 155 | 156 | public static int random(int howbig) { 157 | return (int) (sRandom.nextFloat() * howbig); 158 | } 159 | 160 | public static int random(int howsmall, int howbig) { 161 | if (howsmall >= howbig) { 162 | return howsmall; 163 | } 164 | return (int) (sRandom.nextFloat() * (howbig - howsmall) + howsmall); 165 | } 166 | 167 | public static float random(float howbig) { 168 | return sRandom.nextFloat() * howbig; 169 | } 170 | 171 | public static float random(float howsmall, float howbig) { 172 | if (howsmall >= howbig) { 173 | return howsmall; 174 | } 175 | return sRandom.nextFloat() * (howbig - howsmall) + howsmall; 176 | } 177 | 178 | public static void randomSeed(long seed) { 179 | sRandom.setSeed(seed); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/internal/ThemeUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.internal; 7 | 8 | import android.content.Context; 9 | import android.content.res.ColorStateList; 10 | import android.content.res.TypedArray; 11 | 12 | public class ThemeUtils { 13 | 14 | private ThemeUtils() {} 15 | 16 | public static int getColorFromAttrRes(int attr, Context context) { 17 | TypedArray a = context.obtainStyledAttributes(new int[] {attr}); 18 | try { 19 | return a.getColor(0, 0); 20 | } finally { 21 | a.recycle(); 22 | } 23 | } 24 | 25 | public static ColorStateList getColorStateListFromAttrRes(int attr, Context context) { 26 | TypedArray a = context.obtainStyledAttributes(new int[] {attr}); 27 | try { 28 | return a.getColorStateList(0); 29 | } finally { 30 | a.recycle(); 31 | } 32 | } 33 | 34 | public static float getFloatFromAttrRes(int attrRes, Context context) { 35 | TypedArray a = context.obtainStyledAttributes(new int[] {attrRes}); 36 | try { 37 | return a.getFloat(0, 0); 38 | } finally { 39 | a.recycle(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /library/src/main/java/me/zhanghai/android/materialedittext/internal/ViewCompat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.internal; 7 | 8 | import android.graphics.drawable.Drawable; 9 | import android.os.Build; 10 | import android.view.View; 11 | 12 | public class ViewCompat { 13 | 14 | @SuppressWarnings("deprecation") 15 | public static void setBackground(View view, Drawable background) { 16 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 17 | view.setBackground(background); 18 | } else { 19 | view.setBackgroundDrawable(background); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | apply plugin: 'com.android.application' 7 | 8 | android { 9 | 10 | compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) 11 | buildToolsVersion project.ANDROID_BUILD_TOOLS_VERSION 12 | 13 | defaultConfig { 14 | applicationId "me.zhanghai.android.materialedittext.sample" 15 | minSdkVersion Integer.parseInt(project.ANDROID_MIN_SDK_VERSION) 16 | targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) 17 | versionCode Integer.parseInt(project.VERSION_CODE) 18 | versionName project.VERSION_NAME 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | 26 | signingConfigs { 27 | release { 28 | storeFile file('../../github.jks') 29 | storePassword System.console() != null ? System.console().readLine("\nKeystore password: ") : "" 30 | keyAlias 'materialedittext' 31 | keyPassword System.console() != null ? System.console().readLine("\nKey password: ") : "" 32 | } 33 | } 34 | 35 | buildTypes { 36 | release { 37 | minifyEnabled true 38 | shrinkResources true 39 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 40 | signingConfig signingConfigs.release 41 | } 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation fileTree(dir: 'libs', include: ['*.jar']) 47 | implementation 'com.android.support:appcompat-v7:28.0.0' 48 | implementation 'com.android.support:design:28.0.0' 49 | implementation 'com.jakewharton:butterknife:9.0.0' 50 | annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0' 51 | implementation project(':library') 52 | } 53 | -------------------------------------------------------------------------------- /sample/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 /opt/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # ButterKnife 20 | -keep class butterknife.** { *; } 21 | -dontwarn butterknife.internal.** 22 | -keep class **$$ViewInjector { *; } 23 | -keepclasseswithmembernames class * { 24 | @butterknife.* ; 25 | } 26 | -keepclasseswithmembernames class * { 27 | @butterknife.* ; 28 | } 29 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 11 | 12 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/materialedittext/sample/AboutActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.sample; 7 | 8 | import android.os.Bundle; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.text.method.LinkMovementMethod; 11 | import android.view.MenuItem; 12 | import android.widget.TextView; 13 | 14 | import butterknife.BindView; 15 | import butterknife.ButterKnife; 16 | 17 | public class AboutActivity extends AppCompatActivity { 18 | 19 | @BindView(R.id.version) 20 | TextView mVersionText; 21 | @BindView(R.id.github) 22 | TextView mGithubText; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | setContentView(R.layout.about_activity); 29 | ButterKnife.bind(this); 30 | 31 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 32 | 33 | String version = getString(R.string.about_version_format, BuildConfig.VERSION_NAME); 34 | mVersionText.setText(version); 35 | mGithubText.setMovementMethod(LinkMovementMethod.getInstance()); 36 | } 37 | 38 | @Override 39 | public boolean onOptionsItemSelected(MenuItem item) { 40 | switch (item.getItemId()) { 41 | case android.R.id.home: 42 | AppUtils.navigateUp(this); 43 | return true; 44 | default: 45 | return super.onOptionsItemSelected(item); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/materialedittext/sample/AppUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.sample; 7 | 8 | import android.app.Activity; 9 | import android.content.Intent; 10 | import android.os.Bundle; 11 | import android.support.v4.app.NavUtils; 12 | import android.support.v4.app.TaskStackBuilder; 13 | 14 | public class AppUtils { 15 | 16 | private AppUtils() {} 17 | 18 | // From http://developer.android.com/training/implementing-navigation/ancestral.html#NavigateUp . 19 | public static void navigateUp(Activity activity, Bundle extras) { 20 | Intent upIntent = NavUtils.getParentActivityIntent(activity); 21 | if (upIntent != null) { 22 | if (extras != null) { 23 | upIntent.putExtras(extras); 24 | } 25 | if (NavUtils.shouldUpRecreateTask(activity, upIntent)) { 26 | // This activity is NOT part of this app's task, so create a new task 27 | // when navigating up, with a synthesized back stack. 28 | TaskStackBuilder.create(activity) 29 | // Add all of this activity's parents to the back stack. 30 | .addNextIntentWithParentStack(upIntent) 31 | // Navigate up to the closest parent. 32 | .startActivities(); 33 | } else { 34 | // This activity is part of this app's task, so simply 35 | // navigate up to the logical parent activity. 36 | // According to http://stackoverflow.com/a/14792752/2420519 37 | //NavUtils.navigateUpTo(activity, upIntent); 38 | upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 39 | activity.startActivity(upIntent); 40 | } 41 | } 42 | activity.finish(); 43 | } 44 | 45 | public static void navigateUp(Activity activity) { 46 | navigateUp(activity, null); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sample/src/main/java/me/zhanghai/android/materialedittext/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | package me.zhanghai.android.materialedittext.sample; 7 | 8 | import android.content.Intent; 9 | import android.os.Bundle; 10 | import android.support.design.widget.TextInputLayout; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.widget.EditText; 15 | 16 | import butterknife.BindView; 17 | import butterknife.ButterKnife; 18 | 19 | public class MainActivity extends AppCompatActivity { 20 | 21 | @BindView(R.id.error_framework_edit) 22 | EditText mErrorFrameworkEdit; 23 | @BindView(R.id.error_material_edit) 24 | EditText mErrorMaterialEdit; 25 | @BindView(R.id.error_edit_layout) 26 | TextInputLayout mErrorEditLayout; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | setContentView(R.layout.main_activity); 33 | ButterKnife.bind(this); 34 | 35 | mErrorFrameworkEdit.setError("An error occurred"); 36 | mErrorMaterialEdit.setError("An error occurred"); 37 | mErrorEditLayout.setError("An error occurred"); 38 | } 39 | 40 | @Override 41 | public boolean onCreateOptionsMenu(Menu menu) { 42 | getMenuInflater().inflate(R.menu.menu_main, menu); 43 | return true; 44 | } 45 | 46 | @Override 47 | public boolean onOptionsItemSelected(MenuItem item) { 48 | switch (item.getItemId()) { 49 | case R.id.action_about: 50 | startActivity(new Intent(this, AboutActivity.class)); 51 | return true; 52 | default: 53 | return super.onOptionsItemSelected(item); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/src/main/launcher_icon-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/sample/src/main/launcher_icon-web.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/about_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 15 | 16 | 25 | 26 | 32 | 33 | 40 | 41 | 48 | 49 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 15 | 16 | 24 | 25 | 26 | 27 | 32 | 33 | 39 | 40 | 47 | 48 | 55 | 56 | 57 | 58 | 64 | 65 | 71 | 72 | 79 | 80 | 87 | 88 | 89 | 90 | 96 | 97 | 102 | 106 | 107 | 108 | 115 | 119 | 120 | 121 | 126 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/sample/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/sample/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/sample/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/sample/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/sample/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 24dp 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | #009688 10 | #00796b 11 | #ffd740 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 16dp 10 | 16dp 11 | -4dp 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | MaterialEditText Sample 11 | 12 | Framework EditText 13 | MaterialEditText 14 | MaterialTextInputLayout 15 | Normal 16 | Error 17 | Disabled 18 | About 19 | 20 | About 21 | MaterialEditText 22 | Version %1$s 23 | Zhang Hai 2015–2016 24 | View on GitHub 25 | 26 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 13 | 17 | 18 | 23 | 24 | -------------------------------------------------------------------------------- /screenshot/materialedittext.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/screenshot/materialedittext.gif -------------------------------------------------------------------------------- /screenshot/materialedittext.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/screenshot/materialedittext.mp4 -------------------------------------------------------------------------------- /screenshot/native-edittext.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/screenshot/native-edittext.gif -------------------------------------------------------------------------------- /screenshot/native-edittext.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/screenshot/native-edittext.mp4 -------------------------------------------------------------------------------- /screenshot/screenshot-raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/screenshot/screenshot-raw.png -------------------------------------------------------------------------------- /screenshot/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghai/MaterialEditText/d32ca941812972143fb9668b9245a11d87e36527/screenshot/screenshot.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Zhang Hai 3 | * All Rights Reserved. 4 | */ 5 | 6 | include ':library', ':sample' 7 | --------------------------------------------------------------------------------