├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crimson
│ │ └── tab
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── crimson
│ │ │ └── tab
│ │ │ ├── ContentFragment.kt
│ │ │ └── MainActivity.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── fragment_content.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
│ └── test
│ └── java
│ └── com
│ └── crimson
│ └── tab
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library_tab
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── crimson
│ │ └── library
│ │ └── tab
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── crimson
│ │ │ └── library
│ │ │ └── tab
│ │ │ ├── AdvancedTabLayout.kt
│ │ │ ├── attrs
│ │ │ └── TabLayoutAttrs.kt
│ │ │ └── widget
│ │ │ ├── MsgView.kt
│ │ │ └── MsgViewExt.kt
│ └── res
│ │ ├── layout
│ │ └── layout_tab.xml
│ │ └── values
│ │ └── values.xml
│ └── test
│ └── java
│ └── com
│ └── crimson
│ └── library
│ └── tab
│ └── ExampleUnitTest.kt
├── settings.gradle
└── snapshot
└── snapshot.gif
/.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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AdvancedTabLayout
2 |
3 | ## 介绍
4 |
5 | TabLayout 控件,支持AndroidX,支持绑定ViewPager2,可设置指示器、下划线、tab分割线,tab字体大小,使用方便。
6 |
7 |
8 |
9 |
10 |
11 | 
12 |
13 |
14 |
15 |
16 |
17 | ## 引入
18 |
19 |
20 | ```
21 | dependencies {
22 |
23 | implementation 'com.github.crimson0829:AdvancedTabLayout:1.6'
24 | }
25 |
26 | ```
27 |
28 |
29 |
30 | ## 使用
31 |
32 | ```
33 |
50 | ```
51 |
52 |
53 | ```
54 |
55 | // 设置绑定ViewPager2
56 | tab_layout.setViewPager2(view_pager2, fragmentActivty, fragments, titles)
57 |
58 | //设置ViewPager2滑动监听
59 | tab_layout.setViewPage2ScrollListener({
60 | //select
61 | }, { position, positionOffset, positionOffsetPixels ->
62 | //scroll
63 | }, {
64 | //state
65 |
66 | })
67 |
68 | // 只设置TabLayout标题
69 | tab_layout.setTabData(listOf(content, content, content))
70 |
71 | //设置TabLayout点击监听
72 | tab_layout.setOnTabSelectListener({
73 | //tabSelect
74 | }, {
75 | //tabReselect
76 | })
77 |
78 |
79 | //设置viewpager2 条目缓存数量
80 | tab_layout.setViewPager2ItemCacheSize(fragment.size())
81 |
82 |
83 | //设置tab属性
84 | // tab_layout.setTabAttrs(TabAttrs().apply {
85 | // textSelectColor=ContextCompat.getColor(baseContext,R.color.colorPrimary)
86 | // // tab点击是否马上切换,如果设置成true,那么smoothscroll将失效
87 | // snap_tab_click=true
88 | // ...
89 | // })
90 |
91 |
92 |
93 | ```
94 |
95 |
96 | ## 属性
97 |
98 |
99 | | 属性 | 定义 |
100 | |:---------------------|:-----------------------|
101 | | tl_indicator_color | 指示器颜色 |
102 | | tl_indicator_height | 指示器高度 |
103 | | tl_indicator_width | 指示器宽度 |
104 | | tl_indicator_margin_left | 指示器左margin |
105 | | tl_indicator_margin_top | 指示器上margin |
106 | | tl_indicator_margin_right | 指示器右margin |
107 | | tl_indicator_margin_bottom | 指示器下margin |
108 | | tl_indicator_corner_radius | 指示器shape |
109 | | tl_indicator_gravity | 指示器gravity |
110 | | tl_indicator_style | 指示器style -> 0:普通 1:三角形 2:块状 |
111 | | tl_indicator_width_equal_title | 指示器是否与标题相等 |
112 | | tl_underline_color | 下划线颜色 |
113 | | tl_underline_height | 下划线高度 |
114 | | tl_underline_gravity | 下划线gravity |
115 | | tl_divider_color | 分割线颜色 |
116 | | tl_divider_width | 分割线宽度 |
117 | | tl_divider_padding | 分割线padding |
118 | | tl_tab_padding | tab padding |
119 | | tl_tab_space_equal | tab是否相等 |
120 | | tl_tab_width | tab宽度 |
121 | | tl_smoothScroll_enable | tab点击是否平滑滑动 |
122 | | tl_tab_snapOnClick | tab点击是否与viewpager2马上切换 |
123 | | tl_textsize | tab字体大小 |
124 | | tl_textSelectSize | tab选中字体大小 |
125 | | tl_textSelectColor | tab选中字体颜色 |
126 | | tl_textUnselectColor | tab未选中字体颜色 |
127 | | tl_textBold | tab字体加粗 -> 0:不加粗 1:选中加粗 2:都加粗 |
128 | | tl_textAllCaps | tab字体是否大写 |
129 |
130 |
131 | 代码设置
132 |
133 | ```
134 |
135 | tab_layout
136 | //设置tab属性
137 | .setTabAttrs(TabAttrs())
138 | //设置指示器属性
139 | .setIndicatorAttrs(IndicatorAttrs())
140 | //设置下划线属性
141 | .setUnderlineAttrs(UnderlineAttrs())
142 | //设置分割线属性
143 | .setDividerLineAttrs(DividerAttrs())
144 |
145 |
146 | ```
147 |
148 |
149 | ## 方法
150 |
151 | ```
152 | //选中当前tab
153 | tab_layout.setCurrentTab(position)
154 |
155 | //根据索引获取title view
156 | tab_layout.getTitleView(position)
157 |
158 | /**
159 | * 显示未读消息
160 | *
161 | * @param position 显示tab位置
162 | * @param num num小于等于0显示红点,num大于0显示数字
163 | */
164 | tab_layout.showMsg(position, num)
165 |
166 | //显示未读红点
167 | tab_layout.showDot(position)
168 |
169 | //隐藏未读消息
170 | tab_layout.hideMsg(position)
171 |
172 | //设置未读消息偏移,原点为文字的右上角.当控件高度固定,消息提示位置易控制,显示效果佳
173 | tab_layout.setMsgMargin(position, leftPadding, bottomPadding)
174 |
175 | //当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置
176 | tab_layout.getMsgView(position)
177 |
178 |
179 | ```
180 |
181 | ## 感谢
182 |
183 |
184 | [FlycoTabLayout](https://github.com/H07000223/FlycoTabLayout)
185 |
186 |
187 |
188 | ## License
189 |
190 | ```
191 | Copyright 2020 crimson0829
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 | ```
204 |
205 |
206 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 29
7 | buildToolsVersion "29.0.2"
8 |
9 | defaultConfig {
10 | applicationId "com.crimson.tab"
11 | minSdkVersion 16
12 | targetSdkVersion 29
13 | versionCode 5
14 | versionName "1.5"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 |
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 |
32 | }
33 |
34 | dependencies {
35 | implementation fileTree(dir: 'libs', include: ['*.jar'])
36 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
37 | implementation 'androidx.appcompat:appcompat:1.1.0'
38 | implementation 'androidx.core:core-ktx:1.2.0'
39 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
40 | implementation project(":library_tab")
41 | testImplementation 'junit:junit:4.12'
42 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
43 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
44 | }
45 |
--------------------------------------------------------------------------------
/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/androidTest/java/com/crimson/tab/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.tab
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.crimson.tab", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/crimson/tab/ContentFragment.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.tab
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import android.view.View
6 | import androidx.fragment.app.Fragment
7 | import kotlinx.android.synthetic.main.fragment_content.*
8 |
9 | /**
10 | * @author crimson
11 | * @date 2020/3/21
12 | */
13 | class ContentFragment(val content: String = ContentFragment::class.java.simpleName) :
14 | Fragment(R.layout.fragment_content) {
15 |
16 | val TAG = ContentFragment::class.java.simpleName
17 |
18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
19 | super.onViewCreated(view, savedInstanceState)
20 |
21 |
22 | tab_layout.setTabData(listOf(content, content, content,content,content,content))
23 | tab_layout.showDot(0)
24 | tab_layout.showMsg(1, 10)
25 | tab_layout.setMsgMargin(1, 30f, 5f)
26 | tab_layout.showMsg(2, 100)
27 | tab_layout.setMsgMargin(2, 30f, 10f)
28 |
29 | tab_layout.setOnTabSelectListener({
30 |
31 | Log.w(TAG, "tabSelect -> $it")
32 |
33 | tab_layout.hideMsg(it)
34 | tv_content.text = "select -> $it"
35 |
36 | }, {
37 |
38 | Log.w(TAG, "tabReselect -> $it")
39 |
40 | tv_content.text = "Reselect -> $it"
41 |
42 | })
43 | }
44 | }
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/crimson/tab/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.tab
2 |
3 | import android.content.res.Resources
4 | import androidx.appcompat.app.AppCompatActivity
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.widget.Toast
8 | import androidx.core.content.ContextCompat
9 | import androidx.fragment.app.Fragment
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.crimson.library.tab.attrs.DividerAttrs
12 | import com.crimson.library.tab.attrs.IndicatorAttrs
13 | import com.crimson.library.tab.attrs.TabAttrs
14 | import com.crimson.library.tab.attrs.UnderlineAttrs
15 | import kotlinx.android.synthetic.main.activity_main.*
16 |
17 | class MainActivity : AppCompatActivity() {
18 |
19 | val TAG = MainActivity::class.java.simpleName
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_main)
24 |
25 | initView()
26 | }
27 |
28 | private fun initView() {
29 |
30 | val fragments = arrayListOf()
31 |
32 | val titles = listOf(
33 | "content1",
34 | "content2",
35 | "content3",
36 | "content4",
37 | "content5",
38 | "content6",
39 | "content7"
40 | ).apply {
41 | forEach {
42 | fragments.add(ContentFragment(it))
43 | }
44 | }
45 |
46 | tab_layout.setViewPager2(view_pager2, this, fragments, titles)
47 |
48 | tab_layout.setViewPage2ScrollListener({
49 | //select
50 | Log.w(TAG, "select -> $it")
51 |
52 | }, { position, positionOffset, positionOffsetPixels ->
53 | //scroll
54 | Log.w(
55 | TAG,
56 | "sroll : position-> $position positionOffset -> $positionOffset positionOffsetPixels->$positionOffsetPixels"
57 | )
58 |
59 | }, {
60 | //state
61 | Log.w(TAG, "state -> $it")
62 |
63 | })
64 |
65 | //设置viewpager2 条目缓存数量
66 | tab_layout.setViewPager2ItemCacheSize(fragments.size)
67 |
68 | // tab_layout.setTabAttrs(TabAttrs().apply {
69 | // textSelectColor=ContextCompat.getColor(baseContext,R.color.colorPrimary)
70 | // // tab点击是否马上切换,如果设置true,那么smoothscroll将失效
71 | // snap_tab_click=true
72 | // })
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | }
81 |
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/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_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
22 |
23 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/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/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | tablayout
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/crimson/tab/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.tab
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 | }
18 |
--------------------------------------------------------------------------------
/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.3.72'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:4.0.1'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 |
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/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/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 22 13:41:48 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-6.1.1-all.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 |
--------------------------------------------------------------------------------
/library_tab/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library_tab/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 29
7 | buildToolsVersion "29.0.2"
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 29
12 | versionCode 1
13 | versionName "1.0"
14 |
15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16 | consumerProguardFiles 'consumer-rules.pro'
17 | }
18 |
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 |
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(dir: 'libs', include: ['*.jar'])
35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
36 | implementation 'androidx.appcompat:appcompat:1.1.0'
37 | implementation 'androidx.core:core-ktx:1.2.0'
38 | api 'androidx.viewpager2:viewpager2:1.0.0'
39 | testImplementation 'junit:junit:4.12'
40 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
42 | }
43 |
--------------------------------------------------------------------------------
/library_tab/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/library_tab/consumer-rules.pro
--------------------------------------------------------------------------------
/library_tab/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 |
--------------------------------------------------------------------------------
/library_tab/src/androidTest/java/com/crimson/library/tab/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.library.tab
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.crimson.library.tab.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/library_tab/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/library_tab/src/main/java/com/crimson/library/tab/AdvancedTabLayout.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.library.tab
2 |
3 | import android.content.Context
4 | import android.graphics.*
5 | import android.graphics.drawable.GradientDrawable
6 | import android.os.Bundle
7 | import android.os.Parcelable
8 | import android.util.AttributeSet
9 | import android.util.SparseBooleanArray
10 | import android.util.TypedValue
11 | import android.view.Gravity
12 | import android.view.View
13 | import android.view.ViewGroup
14 | import android.widget.HorizontalScrollView
15 | import android.widget.LinearLayout
16 | import androidx.appcompat.widget.AppCompatTextView
17 | import androidx.fragment.app.Fragment
18 | import androidx.fragment.app.FragmentActivity
19 | import androidx.recyclerview.widget.RecyclerView
20 | import androidx.viewpager2.adapter.FragmentStateAdapter
21 | import androidx.viewpager2.widget.ViewPager2
22 | import com.crimson.library.tab.attrs.DividerAttrs
23 | import com.crimson.library.tab.attrs.IndicatorAttrs
24 | import com.crimson.library.tab.attrs.TabAttrs
25 | import com.crimson.library.tab.attrs.UnderlineAttrs
26 | import com.crimson.library.tab.widget.MsgView
27 | import com.crimson.library.tab.widget.show
28 | import java.util.*
29 |
30 | /**
31 | * AdvancedTabLayout,可设置ViewPager2 :调用setViewPager2()
32 | * 如不想关联Viewpager2 调用:setTabData()
33 | */
34 | class AdvancedTabLayout @JvmOverloads constructor(
35 | context: Context,
36 | attrs: AttributeSet? = null,
37 | defStyleAttr: Int = 0
38 | ) : HorizontalScrollView(context, attrs, defStyleAttr) {
39 |
40 | companion object {
41 | private const val STYLE_NORMAL = 0
42 | private const val STYLE_TRIANGLE = 1
43 | private const val STYLE_BLOCK = 2
44 |
45 | private const val TEXT_BOLD_NONE = 0
46 | private const val TEXT_BOLD_WHEN_SELECT = 1
47 | private const val TEXT_BOLD_BOTH = 2
48 | }
49 |
50 | private var mVP2: ViewPager2? = null
51 | private var mViewPager2PageChangeCallback: ViewPager2PageChangeCallback? = null
52 | private var mTitles = arrayListOf()
53 | private val mTabsContainer: LinearLayout
54 | private var mCurrentTab = 0
55 | private var mCurrentPositionOffset = 0f
56 | private var tabCount = 0
57 |
58 | /**
59 | * 用于绘制显示器
60 | */
61 | private val mIndicatorRect = Rect()
62 |
63 | /**
64 | * 用于实现滚动居中
65 | */
66 | private val mTabRect = Rect()
67 | private val mIndicatorDrawable = GradientDrawable()
68 | private val mRectPaint = Paint(Paint.ANTI_ALIAS_FLAG)
69 | private val mDividerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
70 | private val mTrianglePaint = Paint(Paint.ANTI_ALIAS_FLAG)
71 | private val mTrianglePath = Path()
72 |
73 | /**
74 | * tab
75 | */
76 | private var mTabPadding = 0f
77 | private var mTabSpaceEqual = false
78 | private var mTabWidth = 0f
79 | private var mSmoothScrollEnable = true
80 |
81 | //如果该值为true,则mSmoothScrollEnable失效
82 | private var mSnapOnTabClick = false
83 |
84 |
85 | /**
86 | * indicator
87 | */
88 | private var mIndicatorColor = 0
89 | private var mIndicatorHeight = 0f
90 | private var mIndicatorWidth = 0f
91 | private var mIndicatorCornerRadius = 0f
92 | private var indicatorMarginLeft = 0f
93 | private var indicatorMarginTop = 0f
94 | private var indicatorMarginRight = 0f
95 | private var indicatorMarginBottom = 0f
96 | private var mIndicatorGravity = 0
97 | private var mIndicatorWidthEqualTitle = false
98 | private var mIndicatorStyle = STYLE_NORMAL
99 |
100 |
101 | /**
102 | * underline
103 | */
104 | private var mUnderlineColor = 0
105 | private var mUnderlineHeight = 0f
106 | private var mUnderlineGravity = 0
107 |
108 | /**
109 | * divider
110 | */
111 | private var mDividerColor = 0
112 | private var mDividerWidth = 0f
113 | private var mDividerPadding = 0f
114 |
115 | /**
116 | * tab text
117 | */
118 | private var mTextsize = 0f
119 | private var mSelectTextSize = 0f
120 | private var mTextSelectColor = 0
121 | private var mTextUnselectColor = 0
122 | private var mTextBold = 0
123 | private var mTextAllCaps = false
124 |
125 | private var mLastScrollX = 0
126 | private var mHeight = 0
127 |
128 | // show MsgView
129 | private val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
130 | private val mInitSetMap = SparseBooleanArray()
131 |
132 | //tab点击回调
133 | private var onTabSelect: (Int) -> Unit = {}
134 | private var onTabReselect: (Int) -> Unit = {}
135 |
136 | //viewpager2滑动回调
137 | private var mVP2OnPageSelected: (Int) -> Unit = {}
138 | private var mVP2OnPageScrolled: (Int, Float, Int) -> Unit = { _, _, _ -> }
139 | private var mVP2OnPageScrollStateChanged: (state: Int) -> Unit = { _ -> }
140 |
141 |
142 | init {
143 | //设置滚动视图是否可以伸缩其内容以填充视口
144 | isFillViewport = true
145 | //重写onDraw方法,需要调用这个方法来清除flag
146 | setWillNotDraw(false)
147 | clipChildren = false
148 | clipToPadding = false
149 | mTabsContainer = LinearLayout(context)
150 | addView(mTabsContainer)
151 | obtainAttributes(context, attrs)
152 |
153 | //get layout_height
154 | val height =
155 | attrs?.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height")
156 | when (height) {
157 | ViewGroup.LayoutParams.MATCH_PARENT.toString() + "" -> {
158 | }
159 | ViewGroup.LayoutParams.WRAP_CONTENT.toString() + "" -> {
160 | }
161 | else -> {
162 | val systemAttrs = intArrayOf(android.R.attr.layout_height)
163 | val a = context.obtainStyledAttributes(attrs, systemAttrs)
164 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT)
165 | a.recycle()
166 | }
167 | }
168 | }
169 |
170 | private fun obtainAttributes(
171 | context: Context,
172 | attrs: AttributeSet?
173 | ) {
174 | val ta = context.obtainStyledAttributes(attrs, R.styleable.AdvancedTabLayout)
175 | mIndicatorStyle = ta.getInt(
176 | R.styleable.AdvancedTabLayout_tl_indicator_style, STYLE_NORMAL
177 | )
178 | mIndicatorColor = ta.getColor(
179 | R.styleable.AdvancedTabLayout_tl_indicator_color,
180 | Color.parseColor(if (mIndicatorStyle == STYLE_BLOCK) "#4B6A87" else "#ffffff")
181 | )
182 | mIndicatorHeight = ta.getDimension(
183 | R.styleable.AdvancedTabLayout_tl_indicator_height,
184 | dp2px(if (mIndicatorStyle == STYLE_TRIANGLE) 4f else (if (mIndicatorStyle == STYLE_BLOCK) -1f else 2f)).toFloat()
185 | )
186 | mIndicatorWidth = ta.getDimension(
187 | R.styleable.AdvancedTabLayout_tl_indicator_width,
188 | dp2px(if (mIndicatorStyle == STYLE_TRIANGLE) 10f else (-1).toFloat()).toFloat()
189 | )
190 | mIndicatorCornerRadius = ta.getDimension(
191 | R.styleable.AdvancedTabLayout_tl_indicator_corner_radius,
192 | dp2px(if (mIndicatorStyle == STYLE_BLOCK) -1f else 0f).toFloat()
193 | )
194 | indicatorMarginLeft = ta.getDimension(
195 | R.styleable.AdvancedTabLayout_tl_indicator_margin_left, dp2px(0f).toFloat()
196 | )
197 | indicatorMarginTop = ta.getDimension(
198 | R.styleable.AdvancedTabLayout_tl_indicator_margin_top,
199 | dp2px(if (mIndicatorStyle == STYLE_BLOCK) 7f else 0f).toFloat()
200 | )
201 | indicatorMarginRight = ta.getDimension(
202 | R.styleable.AdvancedTabLayout_tl_indicator_margin_right, dp2px(0f).toFloat()
203 | )
204 | indicatorMarginBottom = ta.getDimension(
205 | R.styleable.AdvancedTabLayout_tl_indicator_margin_bottom,
206 | dp2px(if (mIndicatorStyle == STYLE_BLOCK) 7f else 0f).toFloat()
207 | )
208 | mIndicatorGravity = ta.getInt(
209 | R.styleable.AdvancedTabLayout_tl_indicator_gravity, Gravity.BOTTOM
210 | )
211 | mIndicatorWidthEqualTitle = ta.getBoolean(
212 | R.styleable.AdvancedTabLayout_tl_indicator_width_equal_title, false
213 | )
214 | mUnderlineColor = ta.getColor(
215 | R.styleable.AdvancedTabLayout_tl_underline_color, Color.parseColor("#ffffff")
216 | )
217 | mUnderlineHeight = ta.getDimension(
218 | R.styleable.AdvancedTabLayout_tl_underline_height, dp2px(0f).toFloat()
219 | )
220 | mUnderlineGravity = ta.getInt(
221 | R.styleable.AdvancedTabLayout_tl_underline_gravity, Gravity.BOTTOM
222 | )
223 | mDividerColor = ta.getColor(
224 | R.styleable.AdvancedTabLayout_tl_divider_color, Color.parseColor("#ffffff")
225 | )
226 | mDividerWidth = ta.getDimension(
227 | R.styleable.AdvancedTabLayout_tl_divider_width, dp2px(0f).toFloat()
228 | )
229 | mDividerPadding = ta.getDimension(
230 | R.styleable.AdvancedTabLayout_tl_divider_padding, dp2px(12f).toFloat()
231 | )
232 | mTextsize = ta.getDimension(
233 | R.styleable.AdvancedTabLayout_tl_textsize, sp2px(14f).toFloat()
234 | )
235 |
236 | mSelectTextSize = ta.getDimension(
237 | R.styleable.AdvancedTabLayout_tl_textSelectSize, sp2px(18f).toFloat()
238 | )
239 | mTextSelectColor = ta.getColor(
240 | R.styleable.AdvancedTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")
241 | )
242 | mTextUnselectColor = ta.getColor(
243 | R.styleable.AdvancedTabLayout_tl_textUnselectColor, Color.parseColor("#AAffffff")
244 | )
245 | mTextBold = ta.getInt(
246 | R.styleable.AdvancedTabLayout_tl_textBold, TEXT_BOLD_NONE
247 | )
248 | mTextAllCaps =
249 | ta.getBoolean(R.styleable.AdvancedTabLayout_tl_textAllCaps, false)
250 | mTabSpaceEqual = ta.getBoolean(
251 | R.styleable.AdvancedTabLayout_tl_tab_space_equal, false
252 | )
253 | mTabWidth = ta.getDimension(
254 | R.styleable.AdvancedTabLayout_tl_tab_width, dp2px(-1f).toFloat()
255 | )
256 | mTabPadding = ta.getDimension(
257 | R.styleable.AdvancedTabLayout_tl_tab_padding,
258 | if (mTabSpaceEqual || mTabWidth > 0) dp2px(0f).toFloat() else dp2px(20f).toFloat()
259 | )
260 | mSnapOnTabClick = ta.getBoolean(
261 | R.styleable.AdvancedTabLayout_tl_tab_snapOnClick, false
262 | )
263 | mSmoothScrollEnable = ta.getBoolean(
264 | R.styleable.AdvancedTabLayout_tl_smoothScroll_enable, true
265 | )
266 | ta.recycle()
267 | }
268 |
269 |
270 | /**
271 | * 设置ViewPager 如果不想绑定fragment 就无需设置 fa 和 fragments
272 | * @param vp2
273 | * @param fa
274 | * @param fragments
275 | * @param titles tab标题
276 | */
277 | fun setViewPager2(
278 | vp2: ViewPager2?,
279 | fa: FragmentActivity?,
280 | fragments: ArrayList?,
281 | titles: List?
282 | ) {
283 |
284 | vp2?.also {
285 | mVP2 = it
286 | if (fa != null && fragments != null) {
287 | mVP2?.adapter = ViewPager2FragmentAdapter(fa, fragments)
288 | }
289 | if (mViewPager2PageChangeCallback == null) {
290 | mViewPager2PageChangeCallback = ViewPager2PageChangeCallback()
291 | }
292 | mViewPager2PageChangeCallback?.let { callback ->
293 | mVP2?.unregisterOnPageChangeCallback(callback)
294 | mVP2?.registerOnPageChangeCallback(callback)
295 | }
296 |
297 | }
298 |
299 | titles?.let {
300 | mTitles.clear()
301 | mTitles.addAll(titles)
302 | }
303 |
304 | notifyData()
305 | }
306 |
307 | /**
308 | * 只设置tab data
309 | */
310 | fun setTabData(titles: List?) {
311 | titles?.let {
312 | mTitles.clear()
313 | mTitles.addAll(titles)
314 | }
315 | notifyData()
316 | }
317 |
318 | /**
319 | * 更新数据
320 | */
321 | fun notifyData() {
322 | mTabsContainer.removeAllViews()
323 | tabCount = mTitles.size
324 | var tabView: View
325 | for (i in 0 until tabCount) {
326 | tabView = View.inflate(context, R.layout.layout_tab, null)
327 | addTab(i, mTitles[i], tabView)
328 | }
329 | updateTabStyles()
330 | }
331 |
332 | fun addNewTab(title: String) {
333 | val tabView = View.inflate(context, R.layout.layout_tab, null)
334 | mTitles.add(title)
335 | addTab(tabCount, title, tabView)
336 | tabCount = mTitles.size
337 | updateTabStyles()
338 | }
339 |
340 | /**
341 | * 创建并添加tab
342 | */
343 | private fun addTab(
344 | position: Int, title: String?, tabView: View
345 | ) {
346 |
347 | val tvTabTitle =
348 | tabView.findViewById(R.id.tv_tab_title) as? AppCompatTextView
349 | tvTabTitle?.text = title ?: ""
350 |
351 | tabView.setOnClickListener { v ->
352 | val position = mTabsContainer.indexOfChild(v)
353 | if (position != -1) {
354 | if (mVP2 != null) {
355 | if (mVP2?.currentItem != position) {
356 | if (mSnapOnTabClick) {
357 | mVP2?.setCurrentItem(position, false)
358 | } else {
359 | mVP2?.currentItem = position
360 | }
361 |
362 | onTabSelect.invoke(position)
363 | } else {
364 | onTabReselect.invoke(position)
365 | }
366 | } else {
367 | if (mCurrentTab != position) {
368 | mCurrentTab = position
369 | scrollToCurrentTab()
370 | updateTabSelection(position)
371 | onTabSelect.invoke(position)
372 | } else {
373 | onTabReselect.invoke(position)
374 | }
375 | }
376 |
377 | }
378 | }
379 | /** 每一个Tab的布局参数 */
380 | var lpTab = if (mTabSpaceEqual) LinearLayout.LayoutParams(
381 | 0,
382 | LayoutParams.MATCH_PARENT,
383 | 1.0f
384 | ) else LinearLayout.LayoutParams(
385 | LayoutParams.WRAP_CONTENT,
386 | LayoutParams.MATCH_PARENT
387 | )
388 | if (mTabWidth > 0) {
389 | lpTab = LinearLayout.LayoutParams(mTabWidth.toInt(), LayoutParams.MATCH_PARENT)
390 | }
391 | mTabsContainer.addView(tabView, position, lpTab)
392 | }
393 |
394 | private fun updateTabStyles() {
395 | for (i in 0 until tabCount) {
396 | val v = mTabsContainer.getChildAt(i)
397 | // v.setPadding((int) mTabPadding, v.getPaddingTop(), (int) mTabPadding, v.getPaddingBottom());
398 | val tvTabTitle =
399 | v?.findViewById(R.id.tv_tab_title) as? AppCompatTextView
400 |
401 | if (tvTabTitle != null) {
402 | tvTabTitle.setTextColor(if (i == mCurrentTab) mTextSelectColor else mTextUnselectColor)
403 | if (mTextBold == TEXT_BOLD_BOTH) {
404 | tvTabTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize)
405 | } else {
406 | tvTabTitle.setTextSize(
407 | TypedValue.COMPLEX_UNIT_PX,
408 | if (i == mCurrentTab) mSelectTextSize else mTextsize
409 | )
410 | }
411 | tvTabTitle.setPadding(mTabPadding.toInt(), 0, mTabPadding.toInt(), 0)
412 | if (mTextAllCaps) {
413 | tvTabTitle.text = tvTabTitle.text.toString().toUpperCase(Locale.getDefault())
414 | }
415 | if (mTextBold == TEXT_BOLD_BOTH) {
416 | tvTabTitle.paint.isFakeBoldText = true
417 | } else if (mTextBold == TEXT_BOLD_NONE) {
418 | tvTabTitle.paint.isFakeBoldText = false
419 | } else if (mTextBold == TEXT_BOLD_WHEN_SELECT) {
420 | tvTabTitle.paint.isFakeBoldText = i == mCurrentTab
421 |
422 | }
423 | }
424 | }
425 | }
426 |
427 |
428 | /**
429 | * HorizontalScrollView滚到当前tab,并且居中显示
430 | */
431 | fun scrollToCurrentTab() {
432 | if (tabCount <= 0) {
433 | return
434 | }
435 | mTabsContainer.let {
436 | val offset = (mCurrentPositionOffset * it.getChildAt(mCurrentTab).width).toInt()
437 |
438 | /**当前Tab的left+当前Tab的Width乘以positionOffset */
439 | var newScrollX = it.getChildAt(mCurrentTab).left + offset
440 | if (mCurrentTab > 0 || offset > 0) {
441 | /**HorizontalScrollView移动到当前tab,并居中 */
442 | newScrollX -= width / 2 - paddingLeft
443 | calcIndicatorRect()
444 | newScrollX += (mTabRect.right - mTabRect.left) / 2
445 | }
446 | if (newScrollX != mLastScrollX) {
447 | mLastScrollX = newScrollX
448 | /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量
449 | * x:表示离起始位置的x水平方向的偏移量
450 | * y:表示离起始位置的y垂直方向的偏移量
451 | */
452 |
453 | if (mSmoothScrollEnable) {
454 | smoothScrollTo(newScrollX, 0)
455 | } else {
456 | scrollTo(newScrollX, 0)
457 | }
458 | }
459 | }
460 |
461 | }
462 |
463 | private fun updateTabSelection(position: Int) {
464 | for (i in 0 until tabCount) {
465 | val tabView = mTabsContainer?.getChildAt(i)
466 | val isSelect = i == position
467 | val tabTitle =
468 | tabView?.findViewById(R.id.tv_tab_title) as? AppCompatTextView
469 | if (tabTitle != null) {
470 | tabTitle.setTextColor(if (isSelect) mTextSelectColor else mTextUnselectColor)
471 | if (mTextBold == TEXT_BOLD_BOTH) {
472 | tabTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize)
473 | } else {
474 | tabTitle.setTextSize(
475 | TypedValue.COMPLEX_UNIT_PX,
476 | if (isSelect) mSelectTextSize else mTextsize
477 | )
478 | }
479 | if (mTextBold == TEXT_BOLD_WHEN_SELECT) {
480 | tabTitle.paint.isFakeBoldText = isSelect
481 | }
482 | }
483 | }
484 | if (mViewPager2PageChangeCallback != null) {
485 | mViewPager2PageChangeCallback?.onPageScrolled(position, 0f, 0)
486 | }
487 | }
488 |
489 | private var margin = 0f
490 |
491 | private fun calcIndicatorRect() {
492 | val currentTabView = mTabsContainer.getChildAt(mCurrentTab)
493 | var left = currentTabView?.left?.toFloat() ?: 0f
494 | var right = currentTabView?.right?.toFloat() ?: 0f
495 |
496 | //for mIndicatorWidthEqualTitle
497 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) {
498 | val tabTitle =
499 | currentTabView?.findViewById(R.id.tv_tab_title) as? AppCompatTextView
500 | mTextPaint.textSize = mTextsize
501 | val textWidth = mTextPaint.measureText(tabTitle?.text.toString())
502 | margin = (right - left - textWidth) / 2
503 | }
504 | if (mCurrentTab < tabCount - 1) {
505 | val nextTabView = mTabsContainer.getChildAt(mCurrentTab + 1)
506 | val nextTabLeft = nextTabView?.left?.toFloat() ?: 0f
507 | val nextTabRight = nextTabView?.right?.toFloat() ?: 0f
508 | left += mCurrentPositionOffset * (nextTabLeft - left)
509 | right += mCurrentPositionOffset * (nextTabRight - right)
510 |
511 | //for mIndicatorWidthEqualTitle
512 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) {
513 | val nextTabTitle =
514 | nextTabView?.findViewById(R.id.tv_tab_title) as? AppCompatTextView
515 | mTextPaint.textSize = mTextsize
516 | val nextTextWidth =
517 | mTextPaint.measureText(nextTabTitle?.text.toString())
518 | val nextMargin = (nextTabRight - nextTabLeft - nextTextWidth) / 2
519 | margin += mCurrentPositionOffset * (nextMargin - margin)
520 | }
521 | }
522 | mIndicatorRect.left = left.toInt()
523 | mIndicatorRect.right = right.toInt()
524 | //for mIndicatorWidthEqualTitle
525 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) {
526 | mIndicatorRect.left = (left + margin - 1).toInt()
527 | mIndicatorRect.right = (right - margin - 1).toInt()
528 | }
529 | mTabRect.left = left.toInt()
530 | mTabRect.right = right.toInt()
531 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip
532 | } else { //indicatorWidth大于0时,圆角矩形以及三角形
533 | var indicatorLeft =
534 | currentTabView.left + (currentTabView.width - mIndicatorWidth) / 2
535 | if (mCurrentTab < tabCount - 1) {
536 | val nextTab = mTabsContainer.getChildAt(mCurrentTab + 1)
537 | indicatorLeft += mCurrentPositionOffset * (currentTabView.width / 2 + nextTab.width / 2)
538 | }
539 | mIndicatorRect.left = indicatorLeft.toInt()
540 | mIndicatorRect.right = (mIndicatorRect.left + mIndicatorWidth).toInt()
541 | }
542 | }
543 |
544 | override fun onDraw(canvas: Canvas) {
545 | super.onDraw(canvas)
546 | if (isInEditMode || tabCount <= 0) {
547 | return
548 | }
549 | val height = height
550 | val paddingLeft = paddingLeft
551 | // draw divider
552 | if (mDividerWidth > 0) {
553 | mDividerPaint.strokeWidth = mDividerWidth
554 | mDividerPaint.color = mDividerColor
555 | for (i in 0 until tabCount - 1) {
556 | val tab = mTabsContainer.getChildAt(i)
557 | canvas.drawLine(
558 | paddingLeft + tab.right.toFloat(),
559 | mDividerPadding,
560 | paddingLeft + tab.right.toFloat(),
561 | height - mDividerPadding,
562 | mDividerPaint
563 | )
564 | }
565 | }
566 |
567 | // draw underline
568 | if (mUnderlineHeight > 0) {
569 | mRectPaint.color = mUnderlineColor
570 | if (mUnderlineGravity == Gravity.BOTTOM) {
571 | canvas.drawRect(
572 | paddingLeft.toFloat(),
573 | height - mUnderlineHeight,
574 | mTabsContainer.width + paddingLeft.toFloat(),
575 | height.toFloat(),
576 | mRectPaint
577 | )
578 | } else {
579 | canvas.drawRect(
580 | paddingLeft.toFloat(),
581 | 0f,
582 | mTabsContainer.width + paddingLeft.toFloat(),
583 | mUnderlineHeight,
584 | mRectPaint
585 | )
586 | }
587 | }
588 |
589 | //draw indicator line
590 | calcIndicatorRect()
591 | if (mIndicatorStyle == STYLE_TRIANGLE) {
592 | if (mIndicatorHeight > 0) {
593 | mTrianglePaint.color = mIndicatorColor
594 | mTrianglePath.reset()
595 | mTrianglePath.moveTo(paddingLeft + mIndicatorRect.left.toFloat(), height.toFloat())
596 | mTrianglePath.lineTo(
597 | paddingLeft + mIndicatorRect.left / 2 + (mIndicatorRect.right / 2).toFloat(),
598 | height - mIndicatorHeight
599 | )
600 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.right.toFloat(), height.toFloat())
601 | mTrianglePath.close()
602 | canvas.drawPath(mTrianglePath, mTrianglePaint)
603 | }
604 | } else if (mIndicatorStyle == STYLE_BLOCK) {
605 | if (mIndicatorHeight < 0) {
606 | mIndicatorHeight = height - indicatorMarginTop - indicatorMarginBottom
607 | }
608 | if (mIndicatorHeight > 0) {
609 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) {
610 | mIndicatorCornerRadius = mIndicatorHeight / 2
611 | }
612 | mIndicatorDrawable.setColor(mIndicatorColor)
613 | mIndicatorDrawable.setBounds(
614 | paddingLeft + indicatorMarginLeft.toInt() + mIndicatorRect.left,
615 | indicatorMarginTop.toInt(),
616 | (paddingLeft + mIndicatorRect.right - indicatorMarginRight).toInt(),
617 | (indicatorMarginTop + mIndicatorHeight).toInt()
618 | )
619 | mIndicatorDrawable.cornerRadius = mIndicatorCornerRadius
620 | mIndicatorDrawable.draw(canvas)
621 | }
622 | } else {
623 | /* mRectPaint.setColor(mIndicatorColor);
624 | calcIndicatorRect();
625 | canvas.drawRect(getPaddingLeft() + mIndicatorRect.left, getHeight() - mIndicatorHeight,
626 | mIndicatorRect.right + getPaddingLeft(), getHeight(), mRectPaint);*/
627 | if (mIndicatorHeight > 0) {
628 | mIndicatorDrawable.setColor(mIndicatorColor)
629 | if (mIndicatorGravity == Gravity.BOTTOM) {
630 | mIndicatorDrawable.setBounds(
631 | paddingLeft + indicatorMarginLeft.toInt() + mIndicatorRect.left,
632 | height - mIndicatorHeight.toInt() - indicatorMarginBottom.toInt(),
633 | paddingLeft + mIndicatorRect.right - indicatorMarginRight.toInt(),
634 | height - indicatorMarginBottom.toInt()
635 | )
636 | } else {
637 | mIndicatorDrawable.setBounds(
638 | paddingLeft + indicatorMarginLeft.toInt() + mIndicatorRect.left,
639 | indicatorMarginTop.toInt(),
640 | paddingLeft + mIndicatorRect.right - indicatorMarginRight.toInt(),
641 | mIndicatorHeight.toInt() + indicatorMarginTop.toInt()
642 | )
643 | }
644 | mIndicatorDrawable.cornerRadius = mIndicatorCornerRadius
645 | mIndicatorDrawable.draw(canvas)
646 | }
647 | }
648 | }
649 |
650 | /**
651 | * 设置tab属性
652 | */
653 | fun setTabAttrs(attrs: TabAttrs = TabAttrs()): AdvancedTabLayout {
654 | mTabPadding = dp2px(attrs.padding.toFloat()).toFloat()
655 | mTabSpaceEqual = attrs.space_equal
656 | mSmoothScrollEnable = attrs.smooth_scroll_enable
657 | mTabWidth = dp2px(attrs.width.toFloat()).toFloat()
658 | mTextsize = sp2px(attrs.textsize.toFloat()).toFloat()
659 | mSelectTextSize = sp2px(attrs.textSelectSize.toFloat()).toFloat()
660 | mTextSelectColor = attrs.textSelectColor
661 | mTextUnselectColor = attrs.textUnselectColor
662 | mTextBold = attrs.textBold
663 | mTextAllCaps = attrs.textAllCaps
664 | mSnapOnTabClick = attrs.snap_tab_click
665 | invalidate()
666 | return this
667 | }
668 |
669 | /**
670 | * 设置指示器属性
671 | */
672 | fun setIndicatorAttrs(attrs: IndicatorAttrs = IndicatorAttrs()): AdvancedTabLayout {
673 | mIndicatorColor = attrs.color
674 | mIndicatorHeight = dp2px(attrs.height.toFloat()).toFloat()
675 | mIndicatorWidth = dp2px(attrs.width.toFloat()).toFloat()
676 | mIndicatorCornerRadius = dp2px(attrs.corner_radius.toFloat()).toFloat()
677 | indicatorMarginLeft = dp2px(attrs.margin_left.toFloat()).toFloat()
678 | indicatorMarginTop = dp2px(attrs.margin_top.toFloat()).toFloat()
679 | indicatorMarginRight = dp2px(attrs.margin_right.toFloat()).toFloat()
680 | indicatorMarginBottom = dp2px(attrs.margin_bottom.toFloat()).toFloat()
681 | mIndicatorGravity = attrs.gravity
682 | mIndicatorWidthEqualTitle = attrs.width_equal_title
683 | mIndicatorStyle = attrs.style
684 | invalidate()
685 | return this
686 |
687 | }
688 |
689 |
690 | /**
691 | * 设置下划线属性
692 | */
693 | fun setUnderlineAttrs(attrs: UnderlineAttrs = UnderlineAttrs()): AdvancedTabLayout {
694 | mUnderlineColor = attrs.color
695 | mUnderlineHeight = dp2px(attrs.height.toFloat()).toFloat()
696 | mUnderlineGravity = attrs.gravity
697 | invalidate()
698 | return this
699 | }
700 |
701 | /**
702 | * 设置分割线属性
703 | */
704 | fun setDividerLineAttrs(attrs: DividerAttrs = DividerAttrs()): AdvancedTabLayout {
705 | mDividerColor = attrs.color
706 | mDividerWidth = dp2px(attrs.width.toFloat()).toFloat()
707 | mDividerPadding = dp2px(attrs.padding.toFloat()).toFloat()
708 | invalidate()
709 | return this
710 | }
711 |
712 | /**
713 | * tab选中监听
714 | */
715 | fun setOnTabSelectListener(
716 | tabSelect: (Int) -> Unit = {},
717 | tabReselect: (Int) -> Unit = {}
718 | ): AdvancedTabLayout {
719 | onTabSelect = tabSelect
720 | onTabReselect = tabReselect
721 | return this
722 | }
723 |
724 | /**
725 | * viewPager2监听
726 | */
727 | fun setViewPage2ScrollListener(
728 | onPageSelected: (position: Int) -> Unit = { _ -> },
729 | onPageScrolled: (position: Int, positionOffset: Float, positionOffsetPixels: Int) -> Unit = { _, _, _ -> },
730 | onPageScrollStateChanged: (state: Int) -> Unit = { _ -> }
731 | ): AdvancedTabLayout {
732 | mVP2OnPageSelected = onPageSelected
733 | mVP2OnPageScrolled = onPageScrolled
734 | mVP2OnPageScrollStateChanged = onPageScrollStateChanged
735 | return this
736 | }
737 |
738 | /**
739 | * 选中当前tab
740 | */
741 | fun setCurrentTab(currentTab: Int, smoothScroll: Boolean = true) {
742 | mCurrentTab = currentTab
743 | mVP2?.setCurrentItem(currentTab, smoothScroll)
744 | updateTabSelection(currentTab)
745 | }
746 |
747 | /**
748 | * 设置viewpager2 条目缓存数量
749 | */
750 | fun setViewPager2ItemCacheSize(cacheSize:Int){
751 | mVP2?.apply {
752 | (getChildAt(0) as? RecyclerView)?.setItemViewCacheSize(cacheSize)
753 | }
754 | }
755 |
756 | /**
757 | * 根据索引获取tab view
758 | */
759 | fun getTabView(position: Int) = mTabsContainer.getChildAt(position)
760 |
761 | /**
762 | * 根据索引获取title view
763 | */
764 | fun getTitleView(tab: Int): AppCompatTextView? {
765 | val tabView = mTabsContainer.getChildAt(tab)
766 | return tabView.findViewById(R.id.tv_tab_title) as? AppCompatTextView
767 | }
768 |
769 |
770 | /**
771 | * 显示未读消息
772 | *
773 | * @param position 显示tab位置
774 | * @param num num小于等于0显示红点,num大于0显示数字
775 | */
776 | fun showMsg(position: Int, num: Int) {
777 | var position = position
778 | if (position >= tabCount) {
779 | position = tabCount - 1
780 | }
781 | val tabView = mTabsContainer.getChildAt(position)
782 | val tipView =
783 | tabView.findViewById(R.id.rtv_msg_tip) as? MsgView
784 | if (tipView != null) {
785 | tipView.show(num)
786 | if (mInitSetMap[position]) {
787 | return
788 | }
789 | setMsgMargin(position, 4f, 2f)
790 | mInitSetMap.put(position, true)
791 | }
792 | }
793 |
794 | /**
795 | * 显示未读红点
796 | *
797 | * @param position 显示tab位置
798 | */
799 | fun showDot(position: Int) {
800 | var position = position
801 | if (position >= tabCount) {
802 | position = tabCount - 1
803 | }
804 | showMsg(position, 0)
805 | }
806 |
807 | /**
808 | * 隐藏未读消息
809 | */
810 | fun hideMsg(position: Int) {
811 | var position = position
812 | if (position >= tabCount) {
813 | position = tabCount - 1
814 | }
815 | val tabView = mTabsContainer.getChildAt(position)
816 | val tipView =
817 | tabView.findViewById(R.id.rtv_msg_tip) as? MsgView
818 | tipView?.visibility = View.GONE
819 | }
820 |
821 | /**
822 | * 设置未读消息偏移,原点为文字的右上角.当控件高度固定,消息提示位置易控制,显示效果佳
823 | */
824 | fun setMsgMargin(
825 | position: Int,
826 | leftPadding: Float,
827 | bottomPadding: Float
828 | ) {
829 | var position = position
830 | if (position >= tabCount) {
831 | position = tabCount - 1
832 | }
833 | val tabView = mTabsContainer.getChildAt(position)
834 | val tipView =
835 | tabView.findViewById(R.id.rtv_msg_tip) as? MsgView
836 | if (tipView != null) {
837 | val tvTabTitle =
838 | tabView.findViewById(R.id.tv_tab_title) as AppCompatTextView
839 | mTextPaint.textSize = mTextsize
840 | val textWidth = mTextPaint.measureText(tvTabTitle.text.toString())
841 | val textHeight = mTextPaint.descent() - mTextPaint.ascent()
842 | val lp = tipView.layoutParams as MarginLayoutParams
843 | lp.leftMargin =
844 | if (mTabWidth >= 0) (mTabWidth / 2 + textWidth / 2 + dp2px(leftPadding)).toInt() else (mTabPadding + textWidth + dp2px(
845 | leftPadding
846 | )).toInt()
847 | lp.topMargin =
848 | if (mHeight > 0) (mHeight - textHeight).toInt() / 2 - dp2px(bottomPadding) else 0
849 | tipView.layoutParams = lp
850 | }
851 |
852 | }
853 |
854 | /**
855 | * 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置
856 | */
857 | fun getMsgView(position: Int): MsgView {
858 | var position = position
859 | if (position >= tabCount) {
860 | position = tabCount - 1
861 | }
862 | val tabView = mTabsContainer.getChildAt(position)
863 | return tabView.findViewById(R.id.rtv_msg_tip) as MsgView
864 | }
865 |
866 | fun getTabCount() = tabCount
867 |
868 | override fun onSaveInstanceState(): Parcelable? {
869 | val bundle = Bundle()
870 | bundle.putParcelable("instanceState", super.onSaveInstanceState())
871 | bundle.putInt("mCurrentTab", mCurrentTab)
872 | return bundle
873 | }
874 |
875 | override fun onRestoreInstanceState(state: Parcelable) {
876 | var state: Parcelable? = state
877 | if (state is Bundle) {
878 | val bundle = state
879 | mCurrentTab = bundle.getInt("mCurrentTab")
880 | state = bundle.getParcelable("instanceState")
881 | if (mCurrentTab != 0 && mTabsContainer.childCount > 0) {
882 | updateTabSelection(mCurrentTab)
883 | scrollToCurrentTab()
884 | }
885 | }
886 | super.onRestoreInstanceState(state)
887 | }
888 |
889 | private fun dp2px(dp: Float): Int {
890 | val scale = context.resources.displayMetrics.density
891 | return (dp * scale + 0.5f).toInt()
892 | }
893 |
894 | private fun sp2px(sp: Float): Int {
895 | val scale = context.resources.displayMetrics.scaledDensity
896 | return (sp * scale + 0.5f).toInt()
897 | }
898 |
899 | /**
900 | * ViewPager2适配器
901 | */
902 | internal inner class ViewPager2FragmentAdapter(
903 | fa: FragmentActivity,
904 | val fragments: MutableList
905 | ) : FragmentStateAdapter(fa) {
906 |
907 | override fun getItemCount(): Int {
908 | return fragments.size
909 | }
910 |
911 | override fun createFragment(position: Int): Fragment {
912 | return fragments[position]
913 |
914 | }
915 |
916 |
917 | }
918 |
919 | /**
920 | * viewPager2监听实现
921 | */
922 | internal inner class ViewPager2PageChangeCallback : ViewPager2.OnPageChangeCallback() {
923 |
924 | override fun onPageSelected(position: Int) {
925 | super.onPageSelected(position)
926 | updateTabSelection(position)
927 | mVP2OnPageSelected.invoke(position)
928 | }
929 |
930 | override fun onPageScrolled(
931 | position: Int,
932 | positionOffset: Float,
933 | positionOffsetPixels: Int
934 | ) {
935 | super.onPageScrolled(position, positionOffset, positionOffsetPixels)
936 |
937 | /**
938 | * position:当前View的位置
939 | * mCurrentPositionOffset:当前View的偏移量比例.[0,1)
940 | */
941 | mCurrentTab = position
942 | mCurrentPositionOffset = positionOffset
943 | scrollToCurrentTab()
944 | invalidate()
945 |
946 | mVP2OnPageScrolled.invoke(position, positionOffset, positionOffsetPixels)
947 |
948 | }
949 |
950 | override fun onPageScrollStateChanged(state: Int) {
951 | super.onPageScrollStateChanged(state)
952 | mVP2OnPageScrollStateChanged.invoke(state)
953 | }
954 |
955 |
956 | }
957 |
958 | }
--------------------------------------------------------------------------------
/library_tab/src/main/java/com/crimson/library/tab/attrs/TabLayoutAttrs.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.library.tab.attrs
2 |
3 | import android.graphics.Color
4 | import android.view.Gravity
5 |
6 | /**
7 | * @author crimson
8 | * @date 2020/3/22
9 | */
10 |
11 |
12 | /**
13 | * Tab属性
14 | */
15 | data class TabAttrs(
16 | var padding: Int = 20,
17 | var width: Int = -1,
18 | var space_equal: Boolean = false,
19 | var textsize: Int = 14,
20 | var textSelectSize: Int = 18,
21 | var textSelectColor: Int = Color.parseColor("#cccccc"),
22 | var textUnselectColor: Int = Color.parseColor("#333333"),
23 | var textBold: Int = 0,
24 | val textAllCaps: Boolean = false,
25 | var smooth_scroll_enable: Boolean = true,
26 | var snap_tab_click: Boolean = false
27 | )
28 |
29 |
30 | /**
31 | * 指示器属性
32 | */
33 | data class IndicatorAttrs(
34 | var color: Int = Color.parseColor("#ffffff"),
35 | var height: Int = 4,
36 | var width: Int = -1,
37 | var margin_left: Int = 0,
38 | var margin_top: Int = 0,
39 | var margin_right: Int = 0,
40 | var margin_bottom: Int = 0,
41 | var corner_radius: Int = 0,
42 | var gravity: Int = Gravity.BOTTOM,
43 | var style: Int = 0,
44 | val width_equal_title: Boolean = false
45 | )
46 |
47 | /**
48 | * 下划线属性
49 | */
50 | data class UnderlineAttrs(
51 | var color: Int = Color.parseColor("#ffffff"),
52 | var height: Int = 0,
53 | var gravity: Int = Gravity.BOTTOM
54 | )
55 |
56 | /**
57 | * 分割线属性
58 | */
59 | data class DividerAttrs(
60 | var color: Int = Color.parseColor("#ffffff"),
61 | var width: Int = 0,
62 | var padding: Int = 12
63 | )
--------------------------------------------------------------------------------
/library_tab/src/main/java/com/crimson/library/tab/widget/MsgView.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.library.tab.widget
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.graphics.drawable.GradientDrawable
6 | import android.graphics.drawable.StateListDrawable
7 | import android.os.Build
8 | import android.util.AttributeSet
9 | import androidx.appcompat.widget.AppCompatTextView
10 | import com.crimson.library.tab.R
11 |
12 | /** 用于需要圆角矩形框背景的TextView的情况,减少直接使用TextView时引入的shape资源文件 */
13 | class MsgView @JvmOverloads constructor(
14 | context: Context,
15 | attrs: AttributeSet? = null,
16 | defStyleAttr: Int = 0
17 | ) : AppCompatTextView(context, attrs, defStyleAttr) {
18 | private val gd_background = GradientDrawable()
19 | private var backgroundColor = 0
20 | private var cornerRadius = 0
21 | private var strokeWidth = 0
22 | private var strokeColor = 0
23 | private var isRadiusHalfHeight = false
24 | private var isWidthHeightEqual = false
25 |
26 | init {
27 | obtainAttributes(context, attrs)
28 | }
29 |
30 | private fun obtainAttributes(
31 | context: Context,
32 | attrs: AttributeSet?
33 | ) {
34 | val ta = context.obtainStyledAttributes(attrs, R.styleable.MsgView)
35 | backgroundColor =
36 | ta.getColor(R.styleable.MsgView_mv_backgroundColor, Color.TRANSPARENT)
37 | cornerRadius = ta.getDimensionPixelSize(R.styleable.MsgView_mv_cornerRadius, 0)
38 | strokeWidth = ta.getDimensionPixelSize(R.styleable.MsgView_mv_strokeWidth, 0)
39 | strokeColor =
40 | ta.getColor(R.styleable.MsgView_mv_strokeColor, Color.TRANSPARENT)
41 | isRadiusHalfHeight = ta.getBoolean(R.styleable.MsgView_mv_isRadiusHalfHeight, false)
42 | isWidthHeightEqual = ta.getBoolean(R.styleable.MsgView_mv_isWidthHeightEqual, false)
43 | ta.recycle()
44 | }
45 |
46 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
47 | if (isWidthHeightEqual() && width > 0 && height > 0) {
48 | val max = Math.max(width, height)
49 | val measureSpec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY)
50 | super.onMeasure(measureSpec, measureSpec)
51 | return
52 | }
53 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
54 | }
55 |
56 | override fun onLayout(
57 | changed: Boolean,
58 | left: Int,
59 | top: Int,
60 | right: Int,
61 | bottom: Int
62 | ) {
63 | super.onLayout(changed, left, top, right, bottom)
64 | if (isRadiusHalfHeight()) {
65 | setCornerRadius(height / 2)
66 | } else {
67 | setBgSelector()
68 | }
69 | }
70 |
71 | override fun setBackgroundColor(backgroundColor: Int) {
72 | this.backgroundColor = backgroundColor
73 | setBgSelector()
74 | }
75 |
76 | fun setCornerRadius(cornerRadius: Int) {
77 | this.cornerRadius = dp2px(cornerRadius.toFloat())
78 | setBgSelector()
79 | }
80 |
81 | fun setStrokeWidth(strokeWidth: Int) {
82 | this.strokeWidth = dp2px(strokeWidth.toFloat())
83 | setBgSelector()
84 | }
85 |
86 | fun setStrokeColor(strokeColor: Int) {
87 | this.strokeColor = strokeColor
88 | setBgSelector()
89 | }
90 |
91 | fun setIsRadiusHalfHeight(isRadiusHalfHeight: Boolean) {
92 | this.isRadiusHalfHeight = isRadiusHalfHeight
93 | setBgSelector()
94 | }
95 |
96 | fun setIsWidthHeightEqual(isWidthHeightEqual: Boolean) {
97 | this.isWidthHeightEqual = isWidthHeightEqual
98 | setBgSelector()
99 | }
100 |
101 | fun getBackgroundColor(): Int {
102 | return backgroundColor
103 | }
104 |
105 | fun getCornerRadius(): Int {
106 | return cornerRadius
107 | }
108 |
109 | fun getStrokeWidth(): Int {
110 | return strokeWidth
111 | }
112 |
113 | fun getStrokeColor(): Int {
114 | return strokeColor
115 | }
116 |
117 | fun isRadiusHalfHeight(): Boolean {
118 | return isRadiusHalfHeight
119 | }
120 |
121 | fun isWidthHeightEqual(): Boolean {
122 | return isWidthHeightEqual
123 | }
124 |
125 | protected fun dp2px(dp: Float): Int {
126 | val scale = context.resources.displayMetrics.density
127 | return (dp * scale + 0.5f).toInt()
128 | }
129 |
130 | protected fun sp2px(sp: Float): Int {
131 | val scale = context.resources.displayMetrics.scaledDensity
132 | return (sp * scale + 0.5f).toInt()
133 | }
134 |
135 | private fun setDrawable(gd: GradientDrawable, color: Int, strokeColor: Int) {
136 | gd.setColor(color)
137 | gd.cornerRadius = cornerRadius.toFloat()
138 | gd.setStroke(strokeWidth, strokeColor)
139 | }
140 |
141 | fun setBgSelector() {
142 | val bg =
143 | StateListDrawable()
144 | setDrawable(gd_background, backgroundColor, strokeColor)
145 | bg.addState(intArrayOf(-android.R.attr.state_pressed), gd_background)
146 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { //16
147 | background = bg
148 | } else {
149 | setBackgroundDrawable(bg)
150 | }
151 | }
152 |
153 |
154 | }
--------------------------------------------------------------------------------
/library_tab/src/main/java/com/crimson/library/tab/widget/MsgViewExt.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.library.tab.widget
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import android.widget.RelativeLayout
6 |
7 | /**
8 | * @author crimson
9 | * @date 2020/3/21
10 | * MsgView 扩展函数
11 | */
12 |
13 | /**
14 | * 展示
15 | */
16 | fun MsgView.show(num: Int) {
17 |
18 | val lp =
19 | layoutParams as? RelativeLayout.LayoutParams
20 | val dm = resources.displayMetrics
21 | visibility = View.VISIBLE
22 | if (num <= 0) {
23 | //圆点,设置默认宽高
24 | setStrokeWidth(0)
25 | text = ""
26 | lp?.width = (5 * dm.density).toInt()
27 | lp?.height = (5 * dm.density).toInt()
28 | lp?.let {
29 | layoutParams = it
30 | }
31 |
32 | } else {
33 | lp?.height = (18 * dm.density).toInt()
34 | when (num) {
35 | in 1..9 -> { //圆
36 | lp?.width = (18 * dm.density).toInt()
37 | text = num.toString()
38 | }
39 | in 10..99 -> { //圆角矩形,圆角是高度的一半,设置默认padding
40 | lp?.width = ViewGroup.LayoutParams.WRAP_CONTENT
41 | setPadding((6 * dm.density).toInt(), 0, (6 * dm.density).toInt(), 0)
42 | text = num.toString()
43 | }
44 | else -> { //数字超过两位,显示99+
45 | lp?.width = ViewGroup.LayoutParams.WRAP_CONTENT
46 | setPadding((6 * dm.density).toInt(), 0, (6 * dm.density).toInt(), 0)
47 | text = "99+"
48 | }
49 | }
50 | lp?.let {
51 | layoutParams = it
52 | }
53 | }
54 | }
55 |
56 | /**
57 | * 设置大小
58 | */
59 | fun MsgView.setSize(size: Int){
60 | val lp = layoutParams as? RelativeLayout.LayoutParams
61 | lp?.width = size
62 | lp?.height = size
63 | lp?.let {
64 | layoutParams = it
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/library_tab/src/main/res/layout/layout_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
30 |
31 |
--------------------------------------------------------------------------------
/library_tab/src/main/res/values/values.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/library_tab/src/test/java/com/crimson/library/tab/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.crimson.library.tab
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 | }
18 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name='tablayout'
2 | include ':app'
3 | include ':library_tab'
4 |
--------------------------------------------------------------------------------
/snapshot/snapshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crimson0829/AdvancedTabLayout/7e7256c6cab33b5aaddcd960b4682e0dc66a86cf/snapshot/snapshot.gif
--------------------------------------------------------------------------------