├── .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 | 22 | 23 | 135 | 136 | 138 | 139 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c563e436c4344814b6b0378967696edf~tplv-k3u1fbpfcp-watermark.image?) 15 | 16 | 实现上图中的几个常用效果`TabLayout` 仅需在xml重配置即可 17 | 18 | 19 | ![tablayout.gif](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e237249806744b98d0acb2f459aed1d~tplv-k3u1fbpfcp-watermark.image?) 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 |