├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── android-flavor.gradle
├── build.gradle.kts
├── proguard-rules.pro
├── src
│ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── grock
│ │ │ └── tc
│ │ │ └── ExampleInstrumentedTest.kt
│ ├── debug
│ │ └── java
│ │ │ └── com
│ │ │ └── grock
│ │ │ └── Utils.kt
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── grock
│ │ │ │ ├── TabLayoutExt.kt
│ │ │ │ ├── ToolsExt.kt
│ │ │ │ ├── ViewPager2Activity.kt
│ │ │ │ └── ViewPager2Activity2.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── ic_cat_paw.png
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── ll_tab_indicator_block.xml
│ │ │ └── ll_tab_indicator_cat_paw.xml
│ │ │ ├── layout
│ │ │ ├── act_viewpager2.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── frag_simple.xml
│ │ │ ├── level1_fragment.xml
│ │ │ ├── levelx_fragment.xml
│ │ │ ├── tab_item.xml
│ │ │ └── tab_item_text.xml
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night
│ │ │ └── themes.xml
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── ids.xml
│ │ │ ├── strings.xml
│ │ │ ├── styles.xml
│ │ │ └── themes.xml
│ └── test
│ │ └── java
│ │ └── com
│ │ └── grock
│ │ └── tc
│ │ └── ExampleUnitTest.kt
└── test.jks
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # 所有的iml 文件
2 | *.iml
3 | # 所有的build 文件夹
4 | build
5 | # 整个 .gradle 文件夹
6 | .gradle
7 | # 整个 androidTest文件夹
8 | andoridTest
9 | #idea caches
10 | .idea/caches
11 | .idea/libraries
12 | .idea/modules
13 |
14 | #忽略本地配置
15 | local.properties
16 |
17 | gradlew.bat
18 |
19 | #忽略 ndk 编译内容
20 | .externalNativeBuild
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | xmlns:android
34 |
35 | ^$
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | xmlns:.*
45 |
46 | ^$
47 |
48 |
49 | BY_NAME
50 |
51 |
52 |
53 |
54 |
55 |
56 | .*:id
57 |
58 | http://schemas.android.com/apk/res/android
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | .*:name
68 |
69 | http://schemas.android.com/apk/res/android
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | name
79 |
80 | ^$
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | style
90 |
91 | ^$
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | .*
101 |
102 | ^$
103 |
104 |
105 | BY_NAME
106 |
107 |
108 |
109 |
110 |
111 |
112 | .*
113 |
114 | http://schemas.android.com/apk/res/android
115 |
116 |
117 | ANDROID_ATTRIBUTE_ORDER
118 |
119 |
120 |
121 |
122 |
123 |
124 | .*
125 |
126 | .*
127 |
128 |
129 | BY_NAME
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | `ViewPager2`正式推出已经一年多了,既不如3那样新潮,也不如老前辈`ViewPager`那样有众多开源库拥簇,比如它的灵魂伴侣`TabLayout`明显后援不足,好在`TabLayout`自身够硬!
3 |
4 | ViewPager2灵魂伴侣是官方提供的:
5 | ```
6 | com.google.android.material.tabs.TabLayout
7 | ```
8 | `TabLayout` 利用其良好的设计,使得自定义非常容易。
9 |
10 | 像匹配`ViewPager`的优秀开源库FlycoTabLayout的效果,使用`TabLayout`都能比较容易的实现:
11 |
12 | FlycoTabLayout 演示
13 |
14 | 
15 |
16 | 实现上图中的几个常用效果`TabLayout` 仅需在xml重配置即可
17 |
18 |
19 | 
20 |
21 | 不过稍微不同的是,上图中第二第三栏选中后的字体是有放大效果的。
22 |
23 | 这是利用`TabLayout.Tab`的`customView`属性达到的。下文便是实现的思路与过程记录。
24 |
25 |
26 | ### 正文
27 |
28 | #### 思路拆解:
29 | * 介于此功能耦合点仅仅是`TabLayoutMediator`,选择使用拓展包装`TabLayoutMediator`,轻量且无侵入性,API还便捷
30 | * 自定义`TabLayoutMediator`,设置`customView`,放入自己的`TextView`
31 | * 内部自动添加一个`addOnTabSelectedListener`,在选中后使用动画渐进式的改变字体大小,同理取消选中时还原
32 |
33 | #### 解决过的坑:
34 | * `TextView`的文本在Size改变时,宽度动态变化,调用`requestLayout()`。Tab栏会因此触发重新测量与重绘,出现短促闪烁。塞两个`TextView`,一个作为最大边界并且设置`INVISIBLE`
35 | * 同样是重测问题,导致`TabLayout`额外多从头绘制一次`Indicator`时,直观表现就是每次切换`Indicator`时,会出现闪现消失。采用自定义了一个`ScaleTexViewTabView`,动态控制是否触发`super.requestLayout`
36 | (因为已经准备了两个View,负责展示效果的View最大范围是明确无法超过既定范围的,所以这个办法不算“黑”)
37 |
38 | * #### 核心API:
39 |
40 | ```Kotlin
41 |
42 | fun TabLayout.createMediatorByCustomTabView(
43 | vp: ViewPager2,
44 | config: CustomTabViewConfig
45 | ): TabLayoutMediator {
46 | return createMediator(vp){tab,pos->
47 | val tabView = config.getCustomView(tab.view.context)
48 | tab.customView = tabView
49 | config.onTabInit(tabView, pos)
50 | }
51 | }
52 |
53 | fun TabLayout.createTextScaleMediatorByTextView(
54 | vp: ViewPager2,
55 | config: TextScaleTabViewConfig
56 | ): TabLayoutMediator {
57 | addScaleAnim(config.scale)
58 | return createMediatorByCustomTabView(vp, config)
59 | }
60 | ```
61 | #### 使用:
62 | * 绑定ViewPager2
63 |
64 | ```kotlin
65 | val mediator2 = vb.tl2.createTextScaleMediatorByTextView(vb.viewPager2,
66 | object : TextScaleTabViewConfig(scaleConfig2) {
67 | override fun getText(position: Int): String {
68 | return tabs[position]
69 | }
70 |
71 | override fun onVisibleTextViewInit(tv: TextView) {
72 | tv.setTextColor(Color.WHITE)
73 | }
74 | })
75 | mediator2.attach()
76 |
77 | ```
78 | * 单独添加Tab
79 | ```java
80 | vb.tl3.addScaleTabByTextView(tabs.toList(),Color.YELLOW,scaleConfig3)
81 | ```
82 |
83 | 本文只是从一种特殊(或者叫奇怪)的角度来定制View的样式,使用kotlin拓展API,实现与使用上保持轻量,不侵入自定义View,不影响XML。
84 |
85 |
86 | [点击直达完整源码~,拷贝即用](https://github.com/HarkBen/LovelyTabLayoutExt/blob/master/app/src/main/java/com/grock/TabLayoutExt.kt)
87 |
88 | # END
89 | ***
90 | ### 引用:
91 | * [H07000223](https://github.com/H07000223)/**[FlycoTabLayout](https://github.com/H07000223/FlycoTabLayout)**
92 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/android-flavor.gradle:
--------------------------------------------------------------------------------
1 | apply plugin:'com.android.application'
2 |
3 | android {
4 | signingConfigs {
5 | b2 {
6 | keyAlias 'android'
7 | keyPassword 'android'
8 | storeFile file("test.jks")
9 | storePassword 'android'
10 | }
11 | b3 {
12 | keyAlias 'android'
13 | keyPassword 'android'
14 | storeFile file("test.jks")
15 | storePassword 'android'
16 | }
17 | b6 {
18 | keyAlias 'android'
19 | keyPassword 'android'
20 | storeFile file("test.jks")
21 | storePassword 'android'
22 | }
23 | }
24 | flavorDimensions "channel"
25 | productFlavors{
26 | b2{
27 | dimension "channel"
28 | versionCode 1
29 | versionName "2.0"
30 | applicationId "com.channel.b2"
31 | //控制manifest 占位符
32 | signingConfig signingConfigs.b2
33 |
34 |
35 | }
36 | b3 {
37 | versionCode 1
38 | versionName "3.0"
39 | dimension "channel"
40 | applicationId "com.channel.b3"
41 | signingConfig signingConfigs.b3
42 | }
43 | b6{
44 | versionCode 1
45 | versionName "6.0"
46 | dimension "channel"
47 | applicationId "com.channel.b6"
48 | signingConfig signingConfigs.b6
49 | }
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("kotlin-android")
4 | }
5 | println("--------build.gradle.kts---")
6 |
7 | android {
8 |
9 | compileSdkVersion(30)
10 |
11 | defaultConfig {
12 | applicationId("com.dzc.chunk")
13 | minSdkVersion(21)
14 | }
15 | compileOptions {
16 | sourceCompatibility(JavaVersion.VERSION_1_8)
17 | targetCompatibility(JavaVersion.VERSION_1_8)
18 | }
19 | kotlinOptions {
20 | jvmTarget = ("1.8")
21 | }
22 | buildFeatures {
23 | viewBinding = true
24 | }
25 | }
26 | dependencies {
27 |
28 | implementation ("org.jetbrains.kotlin:kotlin-stdlib:1.3.72")
29 | implementation ("androidx.core:core-ktx:1.3.2")
30 | implementation ("androidx.appcompat:appcompat:1.2.0")
31 | implementation ("com.google.android.material:material:1.3.0")
32 | implementation ("androidx.constraintlayout:constraintlayout:2.0.4")
33 |
34 | }
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/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---1-11.
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
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/grock/tc/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.grock.tc
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.grock.tc", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/debug/java/com/grock/Utils.kt:
--------------------------------------------------------------------------------
1 | package com.grock
2 |
3 | /**
4 | * create by zhusw on 6/7/21 16:12
5 | */
6 | //object Utils {//类声明重复
7 | // fun geText(s:String):String{
8 | // return "debug $s"
9 | // }
10 | //}
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/grock/TabLayoutExt.kt:
--------------------------------------------------------------------------------
1 | package com.grock
2 |
3 | import android.animation.ValueAnimator
4 | import android.content.Context
5 | import android.graphics.Typeface
6 | import android.view.Gravity
7 | import android.view.View
8 | import android.widget.FrameLayout
9 | import android.widget.TextView
10 | import androidx.appcompat.widget.AppCompatTextView
11 | import androidx.core.animation.doOnEnd
12 | import androidx.core.animation.doOnStart
13 | import androidx.core.view.doOnDetach
14 | import androidx.viewpager2.widget.ViewPager2
15 | import com.google.android.material.tabs.TabLayout
16 | import com.google.android.material.tabs.TabLayoutMediator
17 | import com.grock.tc.R
18 |
19 | interface CustomTabViewConfig {
20 | fun getCustomView(context: Context): T
21 | fun onTabInit(tabView: T, position: Int)
22 | }
23 |
24 | class FixedWidthTextTabView(context: Context) : FrameLayout(context) {
25 | /**
26 | * 用于测量时能获得最大尺寸,
27 | */
28 | val boundSizeTextView: TextView = TextView(context).apply {
29 | visibility = View.INVISIBLE
30 | }
31 | val dynamicSizeTextView: ScaleTexViewTabView = ScaleTexViewTabView(context).apply {
32 | gravity = Gravity.CENTER
33 | }
34 | init {
35 | addView(boundSizeTextView)
36 | addView(dynamicSizeTextView)
37 | }
38 | }
39 |
40 | abstract class FixedWidthTextTabViewConfig(val scale: TextScaleConfig) :
41 | CustomTabViewConfig {
42 | abstract fun getText(position: Int): String
43 | abstract fun onVisibleTextViewInit(tv: TextView)
44 | final override fun onTabInit(tabView: FixedWidthTextTabView, position: Int) {
45 | val text = getText(position)
46 | tabView.boundSizeTextView.text = text
47 | tabView.boundSizeTextView.textSizePx = scale.onSelectTextSize
48 | if(scale.switchBold){
49 | tabView.boundSizeTextView.typeface = Typeface.DEFAULT_BOLD
50 | }
51 | tabView.dynamicSizeTextView.text = text
52 | tabView.dynamicSizeTextView.textSizePx = scale.onUnSelectTextSize
53 | onVisibleTextViewInit(tabView.dynamicSizeTextView)
54 | }
55 |
56 | final override fun getCustomView(context: Context): FixedWidthTextTabView {
57 | return FixedWidthTextTabView(context)
58 | }
59 | }
60 |
61 | data class TextScaleConfig(
62 | val onSelectTextSize: Int,
63 | val onUnSelectTextSize: Int,
64 | val switchBold:Boolean = true
65 | )
66 |
67 | private val defaultScaleConfig = TextScaleConfig(18.dp, 16.dp)
68 |
69 | abstract class TextScaleTabViewConfig(scale: TextScaleConfig = defaultScaleConfig) :
70 | FixedWidthTextTabViewConfig(
71 | scale
72 | )
73 |
74 | private fun TabLayout.addScaleAnim(config: TextScaleConfig = defaultScaleConfig) {
75 | val key = R.id.tag_tabLayout_scale_ext
76 | val lastListener = getTag(key)
77 | if (lastListener != null) return
78 | val duration = 50L
79 | val animCacheMap = HashMap()
80 | fun cancelLastAnim(tv: TextView) {
81 | animCacheMap[tv]?.cancel()
82 | }
83 | doOnDetach {
84 | animCacheMap.forEach {
85 | it.value.cancel()
86 | }
87 | }
88 | val listener = object : TabLayout.OnTabSelectedListener {
89 | private fun scaleTextSize(stv: ScaleTexViewTabView, size: Int, isBold: Boolean) {
90 | cancelLastAnim(stv)
91 | val animator =
92 | ValueAnimator.ofInt(stv.textSize.toInt(), size)
93 | animator.duration = duration
94 | animator.addUpdateListener { anim ->
95 | val textSize = anim.animatedValue as? Int
96 | textSize?.let { ts ->
97 | stv.textSizePx = size
98 | }
99 | }
100 | animator.doOnStart {
101 | stv.skipRequestLayout = true
102 | }
103 | animator.doOnEnd {
104 | /*最后做一下修正*/
105 | stv.textSizePx = size
106 | if(config.switchBold){
107 | stv.boldText = isBold
108 | }
109 | stv.skipRequestLayout = false
110 | animCacheMap.remove(stv)
111 | }
112 | animCacheMap[stv] = animator
113 | animator.start()
114 | }
115 |
116 | override fun onTabSelected(tab: TabLayout.Tab) {
117 | val cusV = tab.customView
118 | if (cusV is FixedWidthTextTabView) {
119 | val onSelectTextSize = config.onSelectTextSize
120 | val onUnSelectTextSize = config.onUnSelectTextSize
121 | if (onSelectTextSize != onUnSelectTextSize) {
122 | scaleTextSize(cusV.dynamicSizeTextView, onSelectTextSize, true)
123 | }
124 | }
125 | }
126 |
127 | override fun onTabUnselected(tab: TabLayout.Tab) {
128 | val cusV = tab.customView
129 | if (cusV is FixedWidthTextTabView) {
130 | val onSelectTextSize = config.onSelectTextSize
131 | val onUnSelectTextSize = config.onUnSelectTextSize
132 | if (onSelectTextSize != onUnSelectTextSize) {
133 | scaleTextSize(cusV.dynamicSizeTextView, onUnSelectTextSize, false)
134 | }
135 | }
136 | }
137 |
138 | override fun onTabReselected(tab: TabLayout.Tab) {
139 | }
140 | }
141 | addOnTabSelectedListener(listener)
142 | setTag(key, listener)
143 |
144 | }
145 |
146 | class ScaleTexViewTabView(context: Context) : AppCompatTextView(context) {
147 | var skipRequestLayout = false
148 | override fun requestLayout() {
149 | if (!skipRequestLayout) {
150 | super.requestLayout()
151 | }
152 | }
153 | }
154 |
155 | fun TabLayout.createMediator(
156 | vp: ViewPager2,
157 | onInit: (tab: TabLayout.Tab, position: Int) -> Unit
158 | ): TabLayoutMediator {
159 | return TabLayoutMediator(this, vp) { tab, pos ->
160 | onInit(tab, pos)
161 | }
162 | }
163 |
164 | fun TabLayout.createMediatorByCustomTabView(
165 | vp: ViewPager2,
166 | config: CustomTabViewConfig
167 | ): TabLayoutMediator {
168 | return createMediator(vp){tab,pos->
169 | val tabView = config.getCustomView(tab.view.context)
170 | tab.customView = tabView
171 | config.onTabInit(tabView, pos)
172 | }
173 | }
174 |
175 |
176 | fun TabLayout.createTextScaleMediatorByTextView(
177 | vp: ViewPager2,
178 | config: TextScaleTabViewConfig
179 | ): TabLayoutMediator {
180 | addScaleAnim(config.scale)
181 | return createMediatorByCustomTabView(vp, config)
182 | }
183 |
184 | fun TabLayout.addScaleTabByTextView(
185 | textList: List,
186 | textColor: Int,
187 | scale: TextScaleConfig = defaultScaleConfig
188 | ) {
189 | removeAllTabs()
190 | addScaleAnim(scale)
191 | textList.forEach { text ->
192 | val tab = newTab().apply {
193 | val tabView = FixedWidthTextTabView(view.context)
194 | customView = tabView
195 | tabView.boundSizeTextView.text = text
196 | tabView.boundSizeTextView.textSizePx = scale.onSelectTextSize
197 | if(scale.switchBold){
198 | //避免设置不必要的字体样式
199 | tabView.boundSizeTextView.boldText = true
200 | }
201 | tabView.dynamicSizeTextView.text = text
202 | tabView.dynamicSizeTextView.textSizePx = scale.onUnSelectTextSize
203 | tabView.dynamicSizeTextView.setTextColor(textColor)
204 | }
205 | addTab(tab)
206 | }
207 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/grock/ToolsExt.kt:
--------------------------------------------------------------------------------
1 | package com.grock
2 |
3 | import android.content.res.Resources
4 | import android.graphics.Typeface
5 | import android.util.TypedValue
6 | import android.widget.TextView
7 |
8 | val Float.dp get():Int = (this * Resources.getSystem().displayMetrics.density).toInt()
9 | val Float.dpF get():Float = this * Resources.getSystem().displayMetrics.density
10 |
11 | val Int.dpF get() = this.dp.toFloat()
12 |
13 | val Int.dp get():Int = (this * Resources.getSystem().displayMetrics.density).toInt()
14 |
15 | var TextView.textSizePx: Int
16 | set(value) {textSizePx
17 | setTextSize(TypedValue.COMPLEX_UNIT_PX, value.toFloat())
18 | }
19 | get() = textSize.toInt()
20 |
21 | var TextView.boldText: Boolean
22 | set(value) {
23 | if (value) {
24 | setTypeface(Typeface.create(typeface, Typeface.BOLD), Typeface.BOLD)
25 | } else {
26 | setTypeface(Typeface.create(typeface, Typeface.NORMAL), Typeface.NORMAL)
27 | }
28 | invalidate()
29 | }
30 | get() = typeface?.isBold ?: false
--------------------------------------------------------------------------------
/app/src/main/java/com/grock/ViewPager2Activity.kt:
--------------------------------------------------------------------------------
1 | package com.grock
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.fragment.app.Fragment
8 | import androidx.viewpager2.adapter.FragmentStateAdapter
9 | import androidx.viewpager2.widget.ViewPager2
10 | import com.google.android.material.tabs.TabLayout
11 | import com.google.android.material.tabs.TabLayoutMediator
12 | import com.grock.tc.R
13 | import com.grock.tc.databinding.ActViewpager2Binding
14 | import com.grock.tc.databinding.FragSimpleBinding
15 | import com.grock.tc.databinding.TabItemBinding
16 |
17 |
18 | /**
19 | *
20 | *create by zhusw on 5/8/21 10:33
21 | */
22 | class ViewPager2Activity : AppCompatActivity() {
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | val vb = ActViewpager2Binding.inflate(layoutInflater)
26 | setContentView(vb.root)
27 |
28 | val tabs = arrayOf("A00001","B00001","C00001","D00001","E00001","F00001","G00001")
29 | val vp = vb.viewPager2
30 | val tl = vb.tl
31 | val list:List = arrayListOf(VFragment.newInstance("1"),
32 | VFragment.newInstance("2"),
33 | VFragment.newInstance("3"),
34 | VFragment.newInstance("4"),
35 | VFragment.newInstance("5"),
36 | VFragment.newInstance("6"),
37 | VFragment.newInstance("7"),
38 | )
39 | vp.offscreenPageLimit = ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT
40 | vp.adapter = FragmentAdapter(this,list)
41 | val mediator = TabLayoutMediator(tl,vp
42 | ) { tab, position ->
43 | val view = TabItemBinding.inflate(LayoutInflater.from(tab.view.context))
44 | tab.customView = view.root
45 | view.tv.text = tabs[position]
46 | }
47 | mediator.attach()
48 | vb.tl.addOnTabSelectedListener(object :TabLayout.OnTabSelectedListener{
49 | override fun onTabSelected(tab: TabLayout.Tab) {
50 |
51 | }
52 |
53 | override fun onTabUnselected(tab: TabLayout.Tab?) {
54 |
55 | }
56 |
57 | override fun onTabReselected(tab: TabLayout.Tab?) {
58 |
59 | }
60 |
61 | })
62 |
63 | }
64 | }
65 |
66 |
67 | class FragmentAdapter(activity: AppCompatActivity,private val list:List):FragmentStateAdapter(activity){
68 | override fun getItemCount(): Int = list.size
69 |
70 | override fun createFragment(position: Int): Fragment= list[position]
71 |
72 | }
73 |
74 | class VFragment : Fragment(R.layout.frag_simple) {
75 | companion object {
76 | fun newInstance(str: String): VFragment {
77 | val f = VFragment()
78 | val bundle = Bundle()
79 | bundle.putString("str", str)
80 | f.arguments = bundle
81 | return f
82 | }
83 | }
84 |
85 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
86 | super.onViewCreated(view, savedInstanceState)
87 | val vb = FragSimpleBinding.bind(view)
88 | arguments?.let {
89 | vb.tv.text = it.getString("str")
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/grock/ViewPager2Activity2.kt:
--------------------------------------------------------------------------------
1 | package com.grock
2 |
3 | import android.graphics.Color
4 | import android.os.Bundle
5 | import android.widget.TextView
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.fragment.app.Fragment
8 | import androidx.viewpager2.widget.ViewPager2
9 | import com.grock.tc.databinding.ActViewpager2Binding
10 |
11 | /**
12 | *
13 | *create by zhusw on 5/8/21 10:33
14 | */
15 | class ViewPager2Activity2 : AppCompatActivity() {
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | val vb = ActViewpager2Binding.inflate(layoutInflater)
19 | setContentView(vb.root)
20 |
21 | val tabs = arrayOf("A1", "B22", "C333", "D4444", "E55555", "F666666", "F7777777")
22 | val vp = vb.viewPager2
23 |
24 | val list: List = arrayListOf(
25 | VFragment.newInstance("1"),
26 | VFragment.newInstance("2"),
27 | VFragment.newInstance("3"),
28 | VFragment.newInstance("4"),
29 | VFragment.newInstance("5"),
30 | VFragment.newInstance("6"),
31 | VFragment.newInstance("7"),
32 | )
33 | vp.offscreenPageLimit = ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT
34 | vp.adapter = FragmentAdapter(this, list)
35 |
36 | val scaleConfig1 = TextScaleConfig(
37 | onSelectTextSize = 18.dp,
38 | onUnSelectTextSize = 12.dp,
39 | switchBold = false
40 | )
41 |
42 | vb.tlNormal.createMediator(vb.viewPager2) { tab, position ->
43 | tab.text = tabs[position]
44 | }.attach()
45 |
46 | val mediator = vb.tl.createTextScaleMediatorByTextView(vb.viewPager2,
47 | object : TextScaleTabViewConfig(scaleConfig1) {
48 | override fun getText(position: Int): String {
49 | return tabs[position]
50 | }
51 |
52 | override fun onVisibleTextViewInit(tv: TextView) {
53 | tv.setTextColor(Color.WHITE)
54 | }
55 | })
56 | mediator.attach()
57 | val scaleConfig2 = TextScaleConfig(
58 | onSelectTextSize = 18.dp,
59 | onUnSelectTextSize = 12.dp,
60 | switchBold = true
61 | )
62 |
63 | val mediator2 = vb.tl2.createTextScaleMediatorByTextView(vb.viewPager2,
64 | object : TextScaleTabViewConfig(scaleConfig2) {
65 | override fun getText(position: Int): String {
66 | return tabs[position]
67 | }
68 |
69 | override fun onVisibleTextViewInit(tv: TextView) {
70 | tv.setTextColor(Color.WHITE)
71 | }
72 | })
73 | mediator2.attach()
74 |
75 | val scaleConfig3 = TextScaleConfig(
76 | onSelectTextSize = 18.dp,
77 | onUnSelectTextSize = 12.dp,
78 | switchBold = true
79 | )
80 | vb.tl3.addScaleTabByTextView(tabs.toList(),Color.YELLOW,scaleConfig3)
81 | }
82 | }
83 |
84 |
85 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cat_paw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juziml/LovelyTabLayoutExt/f5c4151c8bd0b6b397814afe266de8a85919b771/app/src/main/res/drawable/ic_cat_paw.png
--------------------------------------------------------------------------------
/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/drawable/ll_tab_indicator_block.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ll_tab_indicator_cat_paw.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/act_viewpager2.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
16 |
24 |
32 |
41 |
49 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
24 |
25 |
37 |
49 |
61 |
73 |
86 |
98 |
99 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/frag_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/level1_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
15 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/levelx_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/tab_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/tab_item_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juziml/LovelyTabLayoutExt/f5c4151c8bd0b6b397814afe266de8a85919b771/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #00000000
11 | #3A51C2
12 | #666666
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | bbbb2
3 | 文本文本文本文本文本文本文本文本文本文本文本文本文本文本文本
4 | release 文本
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
27 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
--------------------------------------------------------------------------------
/app/src/test/java/com/grock/tc/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.grock.tc
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/test.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juziml/LovelyTabLayoutExt/f5c4151c8bd0b6b397814afe266de8a85919b771/app/test.jks
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.4.30"
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath "com.android.tools.build:gradle:4.1.2"
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 |
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 | jcenter()
21 | }
22 | }
23 |
24 | task clean(type: Delete) {
25 | delete rootProject.buildDir
26 | }
--------------------------------------------------------------------------------
/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=-Xmx2048m -Dfile.encoding=UTF-8
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
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/juziml/LovelyTabLayoutExt/f5c4151c8bd0b6b397814afe266de8a85919b771/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Sep 19 08:46:24 CST 2021
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-6.5-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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "LovelyTabLayoutExt"
--------------------------------------------------------------------------------