├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── chilanka.otf
│ ├── java
│ └── github
│ │ └── xuqk
│ │ └── kdtablayout
│ │ └── sample
│ │ ├── BadgeTabActivity.kt
│ │ ├── CustomTabActivity.kt
│ │ ├── DynamicTabActivity.kt
│ │ ├── Ext.kt
│ │ ├── FixedTabActivity.kt
│ │ ├── MainActivity.kt
│ │ ├── OnlyIndicatorActivity.kt
│ │ ├── ScrollableTabActivity.kt
│ │ ├── adapter
│ │ ├── ViewPager2Adapter.kt
│ │ └── ViewPagerAdapter.kt
│ │ └── widget
│ │ ├── CustomFontTab.kt
│ │ ├── ShadowGradientTab.kt
│ │ └── TextViewTab.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── bg_round_stroke_r24.xml
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_badge_tab.xml
│ ├── activity_custom_tab.xml
│ ├── activity_dynamic_tab.xml
│ ├── activity_fixed_tab.xml
│ ├── activity_main.xml
│ ├── activity_only_indicator.xml
│ └── activity_scrollable_tab.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── demo.gif
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── kdtablayout
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ └── java
│ └── github
│ └── xuqk
│ └── kdtablayout
│ ├── Ext.kt
│ ├── KDTabAdapter.kt
│ ├── KDTabLayout.kt
│ ├── KDViewPagerHelper.kt
│ └── widget
│ ├── KDBadge.kt
│ ├── KDTab.kt
│ ├── KDTabIndicator.kt
│ ├── badge
│ ├── KDContentEndRelativeBadge.kt
│ ├── KDNumberBadge.kt
│ └── KDTabEndRelativeBadge.kt
│ ├── indicator
│ ├── DotMorphingIndicator.kt
│ ├── DotWithStrokeIndicator.kt
│ └── KDRecIndicator.kt
│ └── tab
│ ├── KDColorClipTextTab.kt
│ ├── KDColorMorphingTextTab.kt
│ └── KDSizeMorphingTextTab.kt
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
16 |
17 |
18 | # Created by https://www.gitignore.io/api/androidstudio
19 |
20 | ### AndroidStudio ###
21 | # Covers files to be ignored for android development using Android Studio.
22 |
23 | # Built application files
24 | *.apk
25 | *.ap_
26 |
27 | # Files for the ART/Dalvik VM
28 | *.dex
29 |
30 | # Java class files
31 | *.class
32 |
33 | # Generated files
34 | bin/
35 | gen/
36 | out/
37 |
38 | # Gradle files
39 | .gradle
40 | .gradle/
41 | build/
42 |
43 | # Signing files
44 | .signing/
45 |
46 | # Local configuration file (sdk path, etc)
47 | local.properties
48 |
49 | # Proguard folder generated by Eclipse
50 | proguard/
51 |
52 | # Log Files
53 | *.log
54 |
55 | # Android Studio
56 | /*/build/
57 | /*/local.properties
58 | /*/out
59 | /*/*/build
60 | /*/*/production
61 | captures/
62 | .navigation/
63 | *.ipr
64 | *~
65 | *.swp
66 |
67 | # Android Patch
68 | gen-external-apklibs
69 |
70 | # External native build folder generated in Android Studio 2.2 and later
71 | .externalNativeBuild
72 |
73 | # NDK
74 | obj/
75 |
76 | # IntelliJ IDEA
77 | *.iml
78 | *.iws
79 | /out/
80 |
81 | # User-specific configurations
82 | .idea/
83 |
84 | # OS-specific files
85 | .DS_Store
86 | .DS_Store?
87 | ._*
88 | .Spotlight-V100
89 | .Trashes
90 | ehthumbs.db
91 | Thumbs.db
92 |
93 | # Legacy Eclipse project files
94 | .classpath
95 | .project
96 | .cproject
97 | .settings/
98 |
99 | # Mobile Tools for Java (J2ME)
100 | .mtj.tmp/
101 |
102 | # Package Files #
103 | *.war
104 | *.ear
105 |
106 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
107 | hs_err_pid*
108 |
109 | ## Plugin-specific files:
110 |
111 | # mpeltonen/sbt-idea plugin
112 | .idea_modules/
113 |
114 | # JIRA plugin
115 | atlassian-ide-plugin.xml
116 |
117 | # Mongo Explorer plugin
118 | .idea/mongoSettings.xml
119 |
120 | # Crashlytics plugin (for Android Studio and IntelliJ)
121 | com_crashlytics_export_strings.xml
122 | crashlytics.properties
123 | crashlytics-build.properties
124 | fabric.properties
125 |
126 | ### AndroidStudio Patch ###
127 |
128 | !/gradle/wrapper/gradle-wrapper.jar
129 |
130 |
131 | # End of https://www.gitignore.io/api/androidstudio
132 |
133 | # bugly
134 | /*/bugly
135 |
136 | # gradle profile reports
137 | /reports
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 KongKong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://jitpack.io/#XuQK/KDTabLayout)
2 |
3 | 一直感觉[MagicIndicator](https://github.com/hackware1993/MagicIndicator)这个库相当牛逼,在自己的工作过程中也帮我解决了很多问题,无奈作者志向高远,已经转行不写代码了,所以我参考了一下该库,自己撸了一个TabLayout。
4 |
5 | 本TabLayout直接继承自ViewGroup,默认实现了一些简单的效果,也留出了接口供用户自定义单个Tab,Indicator,Badge等控件,可以和ViewPager2和ViewPager搭配使用。
6 |
7 | 效果图:
8 |
9 | 
10 |
11 | # 使用方式:
12 |
13 | ## 将JitPack存储库添加到构建文件(项目根目录下build.gradle文件)
14 |
15 | ```groovy
16 | allprojects {
17 | repositories {
18 | ...
19 | maven { url 'https://jitpack.io' }
20 | }
21 | }
22 | ```
23 |
24 | ## 添加依赖项
25 |
26 | ```groovy
27 | // 版本号参看Release
28 | implementation 'com.github.XuQK:KDTabLayout:versionCode'
29 |
30 | // 如果要使用ViewPager2,请添加ViewPager2库
31 | implementation 'androidx.viewpager2:viewpager2:versionCode'
32 | ```
33 |
34 | ## 使用说明
35 |
36 | ### 简单使用
37 |
38 | 1. 将KDTabLayout添加到你的布局文件中
39 |
40 | 2. 为KDTabLayout配置Adapter
41 |
42 | ```kotlin
43 | // KDTabLayout本身主要有tabMode和scrollBiasX两个属性可设置,具体见类field注释,默认是TAB_MODE_FLEXIBLE
44 | tab2.tabMode = KDTabLayout.TAB_MODE_SCROLLABLE
45 | tabLayout.contentAdapter = object : KDTabAdapter() {
46 | override fun createTab(position: Int): KDTab? {
47 | // 如果该方法返回null,则TabLayout只会绘制Indicator
48 | // 这是一个Tab中文字颜色和大小会随滚动进度变化的Tab
49 | return KDColorMorphingTextTab(context, data[position]).apply {
50 | horizontalPadding = 16f
51 | selectedTextColor = Color.parseColor("#ff5722")
52 | normalTextColor = Color.parseColor("#9e9e9e")
53 | setOnClickListener {
54 | viewPager2.currentItem = position
55 | }
56 | }
57 | }
58 |
59 | override fun createIndicator(): KDTabIndicator? {
60 | // 如果该方法返回null,则TabLayout不会绘制Indicator
61 | // 这是一个普通的矩形Indicator
62 | return KDRecIndicator(tab2).apply {
63 | indicatorHeight = 6f
64 | color = 0xffff5722.toInt()
65 | cornerRadius = 3f
66 | mode = KDRecIndicator.MODE_EXACT
67 | indicatorWidth = 16f
68 | startInterpolator = AccelerateInterpolator()
69 | endInterpolator = DecelerateInterpolator(2f)
70 | }
71 | }
72 |
73 | override fun getTabCount(): Int {
74 | return ZH.size
75 | }
76 | }
77 |
78 | // 与ViewPager2联动
79 | tabLayout.setViewPager2(viewPager2)
80 | // 与ViewPager联动
81 | tabLayout.setViewPager(viewPager)
82 | ```
83 |
84 | 以上配置后,即可顺利使用。
85 |
86 | ### 自定义Tab
87 |
88 | 只需继承`KDTab`,然后根据需要重写其中方法即可
89 |
90 | ```kotlin
91 | class XTab(context: Context) : KDTab(context) {
92 | /**
93 | * @param selectedFraction 该Tab被选中的比例,即滚动完成度
94 | * @param selectedInLeft 该Tab是否是从左边滚动到右边
95 | */
96 | override fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean) {
97 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
98 | }
99 |
100 | /**
101 | * 重置为初始状态
102 | */
103 | override fun reset() {
104 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
105 | }
106 |
107 | /**
108 | * 设置为选中状态
109 | */
110 | override fun selectTab() {
111 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
112 | }
113 |
114 | /**
115 | * 计算内容尺寸
116 | */
117 | override fun computeContentBounds() {
118 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
119 | }
120 | }
121 | ```
122 |
123 | ### 自定义Indicator
124 |
125 | 只需继承`KDTabIndicator`,根据需要重写其中方法即可
126 |
127 | ```kotlin
128 | class XIndicator(tabLayout: KDTabLayout) : KDTabIndicator(tabLayout) {
129 | /**
130 | * 初始化方法,在TabLayout布局尺寸改变,或adapter重新设置后会调用
131 | */
132 | override fun init() {
133 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
134 | }
135 |
136 | /**
137 | * 此方法参数都是以滚动方向为参照
138 | */
139 | override fun onTabScrolled(startItem: Int, endItem: Int, scrolledFraction: Float) {
140 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
141 | }
142 |
143 | override fun draw(canvas: Canvas) {
144 | TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
145 | }
146 |
147 | /**
148 | * 获取Indicator占用的宽度
149 | * 此方法在且仅在Indicator单独使用时需要实现
150 | */
151 | override fun getWidth(): Int {
152 | return 0
153 | }
154 |
155 | /**
156 | * 获取Indicator占用的高度
157 | * 此方法在且仅在Indicator单独使用时需要实现
158 | */
159 | override fun getHeight(): Int {
160 | return 0
161 | }
162 | }
163 | ```
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | android {
6 | namespace 'github.xuqk.kdtablayout.sample'
7 | compileSdkVersion 32
8 | defaultConfig {
9 | applicationId "github.xuqk.kdtablayout.demo"
10 | minSdkVersion 21
11 | targetSdkVersion 32
12 | versionCode 1
13 | versionName "1.0"
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | viewBinding {
24 | enabled = true
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
31 | implementation 'androidx.appcompat:appcompat:1.4.1'
32 | implementation 'androidx.core:core-ktx:1.7.0'
33 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
34 |
35 | implementation project(':kdtablayout')
36 | // releaseImplementation 'com.github.XuQK:KDTabLayout:1.1.2'
37 |
38 | implementation 'androidx.viewpager2:viewpager2:1.0.0'
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
28 |
31 |
34 |
37 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/assets/chilanka.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/assets/chilanka.otf
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/BadgeTabActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.view.animation.DecelerateInterpolator
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.viewpager2.widget.ViewPager2
8 | import github.xuqk.kdtablayout.KDTabAdapter
9 | import github.xuqk.kdtablayout.KDTabLayout
10 | import github.xuqk.kdtablayout.sample.adapter.ViewPager2Adapter
11 | import github.xuqk.kdtablayout.sample.databinding.ActivityBadgeTabBinding
12 | import github.xuqk.kdtablayout.widget.KDTab
13 | import github.xuqk.kdtablayout.widget.KDTabIndicator
14 | import github.xuqk.kdtablayout.widget.badge.KDContentEndRelativeBadge
15 | import github.xuqk.kdtablayout.widget.badge.KDTabEndRelativeBadge
16 | import github.xuqk.kdtablayout.widget.indicator.KDRecIndicator
17 | import github.xuqk.kdtablayout.widget.tab.KDColorMorphingTextTab
18 |
19 | /**
20 | * Created By:XuQK
21 | * Created Date:2/23/20 6:08 PM
22 | * Creator Email:xuqiankun66@gmail.com
23 | * Description:
24 | */
25 | class BadgeTabActivity : AppCompatActivity() {
26 |
27 | private lateinit var binding: ActivityBadgeTabBinding
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | super.onCreate(savedInstanceState)
31 | binding = ActivityBadgeTabBinding.inflate(layoutInflater)
32 | setContentView(binding.root)
33 |
34 | val data = ZH.copyOfRange(0, 3)
35 |
36 | binding.tab0.tabMode = KDTabLayout.TAB_MODE_SPREAD
37 | binding.tab0.contentAdapter = object : KDTabAdapter() {
38 | override fun createTab(position: Int): KDTab? {
39 | return KDColorMorphingTextTab(this@BadgeTabActivity, data[position]).apply {
40 | weight = position + 2
41 | selectedTextColor = Color.parseColor("#ff5722")
42 | normalTextColor = Color.parseColor("#9e9e9e")
43 | selectedTextSize = 18f
44 | normalTextSize = 14f
45 | selectedBold = true
46 | setOnClickListener {
47 | binding.vp2.currentItem = position
48 | }
49 |
50 | badge = KDTabEndRelativeBadge(this).apply {
51 | count = position
52 | showCount = true
53 | size = 12f
54 | marginEnd = 12f
55 | marginTop = 12f
56 | }
57 | }
58 | }
59 |
60 | override fun createIndicator(): KDTabIndicator? {
61 | return KDRecIndicator(binding.tab0).apply {
62 | indicatorHeight = 4f
63 | color = 0xffff5722.toInt()
64 | mode = KDRecIndicator.MODE_MATCH
65 | endInterpolator = DecelerateInterpolator(2f)
66 | }
67 | }
68 |
69 | override fun getTabCount(): Int {
70 | return data.size
71 | }
72 | }
73 |
74 | binding.tab1.tabMode = KDTabLayout.TAB_MODE_PACK
75 | binding.tab1.contentAdapter = object : KDTabAdapter() {
76 | override fun createTab(position: Int): KDTab? {
77 | return KDColorMorphingTextTab(this@BadgeTabActivity, data[position]).apply {
78 | horizontalPadding = 12f
79 | selectedTextColor = Color.parseColor("#039be5")
80 | normalTextColor = Color.parseColor("#9e9e9e")
81 | setOnClickListener {
82 | binding.vp2.currentItem = position
83 | badge?.dismiss()
84 | }
85 |
86 | badge = KDContentEndRelativeBadge(this).apply {
87 | count = position
88 | showCount = true
89 | size = 12f
90 | }
91 | }
92 | }
93 |
94 | override fun createIndicator(): KDTabIndicator? {
95 | return KDRecIndicator(binding.tab1).apply {
96 | indicatorHeight = 4f
97 | color = 0xff039be5.toInt()
98 | mode = KDRecIndicator.MODE_MATCH
99 | endInterpolator = DecelerateInterpolator(2f)
100 | }
101 | }
102 |
103 | override fun getTabCount(): Int {
104 | return data.size
105 | }
106 | }
107 |
108 | binding.tab0.setViewPager2(binding.vp2)
109 | binding.tab1.setViewPager2(binding.vp2)
110 | binding.vp2.adapter = ViewPager2Adapter(data.toMutableList())
111 | binding.vp2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
112 | override fun onPageSelected(position: Int) {
113 | binding.tab0.getTab(position)?.badge?.dismiss()
114 | }
115 | })
116 | }
117 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/CustomTabActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import github.xuqk.kdtablayout.KDTabAdapter
7 | import github.xuqk.kdtablayout.KDTabLayout
8 | import github.xuqk.kdtablayout.sample.adapter.ViewPager2Adapter
9 | import github.xuqk.kdtablayout.sample.databinding.ActivityCustomTabBinding
10 | import github.xuqk.kdtablayout.sample.widget.ShadowGradientTab
11 | import github.xuqk.kdtablayout.widget.KDTab
12 | import github.xuqk.kdtablayout.widget.KDTabIndicator
13 |
14 | /**
15 | * Created By:XuQK
16 | * Created Date:2/29/20 9:44 PM
17 | * Creator Email:xuqiankun66@gmail.com
18 | * Description:
19 | */
20 | class CustomTabActivity : AppCompatActivity() {
21 |
22 | private lateinit var binding: ActivityCustomTabBinding
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | binding = ActivityCustomTabBinding.inflate(layoutInflater)
27 | setContentView(binding.root)
28 |
29 | val data = ZH.copyOfRange(0, 4)
30 |
31 | binding.tab0.tabMode = KDTabLayout.TAB_MODE_SPREAD
32 | binding.tab0.contentAdapter = object : KDTabAdapter() {
33 | override fun createTab(position: Int): KDTab? {
34 | return ShadowGradientTab(this@CustomTabActivity, data[position]).apply {
35 | selectedTextColor = Color.WHITE
36 | normalTextColor = 0xFFBDC1C9.toInt()
37 | selectedTextSize = 16f
38 | normalTextSize = 12f
39 | setOnClickListener {
40 | binding.vp2.currentItem = position
41 | }
42 | }
43 | }
44 |
45 | override fun createIndicator(): KDTabIndicator? {
46 | return null
47 | }
48 |
49 | override fun getTabCount(): Int {
50 | return data.size
51 | }
52 | }
53 |
54 | binding.tab0.setViewPager2(binding.vp2)
55 | binding.vp2.adapter = ViewPager2Adapter(data.toMutableList())
56 | }
57 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/DynamicTabActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import github.xuqk.kdtablayout.KDTabAdapter
6 | import github.xuqk.kdtablayout.sample.adapter.ViewPager2Adapter
7 | import github.xuqk.kdtablayout.sample.databinding.ActivityDynamicTabBinding
8 | import github.xuqk.kdtablayout.widget.KDTab
9 | import github.xuqk.kdtablayout.widget.KDTabIndicator
10 | import github.xuqk.kdtablayout.widget.indicator.KDRecIndicator
11 | import github.xuqk.kdtablayout.widget.tab.KDColorClipTextTab
12 | import java.util.*
13 |
14 | /**
15 | * Created By:XuQK
16 | * Created Date:2/23/20 5:23 PM
17 | * Creator Email:xuqiankun66@gmail.com
18 | * Description:
19 | */
20 | class DynamicTabActivity : AppCompatActivity() {
21 |
22 | private lateinit var binding: ActivityDynamicTabBinding
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | binding = ActivityDynamicTabBinding.inflate(layoutInflater)
27 | setContentView(binding.root)
28 |
29 | val data = mutableListOf()
30 | data.addAll(ZH)
31 |
32 | binding.vp2.adapter = ViewPager2Adapter(data)
33 |
34 | binding.tab.setViewPager2(binding.vp2)
35 | binding.tab.contentAdapter = object : KDTabAdapter() {
36 | override fun createTab(position: Int): KDTab? {
37 | return KDColorClipTextTab(this@DynamicTabActivity, data[position]).apply {
38 | horizontalPadding = 16f
39 | selectedTextSize = 22f
40 | normalTextSize = 16f
41 | setOnClickListener {
42 | binding.vp2.currentItem = position
43 | }
44 | }
45 | }
46 |
47 | override fun createIndicator(): KDTabIndicator? {
48 | return KDRecIndicator(binding.tab).apply {
49 | indicatorHeight = 4f
50 | color = 0x4cff0000
51 | marginBottom = 4f
52 | cornerRadius = 2f
53 | mode = KDRecIndicator.MODE_MATCH
54 | }
55 | }
56 |
57 | override fun getTabCount(): Int {
58 | return data.size
59 | }
60 | }
61 |
62 | binding.btn.setOnClickListener {
63 | var n = Random().nextInt(ZH.size + 1)
64 | if (n == 0) n = ZH.size
65 | val newData = ZH.copyOfRange(0, n)
66 | data.clear()
67 | data.addAll(newData)
68 |
69 | binding.vp2.adapter!!.notifyDataSetChanged()
70 | binding.tab.init()
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/Ext.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.content.Context
4 | import android.util.TypedValue
5 |
6 | /**
7 | * Created By:XuQK
8 | * Created Date:2/23/20 4:11 PM
9 | * Creator Email:xuqiankun66@gmail.com
10 | * Description:
11 | */
12 |
13 | fun dpToPx(context: Context, dp: Float): Int {
14 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.resources.displayMetrics).toInt()
15 | }
16 |
17 | val ZH = arrayOf(
18 | "纸杯蛋糕",
19 | "甜甜圈",
20 | "闪电泡芙",
21 | "优格冰淇淋",
22 | "姜饼",
23 | "蜂巢",
24 | "冰淇淋三明治",
25 | "雷根糖",
26 | "奇巧巧克力",
27 | "棒棒糖",
28 | "棉花糖",
29 | "牛轧糖",
30 | "奥利奥",
31 | "派")
32 |
33 | val EN = arrayOf(
34 | "Cupcake",
35 | "Donut",
36 | "Eclair",
37 | "Froyo",
38 | "Gingerbread",
39 | "Honeycomb",
40 | "Ice Cream Sandwich",
41 | "Jelly Bean",
42 | "KitKat",
43 | "Lollipop",
44 | "Marshmallow",
45 | "Nougat",
46 | "Oreo",
47 | "Pie")
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/FixedTabActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.view.animation.DecelerateInterpolator
6 | import androidx.appcompat.app.AppCompatActivity
7 | import github.xuqk.kdtablayout.KDTabAdapter
8 | import github.xuqk.kdtablayout.KDTabLayout
9 | import github.xuqk.kdtablayout.sample.adapter.ViewPagerAdapter
10 | import github.xuqk.kdtablayout.sample.databinding.ActivityFixedTabBinding
11 | import github.xuqk.kdtablayout.sample.widget.TextViewTab
12 | import github.xuqk.kdtablayout.widget.KDTab
13 | import github.xuqk.kdtablayout.widget.KDTabIndicator
14 | import github.xuqk.kdtablayout.widget.indicator.KDRecIndicator
15 | import github.xuqk.kdtablayout.widget.tab.KDColorClipTextTab
16 | import github.xuqk.kdtablayout.widget.tab.KDColorMorphingTextTab
17 |
18 | /**
19 | * Created By:XuQK
20 | * Created Date:2/23/20 4:57 PM
21 | * Creator Email:xuqiankun66@gmail.com
22 | * Description:
23 | */
24 | class FixedTabActivity : AppCompatActivity() {
25 |
26 | private lateinit var binding: ActivityFixedTabBinding
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | binding = ActivityFixedTabBinding.inflate(layoutInflater)
31 | setContentView(binding.root)
32 |
33 | val data = ZH.copyOfRange(0, 3)
34 |
35 | binding.tab0.tabMode = KDTabLayout.TAB_MODE_SPREAD
36 | binding.tab0.contentAdapter = object : KDTabAdapter() {
37 | override fun createTab(position: Int): KDTab? {
38 | return KDColorMorphingTextTab(this@FixedTabActivity, data[position]).apply {
39 | weight = position + 2
40 | selectedTextColor = Color.parseColor("#ff5722")
41 | normalTextColor = Color.parseColor("#9e9e9e")
42 | selectedTextSize = 18f
43 | normalTextSize = 14f
44 | selectedBold = true
45 | setOnClickListener {
46 | binding.vp.currentItem = position
47 | }
48 | }
49 | }
50 |
51 | override fun createIndicator(): KDTabIndicator? {
52 | return KDRecIndicator(binding.tab0).apply {
53 | indicatorHeight = 4f
54 | startColor = 0xffFF4343.toInt()
55 | endColor = 0x00FF7444.toInt()
56 | mode = KDRecIndicator.MODE_MATCH
57 | endInterpolator = DecelerateInterpolator(2f)
58 | }
59 | }
60 |
61 | override fun getTabCount(): Int {
62 | return data.size
63 | }
64 | }
65 |
66 | binding.tab1.tabMode = KDTabLayout.TAB_MODE_PACK
67 | binding.tab1.contentAdapter = object : KDTabAdapter() {
68 | override fun createTab(position: Int): KDTab? {
69 | return KDColorMorphingTextTab(this@FixedTabActivity, data[position]).apply {
70 | horizontalPadding = 12f
71 | selectedTextColor = Color.parseColor("#039be5")
72 | normalTextColor = Color.parseColor("#9e9e9e")
73 | setOnClickListener {
74 | binding.vp.currentItem = position
75 | }
76 | }
77 | }
78 |
79 | override fun createIndicator(): KDTabIndicator? {
80 | return KDRecIndicator(binding.tab1).apply {
81 | indicatorHeight = 4f
82 | color = 0xff039be5.toInt()
83 | mode = KDRecIndicator.MODE_MATCH
84 | endInterpolator = DecelerateInterpolator(2f)
85 | }
86 | }
87 |
88 | override fun getTabCount(): Int {
89 | return data.size
90 | }
91 | }
92 |
93 | binding.tab2.tabMode = KDTabLayout.TAB_MODE_PACK
94 | binding.tab2.contentAdapter = object : KDTabAdapter() {
95 | override fun createTab(position: Int): KDTab? {
96 | return KDColorClipTextTab(this@FixedTabActivity, data[position]).apply {
97 | horizontalPadding = 12f
98 | selectedTextColor = Color.parseColor("#ffffff")
99 | normalTextColor = Color.parseColor("#ff5722")
100 | setOnClickListener {
101 | binding.vp.currentItem = position
102 | }
103 | }
104 | }
105 |
106 | override fun createIndicator(): KDTabIndicator? {
107 | return KDRecIndicator(binding.tab2).apply {
108 | indicatorHeight = 48f
109 | color = 0xffff5722.toInt()
110 | cornerRadius = 24f
111 | mode = KDRecIndicator.MODE_MATCH
112 | }
113 | }
114 |
115 | override fun getTabCount(): Int {
116 | return data.size
117 | }
118 | }
119 |
120 | binding.tab3.tabMode = KDTabLayout.TAB_MODE_SPREAD
121 | binding.tab3.contentAdapter = object : KDTabAdapter() {
122 | override fun createTab(position: Int): KDTab? {
123 | return TextViewTab(this@FixedTabActivity, data[position]).apply {
124 | setOnClickListener {
125 | binding.vp.currentItem = position
126 | }
127 | }
128 | }
129 |
130 | override fun getTabCount(): Int {
131 | return data.size
132 | }
133 | }
134 |
135 | binding.tab0.setViewPager(binding.vp)
136 | binding.tab1.setViewPager(binding.vp)
137 | binding.tab2.setViewPager(binding.vp)
138 | binding.tab3.setViewPager(binding.vp)
139 | binding.vp.adapter = ViewPagerAdapter(data.toMutableList())
140 | }
141 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import github.xuqk.kdtablayout.sample.databinding.ActivityMainBinding
7 |
8 | class MainActivity : AppCompatActivity() {
9 |
10 | private lateinit var binding: ActivityMainBinding
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | binding = ActivityMainBinding.inflate(layoutInflater)
15 | setContentView(binding.root)
16 |
17 | binding.btnScrollableTab.setOnClickListener {
18 | startActivity(
19 | Intent(this, ScrollableTabActivity::class.java)
20 | )
21 | }
22 |
23 | binding.btnFixedTab.setOnClickListener {
24 | startActivity(
25 | Intent(this, FixedTabActivity::class.java)
26 | )
27 | }
28 |
29 | binding.btnDynamicTab.setOnClickListener {
30 | startActivity(
31 | Intent(this, DynamicTabActivity::class.java)
32 | )
33 | }
34 |
35 | binding.btnOnlyIndicatorTab.setOnClickListener {
36 | startActivity(
37 | Intent(this, OnlyIndicatorActivity::class.java)
38 | )
39 | }
40 |
41 | binding.btnBadgeTab.setOnClickListener {
42 | startActivity(
43 | Intent(this, BadgeTabActivity::class.java)
44 | )
45 | }
46 |
47 | binding.btnCustomTab.setOnClickListener {
48 | startActivity(
49 | Intent(this, CustomTabActivity::class.java)
50 | )
51 | }
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/OnlyIndicatorActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import github.xuqk.kdtablayout.KDTabAdapter
6 | import github.xuqk.kdtablayout.sample.adapter.ViewPager2Adapter
7 | import github.xuqk.kdtablayout.sample.databinding.ActivityOnlyIndicatorBinding
8 | import github.xuqk.kdtablayout.widget.KDTab
9 | import github.xuqk.kdtablayout.widget.KDTabIndicator
10 | import github.xuqk.kdtablayout.widget.indicator.DotMorphingIndicator
11 | import github.xuqk.kdtablayout.widget.indicator.DotWithStrokeIndicator
12 | import github.xuqk.kdtablayout.widget.indicator.KDRecIndicator
13 |
14 | /**
15 | * Created By:XuQK
16 | * Created Date:2/23/20 6:51 PM
17 | * Creator Email:xuqiankun66@gmail.com
18 | * Description:
19 | */
20 | class OnlyIndicatorActivity : AppCompatActivity() {
21 |
22 | private lateinit var binding: ActivityOnlyIndicatorBinding
23 |
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | binding = ActivityOnlyIndicatorBinding.inflate(layoutInflater)
27 | setContentView(binding.root)
28 |
29 | val data = ZH
30 |
31 | binding.tab0.contentAdapter = object : KDTabAdapter() {
32 | override fun createTab(position: Int): KDTab? {
33 | return null
34 | }
35 |
36 | override fun createIndicator(): KDTabIndicator? {
37 | return KDRecIndicator(binding.tab0).apply {
38 | indicatorHeight = 4f
39 | color = 0xffff5722.toInt()
40 | mode = KDRecIndicator.MODE_MATCH
41 | }
42 | }
43 |
44 | override fun getTabCount(): Int {
45 | return data.size
46 | }
47 | }
48 |
49 | binding.tab1.contentAdapter = object : KDTabAdapter() {
50 | override fun createTab(position: Int): KDTab? {
51 | return null
52 | }
53 |
54 | override fun createIndicator(): KDTabIndicator? {
55 | return DotWithStrokeIndicator(binding.tab1)
56 | .apply {
57 | strokeWidth = 2f
58 | }
59 | }
60 |
61 | override fun getTabCount(): Int {
62 | return data.size
63 | }
64 | }
65 |
66 | binding.tab2.contentAdapter = object : KDTabAdapter() {
67 | override fun createTab(position: Int): KDTab? {
68 | return null
69 | }
70 |
71 | override fun createIndicator(): KDTabIndicator? {
72 | return DotMorphingIndicator(binding.tab2)
73 | }
74 |
75 | override fun getTabCount(): Int {
76 | return data.size
77 | }
78 | }
79 |
80 | binding.tab0.setViewPager2(binding.vp2)
81 | binding.tab1.setViewPager2(binding.vp2)
82 | binding.tab2.setViewPager2(binding.vp2)
83 | binding.vp2.adapter = ViewPager2Adapter(data.toMutableList())
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/ScrollableTabActivity.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.view.animation.AccelerateInterpolator
6 | import android.view.animation.DecelerateInterpolator
7 | import androidx.appcompat.app.AppCompatActivity
8 | import github.xuqk.kdtablayout.KDTabAdapter
9 | import github.xuqk.kdtablayout.sample.adapter.ViewPager2Adapter
10 | import github.xuqk.kdtablayout.sample.databinding.ActivityScrollableTabBinding
11 | import github.xuqk.kdtablayout.sample.widget.CustomFontTab
12 | import github.xuqk.kdtablayout.widget.KDTab
13 | import github.xuqk.kdtablayout.widget.KDTabIndicator
14 | import github.xuqk.kdtablayout.widget.indicator.KDRecIndicator
15 | import github.xuqk.kdtablayout.widget.tab.KDColorClipTextTab
16 | import github.xuqk.kdtablayout.widget.tab.KDColorMorphingTextTab
17 |
18 | /**
19 | * Created By:XuQK
20 | * Created Date:2/23/20 4:48 PM
21 | * Creator Email:xuqiankun66@gmail.com
22 | * Description:
23 | */
24 | class ScrollableTabActivity : AppCompatActivity() {
25 |
26 | private lateinit var binding: ActivityScrollableTabBinding
27 |
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | super.onCreate(savedInstanceState)
30 | binding = ActivityScrollableTabBinding.inflate(layoutInflater)
31 | setContentView(binding.root)
32 |
33 | binding.tab0.scrollBiasX = -100f
34 | binding.tab0.contentAdapter = object : KDTabAdapter() {
35 | override fun createTab(position: Int): KDTab? {
36 | return KDColorMorphingTextTab(this@ScrollableTabActivity, ZH[position]).apply {
37 | horizontalPadding = 16f
38 | selectedTextSize = 22f
39 | normalTextSize = 16f
40 | setOnClickListener {
41 | binding.vp2.currentItem = position
42 | }
43 | }
44 | }
45 |
46 | override fun createIndicator(): KDTabIndicator? {
47 | return null
48 | }
49 |
50 | override fun getTabCount(): Int {
51 | return ZH.size
52 | }
53 | }
54 |
55 | binding.tab1.scrollBiasX = -50f
56 | binding.tab1.contentAdapter = object : KDTabAdapter() {
57 | override fun createTab(position: Int): KDTab? {
58 | return KDColorClipTextTab(this@ScrollableTabActivity, ZH[position]).apply {
59 | horizontalPadding = 16f
60 | selectedTextSize = 22f
61 | normalTextSize = 16f
62 | setOnClickListener {
63 | binding.vp2.currentItem = position
64 | }
65 | }
66 | }
67 |
68 | override fun createIndicator(): KDTabIndicator? {
69 | return null
70 | }
71 |
72 | override fun getTabCount(): Int {
73 | return ZH.size
74 | }
75 | }
76 |
77 | binding.tab2.contentAdapter = object : KDTabAdapter() {
78 | override fun createTab(position: Int): KDTab? {
79 | return KDColorMorphingTextTab(this@ScrollableTabActivity, ZH[position]).apply {
80 | horizontalPadding = 16f
81 | selectedTextColor = Color.parseColor("#ff5722")
82 | normalTextColor = Color.parseColor("#9e9e9e")
83 | selectedTextSize = 32f
84 | normalTextSize = 16f
85 | resizeWithFontSize = true
86 | setOnClickListener {
87 | binding.vp2.currentItem = position
88 | }
89 | }
90 | }
91 |
92 | override fun createIndicator(): KDTabIndicator? {
93 | return KDRecIndicator(binding.tab2).apply {
94 | indicatorHeight = 6f
95 | color = 0xffff5722.toInt()
96 | cornerRadius = 3f
97 | mode = KDRecIndicator.MODE_EXACT
98 | indicatorWidth = 16f
99 | startInterpolator = AccelerateInterpolator()
100 | endInterpolator = DecelerateInterpolator(2f)
101 | }
102 | }
103 |
104 | override fun getTabCount(): Int {
105 | return ZH.size
106 | }
107 | }
108 |
109 | binding.tab3.scrollBiasX = 50f
110 | binding.tab3.needCompleteScroll = true
111 | binding.tab3.contentAdapter = object : KDTabAdapter() {
112 | override fun createTab(position: Int): KDTab? {
113 | return KDColorMorphingTextTab(this@ScrollableTabActivity, ZH[position]).apply {
114 | horizontalPadding = 16f
115 | selectedTextColor = Color.parseColor("#ff5722")
116 | normalTextColor = Color.BLACK
117 | setOnClickListener {
118 | binding.vp2.currentItem = position
119 | }
120 | }
121 | }
122 |
123 | override fun createIndicator(): KDTabIndicator? {
124 | return KDRecIndicator(binding.tab3).apply {
125 | indicatorHeight = 36f
126 | color = 0x4c03a9f4
127 | marginBottom = 6f
128 | cornerRadius = 18f
129 | mode = KDRecIndicator.MODE_MATCH
130 | }
131 | }
132 |
133 | override fun getTabCount(): Int {
134 | return ZH.size
135 | }
136 | }
137 |
138 | binding.tab4.scrollBiasX = 100f
139 | binding.tab4.post {
140 | binding.tab4.contentAdapter = object : KDTabAdapter() {
141 | override fun createTab(position: Int): KDTab? {
142 | return KDColorMorphingTextTab(this@ScrollableTabActivity, ZH[position]).apply {
143 | horizontalPadding = 16f
144 | selectedTextColor = Color.parseColor("#039be5")
145 | normalTextColor = Color.parseColor("#9e9e9e")
146 | setOnClickListener {
147 | binding.vp2.currentItem = position
148 | }
149 | }
150 | }
151 |
152 | override fun createIndicator(): KDTabIndicator? {
153 | return KDRecIndicator(binding.tab4).apply {
154 | indicatorHeight = 6f
155 | color = 0xff039be5.toInt()
156 | marginBottom = 42f
157 | mode = KDRecIndicator.MODE_EXACT
158 | indicatorWidth = 16f
159 | endInterpolator = DecelerateInterpolator(2f)
160 | }
161 | }
162 |
163 | override fun getTabCount(): Int {
164 | return ZH.size
165 | }
166 | }
167 | }
168 |
169 |
170 | binding.tab5.contentAdapter = object : KDTabAdapter() {
171 | override fun createTab(position: Int): KDTab? {
172 | return CustomFontTab(this@ScrollableTabActivity, EN[position]).apply {
173 | horizontalPadding = 16f
174 | selectedTextColor = Color.parseColor("#673ab7")
175 | normalTextColor = Color.parseColor("#9ccc65")
176 | setOnClickListener {
177 | binding.vp2.currentItem = position
178 | }
179 | }
180 | }
181 |
182 | override fun createIndicator(): KDTabIndicator? {
183 | return KDRecIndicator(binding.tab5).apply {
184 | indicatorHeight = 6f
185 | color = 0xff673ab7.toInt()
186 | mode = KDRecIndicator.MODE_WRAP_CONTENT
187 | }
188 | }
189 |
190 | override fun getTabCount(): Int {
191 | return EN.size
192 | }
193 | }
194 |
195 | binding.vp2.adapter = ViewPager2Adapter(ZH.toMutableList())
196 | binding.tab0.setViewPager2(binding.vp2)
197 | binding.tab1.setViewPager2(binding.vp2)
198 | binding.tab2.setViewPager2(binding.vp2)
199 | binding.tab3.setViewPager2(binding.vp2)
200 | binding.tab4.setViewPager2(binding.vp2)
201 | binding.tab5.setViewPager2(binding.vp2)
202 | }
203 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/adapter/ViewPager2Adapter.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample.adapter
2 |
3 | import android.graphics.Color
4 | import android.util.TypedValue
5 | import android.view.Gravity
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.TextView
9 | import androidx.recyclerview.widget.RecyclerView
10 |
11 | /**
12 | * Created By:XuQK
13 | * Created Date:2/23/20 5:25 PM
14 | * Creator Email:xuqiankun66@gmail.com
15 | * Description:
16 | */
17 | class ViewPager2Adapter(private val data: MutableList = mutableListOf()) : RecyclerView.Adapter() {
18 |
19 | fun setNewData(newData: List) {
20 | data.clear()
21 | data.addAll(newData)
22 | notifyDataSetChanged()
23 | }
24 |
25 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPager2Holder {
26 | val tv = TextView(parent.context).apply {
27 | setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
28 | setTextColor(Color.RED)
29 | gravity = Gravity.CENTER
30 | }
31 | tv.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
32 | return ViewPager2Holder(
33 | tv
34 | )
35 | }
36 |
37 | override fun getItemCount(): Int {
38 | return data.size
39 | }
40 |
41 | override fun onBindViewHolder(holder: ViewPager2Holder, position: Int) {
42 | (holder.itemView as TextView).text = data[position] + "\n\nViewPager2"
43 | }
44 |
45 | class ViewPager2Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
46 |
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/adapter/ViewPagerAdapter.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample.adapter
2 |
3 | import android.graphics.Color
4 | import android.util.TypedValue
5 | import android.view.Gravity
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.TextView
9 | import androidx.viewpager.widget.PagerAdapter
10 |
11 | /**
12 | * Created By:XuQK
13 | * Created Date:2/23/20 5:28 PM
14 | * Creator Email:xuqiankun66@gmail.com
15 | * Description:
16 | */
17 | class ViewPagerAdapter(private val data: MutableList = mutableListOf()) : PagerAdapter() {
18 |
19 | fun setNewData(newData: Set) {
20 | data.clear()
21 | data.addAll(newData)
22 | notifyDataSetChanged()
23 | }
24 |
25 | override fun isViewFromObject(view: View, `object`: Any): Boolean {
26 | return view == `object`
27 | }
28 |
29 | override fun getCount(): Int {
30 | return data.size
31 | }
32 |
33 | override fun instantiateItem(container: ViewGroup, position: Int): Any {
34 | val tv = TextView(container.context).apply {
35 | setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
36 | setTextColor(Color.RED)
37 | text = data[position] + "\n\nViewPager"
38 | gravity = Gravity.CENTER
39 | }
40 | tv.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
41 | container.addView(tv)
42 | return tv
43 | }
44 |
45 | override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
46 | container.removeView(`object` as View)
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/widget/CustomFontTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample.widget
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.Typeface
6 | import github.xuqk.kdtablayout.widget.tab.KDColorMorphingTextTab
7 |
8 | /**
9 | * Created By:XuQK
10 | * Created Date:21-3-5 下午2:57
11 | * Creator Email:xu.qiankun@xiji.com
12 | * Description:
13 | */
14 |
15 | @SuppressLint("ViewConstructor")
16 | class CustomFontTab(context: Context, text: String) : KDColorMorphingTextTab(context, text) {
17 |
18 | private val customTypeface by lazy {
19 | Typeface.createFromAsset(context.assets, "chilanka.otf")
20 | }
21 |
22 | override fun setPaintParam() {
23 | super.setPaintParam()
24 | paint.typeface = customTypeface
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/widget/ShadowGradientTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample.widget
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.*
6 | import github.xuqk.kdtablayout.sample.dpToPx
7 | import github.xuqk.kdtablayout.widget.tab.KDColorMorphingTextTab
8 |
9 | /**
10 | * Created By:XuQK
11 | * Created Date:3/1/20 12:37 PM
12 | * Creator Email:xuqiankun66@gmail.com
13 | * Description:
14 | */
15 | @SuppressLint("ViewConstructor")
16 | class ShadowGradientTab(context: Context, text: String) : KDColorMorphingTextTab(context, text) {
17 | private val paddingHorizontal = dpToPx(context, 5f)
18 | private val paddingVertical = dpToPx(context, 10f)
19 | private val shadowRadius = dpToPx(context, 4f).toFloat()
20 | private val shadowY = dpToPx(context, 5f).toFloat()
21 | private val rectRadius = dpToPx(context, 10f)
22 | private val rect = RectF()
23 |
24 | private val startColorRBG = intArrayOf(38, 160, 244)
25 | private val endColorRBG = intArrayOf(38, 105, 244)
26 | private val normalBgColor = Color.WHITE
27 |
28 | private var fraction: Float = 0f
29 |
30 | override fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean) {
31 | super.onScrolling(selectedFraction, selectedInLeft)
32 | fraction = selectedFraction
33 | }
34 |
35 | override fun reset() {
36 | super.reset()
37 | fraction = 0f
38 | }
39 |
40 | override fun selectTab() {
41 | super.selectTab()
42 | fraction = 1f
43 | }
44 |
45 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
46 | super.onLayout(changed, left, top, right, bottom)
47 | if (changed) {
48 | rect.set(
49 | paddingHorizontal.toFloat(),
50 | paddingVertical.toFloat(),
51 | (width - paddingHorizontal).toFloat(),
52 | (height - paddingVertical).toFloat()
53 | )
54 | }
55 | }
56 |
57 | override fun drawContent(canvas: Canvas) {
58 | // 绘制白色底色
59 | paint.reset()
60 | paint.color = normalBgColor
61 | paint.alpha = 255
62 | paint.setShadowLayer(shadowRadius * fraction, 0f, shadowY * fraction, 0x4c26A0F4.toInt())
63 | canvas.drawRoundRect(rect, rectRadius.toFloat(), rectRadius.toFloat(), paint)
64 |
65 | // 如果fraction大于0,绘制覆盖的渐变色
66 | if (fraction > 0f) {
67 | paint.reset()
68 | val start = Color.argb((255 * fraction).toInt(), startColorRBG[0], startColorRBG[1], startColorRBG[2])
69 | val end = Color.argb((255 * fraction).toInt(), endColorRBG[0], endColorRBG[1], endColorRBG[2])
70 |
71 | paint.shader = LinearGradient(
72 | rect.left,
73 | rect.bottom,
74 | rect.right,
75 | rect.top,
76 | start,
77 | end,
78 | Shader.TileMode.CLAMP
79 | )
80 | canvas.drawRoundRect(rect, rectRadius.toFloat(), rectRadius.toFloat(), paint)
81 | }
82 |
83 | super.drawContent(canvas)
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/java/github/xuqk/kdtablayout/sample/widget/TextViewTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.sample.widget
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.Typeface
6 | import android.view.Gravity
7 | import android.widget.TextView
8 | import github.xuqk.kdtablayout.widget.KDTab
9 |
10 | /**
11 | * Created By:XuQK
12 | * Created Date:21-3-5 下午2:57
13 | * Creator Email:xu.qiankun@xiji.com
14 | * Description:
15 | */
16 |
17 | @SuppressLint("ViewConstructor")
18 | class TextViewTab(context: Context, text: String) : KDTab(context) {
19 |
20 | private val tv: TextView = TextView(context).apply {
21 | this.text = text
22 | textSize = 16f
23 | }
24 |
25 | init {
26 | addView(
27 | tv,
28 | LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
29 | gravity = Gravity.CENTER
30 | }
31 | )
32 | }
33 |
34 | override fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean) {
35 | if (selectedFraction > 0.5f) {
36 | tv.typeface = Typeface.DEFAULT_BOLD
37 | } else {
38 | tv.typeface = Typeface.DEFAULT
39 | }
40 | }
41 |
42 | override fun reset() {
43 | tv.typeface = Typeface.DEFAULT
44 | }
45 |
46 | override fun selectTab() {
47 | tv.typeface = Typeface.DEFAULT_BOLD
48 | }
49 |
50 | override fun computeContentBounds() {
51 |
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_round_stroke_r24.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_badge_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_custom_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_dynamic_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
18 |
19 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fixed_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
31 |
32 |
38 |
39 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
21 |
22 |
27 |
28 |
33 |
34 |
39 |
40 |
45 |
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_only_indicator.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
18 |
19 |
25 |
26 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_scrollable_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
23 |
24 |
30 |
31 |
37 |
38 |
44 |
45 |
51 |
52 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/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 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | KDTabLayout
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.6.21'
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:7.2.0'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | maven { url 'https://jitpack.io' }
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/demo.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Dec 11 17:30:32 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
--------------------------------------------------------------------------------
/kdtablayout/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/kdtablayout/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'maven-publish'
4 |
5 | android {
6 | namespace 'github.xuqk.kdtablayout'
7 | compileSdkVersion 32
8 |
9 | defaultConfig {
10 | minSdkVersion 21
11 | targetSdkVersion 32
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles 'consumer-rules.pro'
15 | }
16 | }
17 |
18 | dependencies {
19 | compileOnly 'androidx.appcompat:appcompat:1.4.1'
20 | compileOnly 'androidx.viewpager2:viewpager2:1.0.0'
21 | }
22 |
23 |
24 | // Because the components are created only during the afterEvaluate phase, you must
25 | // configure your publications using the afterEvaluate() lifecycle method.
26 | afterEvaluate {
27 | publishing {
28 | publications {
29 | // Creates a Maven publication called "release".
30 | release(MavenPublication) {
31 | // Applies the component for the release build variant.
32 | from components.release
33 |
34 | // You can then customize attributes of the publication as shown below.
35 | groupId = 'com.github.XuQK'
36 | artifactId = 'KDTabLayout'
37 | version = '1.1.9'
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/kdtablayout/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XuQK/KDTabLayout/d78ab6eb89205cfa841d78dd2e37eabc93f492fd/kdtablayout/consumer-rules.pro
--------------------------------------------------------------------------------
/kdtablayout/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 |
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/Ext.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout
2 |
3 | import android.animation.TypeEvaluator
4 | import android.content.Context
5 | import android.graphics.Color
6 | import android.graphics.Paint
7 | import android.util.Log
8 | import android.util.TypedValue
9 |
10 |
11 | /**
12 | * Created By:XuQK
13 | * Created Date:2/15/20 9:11 PM
14 | * Creator Email:xuqiankun66@gmail.com
15 | * Description:
16 | */
17 |
18 | internal fun log(any: Any?) {
19 | Log.d("KDTagLayout", any.toString())
20 | }
21 |
22 | internal fun dpToPx(context: Context, dp: Float): Int {
23 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.resources.displayMetrics).toInt()
24 | }
25 |
26 | /**
27 | * 获取baseline离中线的距离
28 | */
29 | fun Paint.FontMetrics.getBaselineToCenter(): Float {
30 | return (descent - ascent) / 2 - descent
31 | }
32 |
33 | class HsvEvaluator : TypeEvaluator {
34 | var startHsv = FloatArray(3)
35 | var endHsv = FloatArray(3)
36 | var outHsv = FloatArray(3)
37 | override fun evaluate(
38 | fraction: Float,
39 | startValue: Int,
40 | endValue: Int
41 | ): Int { // 把 ARGB 转换成 HSV
42 | Color.colorToHSV(startValue, startHsv)
43 | Color.colorToHSV(endValue, endHsv)
44 | // 计算当前动画完成度(fraction)所对应的颜色值
45 | if (endHsv[0] - startHsv[0] > 180) {
46 | endHsv[0] = endHsv[0] - 360
47 | } else if (endHsv[0] - startHsv[0] < -180) {
48 | endHsv[0] = endHsv[0] + 360
49 | }
50 | outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction
51 | if (outHsv[0] > 360) {
52 | outHsv[0] = outHsv[0] - 360
53 | } else if (outHsv[0] < 0) {
54 | outHsv[0] = outHsv[0] + 360
55 | }
56 | outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction
57 | outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction
58 | // 计算当前动画完成度(fraction)所对应的透明度
59 | val alpha = (startValue.ushr(24) +
60 | (endValue.ushr(24) - startValue.ushr(24)) * fraction).toInt()
61 | // 把 HSV 转换回 ARGB 返回
62 | return Color.HSVToColor(alpha, outHsv)
63 | }
64 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/KDTabAdapter.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout
2 |
3 | import github.xuqk.kdtablayout.widget.KDTab
4 | import github.xuqk.kdtablayout.widget.KDTabIndicator
5 |
6 | /**
7 | * Created By:XuQK
8 | * Created Date:2/16/20 2:18 PM
9 | * Creator Email:xuqiankun66@gmail.com
10 | * Description:
11 | */
12 | abstract class KDTabAdapter {
13 |
14 | /**
15 | * 创建对应位置的Tab,如果不需要Tab展示,返回null即可
16 | */
17 | open fun createTab(position: Int): KDTab? {
18 | return null
19 | }
20 |
21 | /**
22 | * 创建Indicator,如果不需要Indicator展示,返回null即可
23 | */
24 | open fun createIndicator(): KDTabIndicator? {
25 | return null
26 | }
27 |
28 | /**
29 | * Tab数量
30 | */
31 | open fun getTabCount(): Int {
32 | return 0
33 | }
34 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/KDTabLayout.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout
2 |
3 | import android.animation.Animator
4 | import android.animation.ValueAnimator
5 | import android.content.Context
6 | import android.graphics.Canvas
7 | import android.os.Bundle
8 | import android.os.Parcelable
9 | import android.util.AttributeSet
10 | import android.view.*
11 | import android.widget.OverScroller
12 | import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
13 | import androidx.viewpager.widget.ViewPager
14 | import androidx.viewpager2.widget.ViewPager2
15 | import github.xuqk.kdtablayout.widget.KDTab
16 | import github.xuqk.kdtablayout.widget.KDTabIndicator
17 | import kotlin.math.abs
18 | import kotlin.math.max
19 |
20 |
21 | /**
22 | * Created By:XuQK
23 | * Created Date:2/15/20 7:39 PM
24 | * Creator Email:xuqiankun66@gmail.com
25 | * Description:
26 | */
27 | class KDTabLayout @JvmOverloads constructor(
28 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
29 | ) : ViewGroup(context, attrs, defStyleAttr), KDViewPagerHelper.ViewPageStateListener {
30 |
31 | companion object {
32 | /**
33 | * Indicates that the pager is in an idle, settled state. The current page
34 | * is fully in view and no animation is in progress.
35 | */
36 | const val SCROLL_STATE_IDLE = 0
37 |
38 | /**
39 | * Indicates that the pager is currently being dragged by the user.
40 | */
41 | const val SCROLL_STATE_DRAGGING = 1
42 |
43 | /**
44 | * Indicates that the pager is in the process of settling to a final position.
45 | */
46 | const val SCROLL_STATE_SETTLING = 2
47 |
48 | private const val DEFAULT_DURATION: Long = 250
49 |
50 | /**
51 | * tab可滚动,此时tab的排列为按照tab本身宽度横向依序排列
52 | */
53 | const val TAB_MODE_SCROLLABLE = 0
54 | /**
55 | * tab不可滚动,此时tab的排列为按照tab本身宽度,以TabLayout的x中线对齐
56 | */
57 | const val TAB_MODE_PACK = 1
58 | /**
59 | * tab不可滚动,此时tab按比例均分整个TabLayout
60 | * 此时Tab中的weight参数生效
61 | */
62 | const val TAB_MODE_SPREAD = 2
63 | /**
64 | * 自适应模式
65 | * 所有tab原本总宽度如果没有超过TabLayout,就按MODE_TAB_SPREAD模式排列,此时Tab中的weight参数生效
66 | * 若超过TabLayout总宽度,则按照MODE_TAB_SCROLLABLE排列
67 | */
68 | const val TAB_MODE_FLEXIBLE = 3
69 | }
70 |
71 | private var savedBundle: Bundle? = null
72 |
73 | // 所有子Tab宽度之和
74 | private var totalWidth: Int = 0
75 | private var scrollable: Boolean = false
76 |
77 | // 由于Tab只管横向滑动,所以滑动只记录x坐标即可
78 | private var lastX: Float = 0f
79 |
80 | private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop / 2
81 | private val overScrollDistance = dpToPx(context, 16f)
82 | private val scroller = OverScroller(context)
83 | private var velocityTracker: VelocityTracker? = null
84 | private val minVelocity = ViewConfiguration.get(context).scaledMinimumFlingVelocity
85 |
86 | /**
87 | * 滚动时,被选中Tab与控件中心x坐标的偏差
88 | */
89 | var scrollBiasX: Float = 0f
90 | set(value) {
91 | field = dpToPx(context, value).toFloat()
92 | }
93 | var tabMode: Int = TAB_MODE_FLEXIBLE
94 |
95 | /**平滑滚动动画时间*/
96 | var smoothScrollDuration: Long = DEFAULT_DURATION
97 | /**
98 | * 为true,从tab2直接滚动到tab10,滚动开始后,会瞬间切到tab2,然后动画滚动到tab10,中间的tab也会对滚动进行响应
99 | * 为false,从tab2直接滚动到tab10,滚动开始后,会从当前停留的点,直接动画滚动到tab10,中间的tab不会对滚动进行响应,变化只发生在tab2和tab10上
100 | * 如果Tab数量很多且可滚动,建议将此项设置为false体验稍好一些
101 | */
102 | var needCompleteScroll: Boolean = false
103 | var contentAdapter: KDTabAdapter? = null
104 | set(value) {
105 | field = value
106 | init()
107 | }
108 |
109 | var currentItem: Int = 0
110 | private set
111 |
112 | private var stopViewPagerAffect: Boolean = true
113 | var indicator: KDTabIndicator? = null
114 | private set
115 | private var tabChangeAnimator: ValueAnimator? = null
116 |
117 | private val vpHelper: KDViewPagerHelper by lazy { KDViewPagerHelper() }
118 |
119 | private var scrollState: Int = SCROLL_STATE_IDLE
120 |
121 | fun setViewPager(viewPager: ViewPager) {
122 | stopViewPagerAffect = false
123 | vpHelper.bindViewPager(viewPager)
124 | vpHelper.stateListener = this
125 | updateTabState(viewPager.currentItem)
126 | }
127 |
128 | fun setViewPager2(viewPager2: ViewPager2) {
129 | stopViewPagerAffect = false
130 | vpHelper.bindViewPager2(viewPager2)
131 | vpHelper.stateListener = this
132 | updateTabState(viewPager2.currentItem)
133 | }
134 |
135 | fun setCurrentItem(position: Int, smooth: Boolean = true) {
136 | if (currentItem == position) return
137 |
138 | if (smooth) {
139 | smoothScrollToItem(position)
140 | } else {
141 | scrollToItem(position)
142 | }
143 |
144 | currentItem = position
145 | }
146 |
147 | private fun scrollToItem(position: Int) {
148 | // 在该方法作用域里用来记录startPosition
149 | val tempStartPosition = currentItem
150 | currentItem = position
151 |
152 | updateTabState(currentItem)
153 | syncIndicatorScrollState(tempStartPosition, position, 1f)
154 | }
155 |
156 | private fun smoothScrollToItem(position: Int) {
157 | if (tabChangeAnimator?.isRunning == true) {
158 | tabChangeAnimator?.cancel()
159 | updateTabState(currentItem)
160 | }
161 |
162 | // 在该方法作用域里用来记录startPosition
163 | val tempStartPosition = currentItem
164 | currentItem = position
165 |
166 | val startTab = getChildAt(tempStartPosition)
167 | val endTab = getChildAt(position)
168 |
169 | val startScrollX = scrollX
170 |
171 | tabChangeAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
172 | duration = this@KDTabLayout.smoothScrollDuration
173 | interpolator = LinearOutSlowInInterpolator()
174 | addListener(object : Animator.AnimatorListener {
175 | override fun onAnimationRepeat(animation: Animator?) {}
176 |
177 | override fun onAnimationEnd(animation: Animator?) {
178 | scrollState = SCROLL_STATE_IDLE
179 | updateTabState(currentItem)
180 | }
181 |
182 | override fun onAnimationCancel(animation: Animator?) {
183 | scrollState = SCROLL_STATE_IDLE
184 | }
185 |
186 | override fun onAnimationStart(animation: Animator?) {
187 | scrollState = SCROLL_STATE_SETTLING
188 | }
189 | })
190 |
191 | addUpdateListener {
192 | if (it.animatedFraction < 1f) {
193 | // 同步tab状态
194 | syncTabScrollState(startTab, endTab, it.animatedFraction)
195 |
196 | // 同步tabLayout scrollX
197 | val endScrollX = getTabScrollXInCenter(endTab)
198 | scrollTo(startScrollX + ((endScrollX - startScrollX) * it.animatedFraction).toInt())
199 |
200 | // 同步indicator状态
201 | syncIndicatorScrollState(tempStartPosition, position, it.animatedFraction)
202 | }
203 | }
204 | start()
205 | }
206 | }
207 |
208 | fun init() {
209 | removeAllViews()
210 | contentAdapter?.let { adapter ->
211 | if (adapter.getTabCount() <= 0) {
212 | indicator = null
213 | return@let
214 | }
215 |
216 | if (currentItem > adapter.getTabCount() - 1) {
217 | currentItem = adapter.getTabCount() - 1
218 | }
219 | (0 until adapter.getTabCount()).forEach { i ->
220 | adapter.createTab(i)?.let {
221 | if (i == currentItem) {
222 | it.selectTab()
223 | } else {
224 | it.reset()
225 | }
226 | addView(it as View)
227 | }
228 | }
229 | indicator = adapter.createIndicator()
230 | if (indicator != null) {
231 | setWillNotDraw(false)
232 | }
233 |
234 | if (childCount > 0 && indicator != null) {
235 | // tab和indicator都有
236 | } else if (childCount == 0 && indicator != null) {
237 | // 只有indicator
238 | } else if (childCount > 0 && indicator == null) {
239 | // 只有tab
240 | } else {
241 | throw IllegalArgumentException("tab和indicator不能都不设置")
242 | }
243 |
244 | post {
245 | indicator?.init()
246 | }
247 | }
248 | }
249 |
250 | fun getTab(position: Int): KDTab? {
251 | return getChildAt(position) as? KDTab
252 | }
253 |
254 | override fun onDraw(canvas: Canvas) {
255 | indicator?.draw(canvas)
256 | }
257 |
258 | fun smoothScrollBy(x: Int) {
259 | if (!scrollable) return
260 | scroller.startScroll(scrollX, 0, x, 0, smoothScrollDuration.toInt())
261 | postInvalidateOnAnimation()
262 | }
263 |
264 | fun smoothScrollTo(destX: Int) {
265 | if (!scrollable) return
266 | val dx = when {
267 | destX < 0 -> -scrollX
268 | destX > totalWidth - width -> totalWidth - width - scrollX
269 | else -> destX - scrollX
270 | }
271 | scroller.startScroll(scrollX, 0, dx, 0, smoothScrollDuration.toInt())
272 | postInvalidateOnAnimation()
273 | }
274 |
275 | private fun scrollTo(destX: Int) {
276 | if (!scrollable) return
277 | val dx = when {
278 | destX < 0 -> 0
279 | destX > totalWidth - width -> totalWidth - width
280 | else -> destX
281 | }
282 |
283 | scrollTo(dx, 0)
284 | }
285 |
286 | override fun computeScroll() {
287 | if (scroller.computeScrollOffset()) {
288 | scrollTo(scroller.currX, 0)
289 | postInvalidate()
290 | }
291 | }
292 |
293 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
294 | when (ev.action) {
295 | MotionEvent.ACTION_DOWN -> {
296 | lastX = ev.rawX
297 | parent?.requestDisallowInterceptTouchEvent(true)
298 | if (!scroller.isFinished) {
299 | scroller.abortAnimation()
300 | return true
301 | }
302 | }
303 | MotionEvent.ACTION_MOVE -> {
304 | val evRawX = ev.rawX
305 | if (abs(evRawX - lastX) > touchSlop) {
306 | return true
307 | }
308 | lastX = evRawX
309 | }
310 | }
311 | return super.onInterceptTouchEvent(ev)
312 | }
313 |
314 | override fun onTouchEvent(event: MotionEvent): Boolean {
315 | if (!scrollable) return true
316 |
317 | if (velocityTracker == null) {
318 | velocityTracker = VelocityTracker.obtain()
319 | }
320 | velocityTracker!!.addMovement(event)
321 |
322 | when (event.action) {
323 | MotionEvent.ACTION_MOVE -> {
324 | val evRawX = event.rawX
325 | val deltaX = (lastX - evRawX).toInt()
326 | val resultX = deltaX + scrollX
327 | when {
328 | resultX < 0 -> scrollTo(0, 0)
329 | resultX > totalWidth - width -> scrollTo(totalWidth - width, 0)
330 | else -> scrollBy(deltaX, 0)
331 | }
332 | lastX = evRawX
333 | }
334 | MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
335 | if (scrollX > 0 && scrollX < totalWidth - width) {
336 | velocityTracker?.let {
337 | it.computeCurrentVelocity(1000)
338 | val xVelocity = it.xVelocity
339 | if (abs(xVelocity) > minVelocity) {
340 | scroller.fling(
341 | scrollX,
342 | 0,
343 | -xVelocity.toInt(),
344 | 0,
345 | 0,
346 | totalWidth - width,
347 | 0,
348 | 0,
349 | overScrollDistance,
350 | 0
351 | )
352 | postInvalidateOnAnimation()
353 | }
354 | }
355 | }
356 |
357 | velocityTracker?.clear()
358 | velocityTracker?.recycle()
359 | velocityTracker = null
360 | }
361 | }
362 | return true
363 | }
364 |
365 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
366 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
367 |
368 | totalWidth = 0
369 | var totalHeight = 0
370 | if (childCount == 0) {
371 | // 没有tab的情况,TabLayout内容宽度与布局宽度一致
372 | totalWidth = indicator?.getWidth() ?: 0
373 | totalHeight = indicator?.getHeight() ?: 0
374 | } else {
375 | var totalWeight = 0
376 | for (i in 0 until childCount) {
377 | val child = getChildAt(i)
378 | measureChild(child, widthMeasureSpec, heightMeasureSpec)
379 |
380 | totalWidth += child.measuredWidth
381 | totalHeight = max(totalHeight, child.height)
382 | totalWeight += (child as KDTab).weight
383 | }
384 |
385 | when (tabMode) {
386 | // tab宽度保持既定不变的情况
387 | TAB_MODE_SCROLLABLE, TAB_MODE_PACK -> {
388 | setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
389 | }
390 | // tab宽度要按比例占满整个TabLayout的情况
391 | TAB_MODE_SPREAD -> {
392 | totalWidth = measuredWidth
393 | for (i in 0 until childCount) {
394 | val child = getChildAt(i)
395 | child.measure(
396 | MeasureSpec.makeMeasureSpec(
397 | totalWidth * (child as KDTab).weight / totalWeight,
398 | MeasureSpec.EXACTLY
399 | ),
400 | MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST)
401 | )
402 | }
403 | }
404 | TAB_MODE_FLEXIBLE -> {
405 | if (totalWidth <= measuredWidth) {
406 | // 所有子tab宽度加起来都比parent宽度小,此时跟SPREAD模式一致
407 | totalWidth = measuredWidth
408 | for (i in 0 until childCount) {
409 | val child = getChildAt(i)
410 | child.measure(
411 | MeasureSpec.makeMeasureSpec(
412 | totalWidth * (child as KDTab).weight / totalWeight,
413 | MeasureSpec.EXACTLY
414 | ),
415 | MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.AT_MOST)
416 | )
417 | }
418 | } else {
419 | // 所有子tab宽度加起来比parent宽度大,此时跟SCROllABLE模式一致
420 | setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
421 | }
422 | }
423 | }
424 | }
425 |
426 | setMeasuredDimension(View.resolveSizeAndState(totalWidth, widthMeasureSpec, 0),
427 | View.resolveSizeAndState(totalHeight, heightMeasureSpec, 0))
428 | }
429 |
430 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
431 | var width = 0
432 | if (tabMode == TAB_MODE_PACK) {
433 | width = (r - l - totalWidth) / 2
434 | }
435 | for (i in 0 until childCount) {
436 | val child = getChildAt(i)
437 | child.layout(width, 0, width + child.measuredWidth, child.measuredHeight)
438 | width += child.measuredWidth
439 | }
440 | scrollable = r - l < width
441 | if (changed) {
442 | indicator?.init()
443 | }
444 |
445 | savedBundle?.let {
446 | updateTabState(it.getInt("currentItem"))
447 | post { scrollTo(it.getInt("scrollX")) }
448 | }
449 | savedBundle = null
450 | }
451 |
452 | override fun onScrolling(scrollFraction: Float, startItem: Int, endItem: Int) {
453 | if (stopViewPagerAffect) return
454 | val startTab: View? = getChildAt(startItem)
455 | val endTab: View? = getChildAt(endItem)
456 |
457 | if (startTab != null && endTab != null) {
458 |
459 | // 整个TabLayout同步滚动
460 | syncLayoutScrollX(startTab, endTab, scrollFraction)
461 |
462 | // 将当前滚动参数同步给关联到的两个tab
463 | syncTabScrollState(startTab, endTab, scrollFraction)
464 | }
465 |
466 | // 将滚动状态同步给indicator
467 | syncIndicatorScrollState(startItem, endItem, scrollFraction)
468 | }
469 |
470 | override fun onScrollStateChanged(state: Int) {
471 | when (state) {
472 | SCROLL_STATE_IDLE -> {
473 | if (stopViewPagerAffect) {
474 | stopViewPagerAffect = false
475 | }
476 | updateTabState(currentItem)
477 | }
478 | SCROLL_STATE_DRAGGING -> tabChangeAnimator?.cancel()
479 | SCROLL_STATE_SETTLING -> {
480 | if (scrollState == SCROLL_STATE_DRAGGING) {
481 | // 表明是用户拖动ViewPager导致的状态变化
482 | } else if (scrollState == SCROLL_STATE_IDLE) {
483 | // 表明是直接调用ViewPager.setCurrentItem方法导致的状态变化
484 | // 此时如果不需要完全滚动,就要禁止ViewPager滚动对Tab的影响,使用Tab自身的滚动方法来进行状态变化
485 | if (!needCompleteScroll) {
486 | stopViewPagerAffect = true
487 | }
488 | }
489 | }
490 | }
491 |
492 | scrollState = state
493 | }
494 |
495 | override fun onTabSelected(position: Int) {
496 | // 只有在禁止ViewPager滚动对Tab滚动的影响的时候,才需要调用Tab自身的滚动方法
497 | if (stopViewPagerAffect) {
498 | smoothScrollToItem(position)
499 | }
500 | }
501 |
502 | private fun updateTabState(currentItem: Int) {
503 | for (i in 0 until childCount) {
504 | val child = getChildAt(i)
505 | if (i == currentItem) {
506 | (child as KDTab).selectTab()
507 | } else {
508 | (child as KDTab).reset()
509 | }
510 | child.invalidate()
511 | }
512 | this.currentItem = currentItem
513 | syncIndicatorScrollState(currentItem, currentItem, 1f)
514 | }
515 |
516 | private fun syncTabScrollState(startTab: View, endTab: View, fraction: Float) {
517 | for (i in 0 until childCount) {
518 | val child = getChildAt(i)
519 | when (child) {
520 | startTab -> (child as KDTab).onScrolling(1 - fraction, startTab.left > endTab.left)
521 | endTab -> (child as KDTab).onScrolling(fraction, startTab.left < endTab.left)
522 | else -> (child as KDTab).reset()
523 | }
524 | child.invalidate()
525 | }
526 |
527 | if (fraction == 1f) {
528 | currentItem = indexOfChild(endTab)
529 | }
530 | }
531 |
532 | private fun syncIndicatorScrollState(startItem: Int, endItem: Int, fraction: Float) {
533 | indicator?.onTabScrolled(startItem, endItem, fraction)
534 | invalidate()
535 | }
536 |
537 | /**
538 | * 根据滚动的两个tab和滚动进程,将tabLayout同步滚动
539 | */
540 | private fun syncLayoutScrollX(startTab: View, endTab: View, fraction: Float) {
541 | if (scrollable) {
542 | val startScrollX = getTabScrollXInCenter(startTab)
543 | val endScrollX = getTabScrollXInCenter(endTab)
544 | scrollTo(startScrollX + ((endScrollX - startScrollX) * fraction).toInt())
545 | }
546 | }
547 |
548 | /**
549 | * tab在正中间时,整个tabLayout的scrollX值
550 | */
551 | private fun getTabScrollXInCenter(tab: View?): Int {
552 | return if (tab == null) {
553 | 0
554 | } else {
555 | tab.left - (width - tab.width) / 2 - scrollBiasX.toInt()
556 | }
557 | }
558 |
559 | override fun onSaveInstanceState(): Parcelable {
560 | savedBundle = null
561 | val bundle = Bundle()
562 | val superData = super.onSaveInstanceState()
563 | bundle.putParcelable("superData", superData)
564 | bundle.putInt("currentItem", currentItem)
565 | bundle.putInt("scrollX", scrollX)
566 | return bundle
567 | }
568 |
569 | override fun onRestoreInstanceState(state: Parcelable?) {
570 | val bundle = state as? Bundle
571 | if (bundle == null) {
572 | super.onRestoreInstanceState(state)
573 | } else {
574 | savedBundle = bundle
575 | super.onRestoreInstanceState(bundle.getParcelable("superData"))
576 | }
577 | }
578 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/KDViewPagerHelper.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout
2 |
3 | import androidx.viewpager.widget.ViewPager
4 | import androidx.viewpager2.widget.ViewPager2
5 |
6 | /**
7 | * Created By:XuQK
8 | * Created Date:2/19/20 3:30 PM
9 | * Creator Email:xuqiankun66@gmail.com
10 | * Description:
11 | */
12 | class KDViewPagerHelper {
13 | /**
14 | * ViewPager的currentItem会在滚动状态变为SETTING之后马上改变
15 | * 因为onPageSelected方法会在滚动状态变为SETTING后马上调用
16 | */
17 | private var prevTotalPositionOffset: Float = 0F
18 |
19 | var stateListener: ViewPageStateListener? = null
20 |
21 | fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
22 | if (prevTotalPositionOffset < position + positionOffset) {
23 |
24 | if (positionOffset == 0f && position > prevTotalPositionOffset.toInt()) {
25 | stateListener?.onScrolling(1f, position - 1, position)
26 | } else {
27 | stateListener?.onScrolling(positionOffset, position, position + 1)
28 | }
29 |
30 | } else if (prevTotalPositionOffset > position + positionOffset) {
31 | stateListener?.onScrolling(1 - positionOffset, position + 1, position)
32 | }
33 |
34 | prevTotalPositionOffset = position + positionOffset
35 | }
36 |
37 | fun onPageSelected(position: Int) {
38 | stateListener?.onTabSelected(position)
39 | }
40 |
41 | fun onPageScrollStateChanged(state: Int) {
42 | stateListener?.onScrollStateChanged(state)
43 | }
44 |
45 | fun bindViewPager2(viewPager: ViewPager2) {
46 | viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
47 | override fun onPageScrolled(
48 | position: Int,
49 | positionOffset: Float,
50 | positionOffsetPixels: Int
51 | ) {
52 | this@KDViewPagerHelper.onPageScrolled(position, positionOffset, positionOffsetPixels)
53 | }
54 |
55 | override fun onPageSelected(position: Int) {
56 | this@KDViewPagerHelper.onPageSelected(position)
57 | }
58 |
59 | override fun onPageScrollStateChanged(state: Int) {
60 | this@KDViewPagerHelper.onPageScrollStateChanged(state)
61 | }
62 | })
63 | }
64 |
65 | fun bindViewPager(viewPager: ViewPager) {
66 | viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
67 | override fun onPageScrollStateChanged(state: Int) {
68 | this@KDViewPagerHelper.onPageScrollStateChanged(state)
69 | }
70 |
71 | override fun onPageScrolled(
72 | position: Int,
73 | positionOffset: Float,
74 | positionOffsetPixels: Int
75 | ) {
76 | this@KDViewPagerHelper.onPageScrolled(position, positionOffset, positionOffsetPixels)
77 | }
78 |
79 | override fun onPageSelected(position: Int) {
80 | this@KDViewPagerHelper.onPageSelected(position)
81 | }
82 |
83 | })
84 | }
85 |
86 | interface ViewPageStateListener {
87 | /**
88 | * 从currentItem到nextItem的滚动过程中的fraction变化
89 | * @param scrollFraction 从一个tab到另一个tab,是0-1
90 | * @param startItem 从currentItem的tab出发
91 | * @param endItem 到nextItem的tab
92 | */
93 | fun onScrolling(scrollFraction: Float, startItem: Int, endItem: Int)
94 |
95 | /**
96 | * 滚动状态变化
97 | */
98 | fun onScrollStateChanged(state: Int)
99 |
100 | fun onTabSelected(position: Int)
101 | }
102 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/KDBadge.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget
2 |
3 | import android.graphics.Canvas
4 |
5 | /**
6 | * Created By:XuQK
7 | * Created Date:3/1/20 2:56 PM
8 | * Creator Email:xuqiankun66@gmail.com
9 | * Description:
10 | */
11 | abstract class KDBadge(protected val tab: KDTab) {
12 | protected var show: Boolean = true
13 |
14 | open fun dismiss() {
15 | if (!show) return
16 | show = false
17 | }
18 |
19 | open fun show() {
20 | show = true
21 | tab.invalidate()
22 | }
23 |
24 | abstract fun draw(canvas: Canvas)
25 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/KDTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget
2 |
3 | import android.content.Context
4 | import android.graphics.Canvas
5 | import android.graphics.Paint
6 | import android.graphics.Rect
7 | import android.widget.FrameLayout
8 |
9 | /**
10 | * Created By:XuQK
11 | * Created Date:2/21/20 1:18 PM
12 | * Creator Email:xuqiankun66@gmail.com
13 | * Description:
14 | */
15 | abstract class KDTab(context: Context) : FrameLayout(context) {
16 |
17 | protected val paint = Paint(Paint.ANTI_ALIAS_FLAG)
18 | /**
19 | * 内容区域在父布局中的位置
20 | */
21 | val contentRect = Rect()
22 |
23 | // ------供用户自定义的属性 START
24 | /**
25 | * tab包含的badge,如果需要可以实现它
26 | */
27 | var badge: KDBadge? = null
28 | /**
29 | * 该tab在整个TabLayout中的宽度比例
30 | * 该参数只在KDTabLayout的tabMode为TAB_MODE_SPREAD或TAB_MODE_FLEXIBLE的非滚动模式下生效
31 | */
32 | var weight: Int = 1
33 | /**单位dp*/
34 | var horizontalPadding: Float = 0f
35 | // ------供用户自定义的属性 END
36 |
37 | init {
38 | this.setWillNotDraw(false)
39 | }
40 |
41 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
42 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
43 | setMeasuredDimension(
44 | widthMeasureSpec,
45 | MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST)
46 | )
47 | }
48 |
49 | /**
50 | * @param selectedFraction 该Tab被选中的比例,即滚动完成度
51 | * @param selectedInLeft 该Tab是否是从左边滚动到右边
52 | */
53 | abstract fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean)
54 |
55 | /**
56 | * 重置为初始状态
57 | */
58 | abstract fun reset()
59 |
60 | /**
61 | * 设置为选中状态
62 | */
63 | abstract fun selectTab()
64 |
65 | /**
66 | * 计算内容尺寸
67 | */
68 | abstract fun computeContentBounds()
69 |
70 | override fun onDraw(canvas: Canvas) {
71 | super.onDraw(canvas)
72 |
73 | drawContent(canvas)
74 | drawBadge(canvas)
75 | }
76 |
77 | protected open fun drawContent(canvas: Canvas) {
78 | }
79 |
80 | protected fun drawBadge(canvas: Canvas) {
81 | badge?.draw(canvas)
82 | }
83 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/KDTabIndicator.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget
2 |
3 | import android.graphics.Canvas
4 | import github.xuqk.kdtablayout.KDTabLayout
5 |
6 | /**
7 | * Created By:XuQK
8 | * Created Date:2/17/20 1:58 PM
9 | * Creator Email:xuqiankun66@gmail.com
10 | * Description:
11 | */
12 | abstract class KDTabIndicator(protected val tabLayout: KDTabLayout) {
13 |
14 | /**
15 | * 初始化方法,在TabLayout布局尺寸改变,或adapter重新设置后会调用
16 | */
17 | abstract fun init()
18 |
19 | /**
20 | * 此方法参数都是以滚动方向为参照
21 | */
22 | abstract fun onTabScrolled(startItem: Int, endItem: Int, scrolledFraction: Float)
23 |
24 | /**
25 | * 绘制Indicator
26 | */
27 | abstract fun draw(canvas: Canvas)
28 |
29 | /**
30 | * 获取Indicator占用的宽度
31 | * 此方法在且仅在Indicator单独使用时需要实现
32 | */
33 | open fun getWidth(): Int = 0
34 |
35 | /**
36 | * 获取Indicator占用的高度
37 | * 此方法在且仅在Indicator单独使用时需要实现
38 | */
39 | open fun getHeight(): Int = 0
40 |
41 | fun postInvalidate() {
42 | tabLayout.postInvalidate()
43 | }
44 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/badge/KDContentEndRelativeBadge.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.badge
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 | import androidx.annotation.Px
6 | import github.xuqk.kdtablayout.dpToPx
7 | import github.xuqk.kdtablayout.getBaselineToCenter
8 | import github.xuqk.kdtablayout.widget.KDTab
9 |
10 | /**
11 | * Created By:XuQK
12 | * Created Date:2/21/20 9:20 PM
13 | * Creator Email:xuqiankun66@gmail.com
14 | * Description:tab的圆形指示badge,定位中心点为Tab内容区域的right和top
15 | */
16 | class KDContentEndRelativeBadge(tab: KDTab) : KDNumberBadge(tab) {
17 | /**
18 | * badge中心点离tab right的距离
19 | */
20 | @get:Px
21 | var xBias: Float = 0f
22 | set(value) {
23 | field = dpToPx(tab.context, value).toFloat()
24 | }
25 | /**
26 | * badge中心点离tab top的距离
27 | */
28 | @get:Px
29 | var yBias: Float = 0f
30 | set(value) {
31 | field = dpToPx(tab.context, value).toFloat()
32 | }
33 |
34 | override fun draw(canvas: Canvas) {
35 | if (!show) return
36 |
37 | tab.computeContentBounds()
38 | val x = tab.contentRect.right - tab.left
39 | val y = tab.contentRect.top
40 |
41 | // 绘制背景
42 | paint.color = bgColor
43 | canvas.drawCircle(x + xBias, y + yBias, size / 2 * fraction, paint)
44 |
45 | if (showCount) {
46 | // 绘制数字
47 | paint.color = countTextColor
48 | paint.textSize = countTextSize * fraction
49 | paint.textAlign = Paint.Align.CENTER
50 | paint.getFontMetrics(fontMetrics)
51 |
52 | val baseLineY = fontMetrics.getBaselineToCenter() + y + yBias
53 | canvas.drawText(count.toString(), x + xBias, baseLineY, paint)
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/badge/KDNumberBadge.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.badge
2 |
3 | import android.animation.ValueAnimator
4 | import android.graphics.Color
5 | import android.graphics.Paint
6 | import androidx.annotation.ColorInt
7 | import androidx.annotation.Px
8 | import github.xuqk.kdtablayout.dpToPx
9 | import github.xuqk.kdtablayout.widget.KDBadge
10 | import github.xuqk.kdtablayout.widget.KDTab
11 |
12 | /**
13 | * Created By:XuQK
14 | * Created Date:2/16/20 2:53 PM
15 | * Creator Email:xuqiankun66@gmail.com
16 | * Description:
17 | */
18 | abstract class KDNumberBadge(tab: KDTab) : KDBadge(tab) {
19 | protected val paint = Paint(Paint.ANTI_ALIAS_FLAG)
20 | protected val fontMetrics = Paint.FontMetrics()
21 |
22 | protected var animator: ValueAnimator? = null
23 | protected var fraction: Float = 1f
24 |
25 | /**指示数字*/
26 | var count: Int = 0
27 | set(value) {
28 | field = value
29 | if (showCount) {
30 | tab.invalidate()
31 | }
32 | }
33 | /**是否展示数字*/
34 | var showCount: Boolean = false
35 | /**badge背景颜色*/
36 | @field:ColorInt
37 | var bgColor: Int = Color.RED
38 | /**数字颜色*/
39 | @field:ColorInt
40 | var countTextColor: Int = Color.WHITE
41 | /**数字字体大小*/
42 | var countTextSize: Float = dpToPx(tab.context, 8f).toFloat()
43 | set(value) {
44 | field = dpToPx(tab.context, value).toFloat()
45 | }
46 | /**
47 | * badge的尺寸
48 | */
49 | @get:Px
50 | var size: Float = 0f
51 | set(value) {
52 | field = dpToPx(tab.context, value).toFloat()
53 | }
54 |
55 | override fun dismiss() {
56 | if (!show) return
57 | animator?.cancel()
58 | animator = ValueAnimator.ofFloat(1f, 0f).apply {
59 | duration = 200
60 | addUpdateListener {
61 | fraction = it.animatedValue as Float
62 | if (fraction == 0f) {
63 | show = false
64 | }
65 | tab.invalidate()
66 | }
67 | start()
68 | }
69 | }
70 |
71 | override fun show() {
72 | show = true
73 | tab.invalidate()
74 | }
75 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/badge/KDTabEndRelativeBadge.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.badge
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 | import androidx.annotation.Px
6 | import github.xuqk.kdtablayout.dpToPx
7 | import github.xuqk.kdtablayout.getBaselineToCenter
8 | import github.xuqk.kdtablayout.widget.KDTab
9 |
10 | /**
11 | * Created By:XuQK
12 | * Created Date:2/21/20 9:20 PM
13 | * Creator Email:xuqiankun66@gmail.com
14 | * Description:tab的圆形指示badge,定位中心点为Tab的right和top
15 | */
16 | class KDTabEndRelativeBadge(tab: KDTab) : KDNumberBadge(tab) {
17 | /**
18 | * badge中心点离tab right的距离
19 | */
20 | @get:Px
21 | var marginEnd: Float = 0f
22 | set(value) {
23 | field = dpToPx(tab.context, value).toFloat()
24 | }
25 | /**
26 | * badge中心点离tab top的距离
27 | */
28 | @get:Px
29 | var marginTop: Float = 0f
30 | set(value) {
31 | field = dpToPx(tab.context, value).toFloat()
32 | }
33 |
34 | override fun draw(canvas: Canvas) {
35 | if (!show) return
36 |
37 | // 绘制背景
38 | paint.color = bgColor
39 | canvas.drawCircle(tab.width - marginEnd, marginTop, size / 2 * fraction, paint)
40 |
41 | if (showCount) {
42 | // 绘制数字
43 | paint.color = countTextColor
44 | paint.textSize = countTextSize * fraction
45 | paint.textAlign = Paint.Align.CENTER
46 | paint.getFontMetrics(fontMetrics)
47 |
48 | val baseLineY = fontMetrics.getBaselineToCenter() + marginTop
49 | canvas.drawText(count.toString(), tab.width - marginEnd, baseLineY, paint)
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/indicator/DotMorphingIndicator.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.indicator
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Color
5 | import android.graphics.Paint
6 | import github.xuqk.kdtablayout.HsvEvaluator
7 | import github.xuqk.kdtablayout.KDTabLayout
8 | import github.xuqk.kdtablayout.dpToPx
9 | import github.xuqk.kdtablayout.widget.KDTabIndicator
10 |
11 | /**
12 | * Created By:XuQK
13 | * Created Date:2/23/20 7:31 PM
14 | * Creator Email:xuqiankun66@gmail.com
15 | * Description:为了便于计算,让左右各有1/2 space的margin
16 | */
17 | class DotMorphingIndicator(tabLayout: KDTabLayout) : KDTabIndicator(tabLayout) {
18 |
19 | var normalSize: Float = dpToPx(tabLayout.context, 8f).toFloat()
20 | set(value) {
21 | field = dpToPx(tabLayout.context, value).toFloat()
22 | }
23 | var selectedSize: Float = dpToPx(tabLayout.context, 12f).toFloat()
24 | set(value) {
25 | field = dpToPx(tabLayout.context, value).toFloat()
26 | }
27 | var space: Float = dpToPx(tabLayout.context, 8f).toFloat()
28 | set(value) {
29 | field = dpToPx(tabLayout.context, value).toFloat()
30 | }
31 | var selectedColor: Int = Color.GRAY
32 | var normalColor: Int = Color.LTGRAY
33 |
34 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
35 | private var dotY: Float = 0f
36 | private val dotXArray = mutableListOf()
37 | private var dotCount: Int = 0
38 | private val hsvEvaluator = HsvEvaluator()
39 |
40 | private var fraction: Float = 0f
41 | private var startItem: Int = 0
42 | private var endItem: Int = 0
43 | private var startDotSize: Float = 0f
44 | private var endDotSize: Float = 0f
45 |
46 | /**
47 | * 初始化方法,在TabLayout布局尺寸改变,或adapter重新设置后会调用
48 | */
49 | override fun init() {
50 | dotXArray.clear()
51 | dotCount = tabLayout.contentAdapter!!.getTabCount()
52 | dotXArray.add((space + normalSize) / 2)
53 | var x: Float = dotXArray[0]
54 | for (i in 1 until dotCount - 1) {
55 | x += space + normalSize
56 | dotXArray.add(x)
57 | }
58 | dotXArray.add(getWidth() - (space + normalSize) / 2)
59 |
60 | dotY = tabLayout.height / 2f
61 |
62 | onTabScrolled(tabLayout.currentItem, tabLayout.currentItem, 0f)
63 |
64 | postInvalidate()
65 | }
66 |
67 | override fun onTabScrolled(startItem: Int, endItem: Int, scrolledFraction: Float) {
68 | fraction = scrolledFraction
69 | this.startItem = startItem
70 | this.endItem = endItem
71 | startDotSize = selectedSize - (selectedSize - normalSize) * scrolledFraction
72 | endDotSize = normalSize + (selectedSize - normalSize) * scrolledFraction
73 | }
74 |
75 | override fun draw(canvas: Canvas) {
76 | dotXArray.forEachIndexed { index, x ->
77 | when (index) {
78 | startItem -> {
79 | paint.color = hsvEvaluator.evaluate(fraction, selectedColor, normalColor)
80 | canvas.drawCircle(x, dotY, startDotSize / 2, paint)
81 | }
82 | endItem -> {
83 | paint.color = hsvEvaluator.evaluate(fraction, normalColor, selectedColor)
84 | canvas.drawCircle(x, dotY, endDotSize / 2, paint)
85 | }
86 | else -> {
87 | paint.color = normalColor
88 | canvas.drawCircle(x, dotY, normalSize / 2, paint)
89 | }
90 | }
91 | }
92 | }
93 |
94 | override fun getWidth(): Int {
95 | return (normalSize * dotCount + space * dotCount).toInt()
96 | }
97 |
98 | override fun getHeight(): Int {
99 | return selectedSize.toInt()
100 | }
101 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/indicator/DotWithStrokeIndicator.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.indicator
2 |
3 | import android.graphics.Canvas
4 | import android.graphics.Paint
5 | import github.xuqk.kdtablayout.KDTabLayout
6 | import github.xuqk.kdtablayout.dpToPx
7 | import github.xuqk.kdtablayout.widget.KDTabIndicator
8 |
9 | /**
10 | * Created By:XuQK
11 | * Created Date:2/23/20 7:31 PM
12 | * Creator Email:xuqiankun66@gmail.com
13 | * Description:为了便于计算,让左右各有1/2 space的margin
14 | */
15 | class DotWithStrokeIndicator(tabLayout: KDTabLayout) : KDTabIndicator(tabLayout) {
16 |
17 | var size: Float = dpToPx(tabLayout.context, 8f).toFloat()
18 | set(value) {
19 | field = dpToPx(tabLayout.context, value).toFloat()
20 | }
21 | var space: Float = dpToPx(tabLayout.context, 8f).toFloat()
22 | set(value) {
23 | field = dpToPx(tabLayout.context, value).toFloat()
24 | }
25 | var color: Int = 0xff2196f3.toInt()
26 | var strokeWidth: Float = 0f
27 | set(value) {
28 | field = dpToPx(tabLayout.context, value).toFloat()
29 | }
30 |
31 | private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
32 | private var dotY: Float = 0f
33 | private val dotXArray = mutableListOf()
34 | private var currentDotX: Float = 0f
35 | private var dotCount: Int = 0
36 |
37 | /**
38 | * 初始化方法,在TabLayout布局尺寸改变,或adapter重新设置后会调用
39 | */
40 | override fun init() {
41 | dotXArray.clear()
42 | dotCount = tabLayout.contentAdapter!!.getTabCount()
43 | dotXArray.add((space + size) / 2)
44 | var x: Float = dotXArray[0]
45 | for (i in 1 until dotCount - 1) {
46 | x += space + size
47 | dotXArray.add(x)
48 | }
49 | dotXArray.add(getWidth() - (space + size) / 2)
50 |
51 | dotY = tabLayout.height / 2f
52 |
53 | onTabScrolled(tabLayout.currentItem, tabLayout.currentItem, 0f)
54 |
55 | postInvalidate()
56 | }
57 |
58 | override fun onTabScrolled(startItem: Int, endItem: Int, scrolledFraction: Float) {
59 | var leftItem: Int = startItem
60 | var rightItem: Int = endItem
61 | var fractionToRight: Float = scrolledFraction
62 | if (endItem < startItem) {
63 | // 滚向右边
64 | leftItem = endItem
65 | rightItem = startItem
66 | fractionToRight = 1 - scrolledFraction
67 | }
68 |
69 | currentDotX = (dotXArray[rightItem] - dotXArray[leftItem]) * fractionToRight + dotXArray[leftItem]
70 | }
71 |
72 | override fun draw(canvas: Canvas) {
73 | paint.reset()
74 | paint.isAntiAlias = true
75 | paint.color = color
76 | paint.style = Paint.Style.STROKE
77 | paint.strokeWidth = strokeWidth
78 | dotXArray.forEach {
79 | canvas.drawCircle(it, dotY, size / 2, paint)
80 | }
81 |
82 | paint.reset()
83 | paint.isAntiAlias = true
84 | paint.color = color
85 | canvas.drawCircle(
86 | currentDotX,
87 | dotY,
88 | size / 2,
89 | paint
90 | )
91 | }
92 |
93 | override fun getWidth(): Int {
94 | return (size * dotCount + space * dotCount).toInt()
95 | }
96 |
97 | override fun getHeight(): Int {
98 | return (size + strokeWidth).toInt()
99 | }
100 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/indicator/KDRecIndicator.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.indicator
2 |
3 | import android.graphics.*
4 | import android.view.animation.Interpolator
5 | import android.view.animation.LinearInterpolator
6 | import androidx.annotation.ColorInt
7 | import androidx.annotation.Px
8 | import github.xuqk.kdtablayout.KDTabLayout
9 | import github.xuqk.kdtablayout.dpToPx
10 | import github.xuqk.kdtablayout.widget.KDTab
11 | import github.xuqk.kdtablayout.widget.KDTabIndicator
12 |
13 | /**
14 | * Created By:XuQK
15 | * Created Date:2/17/20 1:41 PM
16 | * Creator Email:xuqiankun66@gmail.com
17 | * Description:矩形Indicator
18 | */
19 | open class KDRecIndicator(tabLayout: KDTabLayout) : KDTabIndicator(tabLayout) {
20 |
21 | companion object {
22 | /**
23 | * Indicator宽度匹配Tab两端,此时marginHorizontal参数会生效
24 | */
25 | const val MODE_MATCH = 0
26 | /**
27 | * Indicator宽度为固定值
28 | */
29 | const val MODE_EXACT = 1
30 | /**
31 | * Indicator宽度匹配内容区域宽度,此时paddingHorizontal参数会生效
32 | * 如果只有Indicator,没有Tab,那么此参数不生效
33 | */
34 | const val MODE_WRAP_CONTENT = 2
35 | }
36 |
37 | protected val paint = Paint(Paint.ANTI_ALIAS_FLAG)
38 | protected val rect = RectF()
39 | protected val context = tabLayout.context
40 |
41 | // ----------供用户自定义的属性 START
42 | /**
43 | * Indicator高度
44 | * 输入值单位为dp,输出值为px
45 | */
46 | @get:Px
47 | var indicatorHeight: Float = 0f
48 | set(value) {
49 | field = dpToPx(context, value).toFloat()
50 | }
51 | /**
52 | * Indicator离底部的距离
53 | * 输入值单位为dp,输出值为px
54 | */
55 | @get:Px
56 | var marginBottom: Float = 0f
57 | set(value) {
58 | field = dpToPx(context, value).toFloat()
59 | }
60 | /**
61 | * Indicator横向Margin,仅在mode为MODE_MATCH时或单独使用Indicator时有效
62 | * 输入值单位为dp,输出值为px
63 | */
64 | @get:Px
65 | var marginHorizontal: Float = 0f
66 | set(value) {
67 | field = dpToPx(context, value).toFloat()
68 | }
69 | /**
70 | * Indicator横向Padding,仅在mode为MODE_WRAP_CONTENT时有效
71 | * 输入值单位为dp,输出值为px
72 | */
73 | @get:Px
74 | var paddingHorizontal: Float = 0f
75 | set(value) {
76 | field = dpToPx(context, value).toFloat()
77 | }
78 | /**
79 | * Indicator圆角数值
80 | * 输入值单位为dp,输出值为px
81 | */
82 | @get:Px
83 | var cornerRadius: Float = 0f
84 | set(value) {
85 | field = dpToPx(context, value).toFloat()
86 | }
87 | /**
88 | * Indicator宽度,仅在mode为MODE_EXACT时或单独使用Indicator时有效
89 | * 输入值单位为dp,输出值为px
90 | */
91 | @get:Px
92 | var indicatorWidth: Float = 0f
93 | set(value) {
94 | field = dpToPx(context, value).toFloat()
95 | }
96 | /**
97 | * MODE_MATCH Indicator宽度匹配整个TAB的宽度,此时indicatorWidth参数无效
98 | * MODE_EXACT Indicator宽度为固定值,此时marginHorizontal参数无效
99 | */
100 | var mode: Int = MODE_MATCH
101 | /**
102 | * Indicator颜色,为ColorInt值
103 | */
104 | @field:ColorInt
105 | var color: Int = Color.BLACK
106 | set(value) {
107 | field = value
108 | startColor = field
109 | endColor = field
110 | }
111 |
112 | /**
113 | * 渐变色start
114 | */
115 | @field:ColorInt
116 | var startColor: Int = Color.BLACK
117 |
118 | /**
119 | * 渐变色end
120 | */
121 | @field:ColorInt
122 | var endColor: Int = Color.BLACK
123 |
124 | var startInterpolator: Interpolator = LinearInterpolator()
125 | var endInterpolator: Interpolator = LinearInterpolator()
126 | // ----------供用户自定义的属性 END
127 |
128 | override fun init() {
129 | onTabScrolled(tabLayout.currentItem, tabLayout.currentItem, 0f)
130 | postInvalidate()
131 | }
132 |
133 | override fun onTabScrolled(startItem: Int, endItem: Int, scrolledFraction: Float) {
134 | val fractionToRight: Float = if (endItem > startItem) {
135 | // 滚向右边
136 | scrolledFraction
137 | } else {
138 | // 滚向左边
139 | 1 - scrolledFraction
140 | }
141 |
142 | val leftX: Int
143 | val rightX: Int
144 | val nextLeftX: Int
145 | val nextRightX: Int
146 | if (tabLayout.childCount == 0) {
147 | val tabWidth = tabLayout.width / tabLayout.contentAdapter!!.getTabCount()
148 | val leftItem: Int
149 | val rightItem: Int
150 | if (endItem > startItem) {
151 | leftItem = startItem
152 | rightItem = endItem
153 | } else {
154 | leftItem = endItem
155 | rightItem = startItem
156 | }
157 | when (mode) {
158 | MODE_EXACT -> {
159 | leftX = (leftItem * tabWidth + (tabWidth - indicatorWidth) / 2).toInt()
160 | rightX = ((leftItem + 1) * tabWidth - (tabWidth - indicatorWidth) / 2).toInt()
161 | nextLeftX = (rightItem * tabWidth + (tabWidth - indicatorWidth) / 2).toInt()
162 | nextRightX = ((rightItem + 1) * tabWidth - (tabWidth - indicatorWidth) / 2).toInt()
163 | }
164 | MODE_MATCH -> {
165 | leftX = leftItem * tabWidth + marginHorizontal.toInt()
166 | rightX = (leftItem + 1) * tabWidth - marginHorizontal.toInt()
167 | nextLeftX = rightItem * tabWidth + marginHorizontal.toInt()
168 | nextRightX = (rightItem + 1) * tabWidth - marginHorizontal.toInt()
169 | }
170 | else -> {
171 | throw IllegalArgumentException("此时mode必须为MODE_EXACT或MODE_MATCH之一")
172 | }
173 | }
174 | } else {
175 | val leftTab: KDTab
176 | val rightTab: KDTab
177 | if (endItem > startItem) {
178 | // 滚向右边
179 | leftTab = tabLayout.getChildAt(startItem) as KDTab
180 | rightTab = tabLayout.getChildAt(endItem) as KDTab
181 | } else {
182 | // 滚向左边
183 | leftTab = tabLayout.getChildAt(endItem) as KDTab
184 | rightTab = tabLayout.getChildAt(startItem) as KDTab
185 | }
186 |
187 | when (mode) {
188 | MODE_EXACT -> {
189 | leftX = (leftTab.left + (leftTab.width - indicatorWidth) / 2).toInt()
190 | rightX = (leftTab.right - (leftTab.width - indicatorWidth) / 2).toInt()
191 | nextLeftX = (rightTab.left + (rightTab.width - indicatorWidth) / 2).toInt()
192 | nextRightX = (rightTab.right - (rightTab.width - indicatorWidth) / 2).toInt()
193 | }
194 | MODE_WRAP_CONTENT -> {
195 | leftTab.computeContentBounds()
196 | rightTab.computeContentBounds()
197 | leftX = leftTab.contentRect.left - paddingHorizontal.toInt()
198 | rightX = leftTab.contentRect.right + paddingHorizontal.toInt()
199 | nextLeftX = rightTab.contentRect.left - paddingHorizontal.toInt()
200 | nextRightX = rightTab.contentRect.right + paddingHorizontal.toInt()
201 | }
202 | MODE_MATCH -> {
203 | leftX = leftTab.left + marginHorizontal.toInt()
204 | rightX = leftTab.right - marginHorizontal.toInt()
205 | nextLeftX = rightTab.left + marginHorizontal.toInt()
206 | nextRightX = rightTab.right - marginHorizontal.toInt()
207 | }
208 | // MODE_MATCH
209 | else -> {
210 | throw IllegalArgumentException("mode必须为MODE_EXACT或MODE_MATCH之一")
211 | }
212 | }
213 | }
214 | rect.top = tabLayout.height - indicatorHeight - marginBottom
215 | rect.bottom = tabLayout.height - marginBottom
216 | rect.left =
217 | leftX + (nextLeftX - leftX) * startInterpolator.getInterpolation(fractionToRight)
218 | rect.right =
219 | rightX + (nextRightX - rightX) * endInterpolator.getInterpolation(fractionToRight)
220 | }
221 |
222 | override fun draw(canvas: Canvas) {
223 | val y = (rect.bottom - rect.top) / 2
224 | paint.shader = LinearGradient(
225 | rect.left,
226 | y,
227 | rect.right,
228 | y,
229 | startColor,
230 | endColor,
231 | Shader.TileMode.CLAMP
232 | )
233 | canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
234 | }
235 |
236 | override fun getWidth(): Int {
237 | return ((indicatorWidth + marginHorizontal * 2) * tabLayout.contentAdapter!!.getTabCount()).toInt()
238 | }
239 |
240 | override fun getHeight(): Int {
241 | return indicatorHeight.toInt()
242 | }
243 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/tab/KDColorClipTextTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.tab
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.Canvas
6 |
7 | /**
8 | * Created By:XuQK
9 | * Created Date:2/20/20 11:30 PM
10 | * Creator Email:xuqiankun66@gmail.com
11 | * Description:
12 | */
13 | @SuppressLint("ViewConstructor")
14 | class KDColorClipTextTab(context: Context, text: String) :
15 | KDSizeMorphingTextTab(context, text) {
16 |
17 | private var selectedFraction: Float = 0f
18 | private var selectedInLeft: Boolean = false
19 |
20 | init {
21 | this.text = text
22 | }
23 |
24 | override fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean) {
25 | super.onScrolling(selectedFraction, selectedInLeft)
26 | this.selectedFraction = selectedFraction
27 | this.selectedInLeft = selectedInLeft
28 | }
29 |
30 | override fun selectTab() {
31 | super.selectTab()
32 | selectedFraction = 1f
33 | }
34 |
35 | override fun reset() {
36 | super.reset()
37 | selectedFraction = 0f
38 | }
39 |
40 | override fun drawContent(canvas: Canvas) {
41 | setPaintParam()
42 |
43 | paint.getFontMetrics(fontMetrics)
44 | val baseLine = (fontMetrics.descent - fontMetrics.ascent) / 2 + height / 2 - fontMetrics.descent
45 |
46 | when (selectedFraction) {
47 | 0f -> {
48 | paint.color = normalTextColor
49 | canvas.drawText(text, (width / 2).toFloat(), baseLine, paint)
50 | }
51 | 1f -> {
52 | paint.color = selectedTextColor
53 | canvas.drawText(text, (width / 2).toFloat(), baseLine, paint)
54 | }
55 | else -> {
56 | if (selectedInLeft) {
57 | val leftWidth = (width * selectedFraction).toInt()
58 |
59 | // 绘制左半部分
60 | drawClipText(canvas, text, selectedTextColor, baseLine,
61 | 0, 0, leftWidth, height)
62 |
63 | // 绘制右半部分
64 | drawClipText(canvas, text, normalTextColor, baseLine,
65 | leftWidth, 0, width, height)
66 | } else {
67 | val leftWidth = (width * (1 - selectedFraction)).toInt()
68 |
69 | // 绘制左半部分
70 | drawClipText(canvas, text, normalTextColor, baseLine,
71 | 0, 0, leftWidth, height)
72 |
73 | // 绘制右半部分
74 | drawClipText(canvas, text, selectedTextColor, baseLine,
75 | leftWidth, 0, width, height)
76 | }
77 | }
78 | }
79 |
80 | computeContentBounds()
81 | }
82 |
83 | private fun drawClipText(canvas: Canvas, text: String, color: Int, baseLine: Float, left: Int, top: Int, right: Int, bottom: Int) {
84 | paint.color = color
85 | canvas.save()
86 | canvas.clipRect(left, top, right, bottom)
87 | canvas.drawText(text, (width / 2).toFloat(), baseLine, paint)
88 | canvas.restore()
89 | }
90 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/tab/KDColorMorphingTextTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.tab
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import github.xuqk.kdtablayout.HsvEvaluator
6 |
7 | /**
8 | * Created By:XuQK
9 | * Created Date:2/21/20 4:24 PM
10 | * Creator Email:xuqiankun66@gmail.com
11 | * Description:
12 | */
13 | @SuppressLint("ViewConstructor")
14 | open class KDColorMorphingTextTab(context: Context, text: String) :
15 | KDSizeMorphingTextTab(context, text) {
16 |
17 | private val hsvEvaluator = HsvEvaluator()
18 |
19 | init {
20 | this.text = text
21 | }
22 |
23 | override fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean) {
24 | super.onScrolling(selectedFraction, selectedInLeft)
25 | textColor = hsvEvaluator.evaluate(selectedFraction, normalTextColor, selectedTextColor)
26 | }
27 | }
--------------------------------------------------------------------------------
/kdtablayout/src/main/java/github/xuqk/kdtablayout/widget/tab/KDSizeMorphingTextTab.kt:
--------------------------------------------------------------------------------
1 | package github.xuqk.kdtablayout.widget.tab
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.graphics.Canvas
6 | import android.graphics.Paint
7 | import android.graphics.Typeface
8 | import androidx.annotation.ColorInt
9 | import github.xuqk.kdtablayout.dpToPx
10 | import github.xuqk.kdtablayout.getBaselineToCenter
11 | import github.xuqk.kdtablayout.widget.KDTab
12 |
13 | /**
14 | * Created By:XuQK
15 | * Created Date:2/21/20 3:06 PM
16 | * Creator Email:xuqiankun66@gmail.com
17 | * Description:
18 | */
19 | @SuppressLint("ViewConstructor")
20 | open class KDSizeMorphingTextTab(context: Context, text: String) :
21 | KDTab(context) {
22 |
23 | protected val fontMetrics = Paint.FontMetrics()
24 |
25 | // ------供用户自定义的属性 START
26 | /**单位dp*/
27 | var selectedTextSize: Float = 16f
28 | /**单位dp*/
29 | var normalTextSize: Float = 16f
30 | // 是否随字体尺寸变化调整tab尺寸
31 | var resizeWithFontSize: Boolean = false
32 | var selectedBold: Boolean = false
33 | @ColorInt
34 | var selectedTextColor: Int = 0xFFFFFFFF.toInt()
35 | @ColorInt
36 | var normalTextColor: Int = 0x4CFFFFFF
37 | // ------供用户自定义的属性 END
38 |
39 | protected var textSize: Float = normalTextSize
40 | protected var textColor: Int = selectedTextColor
41 | protected var bold: Boolean = false
42 |
43 | var text: String = ""
44 | set(value) {
45 | if (field != value) {
46 | field = value
47 | requestLayout()
48 | postInvalidate()
49 | }
50 | }
51 |
52 | init {
53 | this.text = text
54 | }
55 |
56 | /**
57 | * Tab滚动中
58 | * @param selectedFraction 该tab被选中的比例[0, 1]
59 | * @param selectedInLeft 该tab被选中区域是否在左边,否就是在右边
60 | */
61 | override fun onScrolling(selectedFraction: Float, selectedInLeft: Boolean) {
62 | if (selectedBold) {
63 | bold = selectedFraction > 0.5f
64 | }
65 | textSize = (selectedTextSize - normalTextSize) * selectedFraction + normalTextSize
66 | if (resizeWithFontSize && selectedTextSize != normalTextSize) {
67 | requestLayout()
68 | }
69 | }
70 |
71 | /**
72 | * 该tab被选中时回调
73 | */
74 | override fun selectTab() {
75 | bold = selectedBold
76 | textSize = selectedTextSize
77 | textColor = selectedTextColor
78 | }
79 |
80 | /**
81 | * 回复至初始状态
82 | */
83 | override fun reset() {
84 | bold = false
85 | textSize = normalTextSize
86 | textColor = normalTextColor
87 | }
88 |
89 | override fun computeContentBounds() {
90 | setPaintParam()
91 | paint.getTextBounds(text, 0, text.length, contentRect)
92 |
93 | val contentWidth = contentRect.width()
94 | val contentHeight = contentRect.height()
95 | contentRect.left = left + (width - contentWidth) / 2
96 | contentRect.right = contentRect.left + contentWidth
97 | contentRect.top = (height - contentHeight) / 2
98 | contentRect.bottom = contentRect.top + contentHeight
99 | }
100 |
101 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
102 | setPaintParam()
103 |
104 | val w = paint.measureText(text) + dpToPx(context, horizontalPadding) * 2
105 | setMeasuredDimension(
106 | resolveSizeAndState(w.toInt(), widthMeasureSpec, 0),
107 | MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST)
108 | )
109 | }
110 |
111 | override fun drawContent(canvas: Canvas) {
112 | setPaintParam()
113 |
114 | paint.getFontMetrics(fontMetrics)
115 | val baselineY = fontMetrics.getBaselineToCenter() + height / 2
116 | canvas.drawText(text, (width / 2).toFloat(), baselineY, paint)
117 | }
118 |
119 | protected open fun setPaintParam() {
120 | paint.reset()
121 | paint.isAntiAlias = true
122 | paint.textSize = dpToPx(context, textSize).toFloat()
123 | paint.color = textColor
124 | paint.typeface = if (bold) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
125 | paint.textAlign = Paint.Align.CENTER
126 | }
127 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':kdtablayout'
2 | rootProject.name='Demo'
3 |
--------------------------------------------------------------------------------