├── settings.gradle ├── Library ├── gradle.properties ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ ├── strings.xml │ │ │ ├── tab_layout_styles.xml │ │ │ └── tab_layout_attrs.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── tigerliang │ │ └── tablayout │ │ └── TabLayout.java ├── proguard-rules.pro ├── build.gradle └── Library.iml ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ ├── .svn │ │ ├── prop-base │ │ │ └── gradle-wrapper.jar.svn-base │ │ ├── text-base │ │ │ ├── gradle-wrapper.jar.svn-base │ │ │ └── gradle-wrapper.properties.svn-base │ │ ├── all-wcprops │ │ └── entries │ └── gradle-wrapper.properties └── .svn │ ├── all-wcprops │ └── entries ├── app ├── src │ └── main │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ ├── .svn │ │ │ │ └── props │ │ │ │ │ └── ic_launcher.png.svn-work │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ ├── .svn │ │ │ │ └── props │ │ │ │ │ └── ic_launcher.png.svn-work │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ ├── .svn │ │ │ │ ├── props │ │ │ │ │ └── ic_launcher.png.svn-work │ │ │ │ └── entries │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ ├── .svn │ │ │ │ ├── props │ │ │ │ │ └── ic_launcher.png.svn-work │ │ │ │ └── entries │ │ │ └── ic_launcher.png │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── dimens.xml │ │ │ ├── .svn │ │ │ │ └── entries │ │ │ └── styles.xml │ │ ├── values-w820dp │ │ │ ├── dimens.xml │ │ │ └── .svn │ │ │ │ └── entries │ │ ├── menu │ │ │ └── menu_main.xml │ │ └── layout │ │ │ ├── subfragment_layout.xml │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── tigerliang │ │ └── example │ │ └── MainActivity.java ├── proguard-rules.pro └── build.gradle ├── .gitignore ├── README.md ├── gradle.properties ├── gradlew.bat ├── maven_push.gradle ├── gradlew └── LICENSE /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':Library' 2 | -------------------------------------------------------------------------------- /Library/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=TabLayout Library 2 | POM_ARTIFACT_ID=tablayout 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /Library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Library 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangtailiang/TabLayout/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/.svn/all-wcprops: -------------------------------------------------------------------------------- 1 | K 25 2 | svn:wc:ra_dav:version-url 3 | V 48 4 | /xiangtailiang/TabLayout/!svn/ver/2/trunk/gradle 5 | END 6 | -------------------------------------------------------------------------------- /gradle/wrapper/.svn/prop-base/gradle-wrapper.jar.svn-base: -------------------------------------------------------------------------------- 1 | K 13 2 | svn:mime-type 3 | V 24 4 | application/octet-stream 5 | END 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/.svn/props/ic_launcher.png.svn-work: -------------------------------------------------------------------------------- 1 | K 13 2 | svn:mime-type 3 | V 24 4 | application/octet-stream 5 | END 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/.svn/props/ic_launcher.png.svn-work: -------------------------------------------------------------------------------- 1 | K 13 2 | svn:mime-type 3 | V 24 4 | application/octet-stream 5 | END 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/.svn/props/ic_launcher.png.svn-work: -------------------------------------------------------------------------------- 1 | K 13 2 | svn:mime-type 3 | V 24 4 | application/octet-stream 5 | END 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/.svn/props/ic_launcher.png.svn-work: -------------------------------------------------------------------------------- 1 | K 13 2 | svn:mime-type 3 | V 24 4 | application/octet-stream 5 | END 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangtailiang/TabLayout/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangtailiang/TabLayout/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangtailiang/TabLayout/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangtailiang/TabLayout/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /gradle/wrapper/.svn/text-base/gradle-wrapper.jar.svn-base: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiangtailiang/TabLayout/HEAD/gradle/wrapper/.svn/text-base/gradle-wrapper.jar.svn-base -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TabLayout 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | build 15 | .gradle 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /Library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/.svn/text-base/gradle-wrapper.properties.svn-base: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradle/.svn/entries: -------------------------------------------------------------------------------- 1 | 10 2 | 3 | dir 4 | 2 5 | https://xiangtailiang@github.com/xiangtailiang/TabLayout/trunk/gradle 6 | https://xiangtailiang@github.com/xiangtailiang/TabLayout 7 | 8 | 9 | 10 | 2015-09-21T14:14:20.000000Z 11 | 2 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | f92337db-937d-e886-4e65-ce57b7db7bff 28 | 29 | wrapper 30 | dir 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TabLayout 2 | ---------- 3 | TabLayout extract from Android Design Library for who do not want to import Android Design Library. 4 | 5 | 6 | Sample 7 | ---------- 8 | You can find a sample APK : [Link](https://github.com/xiangtailiang/TabLayout/releases) 9 | 10 | How to use 11 | ---------- 12 | Import dependency using Gradle: 13 | ``` 14 | compile 'com.github.xiangtailiang.tablayout:library:1.0.0' 15 | ``` 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/.svn/entries: -------------------------------------------------------------------------------- 1 | 10 2 | 3 | dir 4 | 0 5 | https://xiangtailiang@github.com/xiangtailiang/TabLayout/trunk/app/src/main/res/values-w820dp 6 | https://xiangtailiang@github.com/xiangtailiang/TabLayout 7 | add 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | f92337db-937d-e886-4e65-ce57b7db7bff 28 | 29 | dimens.xml 30 | file 31 | 32 | 33 | 34 | add 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /gradle/wrapper/.svn/all-wcprops: -------------------------------------------------------------------------------- 1 | K 25 2 | svn:wc:ra_dav:version-url 3 | V 56 4 | /xiangtailiang/TabLayout/!svn/ver/2/trunk/gradle/wrapper 5 | END 6 | gradle-wrapper.jar 7 | K 25 8 | svn:wc:ra_dav:version-url 9 | V 75 10 | /xiangtailiang/TabLayout/!svn/ver/2/trunk/gradle/wrapper/gradle-wrapper.jar 11 | END 12 | gradle-wrapper.properties 13 | K 25 14 | svn:wc:ra_dav:version-url 15 | V 82 16 | /xiangtailiang/TabLayout/!svn/ver/2/trunk/gradle/wrapper/gradle-wrapper.properties 17 | END 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/subfragment_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/.svn/entries: -------------------------------------------------------------------------------- 1 | 10 2 | 3 | dir 4 | 0 5 | https://xiangtailiang@github.com/xiangtailiang/TabLayout/trunk/app/src/main/res/mipmap-xhdpi 6 | https://xiangtailiang@github.com/xiangtailiang/TabLayout 7 | add 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | f92337db-937d-e886-4e65-ce57b7db7bff 28 | 29 | ic_launcher.png 30 | file 31 | 32 | 33 | 34 | add 35 | 36 | 37 | 38 | 39 | 40 | has-props 41 | has-prop-mods 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/.svn/entries: -------------------------------------------------------------------------------- 1 | 10 2 | 3 | dir 4 | 0 5 | https://xiangtailiang@github.com/xiangtailiang/TabLayout/trunk/app/src/main/res/mipmap-xxhdpi 6 | https://xiangtailiang@github.com/xiangtailiang/TabLayout 7 | add 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | f92337db-937d-e886-4e65-ce57b7db7bff 28 | 29 | ic_launcher.png 30 | file 31 | 32 | 33 | 34 | add 35 | 36 | 37 | 38 | 39 | 40 | has-props 41 | has-prop-mods 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/values/.svn/entries: -------------------------------------------------------------------------------- 1 | 10 2 | 3 | dir 4 | 0 5 | https://xiangtailiang@github.com/xiangtailiang/TabLayout/trunk/app/src/main/res/values 6 | https://xiangtailiang@github.com/xiangtailiang/TabLayout 7 | add 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | f92337db-937d-e886-4e65-ce57b7db7bff 28 | 29 | styles.xml 30 | file 31 | 32 | 33 | 34 | add 35 | 36 | strings.xml 37 | file 38 | 39 | 40 | 41 | add 42 | 43 | dimens.xml 44 | file 45 | 46 | 47 | 48 | add 49 | 50 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.0.0 2 | VERSION_CODE=100 3 | GROUP=com.github.xiangtailiang 4 | 5 | POM_DESCRIPTION=TabLayout extract from Android Design Library for who do not want to import Android Design Library. 6 | POM_URL=https://github.com/xiangtailiang/TabLayout 7 | POM_SCM_URL=https://github.com/xiangtailiang/TabLayout 8 | POM_SCM_CONNECTION=scm:git@github.com/xiangtailiang/TabLayout.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com/xiangtailiang/TabLayout.git 10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 12 | POM_LICENCE_DIST=repo 13 | POM_DEVELOPER_ID=xiangtailiang 14 | POM_DEVELOPER_NAME=xiangtailiang 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/tiger/Androidsdk/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 | -------------------------------------------------------------------------------- /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 /Users/tiger/Androidsdk/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 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Library/src/main/res/values/tab_layout_styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /gradle/wrapper/.svn/entries: -------------------------------------------------------------------------------- 1 | 10 2 | 3 | dir 4 | 2 5 | https://xiangtailiang@github.com/xiangtailiang/TabLayout/trunk/gradle/wrapper 6 | https://xiangtailiang@github.com/xiangtailiang/TabLayout 7 | 8 | 9 | 10 | 2015-09-21T14:14:20.000000Z 11 | 2 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | f92337db-937d-e886-4e65-ce57b7db7bff 28 | 29 | gradle-wrapper.jar 30 | file 31 | 32 | 33 | 34 | 35 | 2015-09-21T14:00:22.000000Z 36 | d7f554d57f4d4428bc2fea69e36ef055 37 | 2015-09-21T14:14:20.000000Z 38 | 2 39 | 40 | has-props 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 49896 62 | 63 | gradle-wrapper.properties 64 | file 65 | 66 | 67 | 68 | 69 | 2015-09-21T14:00:22.000000Z 70 | 2c4ffedb712cb41e8811e881a2103264 71 | 2015-09-21T14:14:20.000000Z 72 | 2 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 232 96 | 97 | -------------------------------------------------------------------------------- /Library/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.1.1' 7 | } 8 | } 9 | apply plugin: 'com.android.library' 10 | apply from: '../maven_push.gradle' 11 | 12 | repositories { 13 | jcenter() 14 | } 15 | 16 | android { 17 | compileSdkVersion 22 18 | buildToolsVersion "22.0.1" 19 | 20 | defaultConfig { 21 | minSdkVersion 8 22 | targetSdkVersion 22 23 | versionCode 1 24 | versionName "1.0" 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_7 29 | targetCompatibility JavaVersion.VERSION_1_7 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | lintOptions { 38 | abortOnError false 39 | } 40 | } 41 | 42 | dependencies { 43 | compile fileTree(dir: 'libs', include: ['*.jar']) 44 | compile "com.android.support:support-v4:22.0.0" 45 | compile 'com.android.support:appcompat-v7:22.1.1' 46 | } 47 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:1.1.1' 7 | } 8 | } 9 | apply plugin: 'com.android.application' 10 | 11 | repositories { 12 | jcenter() 13 | } 14 | 15 | android { 16 | compileSdkVersion 22 17 | buildToolsVersion "22.0.1" 18 | 19 | defaultConfig { 20 | applicationId "com.tigerliang.tablayout.example" 21 | minSdkVersion 9 22 | targetSdkVersion 22 23 | versionCode 1 24 | versionName "1.0" 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_7 29 | targetCompatibility JavaVersion.VERSION_1_7 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | lintOptions { 38 | abortOnError false 39 | } 40 | } 41 | 42 | dependencies { 43 | compile fileTree(dir: 'libs', include: ['*.jar']) 44 | compile 'com.android.support:appcompat-v7:22.1.1' 45 | compile 'com.android.support:support-v4:22.1.1' 46 | 47 | compile project(':Library') 48 | } 49 | -------------------------------------------------------------------------------- /Library/src/main/res/values/tab_layout_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/tigerliang/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tigerliang.example; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentPagerAdapter; 8 | import android.support.v4.view.ViewPager; 9 | import android.support.v7.app.ActionBarActivity; 10 | import android.view.LayoutInflater; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ArrayAdapter; 16 | import android.widget.ListView; 17 | import com.tigerliang.tablayout.TabLayout; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | 23 | public class MainActivity extends ActionBarActivity { 24 | 25 | private ViewPager mViewPager; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_main); 31 | mViewPager = (ViewPager) findViewById(R.id.viewpager); 32 | Adapter adapter = new Adapter(getSupportFragmentManager()); 33 | adapter.addFragment(new SubTabFragment(), "TAB 1"); 34 | adapter.addFragment(new SubTabFragment(), "TAB 2"); 35 | adapter.addFragment(new SubTabFragment(), "TAB 3"); 36 | mViewPager.setAdapter(adapter); 37 | 38 | TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout); 39 | tabLayout.setupWithViewPager(mViewPager); 40 | } 41 | 42 | 43 | @Override 44 | public boolean onCreateOptionsMenu(Menu menu) { 45 | // Inflate the menu; this adds items to the action bar if it is present. 46 | getMenuInflater().inflate(R.menu.menu_main, menu); 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean onOptionsItemSelected(MenuItem item) { 52 | // Handle action bar item clicks here. The action bar will 53 | // automatically handle clicks on the Home/Up button, so long 54 | // as you specify a parent activity in AndroidManifest.xml. 55 | int id = item.getItemId(); 56 | 57 | //noinspection SimplifiableIfStatement 58 | if (id ==R.id.action_settings) { 59 | return true; 60 | } 61 | 62 | return super.onOptionsItemSelected(item); 63 | } 64 | } 65 | 66 | class Adapter extends FragmentPagerAdapter { 67 | private final List mFragments = new ArrayList<>(); 68 | private final List mFragmentTitles = new ArrayList<>(); 69 | 70 | public Adapter(FragmentManager fm) { 71 | super(fm); 72 | } 73 | 74 | public void addFragment(Fragment fragment, String title) { 75 | mFragments.add(fragment); 76 | mFragmentTitles.add(title); 77 | } 78 | 79 | @Override 80 | public Fragment getItem(int position) { 81 | return mFragments.get(position); 82 | } 83 | 84 | @Override 85 | public int getCount() { 86 | return mFragments.size(); 87 | } 88 | 89 | @Override 90 | public CharSequence getPageTitle(int position) { 91 | return mFragmentTitles.get(position); 92 | } 93 | } 94 | 95 | class SubTabFragment extends Fragment { 96 | 97 | @Nullable 98 | @Override 99 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 100 | View root = inflater.inflate(R.layout.subfragment_layout, container, false); 101 | ListView listView = (ListView) root.findViewById(R.id.list_view); 102 | String[] elements = new String[50]; 103 | for (int i = 0; i < elements.length; i++) { 104 | elements[i] = "row " + i; 105 | } 106 | 107 | listView.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, elements)); 108 | return root; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /maven_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 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | task installArchives(type: Upload) { 91 | description "Installs the artifacts to the local Maven repository." 92 | configuration = configurations['archives'] 93 | repositories { 94 | mavenDeployer { 95 | pom.groupId = GROUP 96 | pom.artifactId = POM_ARTIFACT_ID 97 | pom.version = VERSION_NAME 98 | 99 | repository url: "file://${System.properties['user.home']}/.m2/repository" 100 | } 101 | } 102 | } 103 | 104 | signing { 105 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 106 | sign configurations.archives 107 | } 108 | 109 | task androidJavadocs(type: Javadoc) { 110 | source = android.sourceSets.main.java.srcDirs 111 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 112 | } 113 | 114 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 115 | classifier = 'javadoc' 116 | from androidJavadocs.destinationDir 117 | } 118 | 119 | task androidSourcesJar(type: Jar) { 120 | classifier = 'sources' 121 | from android.sourceSets.main.java.srcDirs 122 | } 123 | 124 | artifacts { 125 | archives androidSourcesJar 126 | //archives androidJavadocsJar 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Library/Library.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Library/src/main/java/com/tigerliang/tablayout/TabLayout.java: -------------------------------------------------------------------------------- 1 | package com.tigerliang.tablayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.ColorStateList; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Paint; 8 | import android.graphics.drawable.Drawable; 9 | import android.support.v4.view.GravityCompat; 10 | import android.support.v4.view.PagerAdapter; 11 | import android.support.v4.view.ViewCompat; 12 | import android.support.v4.view.ViewPager; 13 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 14 | import android.support.v7.internal.widget.TintManager; 15 | import android.text.TextUtils; 16 | import android.util.AttributeSet; 17 | import android.view.Gravity; 18 | import android.view.LayoutInflater; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.view.ViewParent; 22 | import android.view.accessibility.AccessibilityEvent; 23 | import android.view.animation.Animation; 24 | import android.view.animation.Interpolator; 25 | import android.view.animation.Transformation; 26 | import android.widget.HorizontalScrollView; 27 | import android.widget.ImageView; 28 | import android.widget.LinearLayout; 29 | import android.widget.TextView; 30 | import android.widget.Toast; 31 | 32 | import java.lang.ref.WeakReference; 33 | import java.util.ArrayList; 34 | import java.util.Iterator; 35 | 36 | /** 37 | * 从Android design support库里面提出取来的TabLayout,专门针对给ViewPager做TAB使用 38 | * 使用方法很简单: 39 | *
  40 |  * 1、创建PagerAdapter并覆盖getPageTitle方法,为每个tab提供名称
  41 |  * 2、调用setupWithViewPager(ViewPager viewPager)方法,就OK。
  42 |  * 
43 | * 44 | * @see Tabs 45 | */ 46 | public class TabLayout extends HorizontalScrollView { 47 | 48 | private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator(); 49 | private static final int MAX_TAB_TEXT_LINES = 2; 50 | 51 | private static final int DEFAULT_HEIGHT = 45; // dps 52 | private static final int TAB_MIN_WIDTH_MARGIN = 56; //dps 53 | private static final int FIXED_WRAP_GUTTER_MIN = 16; //dps 54 | private static final int MOTION_NON_ADJACENT_OFFSET = 24; 55 | 56 | private static final int ANIMATION_DURATION = 300; 57 | 58 | /** 59 | * Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab 60 | * labels and a larger number of tabs. They are best used for browsing contexts in touch 61 | * interfaces when users don’t need to directly compare the tab labels. 62 | * 63 | * @attr android.support.design.R.attr.tabMode 64 | * @see #setTabMode(int) 65 | * @see #getTabMode() 66 | */ 67 | public static final int MODE_SCROLLABLE = 0; 68 | 69 | /** 70 | * Fixed tabs display all tabs concurrently and are best used with content that benefits from 71 | * quick pivots between tabs. The maximum number of tabs is limited by the view’s width. 72 | * Fixed tabs have equal width, based on the widest tab label. 73 | * 74 | * @attr android.support.design.R.attr.tabMode 75 | * @see #setTabMode(int) 76 | * @see #getTabMode() 77 | */ 78 | public static final int MODE_FIXED = 1; 79 | 80 | /** 81 | * Gravity used to fill the {@link TabLayout} as much as possible. This option only takes effect 82 | * when used with {@link #MODE_FIXED}. 83 | * 84 | * @attr android.support.design.R.attr.tabGravity 85 | * @see #setTabGravity(int) 86 | * @see #getTabGravity() 87 | */ 88 | public static final int GRAVITY_FILL = 0; 89 | 90 | /** 91 | * Gravity used to lay out the tabs in the center of the {@link TabLayout}. 92 | * 93 | * @attr android.support.design.R.attr.tabGravity 94 | * @see #setTabGravity(int) 95 | * @see #getTabGravity() 96 | */ 97 | public static final int GRAVITY_CENTER = 1; 98 | 99 | /** 100 | * Callback interface invoked when a tab's selection state changes. 101 | */ 102 | public interface OnTabSelectedListener { 103 | 104 | /** 105 | * Called when a tab enters the selected state. 106 | * 107 | * @param tab The tab that was selected 108 | */ 109 | public void onTabSelected(Tab tab); 110 | 111 | /** 112 | * Called when a tab exits the selected state. 113 | * 114 | * @param tab The tab that was unselected 115 | */ 116 | public void onTabUnselected(Tab tab); 117 | 118 | /** 119 | * Called when a tab that is already selected is chosen again by the user. Some applications 120 | * may use this action to return to the top level of a category. 121 | * 122 | * @param tab The tab that was reselected. 123 | */ 124 | public void onTabReselected(Tab tab); 125 | } 126 | 127 | private final ArrayList mTabs = new ArrayList<>(); 128 | private Tab mSelectedTab; 129 | 130 | private final SlidingTabStrip mTabStrip; 131 | 132 | private int mTabPaddingStart; 133 | private int mTabPaddingTop; 134 | private int mTabPaddingEnd; 135 | private int mTabPaddingBottom; 136 | 137 | private final int mTabTextAppearance; 138 | private int mTabSelectedTextColor; 139 | private boolean mTabSelectedTextColorSet; 140 | private final int mTabBackgroundResId; 141 | 142 | private final int mTabMinWidth; 143 | private int mTabMaxWidth; 144 | private final int mRequestedTabMaxWidth; 145 | 146 | private int mContentInsetStart; 147 | 148 | private int mTabGravity; 149 | private int mMode; 150 | 151 | private OnTabSelectedListener mOnTabSelectedListener; 152 | private OnClickListener mTabClickListener; 153 | 154 | public TabLayout(Context context) { 155 | this(context, null); 156 | } 157 | 158 | public TabLayout(Context context, AttributeSet attrs) { 159 | this(context, attrs, 0); 160 | } 161 | 162 | public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 163 | super(context, attrs, defStyleAttr); 164 | 165 | // Disable the Scroll Bar 166 | setHorizontalScrollBarEnabled(false); 167 | // Set us to fill the View port 168 | setFillViewport(true); 169 | 170 | // Add the TabStrip 171 | mTabStrip = new SlidingTabStrip(context); 172 | addView(mTabStrip, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 173 | 174 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout, 175 | defStyleAttr, R.style.Widget_Design_TabLayout); 176 | 177 | mTabStrip.setSelectedIndicatorHeight( 178 | a.getDimensionPixelSize(R.styleable.TabLayout_tabIndicatorHeight, 0)); 179 | mTabStrip.setSelectedIndicatorColor(a.getColor(R.styleable.TabLayout_tabIndicatorColor, 0)); 180 | mTabStrip.setSelectedIndicatorScrollable(a.getBoolean(R.styleable.TabLayout_tabIndicatorScrollable, true)); 181 | 182 | mTabTextAppearance = a.getResourceId(R.styleable.TabLayout_tabTextAppearance, 183 | R.style.TextAppearance_Design_Tab); 184 | 185 | mTabPaddingStart = mTabPaddingTop = mTabPaddingEnd = mTabPaddingBottom = a 186 | .getDimensionPixelSize(R.styleable.TabLayout_tabPadding, 0); 187 | mTabPaddingStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingStart, 188 | mTabPaddingStart); 189 | mTabPaddingTop = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingTop, 190 | mTabPaddingTop); 191 | mTabPaddingEnd = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingEnd, 192 | mTabPaddingEnd); 193 | mTabPaddingBottom = a.getDimensionPixelSize(R.styleable.TabLayout_tabPaddingBottom, 194 | mTabPaddingBottom); 195 | 196 | if (a.hasValue(R.styleable.TabLayout_tabSelectedTextColor)) { 197 | mTabSelectedTextColor = a.getColor(R.styleable.TabLayout_tabSelectedTextColor, 0); 198 | mTabSelectedTextColorSet = true; 199 | } 200 | 201 | mTabMinWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMinWidth, 0); 202 | mRequestedTabMaxWidth = a.getDimensionPixelSize(R.styleable.TabLayout_tabMaxWidth, 0); 203 | mTabBackgroundResId = a.getResourceId(R.styleable.TabLayout_tabBackground, 0); 204 | mContentInsetStart = a.getDimensionPixelSize(R.styleable.TabLayout_tabContentStart, 0); 205 | mMode = a.getInt(R.styleable.TabLayout_tabMode, MODE_FIXED); 206 | mTabGravity = a.getInt(R.styleable.TabLayout_tabGravity, GRAVITY_FILL); 207 | a.recycle(); 208 | 209 | // Now apply the tab mode and gravity 210 | applyModeAndGravity(); 211 | } 212 | 213 | /** 214 | * Set the scroll position of the tabs. This is useful for when the tabs are being displayed as 215 | * part of a scrolling container such as {@link ViewPager}. 216 | *

217 | * Calling this method does not update the selected tab, it is only used for drawing purposes. 218 | */ 219 | public void setScrollPosition(int position, float positionOffset) { 220 | if (isAnimationRunning(getAnimation())) { 221 | return; 222 | } 223 | if (position < 0 || position >= mTabStrip.getChildCount()) { 224 | return; 225 | } 226 | 227 | // Set the indicator position and update the scroll to match 228 | mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset); 229 | scrollTo(calculateScrollXForTab(position, positionOffset), 0); 230 | 231 | // Update the 'selected state' view as we scroll 232 | setSelectedTabView(Math.round(position + positionOffset)); 233 | } 234 | 235 | /** 236 | * Add new {@link Tab}s populated from a {@link PagerAdapter}. Each tab will have it's text set 237 | * to the value returned from {@link PagerAdapter#getPageTitle(int)}. 238 | * 239 | * @param adapter the adapter to populate from 240 | */ 241 | public void addTabsFromPagerAdapter(PagerAdapter adapter) { 242 | for (int i = 0, count = adapter.getCount(); i < count; i++) { 243 | addTab(newTab().setText(adapter.getPageTitle(i))); 244 | } 245 | } 246 | 247 | public void setupWithViewPager(ViewPager viewPager) { 248 | PagerAdapter adapter = viewPager.getAdapter(); 249 | if (adapter == null) { 250 | throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set"); 251 | } else { 252 | this.addTabsFromPagerAdapter(adapter); 253 | viewPager.setOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(this)); 254 | this.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager)); 255 | if (this.mSelectedTab == null || this.mSelectedTab.getPosition() != viewPager.getCurrentItem()) { 256 | this.getTabAt(viewPager.getCurrentItem()).select(); 257 | } 258 | 259 | } 260 | } 261 | 262 | public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener { 263 | private final ViewPager mViewPager; 264 | 265 | public ViewPagerOnTabSelectedListener(ViewPager viewPager) { 266 | this.mViewPager = viewPager; 267 | } 268 | 269 | public void onTabSelected(TabLayout.Tab tab) { 270 | this.mViewPager.setCurrentItem(tab.getPosition()); 271 | } 272 | 273 | public void onTabUnselected(TabLayout.Tab tab) { 274 | } 275 | 276 | public void onTabReselected(TabLayout.Tab tab) { 277 | } 278 | } 279 | 280 | public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener { 281 | private final WeakReference mTabLayoutRef; 282 | private int mPreviousScrollState; 283 | private int mScrollState; 284 | 285 | public TabLayoutOnPageChangeListener(TabLayout tabLayout) { 286 | this.mTabLayoutRef = new WeakReference(tabLayout); 287 | } 288 | 289 | public void onPageScrollStateChanged(int state) { 290 | this.mPreviousScrollState = this.mScrollState; 291 | this.mScrollState = state; 292 | } 293 | 294 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 295 | TabLayout tabLayout = (TabLayout) this.mTabLayoutRef.get(); 296 | if (tabLayout != null) { 297 | tabLayout.setScrollPosition(position, positionOffset); 298 | } 299 | 300 | } 301 | 302 | public void onPageSelected(int position) { 303 | TabLayout tabLayout = (TabLayout) this.mTabLayoutRef.get(); 304 | if (tabLayout != null) { 305 | tabLayout.selectTab(tabLayout.getTabAt(position)); 306 | } 307 | 308 | } 309 | } 310 | 311 | /** 312 | * Create a {@link ViewPager.OnPageChangeListener} which implements the 313 | * necessary calls back to this layout so that the tabs position is kept in sync. 314 | *

315 | * If you need to have a custom {@link ViewPager.OnPageChangeListener} for your own 316 | * purposes, you can still use the instance returned from this method, but making sure to call 317 | * through to all of the methods. 318 | */ 319 | public ViewPager.OnPageChangeListener createOnPageChangeListener() { 320 | return new ViewPager.SimpleOnPageChangeListener() { 321 | @Override 322 | public void onPageScrolled(int position, float positionOffset, 323 | int positionOffsetPixels) { 324 | setScrollPosition(position, positionOffset); 325 | } 326 | 327 | @Override 328 | public void onPageSelected(int position) { 329 | getTabAt(position).select(); 330 | } 331 | }; 332 | } 333 | 334 | /** 335 | * Add a tab to this layout. The tab will be added at the end of the list. 336 | * If this is the first tab to be added it will become the selected tab. 337 | * 338 | * @param tab Tab to add 339 | */ 340 | public void addTab(Tab tab) { 341 | addTab(tab, mTabs.isEmpty()); 342 | } 343 | 344 | /** 345 | * Add a tab to this layout. The tab will be inserted at position. 346 | * If this is the first tab to be added it will become the selected tab. 347 | * 348 | * @param tab The tab to add 349 | * @param position The new position of the tab 350 | */ 351 | public void addTab(Tab tab, int position) { 352 | addTab(tab, position, mTabs.isEmpty()); 353 | } 354 | 355 | /** 356 | * Add a tab to this layout. The tab will be added at the end of the list. 357 | * 358 | * @param tab Tab to add 359 | * @param setSelected True if the added tab should become the selected tab. 360 | */ 361 | public void addTab(Tab tab, boolean setSelected) { 362 | if (tab.mParent != this) { 363 | throw new IllegalArgumentException("Tab belongs to a different TabLayout."); 364 | } 365 | 366 | addTabView(tab, setSelected); 367 | configureTab(tab, mTabs.size()); 368 | if (setSelected) { 369 | tab.select(); 370 | } 371 | } 372 | 373 | /** 374 | * Add a tab to this layout. The tab will be inserted at position. 375 | * 376 | * @param tab The tab to add 377 | * @param position The new position of the tab 378 | * @param setSelected True if the added tab should become the selected tab. 379 | */ 380 | public void addTab(Tab tab, int position, boolean setSelected) { 381 | if (tab.mParent != this) { 382 | throw new IllegalArgumentException("Tab belongs to a different TabLayout."); 383 | } 384 | 385 | addTabView(tab, position, setSelected); 386 | configureTab(tab, position); 387 | if (setSelected) { 388 | tab.select(); 389 | } 390 | } 391 | 392 | /** 393 | * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will handle switching to and from tabs. 394 | * 395 | * @param onTabSelectedListener Listener to handle tab selection events 396 | */ 397 | public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) { 398 | mOnTabSelectedListener = onTabSelectedListener; 399 | } 400 | 401 | /** 402 | * Create and return a new {@link Tab}. You need to manually add this using 403 | * {@link #addTab(Tab)} or a related method. 404 | * 405 | * @return A new Tab 406 | * @see #addTab(Tab) 407 | */ 408 | public Tab newTab() { 409 | return new Tab(this); 410 | } 411 | 412 | /** 413 | * Returns the number of tabs currently registered with the action bar. 414 | * 415 | * @return Tab count 416 | */ 417 | public int getTabCount() { 418 | return mTabs.size(); 419 | } 420 | 421 | /** 422 | * Returns the tab at the specified index. 423 | */ 424 | public Tab getTabAt(int index) { 425 | return mTabs.get(index); 426 | } 427 | 428 | /** 429 | * Remove a tab from the layout. If the removed tab was selected it will be deselected 430 | * and another tab will be selected if present. 431 | * 432 | * @param tab The tab to remove 433 | */ 434 | public void removeTab(Tab tab) { 435 | if (tab.mParent != this) { 436 | throw new IllegalArgumentException("Tab does not belong to this TabLayout."); 437 | } 438 | 439 | removeTabAt(tab.getPosition()); 440 | } 441 | 442 | /** 443 | * Remove a tab from the layout. If the removed tab was selected it will be deselected 444 | * and another tab will be selected if present. 445 | * 446 | * @param position Position of the tab to remove 447 | */ 448 | public void removeTabAt(int position) { 449 | final int selectedTabPosition = mSelectedTab != null ? mSelectedTab.getPosition() : 0; 450 | removeTabViewAt(position); 451 | 452 | Tab removedTab = mTabs.remove(position); 453 | if (removedTab != null) { 454 | removedTab.setPosition(Tab.INVALID_POSITION); 455 | } 456 | 457 | final int newTabCount = mTabs.size(); 458 | for (int i = position; i < newTabCount; i++) { 459 | mTabs.get(i).setPosition(i); 460 | } 461 | 462 | if (selectedTabPosition == position) { 463 | selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1))); 464 | } 465 | } 466 | 467 | /** 468 | * Remove all tabs from the action bar and deselect the current tab. 469 | */ 470 | public void removeAllTabs() { 471 | // Remove all the views 472 | mTabStrip.removeAllViews(); 473 | 474 | for (Iterator i = mTabs.iterator(); i.hasNext(); ) { 475 | Tab tab = i.next(); 476 | tab.setPosition(Tab.INVALID_POSITION); 477 | i.remove(); 478 | } 479 | } 480 | 481 | /** 482 | * Set the behavior mode for the Tabs in this layout. The valid input options are: 483 | *

    484 | *
  • {@link #MODE_FIXED}: Fixed tabs display all tabs concurrently and are best used 485 | * with content that benefits from quick pivots between tabs.
  • 486 | *
  • {@link #MODE_SCROLLABLE}: Scrollable tabs display a subset of tabs at any given moment, 487 | * and can contain longer tab labels and a larger number of tabs. They are best used for 488 | * browsing contexts in touch interfaces when users don’t need to directly compare the tab 489 | * labels. This mode is commonly used with a {@link ViewPager}.
  • 490 | *
491 | * 492 | * @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}. 493 | */ 494 | public void setTabMode(int mode) { 495 | if (mode != mMode) { 496 | mMode = mode; 497 | applyModeAndGravity(); 498 | } 499 | } 500 | 501 | /** 502 | * Returns the current mode used by this {@link TabLayout}. 503 | * 504 | * @see #setTabMode(int) 505 | */ 506 | public int getTabMode() { 507 | return mMode; 508 | } 509 | 510 | /** 511 | * Set the gravity to use when laying out the tabs. 512 | * 513 | * @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. 514 | */ 515 | public void setTabGravity(int gravity) { 516 | if (mTabGravity != gravity) { 517 | mTabGravity = gravity; 518 | applyModeAndGravity(); 519 | } 520 | } 521 | 522 | /** 523 | * The current gravity used for laying out tabs. 524 | * 525 | * @return one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}. 526 | */ 527 | public int getTabGravity() { 528 | return mTabGravity; 529 | } 530 | 531 | /** 532 | * Set the text color to use when a tab is selected. 533 | * 534 | * @param textColor 535 | */ 536 | public void setTabSelectedTextColor(int textColor) { 537 | if (!mTabSelectedTextColorSet || mTabSelectedTextColor != textColor) { 538 | mTabSelectedTextColor = textColor; 539 | mTabSelectedTextColorSet = true; 540 | 541 | for (int i = 0, z = mTabStrip.getChildCount(); i < z; i++) { 542 | updateTab(i); 543 | } 544 | } 545 | } 546 | 547 | /** 548 | * Returns the text color currently used when a tab is selected. 549 | */ 550 | public int getTabSelectedTextColor() { 551 | return mTabSelectedTextColor; 552 | } 553 | 554 | private TabView createTabView(Tab tab) { 555 | final TabView tabView = new TabView(getContext(), tab); 556 | tabView.setFocusable(true); 557 | 558 | if (mTabClickListener == null) { 559 | mTabClickListener = new OnClickListener() { 560 | @Override 561 | public void onClick(View view) { 562 | TabView tabView = (TabView) view; 563 | tabView.getTab().select(); 564 | } 565 | }; 566 | } 567 | tabView.setOnClickListener(mTabClickListener); 568 | return tabView; 569 | } 570 | 571 | private void configureTab(Tab tab, int position) { 572 | tab.setPosition(position); 573 | mTabs.add(position, tab); 574 | 575 | final int count = mTabs.size(); 576 | for (int i = position + 1; i < count; i++) { 577 | mTabs.get(i).setPosition(i); 578 | } 579 | } 580 | 581 | private void updateTab(int position) { 582 | final TabView view = (TabView) mTabStrip.getChildAt(position); 583 | if (view != null) { 584 | view.update(); 585 | } 586 | } 587 | 588 | private void addTabView(Tab tab, boolean setSelected) { 589 | final TabView tabView = createTabView(tab); 590 | mTabStrip.addView(tabView, createLayoutParamsForTabs()); 591 | if (setSelected) { 592 | tabView.setSelected(true); 593 | } 594 | } 595 | 596 | private void addTabView(Tab tab, int position, boolean setSelected) { 597 | final TabView tabView = createTabView(tab); 598 | mTabStrip.addView(tabView, position, createLayoutParamsForTabs()); 599 | if (setSelected) { 600 | tabView.setSelected(true); 601 | } 602 | } 603 | 604 | private LinearLayout.LayoutParams createLayoutParamsForTabs() { 605 | final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 606 | LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 607 | updateTabViewLayoutParams(lp); 608 | return lp; 609 | } 610 | 611 | private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) { 612 | if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) { 613 | lp.width = 0; 614 | lp.weight = 1; 615 | } else { 616 | lp.width = LinearLayout.LayoutParams.WRAP_CONTENT; 617 | lp.weight = 0; 618 | } 619 | } 620 | 621 | private int dpToPx(int dps) { 622 | return Math.round(getResources().getDisplayMetrics().density * dps); 623 | } 624 | 625 | @Override 626 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 627 | // If we have a MeasureSpec which allows us to decide our height, try and use the default 628 | // height 629 | switch (MeasureSpec.getMode(heightMeasureSpec)) { 630 | case MeasureSpec.AT_MOST: 631 | heightMeasureSpec = MeasureSpec.makeMeasureSpec( 632 | Math.min(dpToPx(DEFAULT_HEIGHT), MeasureSpec.getSize(heightMeasureSpec)), 633 | MeasureSpec.EXACTLY); 634 | break; 635 | case MeasureSpec.UNSPECIFIED: 636 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(dpToPx(DEFAULT_HEIGHT), 637 | MeasureSpec.EXACTLY); 638 | break; 639 | } 640 | 641 | // Now super measure itself using the (possibly) modified height spec 642 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 643 | 644 | if (mMode == MODE_FIXED && getChildCount() == 1) { 645 | // If we're in fixed mode then we need to make the tab strip is the same width as us 646 | // so we don't scroll 647 | final View child = getChildAt(0); 648 | final int width = getMeasuredWidth(); 649 | 650 | if (child.getMeasuredWidth() > width) { 651 | // If the child is wider than us, re-measure it with a widthSpec set to exact our 652 | // measure width 653 | int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() 654 | + getPaddingBottom(), child.getLayoutParams().height); 655 | int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 656 | child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 657 | } 658 | } 659 | 660 | // Now update the tab max width. We do it here as the default tab min width is 661 | // layout width - 56dp 662 | int maxTabWidth = mRequestedTabMaxWidth; 663 | final int defaultTabMaxWidth = getMeasuredWidth() - dpToPx(TAB_MIN_WIDTH_MARGIN); 664 | if (maxTabWidth == 0 || maxTabWidth > defaultTabMaxWidth) { 665 | // If the request tab max width is 0, or larger than our default, use the default 666 | maxTabWidth = defaultTabMaxWidth; 667 | } 668 | mTabMaxWidth = maxTabWidth; 669 | } 670 | 671 | private void removeTabViewAt(int position) { 672 | mTabStrip.removeViewAt(position); 673 | requestLayout(); 674 | } 675 | 676 | private void animateToTab(int newPosition) { 677 | clearAnimation(); 678 | 679 | if (newPosition == Tab.INVALID_POSITION) { 680 | return; 681 | } 682 | 683 | if (getWindowToken() == null || !ViewCompat.isLaidOut(this)) { 684 | // If we don't have a window token, or we haven't been laid out yet just draw the new 685 | // position now 686 | setScrollPosition(newPosition, 0f); 687 | return; 688 | } 689 | 690 | final int startScrollX = getScrollX(); 691 | final int targetScrollX = calculateScrollXForTab(newPosition, 0); 692 | final int duration = ANIMATION_DURATION; 693 | 694 | if (startScrollX != targetScrollX) { 695 | final Animation animation = new Animation() { 696 | @Override 697 | protected void applyTransformation(float interpolatedTime, Transformation t) { 698 | final float value = lerp(startScrollX, targetScrollX, interpolatedTime); 699 | scrollTo((int) value, 0); 700 | } 701 | }; 702 | animation.setInterpolator(INTERPOLATOR); 703 | animation.setDuration(duration); 704 | startAnimation(animation); 705 | } 706 | 707 | // Now animate the indicator 708 | mTabStrip.animateIndicatorToPosition(newPosition, duration); 709 | } 710 | 711 | private void setSelectedTabView(int position) { 712 | final int tabCount = mTabStrip.getChildCount(); 713 | for (int i = 0; i < tabCount; i++) { 714 | final View child = mTabStrip.getChildAt(i); 715 | final boolean isSelected = i == position; 716 | child.setSelected(isSelected); 717 | } 718 | } 719 | 720 | private static boolean isAnimationRunning(Animation animation) { 721 | return animation != null && animation.hasStarted() && !animation.hasEnded(); 722 | } 723 | 724 | void selectTab(Tab tab) { 725 | if (mSelectedTab == tab) { 726 | if (mSelectedTab != null) { 727 | if (mOnTabSelectedListener != null) { 728 | mOnTabSelectedListener.onTabReselected(mSelectedTab); 729 | } 730 | animateToTab(tab.getPosition()); 731 | } 732 | } else { 733 | final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION; 734 | setSelectedTabView(newPosition); 735 | 736 | if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION) 737 | && newPosition != Tab.INVALID_POSITION) { 738 | // If we don't currently have a tab, just draw the indicator 739 | setScrollPosition(newPosition, 0f); 740 | } else { 741 | animateToTab(newPosition); 742 | } 743 | 744 | if (mSelectedTab != null && mOnTabSelectedListener != null) { 745 | mOnTabSelectedListener.onTabUnselected(mSelectedTab); 746 | } 747 | mSelectedTab = tab; 748 | if (mSelectedTab != null && mOnTabSelectedListener != null) { 749 | mOnTabSelectedListener.onTabSelected(mSelectedTab); 750 | } 751 | } 752 | } 753 | 754 | private int calculateScrollXForTab(int position, float positionOffset) { 755 | if (mMode == MODE_SCROLLABLE) { 756 | final View selectedChild = mTabStrip.getChildAt(position); 757 | final View nextChild = position + 1 < mTabStrip.getChildCount() 758 | ? mTabStrip.getChildAt(position + 1) 759 | : null; 760 | final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0; 761 | final int nextWidth = nextChild != null ? nextChild.getWidth() : 0; 762 | 763 | return (int) (selectedChild.getLeft() 764 | + ((selectedWidth + nextWidth) * positionOffset * 0.5f) 765 | + selectedChild.getWidth() * 0.5f 766 | - getWidth() * 0.5f); 767 | } 768 | return 0; 769 | } 770 | 771 | private void applyModeAndGravity() { 772 | int paddingStart = 0; 773 | if (mMode == MODE_SCROLLABLE) { 774 | // If we're scrollable, or fixed at start, inset using padding 775 | paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart); 776 | } 777 | ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0); 778 | 779 | switch (mMode) { 780 | case MODE_FIXED: 781 | mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL); 782 | break; 783 | case MODE_SCROLLABLE: 784 | mTabStrip.setGravity(GravityCompat.START); 785 | break; 786 | } 787 | 788 | updateTabViewsLayoutParams(); 789 | } 790 | 791 | private void updateTabViewsLayoutParams() { 792 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 793 | View child = mTabStrip.getChildAt(i); 794 | updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams()); 795 | child.requestLayout(); 796 | } 797 | } 798 | 799 | /** 800 | * A tab in this layout. Instances can be created via {@link #newTab()}. 801 | */ 802 | public static final class Tab { 803 | 804 | /** 805 | * An invalid position for a tab. 806 | * 807 | * @see #getPosition() 808 | */ 809 | public static final int INVALID_POSITION = -1; 810 | 811 | private Object mTag; 812 | private Drawable mIcon; 813 | private CharSequence mText; 814 | private CharSequence mContentDesc; 815 | private int mPosition = INVALID_POSITION; 816 | private View mCustomView; 817 | 818 | private final TabLayout mParent; 819 | 820 | Tab(TabLayout parent) { 821 | mParent = parent; 822 | } 823 | 824 | /** 825 | * @return This Tab's tag object. 826 | */ 827 | public Object getTag() { 828 | return mTag; 829 | } 830 | 831 | /** 832 | * Give this Tab an arbitrary object to hold for later use. 833 | * 834 | * @param tag Object to store 835 | * @return The current instance for call chaining 836 | */ 837 | public Tab setTag(Object tag) { 838 | mTag = tag; 839 | return this; 840 | } 841 | 842 | View getCustomView() { 843 | return mCustomView; 844 | } 845 | 846 | /** 847 | * Set a custom view to be used for this tab. This overrides values set by {@link 848 | * #setText(CharSequence)} and {@link #setIcon(Drawable)}. 849 | * 850 | * @param view Custom view to be used as a tab. 851 | * @return The current instance for call chaining 852 | */ 853 | public Tab setCustomView(View view) { 854 | mCustomView = view; 855 | if (mPosition >= 0) { 856 | mParent.updateTab(mPosition); 857 | } 858 | return this; 859 | } 860 | 861 | /** 862 | * Set a custom view to be used for this tab. This overrides values set by {@link 863 | * #setText(CharSequence)} and {@link #setIcon(Drawable)}. 864 | * 865 | * @param layoutResId A layout resource to inflate and use as a custom tab view 866 | * @return The current instance for call chaining 867 | */ 868 | public Tab setCustomView(int layoutResId) { 869 | return setCustomView( 870 | LayoutInflater.from(mParent.getContext()).inflate(layoutResId, null)); 871 | } 872 | 873 | /** 874 | * Return the icon associated with this tab. 875 | * 876 | * @return The tab's icon 877 | */ 878 | public Drawable getIcon() { 879 | return mIcon; 880 | } 881 | 882 | /** 883 | * Return the current position of this tab in the action bar. 884 | * 885 | * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in 886 | * the action bar. 887 | */ 888 | public int getPosition() { 889 | return mPosition; 890 | } 891 | 892 | void setPosition(int position) { 893 | mPosition = position; 894 | } 895 | 896 | /** 897 | * Return the text of this tab. 898 | * 899 | * @return The tab's text 900 | */ 901 | public CharSequence getText() { 902 | return mText; 903 | } 904 | 905 | /** 906 | * Set the icon displayed on this tab. 907 | * 908 | * @param icon The drawable to use as an icon 909 | * @return The current instance for call chaining 910 | */ 911 | public Tab setIcon(Drawable icon) { 912 | mIcon = icon; 913 | if (mPosition >= 0) { 914 | mParent.updateTab(mPosition); 915 | } 916 | return this; 917 | } 918 | 919 | /** 920 | * Set the icon displayed on this tab. 921 | * 922 | * @param resId A resource ID referring to the icon that should be displayed 923 | * @return The current instance for call chaining 924 | */ 925 | public Tab setIcon(int resId) { 926 | return setIcon(TintManager.getDrawable(mParent.getContext(), resId)); 927 | } 928 | 929 | /** 930 | * Set the text displayed on this tab. Text may be truncated if there is not room to display 931 | * the entire string. 932 | * 933 | * @param text The text to display 934 | * @return The current instance for call chaining 935 | */ 936 | public Tab setText(CharSequence text) { 937 | mText = text; 938 | if (mPosition >= 0) { 939 | mParent.updateTab(mPosition); 940 | } 941 | return this; 942 | } 943 | 944 | /** 945 | * Set the text displayed on this tab. Text may be truncated if there is not room to display 946 | * the entire string. 947 | * 948 | * @param resId A resource ID referring to the text that should be displayed 949 | * @return The current instance for call chaining 950 | */ 951 | public Tab setText(int resId) { 952 | return setText(mParent.getResources().getText(resId)); 953 | } 954 | 955 | /** 956 | * Select this tab. Only valid if the tab has been added to the action bar. 957 | */ 958 | public void select() { 959 | mParent.selectTab(this); 960 | } 961 | 962 | /** 963 | * Set a description of this tab's content for use in accessibility support. If no content 964 | * description is provided the title will be used. 965 | * 966 | * @param resId A resource ID referring to the description text 967 | * @return The current instance for call chaining 968 | * @see #setContentDescription(CharSequence) 969 | * @see #getContentDescription() 970 | */ 971 | public Tab setContentDescription(int resId) { 972 | return setContentDescription(mParent.getResources().getText(resId)); 973 | } 974 | 975 | /** 976 | * Set a description of this tab's content for use in accessibility support. If no content 977 | * description is provided the title will be used. 978 | * 979 | * @param contentDesc Description of this tab's content 980 | * @return The current instance for call chaining 981 | * @see #setContentDescription(int) 982 | * @see #getContentDescription() 983 | */ 984 | public Tab setContentDescription(CharSequence contentDesc) { 985 | mContentDesc = contentDesc; 986 | if (mPosition >= 0) { 987 | mParent.updateTab(mPosition); 988 | } 989 | return this; 990 | } 991 | 992 | /** 993 | * Gets a brief description of this tab's content for use in accessibility support. 994 | * 995 | * @return Description of this tab's content 996 | * @see #setContentDescription(CharSequence) 997 | * @see #setContentDescription(int) 998 | */ 999 | public CharSequence getContentDescription() { 1000 | return mContentDesc; 1001 | } 1002 | } 1003 | 1004 | class TabView extends LinearLayout implements OnLongClickListener { 1005 | private final Tab mTab; 1006 | private TextView mTextView; 1007 | private ImageView mIconView; 1008 | private View mCustomView; 1009 | 1010 | public TabView(Context context, Tab tab) { 1011 | super(context); 1012 | mTab = tab; 1013 | if (mTabBackgroundResId != 0) { 1014 | setBackgroundDrawable(TintManager.getDrawable(context, mTabBackgroundResId)); 1015 | } 1016 | ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop, 1017 | mTabPaddingEnd, mTabPaddingBottom); 1018 | setGravity(Gravity.CENTER); 1019 | update(); 1020 | } 1021 | 1022 | @Override 1023 | public void setSelected(boolean selected) { 1024 | final boolean changed = (isSelected() != selected); 1025 | super.setSelected(selected); 1026 | if (changed && selected) { 1027 | sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1028 | 1029 | if (mTextView != null) { 1030 | mTextView.setSelected(selected); 1031 | } 1032 | if (mIconView != null) { 1033 | mIconView.setSelected(selected); 1034 | } 1035 | } 1036 | } 1037 | 1038 | 1039 | @Override 1040 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1041 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1042 | 1043 | if (getMeasuredWidth() > mTabMaxWidth) { 1044 | // Re-measure if we went beyond our maximum size. 1045 | super.onMeasure(MeasureSpec.makeMeasureSpec( 1046 | mTabMaxWidth, MeasureSpec.EXACTLY), heightMeasureSpec); 1047 | } else if (mTabMinWidth > 0 && getMeasuredHeight() < mTabMinWidth) { 1048 | // Re-measure if we're below our minimum size. 1049 | super.onMeasure(MeasureSpec.makeMeasureSpec( 1050 | mTabMinWidth, MeasureSpec.EXACTLY), heightMeasureSpec); 1051 | } 1052 | } 1053 | 1054 | final void update() { 1055 | final Tab tab = mTab; 1056 | final View custom = tab.getCustomView(); 1057 | if (custom != null) { 1058 | final ViewParent customParent = custom.getParent(); 1059 | if (customParent != this) { 1060 | if (customParent != null) { 1061 | ((ViewGroup) customParent).removeView(custom); 1062 | } 1063 | addView(custom); 1064 | } 1065 | mCustomView = custom; 1066 | if (mTextView != null) { 1067 | mTextView.setVisibility(GONE); 1068 | } 1069 | if (mIconView != null) { 1070 | mIconView.setVisibility(GONE); 1071 | mIconView.setImageDrawable(null); 1072 | } 1073 | } else { 1074 | if (mCustomView != null) { 1075 | removeView(mCustomView); 1076 | mCustomView = null; 1077 | } 1078 | 1079 | final Drawable icon = tab.getIcon(); 1080 | final CharSequence text = tab.getText(); 1081 | 1082 | if (icon != null) { 1083 | if (mIconView == null) { 1084 | ImageView iconView = new ImageView(getContext()); 1085 | LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, 1086 | LayoutParams.WRAP_CONTENT); 1087 | lp.gravity = Gravity.CENTER_VERTICAL; 1088 | iconView.setLayoutParams(lp); 1089 | addView(iconView, 0); 1090 | mIconView = iconView; 1091 | } 1092 | mIconView.setImageDrawable(icon); 1093 | mIconView.setVisibility(VISIBLE); 1094 | } else if (mIconView != null) { 1095 | mIconView.setVisibility(GONE); 1096 | mIconView.setImageDrawable(null); 1097 | } 1098 | 1099 | final boolean hasText = !TextUtils.isEmpty(text); 1100 | if (hasText) { 1101 | if (mTextView == null) { 1102 | TextView textView = new TextView(getContext()); 1103 | textView.setTextAppearance(getContext(), mTabTextAppearance); 1104 | textView.setMaxLines(MAX_TAB_TEXT_LINES); 1105 | textView.setEllipsize(TextUtils.TruncateAt.END); 1106 | textView.setGravity(Gravity.CENTER); 1107 | if (mTabSelectedTextColorSet) { 1108 | textView.setTextColor(createColorStateList( 1109 | textView.getCurrentTextColor(), mTabSelectedTextColor)); 1110 | } 1111 | 1112 | addView(textView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 1113 | mTextView = textView; 1114 | } 1115 | mTextView.setText(text); 1116 | mTextView.setVisibility(VISIBLE); 1117 | } else if (mTextView != null) { 1118 | mTextView.setVisibility(GONE); 1119 | mTextView.setText(null); 1120 | } 1121 | 1122 | if (mIconView != null) { 1123 | mIconView.setContentDescription(tab.getContentDescription()); 1124 | } 1125 | 1126 | if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) { 1127 | setOnLongClickListener(this); 1128 | } else { 1129 | setOnLongClickListener(null); 1130 | setLongClickable(false); 1131 | } 1132 | } 1133 | } 1134 | 1135 | @Override 1136 | public boolean onLongClick(View v) { 1137 | final int[] screenPos = new int[2]; 1138 | getLocationOnScreen(screenPos); 1139 | 1140 | final Context context = getContext(); 1141 | final int width = getWidth(); 1142 | final int height = getHeight(); 1143 | final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; 1144 | 1145 | Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(), 1146 | Toast.LENGTH_SHORT); 1147 | // Show under the tab 1148 | cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 1149 | (screenPos[0] + width / 2) - screenWidth / 2, height); 1150 | 1151 | cheatSheet.show(); 1152 | return true; 1153 | } 1154 | 1155 | private ColorStateList createColorStateList(int defaultColor, int selectedColor) { 1156 | final int[][] states = new int[2][]; 1157 | final int[] colors = new int[2]; 1158 | int i = 0; 1159 | 1160 | states[i] = SELECTED_STATE_SET; 1161 | colors[i] = selectedColor; 1162 | i++; 1163 | 1164 | // Default enabled state 1165 | states[i] = EMPTY_STATE_SET; 1166 | colors[i] = defaultColor; 1167 | i++; 1168 | 1169 | return new ColorStateList(states, colors); 1170 | } 1171 | 1172 | public Tab getTab() { 1173 | return mTab; 1174 | } 1175 | } 1176 | 1177 | private class SlidingTabStrip extends LinearLayout { 1178 | private int mSelectedIndicatorHeight; 1179 | private final Paint mSelectedIndicatorPaint; 1180 | 1181 | private int mSelectedPosition = -1; 1182 | private float mSelectionOffset; 1183 | 1184 | private int mIndicatorLeft = -1; 1185 | private int mIndicatorRight = -1; 1186 | 1187 | private boolean mIsIndicatorScrollable = true; 1188 | private boolean mHasInit = false; 1189 | 1190 | SlidingTabStrip(Context context) { 1191 | super(context); 1192 | setWillNotDraw(false); 1193 | mSelectedIndicatorPaint = new Paint(); 1194 | } 1195 | 1196 | void setSelectedIndicatorColor(int color) { 1197 | mSelectedIndicatorPaint.setColor(color); 1198 | ViewCompat.postInvalidateOnAnimation(this); 1199 | } 1200 | 1201 | void setSelectedIndicatorHeight(int height) { 1202 | mSelectedIndicatorHeight = height; 1203 | ViewCompat.postInvalidateOnAnimation(this); 1204 | } 1205 | 1206 | void setIndicatorPositionFromTabPosition(int position, float positionOffset) { 1207 | if (!mIsIndicatorScrollable && mHasInit) return; 1208 | 1209 | if (isAnimationRunning(getAnimation())) { 1210 | return; 1211 | } 1212 | mSelectedPosition = position; 1213 | mSelectionOffset = positionOffset; 1214 | updateIndicatorPosition(); 1215 | mHasInit = true; 1216 | } 1217 | 1218 | void setSelectedIndicatorScrollable(boolean isScrollable) { 1219 | mIsIndicatorScrollable = isScrollable; 1220 | } 1221 | 1222 | @Override 1223 | protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 1224 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1225 | 1226 | if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { 1227 | // HorizontalScrollView will first measure use with UNSPECIFIED, and then with 1228 | // EXACTLY. Ignore the first call since anything we do will be overwritten anyway 1229 | return; 1230 | } 1231 | 1232 | if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) { 1233 | final int count = getChildCount(); 1234 | 1235 | final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1236 | 1237 | // First we'll find the largest tab 1238 | int largestTabWidth = 0; 1239 | for (int i = 0, z = count; i < z; i++) { 1240 | final View child = getChildAt(i); 1241 | child.measure(unspecifiedSpec, heightMeasureSpec); 1242 | largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth()); 1243 | } 1244 | 1245 | if (largestTabWidth <= 0) { 1246 | // If we don't have a largest child yet, skip until the next measure pass 1247 | return; 1248 | } 1249 | 1250 | final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN); 1251 | if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) { 1252 | // If the tabs fit within our width minus gutters, we will set all tabs to have 1253 | // the same width 1254 | for (int i = 0; i < count; i++) { 1255 | final View child = getChildAt(i); 1256 | final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1257 | lp.width = largestTabWidth; 1258 | lp.weight = 0; 1259 | } 1260 | } else { 1261 | // If the tabs will wrap to be larger than the width minus gutters, we need 1262 | // to switch to GRAVITY_FILL 1263 | mTabGravity = GRAVITY_FILL; 1264 | updateTabViewsLayoutParams(); 1265 | } 1266 | 1267 | // Now re-measure after our changes 1268 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1269 | } 1270 | } 1271 | 1272 | @Override 1273 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 1274 | super.onLayout(changed, l, t, r, b); 1275 | 1276 | if (!isAnimationRunning(getAnimation())) { 1277 | // If we've been layed out, and we're not currently in an animation, update the 1278 | // indicator position 1279 | updateIndicatorPosition(); 1280 | } 1281 | } 1282 | 1283 | private void updateIndicatorPosition() { 1284 | final View selectedTitle = getChildAt(mSelectedPosition); 1285 | int left, right; 1286 | 1287 | if (selectedTitle != null && selectedTitle.getWidth() > 0) { 1288 | left = selectedTitle.getLeft(); 1289 | right = selectedTitle.getRight(); 1290 | 1291 | if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) { 1292 | // Draw the selection partway between the tabs 1293 | View nextTitle = getChildAt(mSelectedPosition + 1); 1294 | left = (int) (mSelectionOffset * nextTitle.getLeft() + 1295 | (1.0f - mSelectionOffset) * left); 1296 | right = (int) (mSelectionOffset * nextTitle.getRight() + 1297 | (1.0f - mSelectionOffset) * right); 1298 | } 1299 | } else { 1300 | left = right = -1; 1301 | } 1302 | 1303 | setIndicatorPosition(left, right); 1304 | } 1305 | 1306 | private void setIndicatorPosition(int left, int right) { 1307 | if (left != mIndicatorLeft || right != mIndicatorRight) { 1308 | // If the indicator's left/right has changed, invalidate 1309 | mIndicatorLeft = left; 1310 | mIndicatorRight = right; 1311 | ViewCompat.postInvalidateOnAnimation(this); 1312 | } 1313 | } 1314 | 1315 | void animateIndicatorToPosition(final int position, int duration) { 1316 | final boolean isRtl = ViewCompat.getLayoutDirection(this) 1317 | == ViewCompat.LAYOUT_DIRECTION_RTL; 1318 | 1319 | final View targetView = getChildAt(position); 1320 | final int targetLeft = targetView.getLeft(); 1321 | final int targetRight = targetView.getRight(); 1322 | final int startLeft; 1323 | final int startRight; 1324 | 1325 | if (Math.abs(position - mSelectedPosition) <= 1) { 1326 | // If the views are adjacent, we'll animate from edge-to-edge 1327 | startLeft = mIndicatorLeft; 1328 | startRight = mIndicatorRight; 1329 | } else { 1330 | // Else, we'll just grow from the nearest edge 1331 | final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET); 1332 | if (position < mSelectedPosition) { 1333 | // We're going end-to-start 1334 | if (isRtl) { 1335 | startLeft = startRight = targetLeft - offset; 1336 | } else { 1337 | startLeft = startRight = targetRight + offset; 1338 | } 1339 | } else { 1340 | // We're going start-to-end 1341 | if (isRtl) { 1342 | startLeft = startRight = targetRight + offset; 1343 | } else { 1344 | startLeft = startRight = targetLeft - offset; 1345 | } 1346 | } 1347 | } 1348 | 1349 | if (startLeft != targetLeft || startRight != targetRight) { 1350 | final Animation anim = new Animation() { 1351 | @Override 1352 | protected void applyTransformation(float interpolatedTime, Transformation t) { 1353 | setIndicatorPosition( 1354 | (int) lerp(startLeft, targetLeft, interpolatedTime), 1355 | (int) lerp(startRight, targetRight, interpolatedTime)); 1356 | } 1357 | }; 1358 | anim.setInterpolator(INTERPOLATOR); 1359 | anim.setDuration(duration); 1360 | anim.setAnimationListener(new Animation.AnimationListener() { 1361 | @Override 1362 | public void onAnimationStart(Animation animation) { 1363 | } 1364 | 1365 | @Override 1366 | public void onAnimationEnd(Animation animation) { 1367 | mSelectedPosition = position; 1368 | mSelectionOffset = 0f; 1369 | } 1370 | 1371 | @Override 1372 | public void onAnimationRepeat(Animation animation) { 1373 | } 1374 | }); 1375 | 1376 | startAnimation(anim); 1377 | } 1378 | } 1379 | 1380 | @Override 1381 | public void draw(Canvas canvas) { 1382 | super.draw(canvas); 1383 | if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) { 1384 | canvas.drawRect(mIndicatorLeft, (float) (getHeight() - mSelectedIndicatorHeight), mIndicatorRight, getHeight(), mSelectedIndicatorPaint); 1385 | } 1386 | 1387 | } 1388 | } 1389 | 1390 | /** 1391 | * Linear interpolation between {@code startValue} and {@code endValue} by the fraction {@code 1392 | * fraction}. 1393 | */ 1394 | static float lerp(float startValue, float endValue, float fraction) { 1395 | return startValue + (fraction * (endValue - startValue)); 1396 | } 1397 | 1398 | } 1399 | --------------------------------------------------------------------------------