├── app ├── .gitignore ├── art │ ├── slidingtab1.png │ ├── slidingtab2.png │ ├── slidingtab3.png │ ├── slidingtab4.png │ ├── slidingtab5.png │ ├── slidingtab6.png │ ├── slidingtab7.png │ └── slidingtab8.png ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── viewpager_item.xml │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── vivian │ │ │ └── slidingtab │ │ │ └── MainActivity.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── vivian │ │ │ └── slidingtab │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── vivian │ │ └── slidingtab │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── slidingtab ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── slidingtab_attrs.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── vivian │ │ │ └── slidingtab │ │ │ └── SlidingTab.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── vivian │ │ │ └── slidingtab │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── vivian │ │ └── slidingtab │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── build.gradle └── bintray.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── README.md ├── README_EN.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /slidingtab/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /slidingtab/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':slidingtab' 2 | rootProject.name='SlidingTab' 3 | -------------------------------------------------------------------------------- /app/art/slidingtab1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab1.png -------------------------------------------------------------------------------- /app/art/slidingtab2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab2.png -------------------------------------------------------------------------------- /app/art/slidingtab3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab3.png -------------------------------------------------------------------------------- /app/art/slidingtab4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab4.png -------------------------------------------------------------------------------- /app/art/slidingtab5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab5.png -------------------------------------------------------------------------------- /app/art/slidingtab6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab6.png -------------------------------------------------------------------------------- /app/art/slidingtab7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab7.png -------------------------------------------------------------------------------- /app/art/slidingtab8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/art/slidingtab8.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SlidingTab 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /slidingtab/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SlidingTab 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | .cxx 9 | .idea/ 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /slidingtab/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivian8725118/SlidingTab/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 03 23:33:22 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/vivian/slidingtab/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vivian.slidingtab; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /slidingtab/src/test/java/com/vivian/slidingtab/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vivian.slidingtab; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/viewpager_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | -------------------------------------------------------------------------------- /slidingtab/src/main/res/values/slidingtab_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /slidingtab/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /slidingtab/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 21 9 | targetSdkVersion 28 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles 'consumer-rules.pro' 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | testImplementation 'junit:junit:4.12' 31 | } 32 | 33 | //放到下面 34 | apply from: 'bintray.gradle' 35 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/vivian/slidingtab/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.vivian.slidingtab; 2 | 3 | import android.content.Context; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | 8 | import androidx.test.platform.app.InstrumentationRegistry; 9 | import androidx.test.runner.AndroidJUnit4; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.vivian.slidingtab", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /slidingtab/src/androidTest/java/com/vivian/slidingtab/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.vivian.slidingtab; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.vivian.slidingtab.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.vivian.slidingtab" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'androidx.appcompat:appcompat:1.1.0' 24 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'androidx.test:runner:1.1.1' 27 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 28 | implementation project(':slidingtab')//引用第三方项目 29 | // implementation 'com.vivian.widgets:slidingtab:1.0.4' 30 | } 31 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 25 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/vivian/slidingtab/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vivian.slidingtab; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.viewpager.widget.PagerAdapter; 12 | import androidx.viewpager.widget.ViewPager; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | SlidingTab slidingTab; 16 | ViewPager viewPager; 17 | Adapter mAdapter; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | viewPager = findViewById(R.id.viewpager); 25 | slidingTab = findViewById(R.id.sliding_tab); 26 | 27 | mAdapter = new Adapter(this); 28 | viewPager.setAdapter(mAdapter); 29 | mAdapter.notifyDataSetChanged(); 30 | 31 | slidingTab.setTitles("课程", "文档"); 32 | slidingTab.bindViewPager(viewPager); 33 | 34 | slidingTab.setOnTabClickListener(new SlidingTab.OnTabClickListener() { 35 | @Override 36 | public void onTabClick(int position) { 37 | viewPager.setCurrentItem(position); 38 | } 39 | }); 40 | } 41 | 42 | 43 | class Adapter extends PagerAdapter { 44 | Context mContext; 45 | 46 | public Adapter(Context context) { 47 | this.mContext = context; 48 | } 49 | 50 | @Override 51 | public int getCount() { 52 | return 2; 53 | } 54 | 55 | int colors[]=new int[]{0xfff3aa0b,0xff1F8F70}; 56 | @NonNull 57 | @Override 58 | public Object instantiateItem(@NonNull ViewGroup container, int position) { 59 | View view = LayoutInflater.from(mContext).inflate(R.layout.viewpager_item, null); 60 | view.setBackgroundColor(colors[position%colors.length]); 61 | //不添加不显示 62 | container.addView(view); 63 | return view; 64 | } 65 | 66 | @Override 67 | public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { 68 | return view == object; 69 | } 70 | 71 | @Override 72 | public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { 73 | container.removeView((View)object); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /slidingtab/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | apply plugin: 'com.jfrog.bintray' 3 | 4 | group = rootProject.ext.pomGroup 5 | version = rootProject.ext.versionName 6 | 7 | def HashMap getConfig(){ 8 | HashMap configMap = new HashMap() 9 | if(configMap.size() == 0){ 10 | 11 | File localPropFile = project.rootProject.file("local.properties") 12 | if(localPropFile.exists()){ 13 | Properties localProperties = new Properties() 14 | localProperties.load(localPropFile.newDataInputStream()) 15 | 16 | configMap.put("id", localProperties.getProperty("bintray.id")) 17 | configMap.put("name", localProperties.getProperty("bintray.name")) 18 | configMap.put("email", localProperties.getProperty("bintray.email")) 19 | configMap.put("user", localProperties.getProperty("bintray.user")) 20 | configMap.put("apiKey", localProperties.getProperty("bintray.apiKey")) 21 | } 22 | } 23 | 24 | return configMap 25 | } 26 | 27 | install { 28 | repositories.mavenInstaller { 29 | // This generates POM.xml with proper parameters 30 | pom { 31 | project { 32 | packaging rootProject.ext.pomPackaging 33 | name rootProject.ext.pomName 34 | url rootProject.ext.pomSiteUrl 35 | 36 | licenses { 37 | license { 38 | name rootProject.ext.pomLicenseName 39 | url rootProject.ext.pomLicenseUrl 40 | } 41 | } 42 | developers { 43 | developer { 44 | def config = getConfig() 45 | id config.get("id") 46 | name config.get("name") 47 | email config.get("email") 48 | } 49 | } 50 | 51 | scm { 52 | connection rootProject.ext.pomGitUrl 53 | developerConnection rootProject.ext.pomGitUrl 54 | url rootProject.ext.pomSiteUrl 55 | } 56 | 57 | } 58 | } 59 | } 60 | } 61 | 62 | task sourcesJar(type: Jar) { 63 | from android.sourceSets.main.java.srcDirs 64 | classifier = 'sources' 65 | } 66 | 67 | task javadoc(type: Javadoc){ 68 | source = android.sourceSets.main.java.srcDirs 69 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 70 | } 71 | 72 | task javadocJar(type: Jar, dependsOn: javadoc) { 73 | classifier = 'javadoc' 74 | from javadoc.destinationDir 75 | } 76 | 77 | artifacts { 78 | archives javadocJar 79 | archives sourcesJar 80 | } 81 | 82 | bintray { 83 | def config = getConfig() 84 | println "user:" + config.get("user") 85 | println "apiKey:" + config.get("apiKey") 86 | user = config.get("user") 87 | key = config.get("apiKey") 88 | configurations = ['archives'] 89 | pkg { 90 | repo = "maven" 91 | name = rootProject.ext.pomName 92 | websiteUrl = rootProject.ext.pomSiteUrl 93 | vcsUrl = rootProject.ext.pomGitUrl 94 | licenses = [rootProject.ext.pomLicenseShort] 95 | publish = true 96 | } 97 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SlidingTab 2 | 可以交叉tab和文字的SlidingTab,支持设置渐变色。 3 | 4 | [ENGLISH_VERSION_README](https://github.com/vivian8725118/SlidingTab/blob/master/README_EN.md) 5 | 6 | # 功能 7 | 8 | ## 1.支持底色交叉文字 9 | ![](https://github.com/vivian8725118/SlidingTab/blob/master/app/art/slidingtab4.png) 10 | 11 | ## 2.支持渐变色 12 |
13 | 14 | 15 | 16 |
17 | 18 | ## 3.支持颜色修改 19 |
20 | 21 |
22 | 23 | # 使用 24 | ## 1、导入包 25 | 26 | ```groovy 27 | implementation 'com.vivian.widgets:slidingtab:1.0.4' 28 | 29 | ``` 30 | 31 | ## 2、 在xml中设置 32 | ```xml 33 | 47 | ``` 48 | 49 | app:strokeWidth="2dp" //设置外围线框宽度 50 | app:mainColor="#1A51AD" //设置主色 51 | app:mainColorRes="@color/colorAccent" //设置主色资源。如果mainColor和mainColorRes同时设置了,按mainColorRes内容显示 52 | app:radius="100dp" //设置圆角尺寸 53 | app:tabHeight="100dp" //设置tab高度 54 | app:textSize="16sp" //设置字体大小 55 | app:startColor="#a1aa0b" //设置渐变色起始颜色 56 | app:endColor="#1F8F70" //设置渐变色结束颜色 57 | 58 | 59 | ## 3、 在java中设置 60 | 61 | ### 3.1 设置titles 62 | ```java 63 | slidingTab.setTitles("课程", "文档"); 64 | ``` 65 | 或者 66 | ```java 67 | slidingTab.setTitles(List titles) 68 | ``` 69 | 70 | ### 3.2 绑定ViewPager的onPageChangeListener 71 | 72 | ```java 73 | slidingTab.bindViewPager(viewPager); 74 | ``` 75 | 76 | 77 | ### 3.3 设置OnTabClickListener 78 | 79 | ```java 80 | slidingTab.setOnTabClickListener(new SlidingTab.OnTabClickListener() { 81 | @Override 82 | public void onTabClick(int position) { 83 | viewPager.setCurrentItem(position); 84 | } 85 | }); 86 | ``` 87 | 88 | # License 89 | 90 | Copyright 2019 Vivian 91 | 92 | Licensed under the Apache License, Version 2.0 (the "License"); 93 | you may not use this file except in compliance with the License. 94 | You may obtain a copy of the License at 95 | 96 | http://www.apache.org/licenses/LICENSE-2.0 97 | 98 | Unless required by applicable law or agreed to in writing, software 99 | distributed under the License is distributed on an "AS IS" BASIS, 100 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 101 | See the License for the specific language governing permissions and 102 | limitations under the License. 103 | 104 | 105 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # SlidingTab 2 | SlidingTab which can cross tab and word,and you can set a gradient color for the whole tab widget. 3 | 4 | [中文版README](https://github.com/vivian8725118/SlidingTab/blob/master/README.md) 5 | 6 | # Feature 7 | 8 | ## 1.Support cross tab and word 9 | ![](https://github.com/vivian8725118/SlidingTab/blob/master/app/art/slidingtab4.png) 10 | 11 | ## 2.Support linear gradient color 12 |
13 | 14 | 15 | 16 |
17 | 18 | ## 3.Support color settings 19 |
20 | 21 |
22 | 23 | # Usage 24 | ## 1. Import package 25 | 26 | ```groovy 27 | implementation 'com.vivian.widgets:slidingtab:1.0.4' 28 | 29 | ``` 30 | 31 | ## 2. Write in xml files 32 | 33 | ```xml 34 | 48 | ``` 49 | 50 | app:strokeWidth="2dp" //set the width of outside roundrect 51 | app:mainColor="#1A51AD" //set main color of the whole tab 52 | app:mainColorRes="@color/colorAccent" //set main color resource.If both "mainColor" and "mainColorRes" are set,it will show as "mainColorRes“ 53 | app:radius="100dp" //set roundcorner radius size 54 | app:tabHeight="100dp" //set the height of tab 55 | app:textSize="16sp" //set textsize 56 | app:startColor="#a1aa0b" //set start color of gradient part 57 | app:endColor="#1F8F70" //set end color of gradient part 58 | 59 | ## 3、 Write in Java code 60 | 61 | ### 3.1 Set titles 62 | 63 | ```java 64 | slidingTab.setTitles("Course", "Document"); 65 | ``` 66 | or 67 | 68 | ```java 69 | slidingTab.setTitles(List titles) 70 | ``` 71 | 72 | ### 3.2 Bind ViewPager.onPageChangeListener 73 | 74 | ```java 75 | slidingTab.bindViewPager(viewPager); 76 | ``` 77 | 78 | ### 3.3 setOnTabClickListener 79 | 80 | ```java 81 | slidingTab.setOnTabClickListener(new SlidingTab.OnTabClickListener() { 82 | @Override 83 | public void onTabClick(int position) { 84 | viewPager.setCurrentItem(position); 85 | } 86 | }); 87 | ``` 88 | 89 | # License 90 | 91 | Copyright 2019 Vivian 92 | 93 | Licensed under the Apache License, Version 2.0 (the "License"); 94 | you may not use this file except in compliance with the License. 95 | You may obtain a copy of the License at 96 | 97 | http://www.apache.org/licenses/LICENSE-2.0 98 | 99 | Unless required by applicable law or agreed to in writing, software 100 | distributed under the License is distributed on an "AS IS" BASIS, 101 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 102 | See the License for the specific language governing permissions and 103 | limitations under the License. 104 | 105 | 106 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /slidingtab/src/main/java/com/vivian/slidingtab/SlidingTab.java: -------------------------------------------------------------------------------- 1 | package com.vivian.slidingtab; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.LinearGradient; 8 | import android.graphics.Paint; 9 | import android.graphics.PorterDuff; 10 | import android.graphics.PorterDuffXfermode; 11 | import android.graphics.RectF; 12 | import android.graphics.Shader; 13 | import android.util.AttributeSet; 14 | import android.view.MotionEvent; 15 | import android.view.View; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | 21 | import androidx.annotation.Nullable; 22 | import androidx.core.content.ContextCompat; 23 | import androidx.viewpager.widget.ViewPager; 24 | 25 | /** 26 | * * _ _ 27 | * * __ _(_)_ _(_) __ _ _ __ 28 | * * \ \ / / \ \ / / |/ _` | '_ \ 29 | * * \ V /| |\ V /| | (_| | | | | 30 | * * \_/ |_| \_/ |_|\__,_|_| |_| 31 | *

32 | * Created by vivian on 2019-12-05. 33 | */ 34 | public class SlidingTab extends View { 35 | public static final float DEFAULT_TEXT_SIZE = 16f; 36 | public static final int DEFAULT_RADIUS = 200; 37 | public static final int DEFAULT_TAB_HEIGHT = 100; 38 | public static final int DEFAULT_STROKE_WIDTH = 2; 39 | 40 | private Paint mPaint; 41 | private Paint mTextPaint; 42 | private RectF mRect; 43 | private RectF mTabRect; 44 | 45 | /** 46 | * Text size,default value is {@link #DEFAULT_TEXT_SIZE} 47 | */ 48 | private float mTextSize = DEFAULT_TEXT_SIZE; 49 | /** 50 | * The radius size of round rect corner,default value is {@link #DEFAULT_RADIUS} 51 | */ 52 | private int mRadius = DEFAULT_RADIUS; 53 | /** 54 | * The height of the tab,default value is {@link #DEFAULT_TAB_HEIGHT} 55 | */ 56 | private int mTabHeight = DEFAULT_TAB_HEIGHT; 57 | private int width; 58 | private int mHeight; 59 | private float mTabWidth; 60 | private float mDistance; 61 | private float mBaseline; 62 | /** 63 | * The width of outside round rect.The unit is pixel. 64 | */ 65 | private float mStrokeWidth = DEFAULT_STROKE_WIDTH; 66 | /** 67 | * The count of tabs. 68 | *

69 | * Default tab's count is 2. 70 | */ 71 | private int mTabCount = 2; 72 | /** 73 | * Main color of tab. 74 | */ 75 | private int mMainColor = Color.BLUE; 76 | /** 77 | * Main color resource of tab. 78 | *

79 | * If both {@link #mMainColor} and {@link #mMainColorRes} are set,{@link #mMainColorRes} will be ignored. 80 | */ 81 | private int mMainColorRes; 82 | private int mBgColor = Color.WHITE; 83 | private int mCurrentPosition = 0; 84 | private float mCurrentLeft = 0; 85 | private float mCurrentOffset = 0; 86 | private int mScrollPosition = 0; 87 | /** 88 | * Start color of gradient part. 89 | */ 90 | private int mStartColor; 91 | /** 92 | * End color of gradient part. 93 | */ 94 | private int mEndColor; 95 | private LinearGradient mLinearGradient; 96 | 97 | /** 98 | * The words you may want to show in each tab. 99 | *

100 | * Use with{@link #setTitles(String...)} or {@link #setTitles(List)}. 101 | */ 102 | private List mTitles = new ArrayList<>(); 103 | /** 104 | * Tab click monitor. 105 | */ 106 | OnTabClickListener mOnTabClickListener; 107 | 108 | public SlidingTab(Context context) { 109 | super(context); 110 | initView(context); 111 | } 112 | 113 | public SlidingTab(Context context, @Nullable AttributeSet attrs) { 114 | super(context, attrs); 115 | dealAttrs(context, attrs); 116 | initView(context); 117 | } 118 | 119 | public SlidingTab(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 120 | super(context, attrs, defStyleAttr); 121 | dealAttrs(context, attrs); 122 | initView(context); 123 | } 124 | 125 | public SlidingTab(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 126 | super(context, attrs, defStyleAttr, defStyleRes); 127 | dealAttrs(context, attrs); 128 | initView(context); 129 | } 130 | 131 | public void dealAttrs(Context context, AttributeSet attrs) { 132 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingTab); 133 | mTextSize = ta.getDimensionPixelSize(R.styleable.SlidingTab_textSize, (int) DEFAULT_TEXT_SIZE); 134 | mRadius = ta.getDimensionPixelSize(R.styleable.SlidingTab_radius, DEFAULT_RADIUS); 135 | mMainColor = ta.getColor(R.styleable.SlidingTab_mainColor, Color.BLUE); 136 | mMainColorRes = ta.getResourceId(R.styleable.SlidingTab_mainColorRes, 0); 137 | if (mMainColorRes != 0) { 138 | mMainColor = ContextCompat.getColor(context, mMainColorRes); 139 | } 140 | mStartColor = ta.getColor(R.styleable.SlidingTab_startColor, 0); 141 | mEndColor = ta.getColor(R.styleable.SlidingTab_endColor, 0); 142 | mTabHeight = ta.getDimensionPixelSize(R.styleable.SlidingTab_tabHeight, DEFAULT_TAB_HEIGHT); 143 | mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SlidingTab_strokeWidth, DEFAULT_TAB_HEIGHT); 144 | ta.recycle(); 145 | } 146 | 147 | public void initView(Context context) { 148 | setBackgroundColor(Color.WHITE); 149 | mPaint = new Paint(); 150 | mPaint.setAntiAlias(true); 151 | mPaint.setDither(false); 152 | mPaint.setColor(mMainColor); 153 | mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); 154 | 155 | mTextPaint = new Paint(); 156 | mTextPaint.setColor(mMainColor); 157 | mTextPaint.setTextSize(mTextSize); 158 | mTextPaint.setAntiAlias(true); 159 | mTextPaint.setTextAlign(Paint.Align.CENTER); 160 | 161 | //计算baseline 162 | Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); 163 | mDistance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom; 164 | mTextPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); 165 | 166 | mRect = new RectF(); 167 | mTabRect = new RectF(); 168 | } 169 | 170 | @Override 171 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 172 | int width = resolveSize(150, widthMeasureSpec); 173 | int height = resolveSize(DEFAULT_TAB_HEIGHT, heightMeasureSpec); 174 | setMeasuredDimension(width, height); 175 | } 176 | 177 | @Override 178 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 179 | super.onSizeChanged(w, h, oldw, oldh); 180 | width = w; 181 | mHeight = h; 182 | 183 | mTabHeight = (int) (mHeight - 2 * mStrokeWidth); 184 | mRect.set(mStrokeWidth, mStrokeWidth, width - mStrokeWidth, mHeight - mStrokeWidth); 185 | 186 | if (mTitles.size() == 0) { 187 | mTabWidth = 100; 188 | } else { 189 | mTabCount = mTitles.size(); 190 | mTabWidth = mRect.width() / mTabCount; 191 | } 192 | 193 | //渐变色 194 | if (mStartColor != 0 && mEndColor != 0) { 195 | mLinearGradient = new LinearGradient(0, 0, width, mHeight, mStartColor, mEndColor, Shader.TileMode.CLAMP); 196 | mPaint.setShader(mLinearGradient); 197 | mTextPaint.setShader(mLinearGradient); 198 | } 199 | } 200 | 201 | @Override 202 | protected void onDraw(Canvas canvas) { 203 | super.onDraw(canvas); 204 | canvas.drawColor(Color.WHITE); 205 | int layerId = canvas.saveLayer(mRect, null); 206 | canvas.drawColor(mBgColor, PorterDuff.Mode.CLEAR); 207 | 208 | //画tab 209 | mPaint.setStrokeWidth(mStrokeWidth); 210 | mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 211 | mCurrentLeft = mStrokeWidth + mScrollPosition * mTabWidth + (mCurrentOffset * mTabWidth); 212 | mTabRect.set(mCurrentLeft, mStrokeWidth, mCurrentLeft + mTabWidth, mTabHeight + mStrokeWidth); 213 | canvas.drawRoundRect(mTabRect, mRadius, mRadius, mPaint); 214 | canvas.save(); 215 | 216 | //画文字 217 | if (mTitles.size() > 0) { 218 | for (int i = 0; i < mTitles.size(); i++) { 219 | mTextPaint.setColor(mMainColor); 220 | mBaseline = mTabRect.centerY() + mDistance; 221 | canvas.drawText(mTitles.get(i), mStrokeWidth + i * mTabWidth + (mTabWidth) / 2, mBaseline, mTextPaint); 222 | } 223 | } 224 | canvas.restoreToCount(layerId); 225 | 226 | //底部 227 | mPaint.setXfermode(null); 228 | mPaint.setColor(mMainColor); 229 | mPaint.setStrokeWidth(mStrokeWidth); 230 | mPaint.setStyle(Paint.Style.STROKE); 231 | canvas.drawRoundRect(mRect, mRadius, mRadius, mPaint); 232 | } 233 | 234 | @Override 235 | public boolean onTouchEvent(MotionEvent event) { 236 | switch (event.getAction()) { 237 | case MotionEvent.ACTION_DOWN: 238 | float x = event.getX(); 239 | int position = (int) (x / mTabWidth); 240 | if (mOnTabClickListener != null) { 241 | mOnTabClickListener.onTabClick(position); 242 | } 243 | break; 244 | default: 245 | break; 246 | } 247 | return super.onTouchEvent(event); 248 | } 249 | 250 | public void setOnTabClickListener(OnTabClickListener onTabClickListener) { 251 | this.mOnTabClickListener = onTabClickListener; 252 | } 253 | 254 | /** 255 | * Interface definition for a callback to be invoked when a tab is clicked. 256 | */ 257 | public interface OnTabClickListener { 258 | /** 259 | * Called when a tab has been clicked. 260 | * 261 | * @param position The position of tab which is clicked. 262 | */ 263 | void onTabClick(int position); 264 | } 265 | 266 | /** 267 | * Update tab position 268 | * 269 | * @param newX 270 | */ 271 | public void slidingTabPosition(int newX) { 272 | mTabRect.set(0, newX, width / mTabCount + newX, mTabHeight); 273 | } 274 | 275 | public void setCurrentPosition(int currentPosition) { 276 | mCurrentPosition = currentPosition; 277 | invalidate(); 278 | } 279 | 280 | public void setScrollFromCurrentPosition(int scrollPosition, float offset) { 281 | mCurrentOffset = offset; 282 | mScrollPosition = scrollPosition; 283 | invalidate(); 284 | } 285 | 286 | public void setTitles(List titles) { 287 | mTitles = titles; 288 | invalidate(); 289 | } 290 | 291 | public void setTitles(String... titles) { 292 | Collections.addAll(mTitles, titles); 293 | } 294 | 295 | public void bindViewPager(ViewPager viewPager) { 296 | if (viewPager == null) { 297 | return; 298 | } 299 | viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 300 | @Override 301 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 302 | setScrollFromCurrentPosition(position, positionOffset); 303 | } 304 | 305 | @Override 306 | public void onPageSelected(int position) { 307 | setCurrentPosition(position); 308 | } 309 | 310 | @Override 311 | public void onPageScrollStateChanged(int state) { 312 | 313 | } 314 | }); 315 | } 316 | } 317 | --------------------------------------------------------------------------------