diff --git a/OneToOne/.gitignore b/OneToOne/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/OneToOne/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/OneToOne/build.gradle b/OneToOne/build.gradle
new file mode 100644
index 000000000..12e2aebd0
--- /dev/null
+++ b/OneToOne/build.gradle
@@ -0,0 +1,91 @@
+apply plugin: 'com.android.application'
+//apply plugin: 'com.android.library'
+apply plugin: 'img-optimizer'
+apply plugin: 'com.alibaba.arouter'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion rootProject.ext.android.compileSdkVersion
+ buildToolsVersion rootProject.ext.android.buildToolsVersion
+
+
+ defaultConfig {
+ applicationId "com.shayu.onetoone"
+ minSdkVersion rootProject.ext.android.minSdkVersion
+ targetSdkVersion rootProject.ext.android.targetSdkVersion
+ versionCode rootProject.ext.android.versionCode
+ versionName rootProject.ext.android.versionName
+ manifestPlaceholders = rootProject.ext.manifestPlaceholders
+ ndk {
+ abiFilters "armeabi-v7a", "arm64-v8a"
+ }
+ }
+ aaptOptions {
+ cruncherEnabled = false
+ useNewCruncher = false
+ }
+ signingConfigs {
+ release {
+ keyAlias 'phonelive'
+ keyPassword 'phonelive'
+ storeFile file('../hdl.jks')
+ storePassword 'phonelive'
+ }
+ //陌陌本地配置需要
+ config {//TODO BEAUTYSDK 这里需要配置keystore相关信息
+ keyAlias 'phonelive'
+ keyPassword 'phonelive'
+ storeFile file('../hdl.jks')//keystore的存放路径
+ storePassword 'phonelive'
+ v2SigningEnabled true // android 5.0-8.0,必须用V2,否则会有安全问题
+ v1SigningEnabled true
+ }
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ shrinkResources false
+ zipAlignEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ debug {
+ minifyEnabled false
+ shrinkResources false
+ zipAlignEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+repositories {
+ flatDir {
+ dirs 'libs', '../libs'
+ }
+}
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation rootProject.ext.dependencies["appcompat-androidx"]
+ implementation rootProject.ext.dependencies["recyclerview-androidx"]
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ //common
+ api project(path: ':common')
+ api project(path:':FaceUnity')//新娱美颜
+
+ api project(path:':TabLayout')
+ api project(path:':ViewPager2Delegate')
+ //implementation 'com.github.angcyo.DslTablayout:TabLayout:3.5.5'
+ //可选
+ //implementation 'com.github.angcyo.DslTablayout:ViewPager2Delegate:3.5.5'
+
+ //ExoPlayer,腾讯的播放器不支持无缝切换
+ implementation 'com.google.android.exoplayer:exoplayer:2.18.5'
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.18.5@aar'
+
+}
diff --git a/OneToOne/consumer-rules.pro b/OneToOne/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/OneToOne/proguard-rules.pro b/OneToOne/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/OneToOne/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/OneToOne/src/androidTest/java/com/shayu/onetoone/ExampleInstrumentedTest.java b/OneToOne/src/androidTest/java/com/shayu/onetoone/ExampleInstrumentedTest.java
new file mode 100644
index 000000000..5c42b5c1e
--- /dev/null
+++ b/OneToOne/src/androidTest/java/com/shayu/onetoone/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.shayu.onetoone;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.shayu.onetoone.test", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/OneToOne/src/main/AndroidManifest.xml b/OneToOne/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..98b9c583b
--- /dev/null
+++ b/OneToOne/src/main/AndroidManifest.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/AppContext.java b/OneToOne/src/main/java/com/shayu/onetoone/AppContext.java
new file mode 100644
index 000000000..4d4843585
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/AppContext.java
@@ -0,0 +1,10 @@
+package com.shayu.onetoone;
+
+import androidx.multidex.MultiDexApplication;
+
+public class AppContext extends MultiDexApplication {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+}
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/LauncherActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/LauncherActivity.java
new file mode 100644
index 000000000..e2420f406
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/LauncherActivity.java
@@ -0,0 +1,18 @@
+package com.shayu.onetoone.activitys;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.shayu.onetoone.R;
+
+public class LauncherActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_launcher);
+ startActivity(new Intent(this, MainActivity.class));
+ }
+}
\ No newline at end of file
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/MainActivity.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/MainActivity.java
new file mode 100644
index 000000000..86b25d080
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/MainActivity.java
@@ -0,0 +1,57 @@
+package com.shayu.onetoone.activitys;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+import androidx.viewpager2.widget.ViewPager2;
+
+import android.os.Bundle;
+
+import com.angcyo.tablayout.DslTabLayout;
+import com.angcyo.tablayout.delegate2.ViewPager2Delegate;
+import com.shayu.onetoone.R;
+import com.shayu.onetoone.activitys.fragments.BaseFragment;
+import com.shayu.onetoone.activitys.fragments.DynamicFragment;
+import com.shayu.onetoone.activitys.fragments.FriendsFragment;
+import com.shayu.onetoone.activitys.fragments.MessageFragment;
+import com.shayu.onetoone.activitys.fragments.MyFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MainActivity extends AppCompatActivity {
+ List fragments = new ArrayList<>();
+ private ViewPager2 viewPager;
+ private DslTabLayout dslTabLayout;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ fragments.add(new FriendsFragment());
+ fragments.add(new DynamicFragment());
+ fragments.add(new MessageFragment());
+ fragments.add(new MyFragment());
+
+ viewPager = findViewById(R.id.viewPager);
+ dslTabLayout = findViewById(R.id.dslTabLayout);
+
+ viewPager.setAdapter(new FragmentStateAdapter(this) {
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ return fragments.get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return fragments.size();
+ }
+ });
+
+ ViewPager2Delegate.Companion.install(viewPager, dslTabLayout, false);
+
+ }
+}
\ No newline at end of file
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/BaseFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/BaseFragment.java
new file mode 100644
index 000000000..f6b121d9c
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/BaseFragment.java
@@ -0,0 +1,7 @@
+package com.shayu.onetoone.activitys.fragments;
+
+import androidx.fragment.app.Fragment;
+
+public class BaseFragment extends Fragment {
+
+}
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/DynamicFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/DynamicFragment.java
new file mode 100644
index 000000000..803deb7f6
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/DynamicFragment.java
@@ -0,0 +1,20 @@
+package com.shayu.onetoone.activitys.fragments;
+
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.shayu.onetoone.R;
+
+public class DynamicFragment extends BaseFragment {
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_dynamic, container, false);
+ }
+}
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/FriendsFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/FriendsFragment.java
new file mode 100644
index 000000000..5dffc6d83
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/FriendsFragment.java
@@ -0,0 +1,20 @@
+package com.shayu.onetoone.activitys.fragments;
+
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.shayu.onetoone.R;
+
+public class FriendsFragment extends BaseFragment {
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_friends, container, false);
+ }
+}
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/MessageFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/MessageFragment.java
new file mode 100644
index 000000000..63bd90fec
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/MessageFragment.java
@@ -0,0 +1,20 @@
+package com.shayu.onetoone.activitys.fragments;
+
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.shayu.onetoone.R;
+
+public class MessageFragment extends BaseFragment {
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_msg, container, false);
+ }
+}
diff --git a/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/MyFragment.java b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/MyFragment.java
new file mode 100644
index 000000000..f8c0e3944
--- /dev/null
+++ b/OneToOne/src/main/java/com/shayu/onetoone/activitys/fragments/MyFragment.java
@@ -0,0 +1,20 @@
+package com.shayu.onetoone.activitys.fragments;
+
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.shayu.onetoone.R;
+
+public class MyFragment extends BaseFragment {
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_my, container, false);
+ }
+}
diff --git a/OneToOne/src/main/res/drawable/tab_dynamic.xml b/OneToOne/src/main/res/drawable/tab_dynamic.xml
new file mode 100644
index 000000000..8afe037a2
--- /dev/null
+++ b/OneToOne/src/main/res/drawable/tab_dynamic.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/drawable/tab_friends.xml b/OneToOne/src/main/res/drawable/tab_friends.xml
new file mode 100644
index 000000000..619280960
--- /dev/null
+++ b/OneToOne/src/main/res/drawable/tab_friends.xml
@@ -0,0 +1,13 @@
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/drawable/tab_msg.xml b/OneToOne/src/main/res/drawable/tab_msg.xml
new file mode 100644
index 000000000..a224bf5c2
--- /dev/null
+++ b/OneToOne/src/main/res/drawable/tab_msg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/drawable/tab_my.xml b/OneToOne/src/main/res/drawable/tab_my.xml
new file mode 100644
index 000000000..85ae03694
--- /dev/null
+++ b/OneToOne/src/main/res/drawable/tab_my.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/layout/activity_launcher.xml b/OneToOne/src/main/res/layout/activity_launcher.xml
new file mode 100644
index 000000000..2c09d16ba
--- /dev/null
+++ b/OneToOne/src/main/res/layout/activity_launcher.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/layout/activity_main.xml b/OneToOne/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..a9dfe788f
--- /dev/null
+++ b/OneToOne/src/main/res/layout/activity_main.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/layout/fragment_dynamic.xml b/OneToOne/src/main/res/layout/fragment_dynamic.xml
new file mode 100644
index 000000000..a60ab8854
--- /dev/null
+++ b/OneToOne/src/main/res/layout/fragment_dynamic.xml
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/layout/fragment_friends.xml b/OneToOne/src/main/res/layout/fragment_friends.xml
new file mode 100644
index 000000000..a4782b7a8
--- /dev/null
+++ b/OneToOne/src/main/res/layout/fragment_friends.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/layout/fragment_msg.xml b/OneToOne/src/main/res/layout/fragment_msg.xml
new file mode 100644
index 000000000..5c1095715
--- /dev/null
+++ b/OneToOne/src/main/res/layout/fragment_msg.xml
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/layout/fragment_my.xml b/OneToOne/src/main/res/layout/fragment_my.xml
new file mode 100644
index 000000000..6c6cdbccd
--- /dev/null
+++ b/OneToOne/src/main/res/layout/fragment_my.xml
@@ -0,0 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_dynamic.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_dynamic.png
new file mode 100644
index 000000000..f9a249ece
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_dynamic.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_dynamic_select.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_dynamic_select.png
new file mode 100644
index 000000000..a53cca0d0
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_dynamic_select.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_friends.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_friends.png
new file mode 100644
index 000000000..c7f787e76
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_friends.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_friends_select.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_friends_select.png
new file mode 100644
index 000000000..aae027322
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_friends_select.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_msg.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_msg.png
new file mode 100644
index 000000000..0ae521d81
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_msg.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_msg_select.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_msg_select.png
new file mode 100644
index 000000000..92d07cda5
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_msg_select.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_my.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_my.png
new file mode 100644
index 000000000..b991bc55b
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_my.png differ
diff --git a/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_my_select.png b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_my_select.png
new file mode 100644
index 000000000..50814b737
Binary files /dev/null and b/OneToOne/src/main/res/mipmap-xxhdpi/ic_tab_my_select.png differ
diff --git a/OneToOne/src/main/res/values-zh-rHK/strings.xml b/OneToOne/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 000000000..6119a1562
--- /dev/null
+++ b/OneToOne/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,4 @@
+
+
+ 1v1
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/values-zh-rTW/strings.xml b/OneToOne/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 000000000..6119a1562
--- /dev/null
+++ b/OneToOne/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,4 @@
+
+
+ 1v1
+
\ No newline at end of file
diff --git a/OneToOne/src/main/res/values/strings.xml b/OneToOne/src/main/res/values/strings.xml
new file mode 100644
index 000000000..86a32d34f
--- /dev/null
+++ b/OneToOne/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ 1v1
+
\ No newline at end of file
diff --git a/OneToOne/src/test/java/com/shayu/onetoone/ExampleUnitTest.java b/OneToOne/src/test/java/com/shayu/onetoone/ExampleUnitTest.java
new file mode 100644
index 000000000..284b73d69
--- /dev/null
+++ b/OneToOne/src/test/java/com/shayu/onetoone/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.shayu.onetoone;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/.gitignore b/TabLayout/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/TabLayout/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/TabLayout/build.gradle b/TabLayout/build.gradle
new file mode 100644
index 000000000..7838d7412
--- /dev/null
+++ b/TabLayout/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion rootProject.ext.android.compileSdkVersion
+ buildToolsVersion rootProject.ext.android.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.android.minSdkVersion
+ targetSdkVersion rootProject.ext.android.targetSdkVersion
+ versionCode rootProject.ext.android.versionCode
+ versionName rootProject.ext.android.versionName
+ manifestPlaceholders = rootProject.ext.manifestPlaceholders
+
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation 'androidx.core:core-ktx:1.7.0'
+}
+
+//apply from: "$gradleHost/master/publish.gradle"
\ No newline at end of file
diff --git a/TabLayout/consumer-rules.pro b/TabLayout/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/TabLayout/proguard-rules.pro b/TabLayout/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/TabLayout/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/TabLayout/src/main/AndroidManifest.xml b/TabLayout/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..e86e891c7
--- /dev/null
+++ b/TabLayout/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt b/TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
new file mode 100644
index 000000000..d790825ad
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
@@ -0,0 +1,200 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.text.TextPaint
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.view.ViewCompat
+
+/**
+ * 基础自绘Drawable
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/25
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+
+abstract class AbsDslDrawable : Drawable() {
+
+ companion object {
+ /**不绘制*/
+ const val DRAW_TYPE_DRAW_NONE = 0x00
+
+ /**[android.view.View.draw]*/
+ const val DRAW_TYPE_DRAW_AFTER = 0x01
+ const val DRAW_TYPE_DRAW_BEFORE = 0x02
+
+ /**[android.view.View.onDraw]*/
+ const val DRAW_TYPE_ON_DRAW_AFTER = 0x04
+ const val DRAW_TYPE_ON_DRAW_BEFORE = 0x08
+ }
+
+ /**画笔*/
+ val textPaint: TextPaint by lazy {
+ TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
+ isFilterBitmap = true
+ style = Paint.Style.FILL
+ textSize = 12 * dp
+ }
+ }
+
+ val drawRect = Rect()
+ val drawRectF = RectF()
+
+ /**需要在那个方法中触发绘制*/
+ var drawType = DRAW_TYPE_ON_DRAW_AFTER
+
+ /**xml属性读取*/
+ open fun initAttribute(
+ context: Context,
+ attributeSet: AttributeSet? = null
+ ) {
+ //val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.xxx)
+ //typedArray.recycle()
+ }
+
+ //
+
+ /**附着的[View]*/
+ val attachView: View?
+ get() = if (callback is View) callback as? View else null
+
+ val isInEditMode: Boolean
+ get() = attachView?.isInEditMode ?: false
+ val paddingLeft: Int
+ get() = attachView?.paddingLeft ?: 0
+ val paddingRight: Int
+ get() = attachView?.paddingRight ?: 0
+ val paddingTop: Int
+ get() = attachView?.paddingTop ?: 0
+ val paddingBottom: Int
+ get() = attachView?.paddingBottom ?: 0
+ val viewHeight: Int
+ get() = attachView?.measuredHeight ?: 0
+ val viewWidth: Int
+ get() = attachView?.measuredWidth ?: 0
+ val viewDrawHeight: Int
+ get() = viewHeight - paddingTop - paddingBottom
+ val viewDrawWidth: Int
+ get() = viewWidth - paddingLeft - paddingRight
+
+ val isViewRtl: Boolean
+ get() = attachView != null && ViewCompat.getLayoutDirection(attachView!!) == ViewCompat.LAYOUT_DIRECTION_RTL
+
+ //
+
+ //
+
+ /**核心方法, 绘制*/
+ override fun draw(canvas: Canvas) {
+
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ return super.getIntrinsicWidth()
+ }
+
+ override fun getMinimumWidth(): Int {
+ return super.getMinimumWidth()
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ return super.getIntrinsicHeight()
+ }
+
+ override fun getMinimumHeight(): Int {
+ return super.getMinimumHeight()
+ }
+
+ override fun setAlpha(alpha: Int) {
+ if (textPaint.alpha != alpha) {
+ textPaint.alpha = alpha
+ invalidateSelf()
+ }
+ }
+
+ override fun getAlpha(): Int {
+ return textPaint.alpha
+ }
+
+ override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) {
+ super.setBounds(left, top, right, bottom)
+ }
+
+ override fun setBounds(bounds: Rect) {
+ super.setBounds(bounds)
+ }
+
+ //不透明度
+ override fun getOpacity(): Int {
+ return if (alpha < 255) PixelFormat.TRANSLUCENT else PixelFormat.OPAQUE
+ }
+
+ override fun getColorFilter(): ColorFilter? {
+ return textPaint.colorFilter
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ textPaint.colorFilter = colorFilter
+ invalidateSelf()
+ }
+
+ override fun mutate(): Drawable {
+ return super.mutate()
+ }
+
+ override fun setDither(dither: Boolean) {
+ textPaint.isDither = dither
+ invalidateSelf()
+ }
+
+ override fun setFilterBitmap(filter: Boolean) {
+ textPaint.isFilterBitmap = filter
+ invalidateSelf()
+ }
+
+ override fun isFilterBitmap(): Boolean {
+ return textPaint.isFilterBitmap
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ }
+
+ override fun onLevelChange(level: Int): Boolean {
+ return super.onLevelChange(level)
+ }
+
+ override fun onStateChange(state: IntArray): Boolean {
+ return super.onStateChange(state)
+ }
+
+ override fun setTintList(tint: ColorStateList?) {
+ super.setTintList(tint)
+ }
+
+ override fun setTintMode(tintMode: PorterDuff.Mode?) {
+ super.setTintMode(tintMode)
+ }
+
+ override fun setTintBlendMode(blendMode: BlendMode?) {
+ super.setTintBlendMode(blendMode)
+ }
+
+ override fun setHotspot(x: Float, y: Float) {
+ super.setHotspot(x, y)
+ }
+
+ override fun setHotspotBounds(left: Int, top: Int, right: Int, bottom: Int) {
+ super.setHotspotBounds(left, top, right, bottom)
+ }
+
+ override fun getHotspotBounds(outRect: Rect) {
+ super.getHotspotBounds(outRect)
+ }
+
+ //
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
new file mode 100644
index 000000000..1962d3d8f
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
@@ -0,0 +1,275 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.Gravity
+import kotlin.math.max
+
+/**
+ * 未读数, 未读小红点, 角标绘制Drawable
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/12/13
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslBadgeDrawable : DslGradientDrawable() {
+
+ val dslGravity = DslGravity()
+
+ /**重力*/
+ var badgeGravity: Int = Gravity.CENTER //Gravity.TOP or Gravity.RIGHT
+
+ /**角标文本颜色*/
+ var badgeTextColor = Color.WHITE
+
+ /**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
+ * null 不绘制角标
+ * "" 空字符绘制圆点
+ * 其他 正常绘制
+ * */
+ var badgeText: String? = null
+
+ /**角标的文本大小*/
+ var badgeTextSize: Float = 12 * dp
+ set(value) {
+ field = value
+ textPaint.textSize = field
+ }
+
+ /**当[badgeText]只有1个字符时, 使用圆形背景*/
+ var badgeAutoCircle: Boolean = true
+
+ /**圆点状态时的半径大小*/
+ var badgeCircleRadius = 4 * dpi
+
+ /**原点状态下, 单独配置的偏移*/
+ var badgeCircleOffsetX: Int = 0
+ var badgeCircleOffsetY: Int = 0
+
+ /**额外偏移距离, 会根据[Gravity]自动取负值*/
+ var badgeOffsetX: Int = 0
+ var badgeOffsetY: Int = 0
+
+ /**文本偏移*/
+ var badgeTextOffsetX: Int = 0
+ var badgeTextOffsetY: Int = 0
+
+ /**圆点状态时无效*/
+ var badgePaddingLeft = 0
+ var badgePaddingRight = 0
+ var badgePaddingTop = 0
+ var badgePaddingBottom = 0
+
+ /**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
+ var badgeMinHeight = -2
+
+ /**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
+ * -1 表示使用使用计算出来的高度值*/
+ var badgeMinWidth = -2
+
+ //计算属性
+ val textWidth: Float
+ get() = textPaint.textWidth(badgeText)
+
+ //最大的宽度
+ val maxWidth: Int
+ get() = max(
+ textWidth.toInt(),
+ originDrawable?.minimumWidth ?: 0
+ ) + badgePaddingLeft + badgePaddingRight
+
+ //最大的高度
+ val maxHeight: Int
+ get() = max(
+ textHeight.toInt(),
+ originDrawable?.minimumHeight ?: 0
+ ) + badgePaddingTop + badgePaddingBottom
+
+ val textHeight: Float
+ get() = textPaint.textHeight()
+
+ //原型状态
+ val isCircle: Boolean
+ get() = TextUtils.isEmpty(badgeText)
+
+ override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
+ super.initAttribute(context, attributeSet)
+ updateOriginDrawable()
+ }
+
+ override fun draw(canvas: Canvas) {
+ //super.draw(canvas)
+
+ if (badgeText == null) {
+ return
+ }
+
+ with(dslGravity) {
+ gravity = if (isViewRtl) {
+ when (badgeGravity) {
+ Gravity.RIGHT -> {
+ Gravity.LEFT
+ }
+ Gravity.LEFT -> {
+ Gravity.RIGHT
+ }
+ else -> {
+ badgeGravity
+ }
+ }
+ } else {
+ badgeGravity
+ }
+
+ setGravityBounds(bounds)
+
+ if (isCircle) {
+ gravityOffsetX = badgeCircleOffsetX
+ gravityOffsetY = badgeCircleOffsetY
+ } else {
+ gravityOffsetX = badgeOffsetX
+ gravityOffsetY = badgeOffsetY
+ }
+
+ val textWidth = textPaint.textWidth(badgeText)
+ val textHeight = textPaint.textHeight()
+
+ val drawHeight = if (isCircle) {
+ badgeCircleRadius.toFloat()
+ } else {
+ val height = textHeight + badgePaddingTop + badgePaddingBottom
+ if (badgeMinHeight > 0) {
+ max(height, badgeMinHeight.toFloat())
+ } else {
+ height
+ }
+ }
+
+ val drawWidth = if (isCircle) {
+ badgeCircleRadius.toFloat()
+ } else {
+ val width = textWidth + badgePaddingLeft + badgePaddingRight
+ if (badgeMinWidth == -1) {
+ max(width, drawHeight)
+ } else if (badgeMinWidth > 0) {
+ max(width, badgeMinWidth.toFloat())
+ } else {
+ width
+ }
+ }
+
+ applyGravity(drawWidth, drawHeight) { centerX, centerY ->
+
+ if (isCircle) {
+ textPaint.color = gradientSolidColor
+
+ //圆心计算
+ val cx: Float
+ val cy: Float
+ if (gravity.isCenter()) {
+ cx = centerX.toFloat()
+ cy = centerY.toFloat()
+ } else {
+ cx = centerX.toFloat() + _gravityOffsetX
+ cy = centerY.toFloat() + _gravityOffsetY
+ }
+
+ //绘制圆
+ textPaint.color = gradientSolidColor
+ canvas.drawCircle(
+ cx,
+ cy,
+ badgeCircleRadius.toFloat(),
+ textPaint
+ )
+
+ //圆的描边
+ if (gradientStrokeWidth > 0 && gradientStrokeColor != Color.TRANSPARENT) {
+ val oldWidth = textPaint.strokeWidth
+ val oldStyle = textPaint.style
+
+ textPaint.color = gradientStrokeColor
+ textPaint.strokeWidth = gradientStrokeWidth.toFloat()
+ textPaint.style = Paint.Style.STROKE
+
+ canvas.drawCircle(
+ cx,
+ cy,
+ badgeCircleRadius.toFloat(),
+ textPaint
+ )
+
+ textPaint.strokeWidth = oldWidth
+ textPaint.style = oldStyle
+ }
+
+ } else {
+ textPaint.color = badgeTextColor
+
+ val textDrawX: Float = centerX - textWidth / 2
+ val textDrawY: Float = centerY + textHeight / 2
+
+ val bgLeft = _gravityLeft
+ val bgTop = _gravityTop
+
+ //绘制背景
+ if (badgeAutoCircle && badgeText?.length == 1) {
+ if (gradientSolidColor != Color.TRANSPARENT) {
+ textPaint.color = gradientSolidColor
+ canvas.drawCircle(
+ centerX.toFloat(),
+ centerY.toFloat(),
+ max(maxWidth, maxHeight).toFloat() / 2,
+ textPaint
+ )
+ }
+ } else {
+ originDrawable?.apply {
+ setBounds(
+ bgLeft, bgTop,
+ (bgLeft + drawWidth).toInt(),
+ (bgTop + drawHeight).toInt()
+ )
+ draw(canvas)
+ }
+ }
+
+ //绘制文本
+ textPaint.color = badgeTextColor
+ canvas.drawText(
+ badgeText!!,
+ textDrawX + badgeTextOffsetX,
+ textDrawY - textPaint.descent() + badgeTextOffsetY,
+ textPaint
+ )
+ }
+ }
+ }
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ val width = if (isCircle) {
+ badgeCircleRadius * 2
+ } else if (badgeAutoCircle && badgeText?.length == 1) {
+ max(maxWidth, maxHeight)
+ } else {
+ maxWidth
+ }
+ return max(badgeMinWidth, width)
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ val height = if (isCircle) {
+ badgeCircleRadius * 2
+ } else if (badgeAutoCircle && badgeText?.length == 1) {
+ max(maxWidth, maxHeight)
+ } else {
+ maxHeight
+ }
+ return max(badgeMinHeight, height)
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslGradientDrawable.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslGradientDrawable.kt
new file mode 100644
index 000000000..336c5b9d7
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslGradientDrawable.kt
@@ -0,0 +1,306 @@
+package com.angcyo.tablayout
+
+import android.content.res.ColorStateList
+import android.content.res.Resources
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.os.Build
+import androidx.annotation.IntDef
+import java.util.*
+
+/**
+ * 用来构建GradientDrawable
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/27
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslGradientDrawable : AbsDslDrawable() {
+
+ /**形状*/
+ @Shape
+ var gradientShape = GradientDrawable.RECTANGLE
+
+ /**填充的颜色*/
+ var gradientSolidColor = Color.TRANSPARENT
+
+ /**边框的颜色*/
+ var gradientStrokeColor = Color.TRANSPARENT
+
+ /**边框的宽度*/
+ var gradientStrokeWidth = 0
+
+ /**蚂蚁线的宽度*/
+ var gradientDashWidth = 0f
+
+ /**蚂蚁线之间的间距*/
+ var gradientDashGap = 0f
+
+ /**
+ * 四个角, 8个设置点的圆角信息
+ * 从 左上y轴->左上x轴->右上x轴->右上y轴..., 开始设置.
+ */
+ var gradientRadii = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)
+
+ /**颜色渐变*/
+ var gradientColors: IntArray? = null
+ var gradientColorsOffsets: FloatArray? = null
+
+ /**渐变中心点坐标*/
+ var gradientCenterX = 0.5f
+ var gradientCenterY = 0.5f
+
+ /**渐变半径, 非比例值, 是px值. [GradientDrawable.RADIAL_GRADIENT]类型才有效*/
+ var gradientRadius = 0.5f
+
+ /** 渐变方向, 默认从左到右 */
+ var gradientOrientation = GradientDrawable.Orientation.LEFT_RIGHT
+
+ /** 渐变类型 */
+ @GradientType
+ var gradientType = GradientDrawable.LINEAR_GRADIENT
+
+ /**真正绘制的[Drawable]*/
+ var originDrawable: Drawable? = null
+
+ /**宽度补偿*/
+ var gradientWidthOffset: Int = 0
+
+ /**高度补偿*/
+ var gradientHeightOffset: Int = 0
+
+ /**当前的配置, 是否能生成有效的[GradientDrawable]*/
+ open fun isValidConfig(): Boolean {
+ return gradientSolidColor != Color.TRANSPARENT ||
+ gradientStrokeColor != Color.TRANSPARENT ||
+ gradientColors != null
+ }
+
+ fun _fillRadii(array: FloatArray, radii: String?) {
+ if (radii.isNullOrEmpty()) {
+ return
+ }
+ val split = radii.split(",")
+ if (split.size != 8) {
+ throw IllegalArgumentException("radii 需要8个值.")
+ } else {
+ val dp = Resources.getSystem().displayMetrics.density
+ for (i in split.indices) {
+ array[i] = split[i].toFloat() * dp
+ }
+ }
+ }
+
+ fun fillRadii(radius: Float) {
+ Arrays.fill(gradientRadii, radius)
+ }
+
+ fun fillRadii(radius: Int) {
+ _fillRadii(gradientRadii, radius.toFloat())
+ }
+
+ fun _fillRadii(array: FloatArray, radius: Float) {
+ Arrays.fill(array, radius)
+ }
+
+ fun _fillRadii(array: FloatArray, radius: Int) {
+ _fillRadii(array, radius.toFloat())
+ }
+
+ fun _fillColor(colors: String?): IntArray? {
+ if (colors.isNullOrEmpty()) {
+ return null
+ }
+ val split = colors.split(",")
+
+ return IntArray(split.size) {
+ val str = split[it]
+ if (str.startsWith("#")) {
+ Color.parseColor(str)
+ } else {
+ str.toInt()
+ }
+ }
+ }
+
+ /**构建或者更新[originDrawable]*/
+ open fun updateOriginDrawable(): GradientDrawable? {
+ val drawable: GradientDrawable? = when (originDrawable) {
+ null -> GradientDrawable()
+ is GradientDrawable -> originDrawable as GradientDrawable
+ else -> {
+ null
+ }
+ }
+
+ drawable?.apply {
+ bounds = this@DslGradientDrawable.bounds
+
+ shape = gradientShape
+ setStroke(
+ gradientStrokeWidth,
+ gradientStrokeColor,
+ gradientDashWidth,
+ gradientDashGap
+ )
+ setColor(gradientSolidColor)
+ cornerRadii = gradientRadii
+
+ if (gradientColors != null) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ setGradientCenter(
+ this@DslGradientDrawable.gradientCenterX,
+ this@DslGradientDrawable.gradientCenterY
+ )
+ }
+ gradientRadius = this@DslGradientDrawable.gradientRadius
+ gradientType = this@DslGradientDrawable.gradientType
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ orientation = gradientOrientation
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ setColors(gradientColors, gradientColorsOffsets)
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ colors = gradientColors
+ }
+ }
+
+ originDrawable = this
+ invalidateSelf()
+ }
+
+ return drawable
+ }
+
+ open fun configDrawable(config: DslGradientDrawable.() -> Unit): DslGradientDrawable {
+ this.config()
+ updateOriginDrawable()
+ return this
+ }
+
+ override fun draw(canvas: Canvas) {
+ super.draw(canvas)
+ originDrawable?.apply {
+ setBounds(
+ this@DslGradientDrawable.bounds.left - gradientWidthOffset / 2,
+ this@DslGradientDrawable.bounds.top - gradientHeightOffset / 2,
+ this@DslGradientDrawable.bounds.right + gradientWidthOffset / 2,
+ this@DslGradientDrawable.bounds.bottom + gradientHeightOffset / 2
+ )
+ draw(canvas)
+ }
+ }
+
+ //
+
+ /**
+ * 4个角, 8个点 圆角配置
+ */
+ fun cornerRadii(radii: FloatArray) {
+ gradientRadii = radii
+ }
+
+ fun cornerRadius(radii: Float) {
+ Arrays.fill(gradientRadii, radii)
+ }
+
+ fun cornerRadius(
+ leftTop: Float = 0f,
+ rightTop: Float = 0f,
+ rightBottom: Float = 0f,
+ leftBottom: Float = 0f
+ ) {
+ gradientRadii[0] = leftTop
+ gradientRadii[1] = leftTop
+ gradientRadii[2] = rightTop
+ gradientRadii[3] = rightTop
+ gradientRadii[4] = rightBottom
+ gradientRadii[5] = rightBottom
+ gradientRadii[6] = leftBottom
+ gradientRadii[7] = leftBottom
+ }
+
+ /**
+ * 只配置左边的圆角
+ */
+ fun cornerRadiiLeft(radii: Float) {
+ gradientRadii[0] = radii
+ gradientRadii[1] = radii
+ gradientRadii[6] = radii
+ gradientRadii[7] = radii
+ }
+
+ fun cornerRadiiRight(radii: Float) {
+ gradientRadii[2] = radii
+ gradientRadii[3] = radii
+ gradientRadii[4] = radii
+ gradientRadii[5] = radii
+ }
+
+ fun cornerRadiiTop(radii: Float) {
+ gradientRadii[0] = radii
+ gradientRadii[1] = radii
+ gradientRadii[2] = radii
+ gradientRadii[3] = radii
+ }
+
+ fun cornerRadiiBottom(radii: Float) {
+ gradientRadii[4] = radii
+ gradientRadii[5] = radii
+ gradientRadii[6] = radii
+ gradientRadii[7] = radii
+ }
+
+ //
+
+ //
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ super.setColorFilter(colorFilter)
+ originDrawable?.colorFilter = colorFilter
+ }
+
+ override fun setTintList(tint: ColorStateList?) {
+ super.setTintList(tint)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ originDrawable?.setTintList(tint)
+ }
+ }
+
+ override fun setState(stateSet: IntArray): Boolean {
+ return originDrawable?.setState(stateSet) ?: super.setState(stateSet)
+ }
+
+ override fun getState(): IntArray {
+ return originDrawable?.state ?: super.getState()
+ }
+
+ //
+}
+
+@IntDef(
+ GradientDrawable.RECTANGLE,
+ GradientDrawable.OVAL,
+ GradientDrawable.LINE,
+ GradientDrawable.RING
+)
+@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
+annotation class Shape
+
+@IntDef(
+ GradientDrawable.LINEAR_GRADIENT,
+ GradientDrawable.RADIAL_GRADIENT,
+ GradientDrawable.SWEEP_GRADIENT
+)
+@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
+annotation class GradientType
+
+/**快速创建[GradientDrawable]*/
+fun dslGradientDrawable(action: DslGradientDrawable.() -> Unit): GradientDrawable {
+ return DslGradientDrawable().run {
+ action()
+ updateOriginDrawable()!!
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
new file mode 100644
index 000000000..66cc1d40e
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
@@ -0,0 +1,215 @@
+package com.angcyo.tablayout
+
+import android.graphics.Rect
+import android.graphics.RectF
+import android.view.Gravity
+
+/**
+ * [android.view.Gravity] 辅助计算类
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/12/13
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+class DslGravity {
+
+ /**束缚范围*/
+ val gravityBounds: RectF = RectF()
+
+ /**束缚重力*/
+ var gravity: Int = Gravity.LEFT or Gravity.TOP
+
+ /**使用中心坐标作为定位参考, 否则就是四条边*/
+ var gravityRelativeCenter: Boolean = true
+
+ /**额外偏移距离, 会根据[Gravity]自动取负值*/
+ var gravityOffsetX: Int = 0
+ var gravityOffsetY: Int = 0
+
+ fun setGravityBounds(rectF: RectF) {
+ gravityBounds.set(rectF)
+ }
+
+ fun setGravityBounds(rect: Rect) {
+ gravityBounds.set(rect)
+ }
+
+ fun setGravityBounds(
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int
+ ) {
+ gravityBounds.set(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
+ }
+
+ fun setGravityBounds(
+ left: Float,
+ top: Float,
+ right: Float,
+ bottom: Float
+ ) {
+ gravityBounds.set(left, top, right, bottom)
+ }
+
+ //计算后的属性
+ var _horizontalGravity: Int = Gravity.LEFT
+ var _verticalGravity: Int = Gravity.TOP
+ var _isCenterGravity: Boolean = false
+ var _targetWidth = 0f
+ var _targetHeight = 0f
+ var _gravityLeft = 0
+ var _gravityTop = 0
+ var _gravityRight = 0
+ var _gravityBottom = 0
+
+ //根据gravity调整后的offset
+ var _gravityOffsetX = 0
+ var _gravityOffsetY = 0
+
+ /**根据[gravity]返回在[gravityBounds]中的[left] [top]位置*/
+ fun applyGravity(
+ width: Float = _targetWidth,
+ height: Float = _targetHeight,
+ callback: (centerX: Int, centerY: Int) -> Unit
+ ) {
+
+ _targetWidth = width
+ _targetHeight = height
+
+ val layoutDirection = 0
+ val absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection)
+ val verticalGravity = gravity and Gravity.VERTICAL_GRAVITY_MASK
+ val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+
+ //调整offset
+ _gravityOffsetX = when (horizontalGravity) {
+ Gravity.RIGHT -> -gravityOffsetX
+ Gravity.END -> -gravityOffsetX
+ else -> gravityOffsetX
+ }
+ _gravityOffsetY = when (verticalGravity) {
+ Gravity.BOTTOM -> -gravityOffsetY
+ else -> gravityOffsetY
+ }
+
+ //计算居中的坐标
+ val centerX = when (horizontalGravity) {
+ Gravity.CENTER_HORIZONTAL -> (gravityBounds.left + gravityBounds.width() / 2 + _gravityOffsetX).toInt()
+ Gravity.RIGHT -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
+ Gravity.END -> (gravityBounds.right + _gravityOffsetX - if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
+ else -> (gravityBounds.left + _gravityOffsetX + if (gravityRelativeCenter) 0f else _targetWidth / 2).toInt()
+ }
+
+ val centerY = when (verticalGravity) {
+ Gravity.CENTER_VERTICAL -> (gravityBounds.top + gravityBounds.height() / 2 + _gravityOffsetY).toInt()
+ Gravity.BOTTOM -> (gravityBounds.bottom + _gravityOffsetY - if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
+ else -> (gravityBounds.top + _gravityOffsetY + if (gravityRelativeCenter) 0f else _targetHeight / 2).toInt()
+ }
+
+ //缓存
+ _horizontalGravity = horizontalGravity
+ _verticalGravity = verticalGravity
+ _isCenterGravity = horizontalGravity == Gravity.CENTER_HORIZONTAL &&
+ verticalGravity == Gravity.CENTER_VERTICAL
+
+ _gravityLeft = (centerX - _targetWidth / 2).toInt()
+ _gravityRight = (centerX + _targetWidth / 2).toInt()
+ _gravityTop = (centerY - _targetHeight / 2).toInt()
+ _gravityBottom = (centerY + _targetHeight / 2).toInt()
+
+ //回调
+ callback(centerX, centerY)
+ }
+}
+
+/**
+ * 默认计算出的是目标中心点坐标参考距离
+ * [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
+ * */
+fun dslGravity(
+ rect: RectF, //计算的矩形
+ gravity: Int, //重力
+ width: Float, //放置目标的宽度
+ height: Float, //放置目标的高度
+ offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
+ offsetY: Int = 0,
+ callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
+): DslGravity {
+ val _dslGravity = DslGravity()
+ _dslGravity.setGravityBounds(rect)
+ _config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
+ return _dslGravity
+}
+
+/**
+ * 默认计算出的是目标中心点坐标参考距离
+ * [com.angcyo.drawable.DslGravity.getGravityRelativeCenter]
+ * */
+fun dslGravity(
+ rect: Rect, //计算的矩形
+ gravity: Int, //重力
+ width: Float, //放置目标的宽度
+ height: Float, //放置目标的高度
+ offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
+ offsetY: Int = 0,
+ callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
+): DslGravity {
+ val _dslGravity = DslGravity()
+ _dslGravity.setGravityBounds(rect)
+ _config(_dslGravity, gravity, width, height, offsetX, offsetY, callback)
+ return _dslGravity
+}
+
+private fun _config(
+ _dslGravity: DslGravity,
+ gravity: Int, //重力
+ width: Float, //放置目标的宽度
+ height: Float, //放置目标的高度
+ offsetX: Int = 0, //额外的偏移,会根据[gravity]进行左右|上下取反
+ offsetY: Int = 0,
+ callback: (dslGravity: DslGravity, centerX: Int, centerY: Int) -> Unit
+) {
+ _dslGravity.gravity = gravity
+ _dslGravity.gravityOffsetX = offsetX
+ _dslGravity.gravityOffsetY = offsetY
+ _dslGravity.applyGravity(width, height) { centerX, centerY ->
+ callback(_dslGravity, centerX, centerY)
+ }
+}
+
+/**居中*/
+fun Int.isCenter(): Boolean {
+ val layoutDirection = 0
+ val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
+ val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
+ val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+
+ return verticalGravity == Gravity.CENTER_VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL
+}
+
+fun Int.isLeft(): Boolean {
+ val layoutDirection = 0
+ val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
+ val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+
+ return horizontalGravity == Gravity.LEFT
+}
+
+fun Int.isRight(): Boolean {
+ val layoutDirection = 0
+ val absoluteGravity = Gravity.getAbsoluteGravity(this, layoutDirection)
+ val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+
+ return horizontalGravity == Gravity.RIGHT
+}
+
+fun Int.isTop(): Boolean {
+ val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
+ return verticalGravity == Gravity.TOP
+}
+
+fun Int.isBottom(): Boolean {
+ val verticalGravity = this and Gravity.VERTICAL_GRAVITY_MASK
+ return verticalGravity == Gravity.BOTTOM
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
new file mode 100644
index 000000000..955680e86
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
@@ -0,0 +1,438 @@
+package com.angcyo.tablayout
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CompoundButton
+
+/**
+ * 用来操作[ViewGroup]中的[child], 支持单选, 多选, 拦截.
+ * 操作的都是可见性为[VISIBLE]的[View]
+ *
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/24
+ */
+
+open class DslSelector {
+
+ var parent: ViewGroup? = null
+ var dslSelectorConfig: DslSelectorConfig = DslSelectorConfig()
+
+ //可见view列表
+ val visibleViewList: MutableList = mutableListOf()
+
+ /**
+ * 选中的索引列表
+ * */
+ val selectorIndexList: MutableList = mutableListOf()
+ get() {
+ field.clear()
+ visibleViewList.forEachIndexed { index, view ->
+ if (view.isSe()) {
+ field.add(index)
+ }
+ }
+
+ return field
+ }
+
+ /**
+ * 选中的View列表
+ * */
+ val selectorViewList: MutableList = mutableListOf()
+ get() {
+ field.clear()
+ visibleViewList.forEachIndexed { index, view ->
+ if (view.isSe() || index == dslSelectIndex) {
+ field.add(view)
+ }
+ }
+ return field
+ }
+
+ //child 点击事件处理
+ val _onChildClickListener = View.OnClickListener {
+ val index = visibleViewList.indexOf(it)
+ val select =
+ if (dslSelectorConfig.dslMultiMode || dslSelectorConfig.dslMinSelectLimit < 1) {
+ !it.isSe()
+ } else {
+ true
+ }
+
+ if (!interceptSelector(index, select, true)) {
+ selector(
+ visibleViewList.indexOf(it),
+ select,
+ notify = true,
+ fromUser = true,
+ forceNotify = it is CompoundButton && dslSelectorConfig.dslMultiMode
+ )
+ }
+ }
+
+ /**兼容[CompoundButton]*/
+ val _onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
+ buttonView.isChecked = buttonView.isSelected //恢复状态 不做任何处理, 在[OnClickListener]中处理
+ /*val index = visibleViewList.indexOf(buttonView)
+
+ if (interceptSelector(index, isChecked, false)) {
+ //拦截了此操作
+ buttonView.isChecked = !isChecked //恢复状态
+ }
+
+ val selectorViewList = selectorViewList
+ val sum = selectorViewList.size
+ //Limit 过滤
+ if (buttonView.isChecked) {
+ if (sum > dslSelectorConfig.dslMaxSelectLimit) {
+ //不允许选择
+ buttonView.isChecked = false //恢复状态
+ }
+ } else {
+ //取消选择, 检查是否达到了 limit
+ if (sum < dslSelectorConfig.dslMinSelectLimit) {
+ //不允许取消选择
+ buttonView.isChecked = true //恢复状态
+ }
+ }
+
+ if (isChecked) {
+ //已经选中了控件
+ } else {
+ //已经取消了控件
+ }*/
+ }
+
+ /**当前选中的索引*/
+ var dslSelectIndex = -1
+
+ /**安装*/
+ fun install(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
+ dslSelectIndex = -1
+ parent = viewGroup
+ updateVisibleList()
+ dslSelectorConfig.config()
+
+ updateStyle()
+ updateClickListener()
+
+ if (dslSelectIndex in 0 until visibleViewList.size) {
+ selector(dslSelectIndex)
+ }
+
+ return this
+ }
+
+ /**更新样式*/
+ fun updateStyle() {
+ visibleViewList.forEachIndexed { index, view ->
+ val selector = dslSelectIndex == index || view.isSe()
+ dslSelectorConfig.onStyleItemView(view, index, selector)
+ }
+ }
+
+ /**更新child的点击事件*/
+ fun updateClickListener() {
+ parent?.apply {
+ for (i in 0 until childCount) {
+ getChildAt(i)?.let {
+ it.setOnClickListener(_onChildClickListener)
+ if (it is CompoundButton) {
+ it.setOnCheckedChangeListener(_onCheckedChangeListener)
+ }
+ }
+ }
+ }
+ }
+
+ /**更新可见视图列表*/
+ fun updateVisibleList(): List {
+ visibleViewList.clear()
+ parent?.apply {
+ for (i in 0 until childCount) {
+ getChildAt(i)?.let {
+ if (it.visibility == View.VISIBLE) {
+ visibleViewList.add(it)
+ }
+ }
+ }
+ }
+ if (dslSelectIndex in visibleViewList.indices) {
+ if (!visibleViewList[dslSelectIndex].isSe()) {
+ visibleViewList[dslSelectIndex].setSe(true)
+ }
+ } else {
+ //如果当前选中的索引, 不在可见视图列表中
+ dslSelectIndex = -1
+ }
+ return visibleViewList
+ }
+
+ /**
+ * 操作单个
+ * @param index 操作目标的索引值
+ * @param select 选中 or 取消选中
+ * @param notify 是否需要通知事件
+ * @param forceNotify 是否强制通知事件.child使用[CompoundButton]时, 推荐使用
+ * */
+ fun selector(
+ index: Int,
+ select: Boolean = true,
+ notify: Boolean = true,
+ fromUser: Boolean = false,
+ forceNotify: Boolean = false
+ ) {
+ val oldSelectorList = selectorIndexList.toList()
+ val lastSelectorIndex: Int? = oldSelectorList.lastOrNull()
+ val reselect = !dslSelectorConfig.dslMultiMode &&
+ oldSelectorList.isNotEmpty() &&
+ oldSelectorList.contains(index)
+
+ var needNotify = _selector(index, select, fromUser) || forceNotify
+
+ if (!oldSelectorList.isChange(selectorIndexList)) {
+ //选中项, 未改变时不通知
+ needNotify = false
+ }
+
+ if (needNotify || reselect) {
+ dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
+ if (notify) {
+ notifySelectChange(lastSelectorIndex ?: -1, reselect, fromUser)
+ }
+ }
+ }
+
+ /**选择所有
+ * [select] true:选择所有, false:取消所有*/
+ fun selectorAll(
+ select: Boolean = true,
+ notify: Boolean = true,
+ fromUser: Boolean = true
+ ) {
+ val indexList = visibleViewList.mapIndexedTo(mutableListOf()) { index, _ ->
+ index
+ }
+ selector(indexList, select, notify, fromUser)
+ }
+
+ /**
+ * 操作多个
+ * @param select 选中 or 取消选中
+ * [selector]
+ * */
+ fun selector(
+ indexList: MutableList,
+ select: Boolean = true,
+ notify: Boolean = true,
+ fromUser: Boolean = false
+ ) {
+ val oldSelectorIndexList = selectorIndexList
+ val lastSelectorIndex: Int? = oldSelectorIndexList.lastOrNull()
+
+ var result = false
+
+ indexList.forEach {
+ result = _selector(it, select, fromUser) || result
+ }
+
+ if (result) {
+ dslSelectIndex = selectorIndexList.lastOrNull() ?: -1
+ if (notify) {
+ notifySelectChange(lastSelectorIndex ?: -1, false, fromUser)
+ }
+ }
+ }
+
+ /**通知事件*/
+ fun notifySelectChange(lastSelectorIndex: Int, reselect: Boolean, fromUser: Boolean) {
+ val indexSelectorList = selectorIndexList
+ dslSelectorConfig.onSelectViewChange(
+ visibleViewList.getOrNull(lastSelectorIndex),
+ selectorViewList,
+ reselect,
+ fromUser
+ )
+ dslSelectorConfig.onSelectIndexChange(
+ lastSelectorIndex,
+ indexSelectorList,
+ reselect,
+ fromUser
+ )
+ }
+
+ /**当前的操作是否被拦截*/
+ fun interceptSelector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
+ val visibleViewList = visibleViewList
+ if (index !in 0 until visibleViewList.size) {
+ return true
+ }
+ return dslSelectorConfig.onSelectItemView(visibleViewList[index], index, select, fromUser)
+ }
+
+ /**@return 是否发生过改变*/
+ fun _selector(index: Int, select: Boolean, fromUser: Boolean): Boolean {
+ val visibleViewList = visibleViewList
+ //超范围过滤
+ if (index !in 0 until visibleViewList.size) {
+ "index out of list.".logi()
+ return false
+ }
+
+ val selectorIndexList = selectorIndexList
+ val selectorViewList = selectorViewList
+
+ if (selectorIndexList.isNotEmpty()) {
+ if (select) {
+ //需要选中某项
+
+ if (dslSelectorConfig.dslMultiMode) {
+ //多选模式
+ if (selectorIndexList.contains(index)) {
+ //已经选中
+ return false
+ }
+ } else {
+ //单选模式
+
+ //取消之前选中
+ selectorIndexList.forEach {
+ if (it != index) {
+ visibleViewList[it].setSe(false)
+ }
+ }
+
+ if (selectorIndexList.contains(index)) {
+ //已经选中
+ return true
+ }
+ }
+
+ } else {
+ //需要取消选中
+ if (!selectorIndexList.contains(index)) {
+ //目标已经是未选中
+ return false
+ }
+ }
+ }
+
+ //Limit 过滤
+ if (select) {
+ val sum = selectorViewList.size + 1
+ if (sum > dslSelectorConfig.dslMaxSelectLimit) {
+ //不允许选择
+ return false
+ }
+ } else {
+ //取消选择, 检查是否达到了 limit
+ val sum = selectorViewList.size - 1
+ if (sum < dslSelectorConfig.dslMinSelectLimit) {
+ //不允许取消选择
+ return false
+ }
+ }
+
+ val selectorView = visibleViewList[index]
+
+ //更新选中样式
+ selectorView.setSe(select)
+
+ if (dslSelectorConfig.dslMultiMode) {
+ //多选
+ } else {
+ //单选
+
+ //取消之前
+ selectorViewList.forEach { view ->
+ //更新样式
+ val indexOf = visibleViewList.indexOf(view)
+ if (indexOf != index &&
+ !dslSelectorConfig.onSelectItemView(view, indexOf, false, fromUser)
+ ) {
+ view.setSe(false)
+ dslSelectorConfig.onStyleItemView(view, indexOf, false)
+ }
+ }
+ }
+
+ dslSelectorConfig.onStyleItemView(selectorView, index, select)
+
+ return true
+ }
+
+ /**是否选中状态*/
+ fun View.isSe(): Boolean {
+ return isSelected || if (this is CompoundButton) isChecked else false
+ }
+
+ fun View.setSe(se: Boolean) {
+ isSelected = se
+ if (this is CompoundButton) isChecked = se
+ }
+}
+
+/**
+ * Dsl配置项
+ * */
+open class DslSelectorConfig {
+
+ /**取消选择时, 最小需要保持多个选中. 可以决定单选时, 是否允许取消所有选中*/
+ var dslMinSelectLimit = 1
+
+ /**多选时, 最大允许多个选中*/
+ var dslMaxSelectLimit = Int.MAX_VALUE
+
+ /**是否是多选模式*/
+ var dslMultiMode: Boolean = false
+
+ /**
+ * 用来初始化[itemView]的样式
+ * [onSelectItemView]
+ * */
+ var onStyleItemView: (itemView: View, index: Int, select: Boolean) -> Unit =
+ { _, _, _ ->
+
+ }
+
+ /**
+ * 选中[View]改变回调, 优先于[onSelectIndexChange]触发, 区别在于参数类型不一样
+ * @param fromView 单选模式下有效, 表示之前选中的[View]
+ * @param reselect 是否是重复选择, 只在单选模式下有效
+ * @param fromUser 是否是用户产生的回调, 而非代码设置
+ * */
+ var onSelectViewChange: (fromView: View?, selectViewList: List, reselect: Boolean, fromUser: Boolean) -> Unit =
+ { _, _, _, _ ->
+
+ }
+
+ /**
+ * 选中改变回调
+ * [onSelectViewChange]
+ * @param fromIndex 单选模式下有效, 表示之前选中的[View], 在可见性[child]列表中的索引
+ * */
+ var onSelectIndexChange: (fromIndex: Int, selectIndexList: List, reselect: Boolean, fromUser: Boolean) -> Unit =
+ { fromIndex, selectList, reselect, fromUser ->
+ "选择:[$fromIndex]->${selectList} reselect:$reselect fromUser:$fromUser".logi()
+ }
+
+ /**
+ * 当需要选中[itemView]时回调, 返回[true]表示拦截默认处理
+ * @param itemView 操作的[View]
+ * @param index [itemView]在可见性view列表中的索引. 非ViewGroup中的索引
+ * @param select 选中 or 取消选中
+ * @return true 表示拦截默认处理
+ * */
+ var onSelectItemView: (itemView: View, index: Int, select: Boolean, fromUser: Boolean) -> Boolean =
+ { _, _, _, _ ->
+ false
+ }
+}
+
+/**[DslSelector]组件*/
+fun dslSelector(viewGroup: ViewGroup, config: DslSelectorConfig.() -> Unit = {}): DslSelector {
+ return DslSelector().apply {
+ install(viewGroup, config)
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
new file mode 100644
index 000000000..0761d1d3a
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
@@ -0,0 +1,222 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Color
+import android.util.AttributeSet
+import android.view.Gravity
+import androidx.annotation.Px
+
+/**
+ * 角标
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/12/13
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslTabBadge : DslBadgeDrawable() {
+
+ /**角标默认配置项*/
+ val defaultBadgeConfig = TabBadgeConfig()
+
+ /**预览的角标属性*/
+ var xmlBadgeText: String? = null
+
+ override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
+ val typedArray =
+ context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+ gradientSolidColor =
+ typedArray.getColor(
+ R.styleable.DslTabLayout_tab_badge_solid_color,
+ defaultBadgeConfig.badgeSolidColor
+ )
+ defaultBadgeConfig.badgeSolidColor = gradientSolidColor
+
+ badgeTextColor =
+ typedArray.getColor(
+ R.styleable.DslTabLayout_tab_badge_text_color,
+ defaultBadgeConfig.badgeTextColor
+ )
+ defaultBadgeConfig.badgeTextColor = badgeTextColor
+
+ gradientStrokeColor =
+ typedArray.getColor(
+ R.styleable.DslTabLayout_tab_badge_stroke_color,
+ defaultBadgeConfig.badgeStrokeColor
+ )
+ defaultBadgeConfig.badgeStrokeColor = gradientStrokeColor
+
+ gradientStrokeWidth =
+ typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_stroke_width,
+ defaultBadgeConfig.badgeStrokeWidth
+ )
+ defaultBadgeConfig.badgeStrokeWidth = gradientStrokeWidth
+
+ badgeGravity = typedArray.getInt(
+ R.styleable.DslTabLayout_tab_badge_gravity,
+ defaultBadgeConfig.badgeGravity
+ )
+ defaultBadgeConfig.badgeGravity = badgeGravity
+
+ badgeOffsetX = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_offset_x,
+ defaultBadgeConfig.badgeOffsetX
+ )
+ defaultBadgeConfig.badgeOffsetX = badgeOffsetX
+ badgeOffsetY = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_offset_y,
+ defaultBadgeConfig.badgeOffsetY
+ )
+ defaultBadgeConfig.badgeOffsetY = badgeOffsetY
+
+ badgeCircleOffsetX = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_circle_offset_x,
+ defaultBadgeConfig.badgeOffsetX
+ )
+ defaultBadgeConfig.badgeCircleOffsetX = badgeCircleOffsetX
+ badgeCircleOffsetY = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_circle_offset_y,
+ defaultBadgeConfig.badgeOffsetY
+ )
+ defaultBadgeConfig.badgeCircleOffsetY = badgeCircleOffsetY
+
+ badgeCircleRadius = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_circle_radius,
+ defaultBadgeConfig.badgeCircleRadius
+ )
+ defaultBadgeConfig.badgeCircleRadius = badgeCircleRadius
+
+ val badgeRadius = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_radius,
+ defaultBadgeConfig.badgeRadius
+ )
+ cornerRadius(badgeRadius.toFloat())
+ defaultBadgeConfig.badgeRadius = badgeRadius
+
+ badgePaddingLeft = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_padding_left,
+ defaultBadgeConfig.badgePaddingLeft
+ )
+ defaultBadgeConfig.badgePaddingLeft = badgePaddingLeft
+
+ badgePaddingRight = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_padding_right,
+ defaultBadgeConfig.badgePaddingRight
+ )
+ defaultBadgeConfig.badgePaddingRight = badgePaddingRight
+
+ badgePaddingTop = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_padding_top,
+ defaultBadgeConfig.badgePaddingTop
+ )
+ defaultBadgeConfig.badgePaddingTop = badgePaddingTop
+
+ badgePaddingBottom = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_padding_bottom,
+ defaultBadgeConfig.badgePaddingBottom
+ )
+ defaultBadgeConfig.badgePaddingBottom = badgePaddingBottom
+
+ xmlBadgeText = typedArray.getString(R.styleable.DslTabLayout_tab_badge_text)
+
+ badgeTextSize = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_badge_text_size,
+ defaultBadgeConfig.badgeTextSize.toInt()
+ ).toFloat()
+ defaultBadgeConfig.badgeTextSize = badgeTextSize
+
+ defaultBadgeConfig.badgeAnchorChildIndex =
+ typedArray.getInteger(
+ R.styleable.DslTabLayout_tab_badge_anchor_child_index,
+ defaultBadgeConfig.badgeAnchorChildIndex
+ )
+ defaultBadgeConfig.badgeIgnoreChildPadding = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_badge_ignore_child_padding,
+ defaultBadgeConfig.badgeIgnoreChildPadding
+ )
+
+ defaultBadgeConfig.badgeMinWidth = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_badge_min_width,
+ defaultBadgeConfig.badgeMinWidth
+ )
+
+ defaultBadgeConfig.badgeMinHeight = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_badge_min_height,
+ defaultBadgeConfig.badgeMinHeight
+ )
+
+ typedArray.recycle()
+ super.initAttribute(context, attributeSet)
+ }
+
+ /**使用指定配置, 更新[DslBadgeDrawable]*/
+ fun updateBadgeConfig(badgeConfig: TabBadgeConfig) {
+ gradientSolidColor = badgeConfig.badgeSolidColor
+ gradientStrokeColor = badgeConfig.badgeStrokeColor
+ gradientStrokeWidth = badgeConfig.badgeStrokeWidth
+ badgeTextColor = badgeConfig.badgeTextColor
+ badgeGravity = badgeConfig.badgeGravity
+ badgeOffsetX = badgeConfig.badgeOffsetX
+ badgeOffsetY = badgeConfig.badgeOffsetY
+ badgeCircleOffsetX = badgeConfig.badgeCircleOffsetX
+ badgeCircleOffsetY = badgeConfig.badgeCircleOffsetY
+ badgeCircleRadius = badgeConfig.badgeCircleRadius
+ badgePaddingLeft = badgeConfig.badgePaddingLeft
+ badgePaddingRight = badgeConfig.badgePaddingRight
+ badgePaddingTop = badgeConfig.badgePaddingTop
+ badgePaddingBottom = badgeConfig.badgePaddingBottom
+ badgeTextSize = badgeConfig.badgeTextSize
+ cornerRadius(badgeConfig.badgeRadius.toFloat())
+ badgeMinHeight = badgeConfig.badgeMinHeight
+ badgeMinWidth = badgeConfig.badgeMinWidth
+ badgeText = badgeConfig.badgeText
+ }
+}
+
+/**角标绘制参数配置*/
+data class TabBadgeConfig(
+ /**角标的文本(默认居中绘制文本,暂不支持修改), 空字符串会绘制成小圆点
+ * null 不绘制角标
+ * "" 空字符绘制圆点
+ * 其他 正常绘制
+ * */
+ var badgeText: String? = null,
+ /**重力*/
+ var badgeGravity: Int = Gravity.CENTER,
+ /**角标背景颜色*/
+ var badgeSolidColor: Int = Color.RED,
+ /**角标边框颜色*/
+ var badgeStrokeColor: Int = Color.TRANSPARENT,
+ /**角标边框宽度*/
+ var badgeStrokeWidth: Int = 0,
+
+ /**角标文本颜色*/
+ var badgeTextColor: Int = Color.WHITE,
+ /**角标文本字体大小*/
+ @Px
+ var badgeTextSize: Float = 12 * dp,
+ /**圆点状态时的半径大小*/
+ var badgeCircleRadius: Int = 4 * dpi,
+ /**圆角大小*/
+ var badgeRadius: Int = 10 * dpi,
+ /**额外偏移距离, 会根据[Gravity]自动取负值*/
+ var badgeOffsetX: Int = 0,
+ var badgeOffsetY: Int = 0,
+ var badgeCircleOffsetX: Int = 0,
+ var badgeCircleOffsetY: Int = 0,
+ /**圆点状态时无效*/
+ var badgePaddingLeft: Int = 4 * dpi,
+ var badgePaddingRight: Int = 4 * dpi,
+ var badgePaddingTop: Int = 0,
+ var badgePaddingBottom: Int = 0,
+
+ var badgeAnchorChildIndex: Int = -1,
+ var badgeIgnoreChildPadding: Boolean = true,
+
+ /**最小的高度大小, px. 大于0生效; 圆点时属性无效*/
+ var badgeMinHeight: Int = -2,
+
+ /**最小的宽度大小, px. 大于0生效; 圆点时属性无效;
+ * -1 表示使用使用计算出来的高度值*/
+ var badgeMinWidth: Int = -1
+)
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
new file mode 100644
index 000000000..b1d4d6003
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
@@ -0,0 +1,279 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.view.ViewCompat
+
+/**
+ * 边框绘制, 支持首尾圆角中间不圆角的样式
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/27
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslTabBorder : DslGradientDrawable() {
+
+ /**
+ * 是否要接管[itemView]背景的绘制
+ * [updateItemBackground]
+ * */
+ var borderDrawItemBackground: Boolean = true
+
+ /**是否保持每个[itemView]的圆角都一样, 否则首尾有圆角, 中间没有圆角*/
+ var borderKeepItemRadius: Boolean = false
+
+ var borderBackgroundDrawable: Drawable? = null
+
+ /**宽度补偿*/
+ var borderBackgroundWidthOffset: Int = 0
+
+ /**高度补偿*/
+ var borderBackgroundHeightOffset: Int = 0
+
+ /**强制指定选中item的背景颜色*/
+ var borderItemBackgroundSolidColor: Int? = null
+
+ /**当item不可选中时的背景绘制颜色
+ * [com.angcyo.tablayout.DslTabLayout.itemEnableSelector]
+ * [borderItemBackgroundSolidColor]*/
+ var borderItemBackgroundSolidDisableColor: Int? = null
+
+ /**强制指定选中item的背景渐变颜色*/
+ var borderItemBackgroundGradientColors: IntArray? = null
+
+ override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
+ val typedArray =
+ context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+
+ val borderBackgroundColor =
+ typedArray.getColor(R.styleable.DslTabLayout_tab_border_solid_color, gradientSolidColor)
+
+ gradientStrokeColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_border_stroke_color,
+ gradientStrokeColor
+ )
+ gradientStrokeWidth = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_border_stroke_width,
+ 2 * dpi
+ )
+ val radiusSize =
+ typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_border_radius_size, 0)
+
+ cornerRadius(radiusSize.toFloat())
+
+ originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_border_drawable)
+
+ borderDrawItemBackground = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_border_draw_item_background,
+ borderDrawItemBackground
+ )
+
+ borderKeepItemRadius = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_border_keep_item_radius,
+ borderKeepItemRadius
+ )
+
+ borderBackgroundWidthOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_border_item_background_width_offset,
+ borderBackgroundWidthOffset
+ )
+
+ borderBackgroundHeightOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_border_item_background_height_offset,
+ borderBackgroundHeightOffset
+ )
+
+ //
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_color)) {
+ borderItemBackgroundSolidColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_border_item_background_solid_color,
+ gradientStrokeColor
+ )
+ }
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color)) {
+ borderItemBackgroundSolidDisableColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_border_item_background_solid_disable_color,
+ borderItemBackgroundSolidColor ?: gradientStrokeColor
+ )
+ }
+
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color) ||
+ typedArray.hasValue(R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color)
+ ) {
+ val startColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_border_item_background_gradient_start_color,
+ gradientStrokeColor
+ )
+ val endColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_border_item_background_gradient_end_color,
+ gradientStrokeColor
+ )
+ borderItemBackgroundGradientColors = intArrayOf(startColor, endColor)
+ }
+
+ typedArray.recycle()
+
+ if (originDrawable == null) {
+ //无自定义的drawable, 那么自绘.
+ borderBackgroundDrawable = DslGradientDrawable().configDrawable {
+ gradientSolidColor = borderBackgroundColor
+ gradientRadii = this@DslTabBorder.gradientRadii
+ }.originDrawable
+
+ updateOriginDrawable()
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ super.draw(canvas)
+
+ originDrawable?.apply {
+ setBounds(
+ paddingLeft,
+ paddingBottom,
+ viewWidth - paddingRight,
+ viewHeight - paddingBottom
+ )
+ draw(canvas)
+ }
+ }
+
+ fun drawBorderBackground(canvas: Canvas) {
+ super.draw(canvas)
+
+ borderBackgroundDrawable?.apply {
+ setBounds(
+ paddingLeft,
+ paddingBottom,
+ viewWidth - paddingRight,
+ viewHeight - paddingBottom
+ )
+ draw(canvas)
+ }
+ }
+
+ var itemSelectBgDrawable: Drawable? = null
+ var itemDeselectBgDrawable: Drawable? = null
+
+ /**开启边框绘制后, [itemView]的背景也需要负责设置*/
+ open fun updateItemBackground(
+ tabLayout: DslTabLayout,
+ itemView: View,
+ index: Int,
+ select: Boolean
+ ) {
+
+ if (!borderDrawItemBackground) {
+ return
+ }
+
+ if (select) {
+
+ val isFirst = index == 0
+ val isLast = index == tabLayout.dslSelector.visibleViewList.size - 1
+
+ val drawable = DslGradientDrawable().configDrawable {
+ gradientWidthOffset = borderBackgroundWidthOffset
+ gradientHeightOffset = borderBackgroundHeightOffset
+
+ gradientSolidColor =
+ borderItemBackgroundSolidColor ?: this@DslTabBorder.gradientStrokeColor
+ if (!tabLayout.itemEnableSelector) {
+ if (borderItemBackgroundSolidDisableColor != null) {
+ gradientSolidColor = borderItemBackgroundSolidDisableColor!!
+ }
+ }
+
+ gradientColors = borderItemBackgroundGradientColors
+
+ if ((isFirst && isLast) || borderKeepItemRadius) {
+ //只有一个child
+ gradientRadii = this@DslTabBorder.gradientRadii
+ } else if (isFirst) {
+ if (tabLayout.isHorizontal()) {
+ if (tabLayout.isLayoutRtl) {
+ gradientRadii = floatArrayOf(
+ 0f,
+ 0f,
+ this@DslTabBorder.gradientRadii[2],
+ this@DslTabBorder.gradientRadii[3],
+ this@DslTabBorder.gradientRadii[4],
+ this@DslTabBorder.gradientRadii[5],
+ 0f,
+ 0f
+ )
+ } else {
+ gradientRadii = floatArrayOf(
+ this@DslTabBorder.gradientRadii[0],
+ this@DslTabBorder.gradientRadii[1],
+ 0f,
+ 0f,
+ 0f,
+ 0f,
+ this@DslTabBorder.gradientRadii[6],
+ this@DslTabBorder.gradientRadii[7]
+ )
+ }
+ } else {
+ gradientRadii = floatArrayOf(
+ this@DslTabBorder.gradientRadii[0],
+ this@DslTabBorder.gradientRadii[1],
+ this@DslTabBorder.gradientRadii[2],
+ this@DslTabBorder.gradientRadii[3],
+ 0f,
+ 0f,
+ 0f,
+ 0f
+ )
+ }
+ } else if (isLast) {
+ if (tabLayout.isHorizontal()) {
+ if (tabLayout.isLayoutRtl) {
+ gradientRadii = floatArrayOf(
+ this@DslTabBorder.gradientRadii[0],
+ this@DslTabBorder.gradientRadii[1],
+ 0f,
+ 0f,
+ 0f,
+ 0f,
+ this@DslTabBorder.gradientRadii[6],
+ this@DslTabBorder.gradientRadii[7]
+ )
+ } else {
+ gradientRadii = floatArrayOf(
+ 0f,
+ 0f,
+ this@DslTabBorder.gradientRadii[2],
+ this@DslTabBorder.gradientRadii[3],
+ this@DslTabBorder.gradientRadii[4],
+ this@DslTabBorder.gradientRadii[5],
+ 0f,
+ 0f
+ )
+ }
+ } else {
+ gradientRadii = floatArrayOf(
+ 0f,
+ 0f,
+ 0f,
+ 0f,
+ this@DslTabBorder.gradientRadii[4],
+ this@DslTabBorder.gradientRadii[5],
+ this@DslTabBorder.gradientRadii[6],
+ this@DslTabBorder.gradientRadii[7]
+ )
+ }
+ }
+ }
+
+ itemSelectBgDrawable = drawable
+
+ ViewCompat.setBackground(itemView, itemSelectBgDrawable)
+ } else {
+ ViewCompat.setBackground(itemView, itemDeselectBgDrawable)
+ }
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
new file mode 100644
index 000000000..f0fe41c35
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
@@ -0,0 +1,153 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+/**
+ * 垂直分割线
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/27
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslTabDivider : DslGradientDrawable() {
+
+ var dividerWidth = 2 * dpi
+ var dividerHeight = 2 * dpi
+ var dividerMarginLeft = 0
+ var dividerMarginRight = 0
+ var dividerMarginTop = 0
+ var dividerMarginBottom = 0
+
+ /**
+ * [LinearLayout.SHOW_DIVIDER_BEGINNING]
+ * [LinearLayout.SHOW_DIVIDER_MIDDLE]
+ * [LinearLayout.SHOW_DIVIDER_END]
+ * */
+ var dividerShowMode = LinearLayout.SHOW_DIVIDER_MIDDLE
+
+ override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
+ super.initAttribute(context, attributeSet)
+ val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+
+ dividerWidth = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_width,
+ dividerWidth
+ )
+ dividerHeight = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_height,
+ dividerHeight
+ )
+ dividerMarginLeft = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_margin_left,
+ dividerMarginLeft
+ )
+ dividerMarginRight = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_margin_right,
+ dividerMarginRight
+ )
+ dividerMarginTop = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_margin_top,
+ dividerMarginTop
+ )
+ dividerMarginBottom = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_margin_bottom,
+ dividerMarginBottom
+ )
+
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_divider_solid_color)) {
+ gradientSolidColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_divider_solid_color,
+ gradientSolidColor
+ )
+ } else if (typedArray.hasValue(R.styleable.DslTabLayout_tab_border_stroke_color)) {
+ gradientSolidColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_border_stroke_color,
+ gradientSolidColor
+ )
+ } else {
+ gradientSolidColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_deselect_color,
+ gradientSolidColor
+ )
+ }
+
+ gradientStrokeColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_divider_stroke_color,
+ gradientStrokeColor
+ )
+ gradientStrokeWidth = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_stroke_width,
+ 0
+ )
+ val radiusSize =
+ typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_divider_radius_size,
+ 2 * dpi
+ )
+
+ cornerRadius(radiusSize.toFloat())
+
+ originDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_divider_drawable)
+
+ dividerShowMode =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_divider_show_mode, dividerShowMode)
+
+ typedArray.recycle()
+
+ if (originDrawable == null) {
+ //无自定义的drawable, 那么自绘.
+
+ updateOriginDrawable()
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ super.draw(canvas)
+
+ originDrawable?.apply {
+ bounds = this@DslTabDivider.bounds
+ draw(canvas)
+ }
+ }
+
+ val _tabLayout: DslTabLayout?
+ get() = if (callback is DslTabLayout) callback as DslTabLayout else null
+
+ /**
+ * [childIndex]位置前面是否需要分割线
+ * */
+ open fun haveBeforeDivider(childIndex: Int, childCount: Int): Boolean {
+ val tabLayout = _tabLayout
+ if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
+ if (childIndex == 0) {
+ return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
+ }
+ return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
+ }
+
+ if (childIndex == 0) {
+ return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
+ }
+ return dividerShowMode and LinearLayout.SHOW_DIVIDER_MIDDLE != 0
+ }
+
+ /**
+ * [childIndex]位置后面是否需要分割线
+ * */
+ open fun haveAfterDivider(childIndex: Int, childCount: Int): Boolean {
+ val tabLayout = _tabLayout
+ if (tabLayout != null && tabLayout.isHorizontal() && tabLayout.isLayoutRtl) {
+ if (childIndex == childCount - 1) {
+ return dividerShowMode and LinearLayout.SHOW_DIVIDER_BEGINNING != 0
+ }
+ }
+
+ if (childIndex == childCount - 1) {
+ return dividerShowMode and LinearLayout.SHOW_DIVIDER_END != 0
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
new file mode 100644
index 000000000..4c21bbd56
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
@@ -0,0 +1,118 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.view.ViewGroup
+
+/**
+ *
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2021/05/19
+ * Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
+ */
+open class DslTabHighlight(val tabLayout: DslTabLayout) : DslGradientDrawable() {
+
+ /**需要绘制的Drawable*/
+ var highlightDrawable: Drawable? = null
+
+ /**宽度测量模式*/
+ var highlightWidth = ViewGroup.LayoutParams.MATCH_PARENT
+
+ /**高度测量模式*/
+ var highlightHeight = ViewGroup.LayoutParams.MATCH_PARENT
+
+ /**宽度补偿*/
+ var highlightWidthOffset = 0
+
+ /**高度补偿*/
+ var highlightHeightOffset = 0
+
+ override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
+ //super.initAttribute(context, attributeSet)
+
+ val typedArray =
+ context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+ highlightDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_highlight_drawable)
+
+ highlightWidth = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_highlight_width,
+ highlightWidth
+ )
+ highlightHeight = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_highlight_height,
+ highlightHeight
+ )
+
+ highlightWidthOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_highlight_width_offset,
+ highlightWidthOffset
+ )
+ highlightHeightOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_highlight_height_offset,
+ highlightHeightOffset
+ )
+
+ typedArray.recycle()
+
+ if (highlightDrawable == null && isValidConfig()) {
+ updateOriginDrawable()
+ }
+ }
+
+ override fun updateOriginDrawable(): GradientDrawable? {
+ val drawable = super.updateOriginDrawable()
+ highlightDrawable = originDrawable
+ return drawable
+ }
+
+ override fun draw(canvas: Canvas) {
+ //super.draw(canvas)
+ val itemView = tabLayout.currentItemView
+ if (itemView != null) {
+ val lp = itemView.layoutParams
+
+ if (lp is DslTabLayout.LayoutParams) {
+ lp.highlightDrawable ?: highlightDrawable
+ } else {
+ highlightDrawable
+ }?.apply {
+
+ val drawWidth: Int = when (highlightWidth) {
+ ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredWidth
+ ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicWidth
+ else -> highlightWidth
+ } + highlightWidthOffset
+
+ val drawHeight: Int = when (highlightHeight) {
+ ViewGroup.LayoutParams.MATCH_PARENT -> itemView.measuredHeight
+ ViewGroup.LayoutParams.WRAP_CONTENT -> intrinsicHeight
+ else -> highlightHeight
+ } + highlightHeightOffset
+
+ val centerX: Int = itemView.left + (itemView.right - itemView.left) / 2
+ val centerY: Int = itemView.top + (itemView.bottom - itemView.top) / 2
+
+ setBounds(
+ centerX - drawWidth / 2,
+ centerY - drawHeight / 2,
+ centerX + drawWidth / 2,
+ centerY + drawHeight / 2
+ )
+
+ draw(canvas)
+ canvas.save()
+ if (tabLayout.isHorizontal()) {
+ canvas.translate(itemView.left.toFloat(), 0f)
+ } else {
+ canvas.translate(0f, itemView.top.toFloat())
+ }
+ itemView.draw(canvas)
+ canvas.restore()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
new file mode 100644
index 000000000..316ced0ad
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
@@ -0,0 +1,931 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.graphics.withSave
+import java.util.*
+import kotlin.math.absoluteValue
+import kotlin.math.max
+
+/**
+ * 指示器
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/25
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslTabIndicator(val tabLayout: DslTabLayout) : DslGradientDrawable() {
+
+ companion object {
+
+ /**非颜色值*/
+ const val NO_COLOR = -2
+
+ //---style---
+
+ /**不绘制指示器*/
+ const val INDICATOR_STYLE_NONE = 0
+
+ /**指示器绘制在[itemView]的顶部*/
+ const val INDICATOR_STYLE_TOP = 0x1
+
+ /**指示器绘制在[itemView]的底部*/
+ const val INDICATOR_STYLE_BOTTOM = 0x2
+
+ /**默认样式,指示器绘制在[itemView]的中心*/
+ const val INDICATOR_STYLE_CENTER = 0x4
+
+ /**前景绘制,
+ * 默认是背景绘制, 指示器绘制[itemView]的背部, [itemView] 请不要设置background, 否则可能看不见*/
+ const val INDICATOR_STYLE_FOREGROUND = 0x1000
+
+ //---gravity---
+
+ /**指示器重力在开始的位置(横向左边, 纵向上边)*/
+ const val INDICATOR_GRAVITY_START = 0x1
+
+ /**指示器重力在结束的位置(横向右边, 纵向下边)*/
+ const val INDICATOR_GRAVITY_END = 0x2
+
+ /**指示器重力在中间*/
+ const val INDICATOR_GRAVITY_CENTER = 0x4
+ }
+
+ /**指示器绘制的样式*/
+ var indicatorStyle = INDICATOR_STYLE_NONE //初始化
+
+ /**[indicatorStyle]*/
+ val _indicatorDrawStyle: Int
+ get() = indicatorStyle.remove(INDICATOR_STYLE_FOREGROUND)
+
+ /**优先将指示器显示在[DslTabLayout]的什么位置
+ * [INDICATOR_GRAVITY_START] 开始的位置
+ * [INDICATOR_GRAVITY_END] 结束的位置
+ * [INDICATOR_GRAVITY_CENTER] 中间的位置*/
+ var indicatorGravity = INDICATOR_GRAVITY_CENTER
+
+ /**
+ * 指示器在流向下一个位置时, 是否采用[Flow]流线的方式改变宽度
+ * */
+ var indicatorEnableFlow: Boolean = false
+
+ /**指示器闪现效果, 从当前位置直接跨越到目标位置*/
+ var indicatorEnableFlash: Boolean = false
+
+ /**使用clip的方式绘制闪现效果*/
+ var indicatorEnableFlashClip: Boolean = true
+
+ /**当目标和当前的索引差值<=此值时, [Flow]效果才有效*/
+ var indicatorFlowStep: Int = 1
+
+ /**指示器绘制实体*/
+ var indicatorDrawable: Drawable? = null
+ set(value) {
+ field = tintDrawableColor(value, indicatorColor)
+ }
+
+ /**过滤[indicatorDrawable]的颜色*/
+ var indicatorColor: Int = NO_COLOR
+ set(value) {
+ field = value
+ indicatorDrawable = indicatorDrawable
+ }
+
+ /**
+ * 指示器的宽度
+ * WRAP_CONTENT: [childView]内容的宽度,
+ * MATCH_PARENT: [childView]的宽度
+ * 40dp: 固定值
+ * */
+ var indicatorWidth = 0 //初始化
+
+ /**宽度补偿*/
+ var indicatorWidthOffset = 0
+
+ /**
+ * 指示器的高度
+ * WRAP_CONTENT: [childView]内容的高度,
+ * MATCH_PARENT: [childView]的高度
+ * 40dp: 固定值
+ * */
+ var indicatorHeight = 0 //初始化
+
+ /**高度补偿*/
+ var indicatorHeightOffset = 0
+
+ /**XY轴方向补偿*/
+ var indicatorXOffset = 0
+
+ /**会根据[indicatorStyle]自动取负值*/
+ var indicatorYOffset = 0
+
+ /**
+ * 宽高[WRAP_CONTENT]时, 内容view的定位索引
+ * */
+ var indicatorContentIndex = -1
+ var indicatorContentId = View.NO_ID
+
+ /**切换时是否需要动画的支持*/
+ var indicatorAnim = true
+
+ /**在获取锚点view的宽高时, 是否需要忽略对应的padding属性*/
+ var ignoreChildPadding: Boolean = true
+
+ init {
+ callback = tabLayout
+ }
+
+ override fun initAttribute(context: Context, attributeSet: AttributeSet?) {
+ val typedArray =
+ context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+
+ indicatorDrawable = typedArray.getDrawable(R.styleable.DslTabLayout_tab_indicator_drawable)
+ indicatorColor =
+ typedArray.getColor(R.styleable.DslTabLayout_tab_indicator_color, indicatorColor)
+ indicatorStyle = typedArray.getInt(
+ R.styleable.DslTabLayout_tab_indicator_style,
+ if (tabLayout.isHorizontal()) INDICATOR_STYLE_BOTTOM else INDICATOR_STYLE_TOP
+ )
+ indicatorGravity = typedArray.getInt(
+ R.styleable.DslTabLayout_tab_indicator_gravity,
+ indicatorGravity
+ )
+
+ //初始化指示器的高度和宽度
+ if (indicatorStyle.have(INDICATOR_STYLE_FOREGROUND)) {
+ //前景绘制
+ indicatorWidth = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_indicator_width,
+ if (tabLayout.isHorizontal()) ViewGroup.LayoutParams.MATCH_PARENT else 3 * dpi
+ )
+ indicatorHeight = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_indicator_height,
+ if (tabLayout.isHorizontal()) 3 * dpi else ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ indicatorXOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_x_offset,
+ if (tabLayout.isHorizontal()) 0 else 2 * dpi
+ )
+ indicatorYOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_y_offset,
+ if (tabLayout.isHorizontal()) 2 * dpi else 0
+ )
+ } else {
+ //背景绘制样式
+ if (tabLayout.isHorizontal()) {
+ indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
+ indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
+ } else {
+ indicatorHeight = ViewGroup.LayoutParams.MATCH_PARENT
+ indicatorWidth = ViewGroup.LayoutParams.MATCH_PARENT
+ }
+ indicatorWidth = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_indicator_width,
+ indicatorWidth
+ )
+ indicatorHeight = typedArray.getLayoutDimension(
+ R.styleable.DslTabLayout_tab_indicator_height,
+ indicatorHeight
+ )
+ indicatorXOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_x_offset,
+ indicatorXOffset
+ )
+ indicatorYOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_y_offset,
+ indicatorYOffset
+ )
+ }
+
+ ignoreChildPadding = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_indicator_ignore_child_padding,
+ !indicatorStyle.have(INDICATOR_STYLE_CENTER)
+ )
+
+ indicatorFlowStep =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_flow_step, indicatorFlowStep)
+ indicatorEnableFlow = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_indicator_enable_flow,
+ indicatorEnableFlow
+ )
+ indicatorEnableFlash = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_indicator_enable_flash,
+ indicatorEnableFlash
+ )
+ indicatorEnableFlashClip = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_indicator_enable_flash_clip,
+ indicatorEnableFlashClip
+ )
+
+ indicatorWidthOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_width_offset,
+ indicatorWidthOffset
+ )
+ indicatorHeightOffset = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_height_offset,
+ indicatorHeightOffset
+ )
+ indicatorContentIndex = typedArray.getInt(
+ R.styleable.DslTabLayout_tab_indicator_content_index,
+ indicatorContentIndex
+ )
+ indicatorContentId = typedArray.getResourceId(
+ R.styleable.DslTabLayout_tab_indicator_content_id,
+ indicatorContentId
+ )
+ indicatorAnim = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_indicator_anim,
+ indicatorAnim
+ )
+
+ //代码构建Drawable
+ gradientShape =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_indicator_shape, gradientShape)
+ gradientSolidColor =
+ typedArray.getColor(
+ R.styleable.DslTabLayout_tab_indicator_solid_color,
+ gradientSolidColor
+ )
+ gradientStrokeColor =
+ typedArray.getColor(
+ R.styleable.DslTabLayout_tab_indicator_stroke_color,
+ gradientStrokeColor
+ )
+ gradientStrokeWidth = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_stroke_width,
+ gradientStrokeWidth
+ )
+ gradientDashWidth = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_dash_width,
+ gradientDashWidth.toInt()
+ ).toFloat()
+ gradientDashGap = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_indicator_dash_gap,
+ gradientDashGap.toInt()
+ ).toFloat()
+
+ val gradientRadius =
+ typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_indicator_radius, 0)
+ if (gradientRadius > 0) {
+ Arrays.fill(gradientRadii, gradientRadius.toFloat())
+ } else {
+ typedArray.getString(R.styleable.DslTabLayout_tab_indicator_radii)?.let {
+ _fillRadii(gradientRadii, it)
+ }
+ }
+
+ val gradientColors =
+ typedArray.getString(R.styleable.DslTabLayout_tab_indicator_gradient_colors)
+
+ this.gradientColors = if (gradientColors.isNullOrEmpty()) {
+ val startColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_indicator_gradient_start_color,
+ Color.TRANSPARENT
+ )
+ val endColor = typedArray.getColor(
+ R.styleable.DslTabLayout_tab_indicator_gradient_end_color,
+ Color.TRANSPARENT
+ )
+ if (startColor != endColor) {
+ intArrayOf(startColor, endColor)
+ } else {
+ this.gradientColors
+ }
+ } else {
+ _fillColor(gradientColors) ?: this.gradientColors
+ }
+ //...end
+
+ typedArray.recycle()
+
+ if (indicatorDrawable == null && isValidConfig()) {
+ updateOriginDrawable()
+ }
+ }
+
+ override fun updateOriginDrawable(): GradientDrawable? {
+ val drawable = super.updateOriginDrawable()
+ indicatorDrawable = originDrawable
+ return drawable
+ }
+
+ open fun tintDrawableColor(drawable: Drawable?, color: Int): Drawable? {
+ if (drawable == null || color == NO_COLOR) {
+ return drawable
+ }
+ return drawable.tintDrawableColor(color)
+ }
+
+ /**指示器需要参考的目标控件*/
+ open fun indicatorContentView(childView: View): View? {
+ val lp = childView.layoutParams as DslTabLayout.LayoutParams
+
+ val contentId =
+ if (lp.indicatorContentId != View.NO_ID) lp.indicatorContentId else indicatorContentId
+
+ if (contentId != View.NO_ID) {
+ return childView.findViewById(contentId)
+ }
+
+ //如果child强制指定了index, 就用指定的.
+ val contentIndex =
+ if (lp.indicatorContentIndex >= 0) lp.indicatorContentIndex else indicatorContentIndex
+
+ return if (contentIndex >= 0 && childView is ViewGroup && contentIndex in 0 until childView.childCount) {
+ //有指定
+ val contentChildView = childView.getChildAt(contentIndex)
+ contentChildView
+ } else {
+ //没有指定
+ null
+ }
+ }
+
+ /**根据指定[index]索引, 获取目标[View]*/
+ open fun targetChildView(
+ index: Int,
+ onChildView: (childView: View, contentChildView: View?) -> Unit
+ ) {
+ tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
+ onChildView(childView, indicatorContentView(childView))
+ }
+ }
+
+ open fun getChildTargetPaddingLeft(childView: View): Int =
+ if (ignoreChildPadding) childView.paddingLeft else 0
+
+ open fun getChildTargetPaddingRight(childView: View): Int =
+ if (ignoreChildPadding) childView.paddingRight else 0
+
+ open fun getChildTargetPaddingTop(childView: View): Int =
+ if (ignoreChildPadding) childView.paddingTop else 0
+
+ open fun getChildTargetPaddingBottom(childView: View): Int =
+ if (ignoreChildPadding) childView.paddingBottom else 0
+
+ open fun getChildTargetWidth(childView: View): Int =
+ if (ignoreChildPadding) childView.viewDrawWidth else childView.measuredWidth
+
+ open fun getChildTargetHeight(childView: View): Int =
+ if (ignoreChildPadding) childView.viewDrawHeight else childView.measuredHeight
+
+ /**
+ * [childview]对应的中心x坐标
+ * */
+ open fun getChildTargetX(index: Int, gravity: Int = indicatorGravity): Int {
+
+ var result = if (index > 0) tabLayout.maxWidth else 0
+
+ targetChildView(index) { childView, contentChildView ->
+ result = if (contentChildView == null) {
+ when (gravity) {
+ INDICATOR_GRAVITY_START -> childView.left
+ INDICATOR_GRAVITY_END -> childView.right
+ else -> childView.left + getChildTargetPaddingLeft(childView) + getChildTargetWidth(
+ childView
+ ) / 2
+ }
+ } else {
+ when (gravity) {
+ INDICATOR_GRAVITY_START -> childView.left + contentChildView.left
+ INDICATOR_GRAVITY_END -> childView.left + contentChildView.right
+ else -> childView.left + contentChildView.left + getChildTargetPaddingLeft(
+ contentChildView
+ ) + getChildTargetWidth(
+ contentChildView
+ ) / 2
+ }
+ }
+ }
+
+ return result
+ }
+
+ open fun getChildTargetY(index: Int, gravity: Int = indicatorGravity): Int {
+
+ var result = if (index > 0) tabLayout.maxHeight else 0
+
+ targetChildView(index) { childView, contentChildView ->
+ result = if (contentChildView == null) {
+ when (gravity) {
+ INDICATOR_GRAVITY_START -> childView.top
+ INDICATOR_GRAVITY_END -> childView.bottom
+ else -> childView.top + getChildTargetPaddingTop(childView) + getChildTargetHeight(
+ childView
+ ) / 2
+ }
+ } else {
+ when (gravity) {
+ INDICATOR_GRAVITY_START -> childView.top + contentChildView.top
+ INDICATOR_GRAVITY_END -> childView.top + childView.bottom
+ else -> childView.top + contentChildView.top + getChildTargetPaddingTop(
+ contentChildView
+ ) + getChildTargetHeight(
+ contentChildView
+ ) / 2
+ }
+ }
+ }
+
+ return result
+ }
+
+ open fun getIndicatorDrawWidth(index: Int): Int {
+ var result = indicatorWidth
+
+ when (indicatorWidth) {
+ ViewGroup.LayoutParams.WRAP_CONTENT -> {
+ tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
+ result = getChildTargetWidth(indicatorContentView(childView) ?: childView)
+ }
+ }
+ ViewGroup.LayoutParams.MATCH_PARENT -> {
+ tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
+ result = childView.measuredWidth
+ }
+ }
+ }
+
+ return result + indicatorWidthOffset
+ }
+
+ open fun getIndicatorDrawHeight(index: Int): Int {
+ var result = indicatorHeight
+
+ when (indicatorHeight) {
+ ViewGroup.LayoutParams.WRAP_CONTENT -> {
+ tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
+ result = getChildTargetHeight(indicatorContentView(childView) ?: childView)
+ }
+ }
+ ViewGroup.LayoutParams.MATCH_PARENT -> {
+ tabLayout.dslSelector.visibleViewList.getOrNull(index)?.also { childView ->
+ result = childView.measuredHeight
+ }
+ }
+ }
+
+ return result + indicatorHeightOffset
+ }
+
+ override fun draw(canvas: Canvas) {
+ //super.draw(canvas)
+ if (!isVisible || _indicatorDrawStyle == INDICATOR_STYLE_NONE || indicatorDrawable == null) {
+ //不绘制
+ return
+ }
+
+ if (tabLayout.isHorizontal()) {
+ drawHorizontal(canvas)
+ } else {
+ drawVertical(canvas)
+ }
+ }
+
+ fun drawHorizontal(canvas: Canvas) {
+ val childSize = tabLayout.dslSelector.visibleViewList.size
+
+ var currentIndex = currentIndex
+
+ if (_targetIndex in 0 until childSize) {
+ currentIndex = max(0, currentIndex)
+ }
+
+ if (currentIndex in 0 until childSize) {
+
+ } else {
+ //无效的index
+ return
+ }
+
+ //"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
+
+ val drawTargetX = getChildTargetX(currentIndex)
+ val drawWidth = getIndicatorDrawWidth(currentIndex)
+ val drawHeight = getIndicatorDrawHeight(currentIndex)
+
+ val drawLeft = drawTargetX - drawWidth / 2 + indicatorXOffset
+
+ //动画过程中的left
+ var animLeft = drawLeft
+ //width
+ var animWidth = drawWidth
+ //动画执行过程中, 高度额外变大的值
+ var animExHeight = 0
+
+ //end value
+ val nextDrawTargetX = getChildTargetX(_targetIndex)
+ val nextDrawWidth = getIndicatorDrawWidth(_targetIndex)
+ val nextDrawLeft = nextDrawTargetX - nextDrawWidth / 2 + indicatorXOffset
+
+ var animEndWidth = nextDrawWidth
+ var animEndLeft = nextDrawLeft
+
+ if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
+
+ //动画过程参数计算变量
+ val animStartLeft = drawLeft
+ val animStartWidth = drawWidth
+
+ val animEndHeight = getIndicatorDrawHeight(_targetIndex)
+
+ if (indicatorEnableFlash) {
+ //闪现效果
+ animWidth = (animWidth * (1 - positionOffset)).toInt()
+ animEndWidth = (animEndWidth * positionOffset).toInt()
+
+ animLeft = drawTargetX - animWidth / 2 + indicatorXOffset
+ animEndLeft = nextDrawLeft
+ } else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
+ //激活了流动效果
+
+ val flowEndWidth: Int
+ if (_targetIndex > currentIndex) {
+ flowEndWidth = animEndLeft - animStartLeft + animEndWidth
+
+ //目标在右边
+ animLeft = if (positionOffset >= 0.5) {
+ (animStartLeft + (animEndLeft - animStartLeft) * (positionOffset - 0.5) / 0.5f).toInt()
+ } else {
+ animStartLeft
+ }
+ } else {
+ flowEndWidth = animStartLeft - animEndLeft + animStartWidth
+
+ //目标在左边
+ animLeft = if (positionOffset >= 0.5) {
+ animEndLeft
+ } else {
+ (animStartLeft - (animStartLeft - animEndLeft) * positionOffset / 0.5f).toInt()
+ }
+ }
+
+ animWidth = if (positionOffset >= 0.5) {
+ (flowEndWidth - (flowEndWidth - animEndWidth) * (positionOffset - 0.5) / 0.5f).toInt()
+ } else {
+ (animStartWidth + (flowEndWidth - animStartWidth) * positionOffset / 0.5f).toInt()
+ }
+ } else {
+ //默认平移效果
+ if (_targetIndex > currentIndex) {
+ //目标在右边
+ animLeft =
+ (animStartLeft + (animEndLeft - animStartLeft) * positionOffset).toInt()
+ } else {
+ //目标在左边
+ animLeft =
+ (animStartLeft - (animStartLeft - animEndLeft) * positionOffset).toInt()
+ }
+
+ //动画过程中的宽度
+ animWidth =
+ (animStartWidth + (animEndWidth - animStartWidth) * positionOffset).toInt()
+ }
+
+ animExHeight = ((animEndHeight - drawHeight) * positionOffset).toInt()
+ }
+
+ //前景
+ val drawTop = when (_indicatorDrawStyle) {
+ //底部绘制
+ INDICATOR_STYLE_BOTTOM -> viewHeight - drawHeight - indicatorYOffset
+ //顶部绘制
+ INDICATOR_STYLE_TOP -> 0 + indicatorYOffset
+ //居中绘制
+ else -> paddingTop + viewDrawHeight / 2 - drawHeight / 2 + indicatorYOffset -
+ animExHeight +
+ (tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
+ }
+
+ indicatorDrawable?.apply {
+ if (indicatorEnableFlash) {
+ //flash
+ if (indicatorEnableFlashClip) {
+ drawIndicatorClipHorizontal(
+ this,
+ canvas,
+ drawLeft,
+ drawTop,
+ drawLeft + drawWidth,
+ drawTop + drawHeight + animExHeight,
+ animWidth,
+ 1 - positionOffset
+ )
+ } else {
+ drawIndicator(
+ this, canvas, animLeft,
+ drawTop,
+ animLeft + animWidth,
+ drawTop + drawHeight + animExHeight,
+ 1 - positionOffset
+ )
+ }
+
+ if (_targetIndex in 0 until childSize) {
+ if (indicatorEnableFlashClip) {
+ drawIndicatorClipHorizontal(
+ this,
+ canvas,
+ nextDrawLeft,
+ drawTop,
+ nextDrawLeft + nextDrawWidth,
+ drawTop + drawHeight + animExHeight,
+ animEndWidth,
+ positionOffset
+ )
+ } else {
+ drawIndicator(
+ this, canvas, animEndLeft,
+ drawTop,
+ animEndLeft + animEndWidth,
+ drawTop + drawHeight + animExHeight,
+ positionOffset
+ )
+ }
+ }
+ } else {
+ //normal
+ drawIndicator(
+ this, canvas, animLeft,
+ drawTop,
+ animLeft + animWidth,
+ drawTop + drawHeight + animExHeight,
+ 1 - positionOffset
+ )
+ }
+ }
+ }
+
+ fun drawIndicator(
+ indicator: Drawable,
+ canvas: Canvas,
+ l: Int,
+ t: Int,
+ r: Int,
+ b: Int,
+ offset: Float
+ ) {
+ indicator.apply {
+ if (this is ITabIndicatorDraw) {
+ setBounds(l, t, r, b)
+ onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
+ } else {
+ val width = r - l
+ val height = b - t
+ setBounds(0, 0, width, height)
+ canvas.withSave {
+ translate(l.toFloat(), t.toFloat())
+ draw(canvas)
+ }
+ }
+ }
+ }
+
+ fun drawIndicatorClipHorizontal(
+ indicator: Drawable,
+ canvas: Canvas,
+ l: Int,
+ t: Int,
+ r: Int,
+ b: Int,
+ endWidth: Int,
+ offset: Float
+ ) {
+ indicator.apply {
+ canvas.save()
+ val dx = (r - l - endWidth) / 2
+ canvas.clipRect(l + dx, t, r - dx, b)
+ setBounds(l, t, r, b)
+ if (this is ITabIndicatorDraw) {
+ onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
+ } else {
+ draw(canvas)
+ }
+ canvas.restore()
+ }
+ }
+
+ fun drawIndicatorClipVertical(
+ indicator: Drawable,
+ canvas: Canvas,
+ l: Int,
+ t: Int,
+ r: Int,
+ b: Int,
+ endHeight: Int,
+ offset: Float
+ ) {
+ indicator.apply {
+ canvas.save()
+ val dy = (b - t - endHeight) / 2
+ canvas.clipRect(l, t + dy, r, b - dy)
+ setBounds(l, t, r, b)
+ if (this is ITabIndicatorDraw) {
+ onDrawTabIndicator(this@DslTabIndicator, canvas, offset)
+ } else {
+ draw(canvas)
+ }
+ canvas.restore()
+ }
+ }
+
+ fun drawVertical(canvas: Canvas) {
+ val childSize = tabLayout.dslSelector.visibleViewList.size
+
+ var currentIndex = currentIndex
+
+ if (_targetIndex in 0 until childSize) {
+ currentIndex = max(0, currentIndex)
+ }
+
+ if (currentIndex in 0 until childSize) {
+
+ } else {
+ //无效的index
+ return
+ }
+
+ //"绘制$currentIndex:$currentSelectIndex $positionOffset".logi()
+
+ val drawTargetY = getChildTargetY(currentIndex)
+ val drawWidth = getIndicatorDrawWidth(currentIndex)
+ val drawHeight = getIndicatorDrawHeight(currentIndex)
+
+ val drawTop = drawTargetY - drawHeight / 2 + indicatorYOffset
+
+ //动画过程中的top
+ var animTop = drawTop
+ //height
+ var animHeight = drawHeight
+ //动画执行过程中, 宽度额外变大的值
+ var animExWidth = 0
+
+ //end value
+ val nextDrawTargetY = getChildTargetY(_targetIndex)
+ val nextDrawHeight = getIndicatorDrawHeight(_targetIndex)
+ val nextDrawTop = nextDrawTargetY - nextDrawHeight / 2 + indicatorYOffset
+
+ var animEndHeight = nextDrawHeight
+ var animEndTop = nextDrawTop
+
+ if (_targetIndex in 0 until childSize && _targetIndex != currentIndex) {
+
+ //动画过程参数计算变量
+ val animStartTop = drawTop
+ val animStartHeight = drawHeight
+
+ val animEndWidth = getIndicatorDrawWidth(_targetIndex)
+
+ if (indicatorEnableFlash) {
+ //闪现效果
+ animHeight = (animHeight * (1 - positionOffset)).toInt()
+ animEndHeight = (animEndHeight * positionOffset).toInt()
+
+ animTop = drawTargetY - animHeight / 2 + indicatorXOffset
+ animEndTop = nextDrawTargetY - animEndHeight / 2 + indicatorXOffset
+ } else if (indicatorEnableFlow && (_targetIndex - currentIndex).absoluteValue <= indicatorFlowStep) {
+ //激活了流动效果
+
+ val flowEndHeight: Int
+ if (_targetIndex > currentIndex) {
+ flowEndHeight = animEndTop - animStartTop + animEndHeight
+
+ //目标在下边
+ animTop = if (positionOffset >= 0.5) {
+ (animStartTop + (animEndTop - animStartTop) * (positionOffset - 0.5) / 0.5f).toInt()
+ } else {
+ animStartTop
+ }
+ } else {
+ flowEndHeight = animStartTop - animEndTop + animStartHeight
+
+ //目标在上边
+ animTop = if (positionOffset >= 0.5) {
+ animEndTop
+ } else {
+ (animStartTop - (animStartTop - animEndTop) * positionOffset / 0.5f).toInt()
+ }
+ }
+
+ animHeight = if (positionOffset >= 0.5) {
+ (flowEndHeight - (flowEndHeight - animEndHeight) * (positionOffset - 0.5) / 0.5f).toInt()
+ } else {
+ (animStartHeight + (flowEndHeight - animStartHeight) * positionOffset / 0.5f).toInt()
+ }
+ } else {
+ if (_targetIndex > currentIndex) {
+ //目标在下边
+ animTop = (animStartTop + (animEndTop - animStartTop) * positionOffset).toInt()
+ } else {
+ //目标在上边
+ animTop = (animStartTop - (animStartTop - animEndTop) * positionOffset).toInt()
+ }
+
+ //动画过程中的宽度
+ animHeight =
+ (animStartHeight + (animEndHeight - animStartHeight) * positionOffset).toInt()
+ }
+
+ animExWidth = ((animEndWidth - drawWidth) * positionOffset).toInt()
+ }
+
+ val drawLeft = when (_indicatorDrawStyle) {
+ INDICATOR_STYLE_BOTTOM -> {
+ //右边/底部绘制
+ viewWidth - drawWidth - indicatorXOffset
+ }
+ INDICATOR_STYLE_TOP -> {
+ //左边/顶部绘制
+ 0 + indicatorXOffset
+ }
+ else -> {
+ //居中绘制
+ paddingLeft + indicatorXOffset + (viewDrawWidth / 2 - drawWidth / 2) -
+ (tabLayout._maxConvexHeight - _childConvexHeight(currentIndex)) / 2
+ }
+ }
+
+ indicatorDrawable?.apply {
+ //flash
+ if (indicatorEnableFlash) {
+ if (indicatorEnableFlashClip) {
+ drawIndicatorClipVertical(
+ this, canvas, drawLeft,
+ drawTop,
+ drawLeft + drawWidth + animExWidth,
+ drawTop + drawHeight,
+ animHeight,
+ 1 - positionOffset
+ )
+ } else {
+ drawIndicator(
+ this, canvas, drawLeft,
+ animTop,
+ drawLeft + drawWidth + animExWidth,
+ animTop + animHeight,
+ 1 - positionOffset
+ )
+ }
+
+ if (_targetIndex in 0 until childSize) {
+ if (indicatorEnableFlashClip) {
+ drawIndicatorClipVertical(
+ this, canvas, drawLeft,
+ nextDrawTop,
+ drawLeft + drawWidth + animExWidth,
+ nextDrawTop + nextDrawHeight,
+ animEndHeight,
+ positionOffset
+ )
+ } else {
+ drawIndicator(
+ this, canvas, drawLeft,
+ animEndTop,
+ drawLeft + drawWidth + animExWidth,
+ animEndTop + animEndHeight,
+ positionOffset
+ )
+ }
+ }
+ } else {
+ drawIndicator(
+ this, canvas, drawLeft,
+ animTop,
+ drawLeft + drawWidth + animExWidth,
+ animTop + animHeight,
+ 1 - positionOffset
+ )
+ }
+ }
+ }
+
+ fun _childConvexHeight(index: Int): Int {
+ if (attachView is ViewGroup) {
+ ((attachView as ViewGroup).getChildAt(index).layoutParams as? DslTabLayout.LayoutParams)?.apply {
+ return layoutConvexHeight
+ }
+ }
+ return 0
+ }
+
+ /**
+ * 距离[_targetIndex]的偏移比例.[0->1]的过程
+ * */
+ var positionOffset: Float = 0f
+ set(value) {
+ field = value
+ invalidateSelf()
+ }
+
+ /**当前绘制的index*/
+ var currentIndex: Int = -1
+
+ /**滚动目标的index*/
+ var _targetIndex = -1
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
new file mode 100644
index 000000000..87bac0550
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
@@ -0,0 +1,2043 @@
+package com.angcyo.tablayout
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.view.*
+import android.view.animation.LinearInterpolator
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.OverScroller
+import android.widget.TextView
+import androidx.core.view.GestureDetectorCompat
+import androidx.core.view.GravityCompat
+import androidx.core.view.ViewCompat
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * https://github.com/angcyo/DslTabLayout
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/23
+ */
+
+open class DslTabLayout(
+ context: Context,
+ val attributeSet: AttributeSet? = null
+) : ViewGroup(context, attributeSet) {
+
+ /**在未指定[minHeight]的[wrap_content]情况下的高度*/
+ var itemDefaultHeight = 40 * dpi
+
+ /**item是否等宽*/
+ var itemIsEquWidth = false
+
+ /**item是否支持选择, 只限制点击事件, 不限制滚动事件*/
+ var itemEnableSelector = true
+
+ /**当子Item数量在此范围内时,开启等宽,此属性优先级最高
+ * [~3] 小于等于3个
+ * [3~] 大于等于3个
+ * [3~5] 3<= <=5
+ * */
+ var itemEquWidthCountRange: IntRange? = null
+
+ /**智能判断Item是否等宽.
+ * 如果所有子项, 未撑满tab时, 则开启等宽模式.此属性会覆盖[itemIsEquWidth]*/
+ var itemAutoEquWidth = false
+
+ /**在等宽的情况下, 指定item的宽度, 小于0, 平分*/
+ var itemWidth = -3
+
+ /**是否绘制指示器*/
+ var drawIndicator = true
+
+ /**指示器*/
+ var tabIndicator: DslTabIndicator = DslTabIndicator(this)
+ set(value) {
+ field = value
+ field.initAttribute(context, attributeSet)
+ }
+
+ /**指示器动画时长*/
+ var tabIndicatorAnimationDuration = 240L
+
+ /**默认选中位置*/
+ var tabDefaultIndex = 0
+
+ /**回调监听器和样式配置器*/
+ var tabLayoutConfig: DslTabLayoutConfig? = null
+ set(value) {
+ field = value
+
+ field?.initAttribute(context, attributeSet)
+ }
+
+ /**边框绘制*/
+ var tabBorder: DslTabBorder? = null
+ set(value) {
+ field = value
+ field?.callback = this
+ field?.initAttribute(context, attributeSet)
+ }
+ var drawBorder = false
+
+ /**垂直分割线*/
+ var tabDivider: DslTabDivider? = null
+ set(value) {
+ field = value
+ field?.callback = this
+ field?.initAttribute(context, attributeSet)
+ }
+ var drawDivider = false
+
+ /**未读数角标*/
+ var tabBadge: DslTabBadge? = null
+ set(value) {
+ field = value
+ field?.callback = this
+ field?.initAttribute(context, attributeSet)
+ }
+ var drawBadge = false
+
+ /**快速角标配置项, 方便使用者*/
+ val tabBadgeConfigMap = mutableMapOf()
+
+ /**角标绘制配置*/
+ var onTabBadgeConfig: (child: View, tabBadge: DslTabBadge, index: Int) -> TabBadgeConfig? =
+ { _, tabBadge, index ->
+ val badgeConfig = getBadgeConfig(index)
+ if (!isInEditMode) {
+ tabBadge.updateBadgeConfig(badgeConfig)
+ }
+ badgeConfig
+ }
+
+ /**是否绘制突出*/
+ var drawHighlight = false
+
+ /**选中突出提示*/
+ var tabHighlight: DslTabHighlight? = null
+ set(value) {
+ field = value
+ field?.callback = this
+ field?.initAttribute(context, attributeSet)
+ }
+
+ /**如果使用了高凸模式. 请使用这个属性设置背景色*/
+ var tabConvexBackgroundDrawable: Drawable? = null
+
+ /**是否激活滑动选择模式*/
+ var tabEnableSelectorMode = false
+
+ /**布局的方向*/
+ var orientation: Int = LinearLayout.HORIZONTAL
+
+ /**布局时, 滚动到居中是否需要动画*/
+ var layoutScrollAnim: Boolean = false
+
+ /**滚动动画的时长*/
+ var scrollAnimDuration = 250
+
+ //
+
+ //fling 速率阈值
+ var _minFlingVelocity = 0
+ var _maxFlingVelocity = 0
+
+ //scroll 阈值
+ var _touchSlop = 0
+
+ //临时变量
+ val _tempRect = Rect()
+
+ //childView选择器
+ val dslSelector: DslSelector by lazy {
+ DslSelector().install(this) {
+ onStyleItemView = { itemView, index, select ->
+ tabLayoutConfig?.onStyleItemView?.invoke(itemView, index, select)
+ }
+ onSelectItemView = { itemView, index, select, fromUser ->
+ tabLayoutConfig?.onSelectItemView?.invoke(itemView, index, select, fromUser)
+ ?: false
+ }
+ onSelectViewChange = { fromView, selectViewList, reselect, fromUser ->
+ tabLayoutConfig?.onSelectViewChange?.invoke(
+ fromView,
+ selectViewList,
+ reselect,
+ fromUser
+ )
+ }
+ onSelectIndexChange = { fromIndex, selectList, reselect, fromUser ->
+ if (tabLayoutConfig == null) {
+ "选择:[$fromIndex]->${selectList} reselect:$reselect fromUser:$fromUser".logi()
+ }
+
+ val toIndex = selectList.lastOrNull() ?: -1
+ _animateToItem(fromIndex, toIndex)
+
+ _scrollToTarget(toIndex, tabIndicator.indicatorAnim)
+ postInvalidate()
+
+ //如果设置[tabLayoutConfig?.onSelectIndexChange], 那么会覆盖[_viewPagerDelegate]的操作.
+ tabLayoutConfig?.onSelectIndexChange?.invoke(
+ fromIndex,
+ selectList,
+ reselect,
+ fromUser
+ ) ?: _viewPagerDelegate?.onSetCurrentItem(fromIndex, toIndex, reselect, fromUser)
+ }
+ }
+ }
+
+ init {
+ val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+ itemIsEquWidth =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_item_is_equ_width, itemIsEquWidth)
+ val maxEquWidthCount =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_item_equ_width_count, -1)
+ if (maxEquWidthCount >= 0) {
+ itemEquWidthCountRange = IntRange(maxEquWidthCount, Int.MAX_VALUE)
+ }
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_item_equ_width_count_range)) {
+ val equWidthCountRangeString =
+ typedArray.getString(R.styleable.DslTabLayout_tab_item_equ_width_count_range)
+ if (equWidthCountRangeString.isNullOrBlank()) {
+ itemEquWidthCountRange = null
+ } else {
+ val rangeList = equWidthCountRangeString.split("~")
+ if (rangeList.size() >= 2) {
+ val min = rangeList.getOrNull(0)?.toIntOrNull() ?: 0
+ val max = rangeList.getOrNull(1)?.toIntOrNull() ?: Int.MAX_VALUE
+ itemEquWidthCountRange = IntRange(min, max)
+ } else {
+ val min = rangeList.getOrNull(0)?.toIntOrNull() ?: Int.MAX_VALUE
+ itemEquWidthCountRange = IntRange(min, Int.MAX_VALUE)
+ }
+ }
+ }
+ itemAutoEquWidth = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_item_auto_equ_width,
+ itemAutoEquWidth
+ )
+ itemWidth =
+ typedArray.getDimensionPixelOffset(R.styleable.DslTabLayout_tab_item_width, itemWidth)
+ itemDefaultHeight = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_item_default_height,
+ itemDefaultHeight
+ )
+ tabDefaultIndex =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_default_index, tabDefaultIndex)
+
+ drawIndicator =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_draw_indicator, drawIndicator)
+ drawDivider =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_draw_divider, drawDivider)
+ drawBorder =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_draw_border, drawBorder)
+ drawBadge =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_draw_badge, drawBadge)
+ drawHighlight =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_draw_highlight, drawHighlight)
+
+ tabEnableSelectorMode =
+ typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_selector_mode,
+ tabEnableSelectorMode
+ )
+
+ tabConvexBackgroundDrawable =
+ typedArray.getDrawable(R.styleable.DslTabLayout_tab_convex_background)
+
+ orientation = typedArray.getInt(R.styleable.DslTabLayout_tab_orientation, orientation)
+
+ layoutScrollAnim =
+ typedArray.getBoolean(R.styleable.DslTabLayout_tab_layout_scroll_anim, layoutScrollAnim)
+ scrollAnimDuration =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_scroll_anim_duration, scrollAnimDuration)
+
+ //preview
+ if (isInEditMode) {
+ val layoutId =
+ typedArray.getResourceId(R.styleable.DslTabLayout_tab_preview_item_layout_id, -1)
+ val layoutCount =
+ typedArray.getInt(R.styleable.DslTabLayout_tab_preview_item_count, 3)
+ if (layoutId != -1) {
+ for (i in 0 until layoutCount) {
+ inflate(layoutId, true).let {
+ if (it is TextView) {
+ if (it.text.isNullOrEmpty()) {
+ it.text = "Item $i"
+ } else {
+ it.text = "${it.text}/$i"
+ }
+ }
+ }
+ }
+ }
+ }
+
+ typedArray.recycle()
+
+ val vc = ViewConfiguration.get(context)
+ _minFlingVelocity = vc.scaledMinimumFlingVelocity
+ _maxFlingVelocity = vc.scaledMaximumFlingVelocity
+ //_touchSlop = vc.scaledTouchSlop
+
+ if (drawIndicator) {
+ //直接初始化的变量, 不会触发set方法.
+ tabIndicator.initAttribute(context, attributeSet)
+ }
+
+ if (drawBorder) {
+ tabBorder = DslTabBorder()
+ }
+ if (drawDivider) {
+ tabDivider = DslTabDivider()
+ }
+ if (drawBadge) {
+ tabBadge = DslTabBadge()
+ }
+ if (drawHighlight) {
+ tabHighlight = DslTabHighlight(this)
+ }
+
+ //样式配置器
+ tabLayoutConfig = DslTabLayoutConfig(this)
+
+ //开启绘制
+ setWillNotDraw(false)
+ }
+
+ //
+
+ //
+
+ /**当前选中item的索引*/
+ val currentItemIndex: Int
+ get() = dslSelector.dslSelectIndex
+
+ /**当前选中的itemView*/
+ val currentItemView: View?
+ get() = dslSelector.visibleViewList.getOrNull(currentItemIndex)
+
+ /**设置tab的位置*/
+ fun setCurrentItem(index: Int, notify: Boolean = true, fromUser: Boolean = false) {
+ if (currentItemIndex == index) {
+ _scrollToTarget(index, tabIndicator.indicatorAnim)
+ return
+ }
+ dslSelector.selector(index, true, notify, fromUser)
+ }
+
+ /**关联[ViewPagerDelegate]*/
+ fun setupViewPager(viewPagerDelegate: ViewPagerDelegate) {
+ _viewPagerDelegate = viewPagerDelegate
+ }
+
+ /**配置一个新的[DslTabLayoutConfig]给[DslTabLayout]*/
+ fun setTabLayoutConfig(
+ config: DslTabLayoutConfig = DslTabLayoutConfig(this),
+ doIt: DslTabLayoutConfig.() -> Unit = {}
+ ) {
+ tabLayoutConfig = config
+ configTabLayoutConfig(doIt)
+ }
+
+ /**配置[DslTabLayoutConfig]*/
+ fun configTabLayoutConfig(config: DslTabLayoutConfig.() -> Unit = {}) {
+ if (tabLayoutConfig == null) {
+ tabLayoutConfig = DslTabLayoutConfig(this)
+ }
+ tabLayoutConfig?.config()
+ dslSelector.updateStyle()
+ }
+
+ /**观察index的改变回调*/
+ fun observeIndexChange(
+ config: DslTabLayoutConfig.() -> Unit = {},
+ action: (fromIndex: Int, toIndex: Int, reselect: Boolean, fromUser: Boolean) -> Unit
+ ) {
+ configTabLayoutConfig {
+ config()
+ onSelectIndexChange = { fromIndex, selectIndexList, reselect, fromUser ->
+ action(fromIndex, selectIndexList.firstOrNull() ?: -1, reselect, fromUser)
+ }
+ }
+ }
+
+ fun getBadgeConfig(index: Int): TabBadgeConfig {
+ return tabBadgeConfigMap.getOrElse(index) {
+ tabBadge?.defaultBadgeConfig?.copy() ?: TabBadgeConfig()
+ }
+ }
+
+ fun updateTabBadge(index: Int, badgeText: String?) {
+ updateTabBadge(index) {
+ this.badgeText = badgeText
+ }
+ }
+
+ /**更新角标*/
+ fun updateTabBadge(index: Int, config: TabBadgeConfig.() -> Unit) {
+ val badgeConfig = getBadgeConfig(index)
+ tabBadgeConfigMap[index] = badgeConfig
+ badgeConfig.config()
+ postInvalidate()
+ }
+
+ //
+
+ //
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ }
+
+ override fun onViewAdded(child: View?) {
+ super.onViewAdded(child)
+ updateTabLayout()
+ }
+
+ override fun onViewRemoved(child: View?) {
+ super.onViewRemoved(child)
+ updateTabLayout()
+ }
+
+ open fun updateTabLayout() {
+ dslSelector.updateVisibleList()
+ dslSelector.updateStyle()
+ dslSelector.updateClickListener()
+ }
+
+ override fun draw(canvas: Canvas) {
+ //Log.e("angcyo", "...draw...")
+
+ if (drawIndicator) {
+ tabIndicator.setBounds(0, 0, measuredWidth, measuredHeight)
+ }
+
+ //自定义的背景
+ tabConvexBackgroundDrawable?.apply {
+ if (isHorizontal()) {
+ setBounds(0, _maxConvexHeight, right - left, bottom - top)
+ } else {
+ setBounds(0, 0, measuredWidth - _maxConvexHeight, bottom - top)
+ }
+
+ if (scrollX or scrollY == 0) {
+ draw(canvas)
+ } else {
+ canvas.holdLocation {
+ draw(canvas)
+ }
+ }
+ }
+
+ super.draw(canvas)
+
+ //突出显示
+ if (drawHighlight) {
+ tabHighlight?.draw(canvas)
+ }
+
+ val visibleChildCount = dslSelector.visibleViewList.size
+
+ //绘制在child的上面
+ if (drawDivider) {
+ if (isHorizontal()) {
+ if (isLayoutRtl) {
+ var right = 0
+ tabDivider?.apply {
+ val top = paddingTop + dividerMarginTop
+ val bottom = measuredHeight - paddingBottom - dividerMarginBottom
+ dslSelector.visibleViewList.forEachIndexed { index, view ->
+
+ if (haveBeforeDivider(index, visibleChildCount)) {
+ right = view.right + dividerMarginLeft + dividerWidth
+ setBounds(right - dividerWidth, top, right, bottom)
+ draw(canvas)
+ }
+
+ if (haveAfterDivider(index, visibleChildCount)) {
+ right = view.right - view.measuredWidth - dividerMarginRight
+ setBounds(right - dividerWidth, top, right, bottom)
+ draw(canvas)
+ }
+
+ }
+ }
+ } else {
+ var left = 0
+ tabDivider?.apply {
+ val top = paddingTop + dividerMarginTop
+ val bottom = measuredHeight - paddingBottom - dividerMarginBottom
+ dslSelector.visibleViewList.forEachIndexed { index, view ->
+
+ if (haveBeforeDivider(index, visibleChildCount)) {
+ left = view.left - dividerMarginRight - dividerWidth
+ setBounds(left, top, left + dividerWidth, bottom)
+ draw(canvas)
+ }
+
+ if (haveAfterDivider(index, visibleChildCount)) {
+ left = view.right + dividerMarginLeft
+ setBounds(left, top, left + dividerWidth, bottom)
+ draw(canvas)
+ }
+
+ }
+ }
+ }
+ } else {
+ var top = 0
+ tabDivider?.apply {
+ val left = paddingStart + dividerMarginLeft
+ val right = measuredWidth - paddingEnd - dividerMarginRight
+ dslSelector.visibleViewList.forEachIndexed { index, view ->
+
+ if (haveBeforeDivider(index, visibleChildCount)) {
+ top = view.top - dividerMarginBottom - dividerHeight
+ setBounds(left, top, right, top + dividerHeight)
+ draw(canvas)
+ }
+
+ if (haveAfterDivider(index, visibleChildCount)) {
+ top = view.bottom + dividerMarginTop
+ setBounds(left, top, right, top + dividerHeight)
+ draw(canvas)
+ }
+ }
+ }
+ }
+ }
+ if (drawBorder) {
+ //边框不跟随滚动
+ canvas.holdLocation {
+ tabBorder?.draw(canvas)
+ }
+ }
+ if (drawIndicator && tabIndicator.indicatorStyle.have(DslTabIndicator.INDICATOR_STYLE_FOREGROUND)) {
+ //前景显示
+ tabIndicator.draw(canvas)
+ }
+ if (drawBadge) {
+ tabBadge?.apply {
+ dslSelector.visibleViewList.forEachIndexed { index, child ->
+ val badgeConfig = onTabBadgeConfig(child, this, index)
+
+ var left: Int
+ var top: Int
+ var right: Int
+ var bottom: Int
+
+ var anchorView: View = child
+
+ if (badgeConfig != null && badgeConfig.badgeAnchorChildIndex >= 0) {
+ anchorView =
+ child.getChildOrNull(badgeConfig.badgeAnchorChildIndex) ?: child
+
+ anchorView.getLocationInParent(this@DslTabLayout, _tempRect)
+
+ left = _tempRect.left
+ top = _tempRect.top
+ right = _tempRect.right
+ bottom = _tempRect.bottom
+ } else {
+ left = anchorView.left
+ top = anchorView.top
+ right = anchorView.right
+ bottom = anchorView.bottom
+ }
+
+ if (badgeConfig != null && badgeConfig.badgeIgnoreChildPadding) {
+ left += anchorView.paddingStart
+ top += anchorView.paddingTop
+ right -= anchorView.paddingEnd
+ bottom -= anchorView.paddingBottom
+ }
+
+ setBounds(left, top, right, bottom)
+
+ updateOriginDrawable()
+
+ if (isInEditMode) {
+ badgeText = if (index == visibleChildCount - 1) {
+ //预览中, 强制最后一个角标为圆点类型, 方便查看预览.
+ ""
+ } else {
+ xmlBadgeText
+ }
+ }
+
+ draw(canvas)
+ }
+ }
+ }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ if (drawBorder) {
+ //边框不跟随滚动
+ canvas.holdLocation {
+ tabBorder?.drawBorderBackground(canvas)
+ }
+ }
+
+ //绘制在child的后面
+ if (drawIndicator && !tabIndicator.indicatorStyle.have(DslTabIndicator.INDICATOR_STYLE_FOREGROUND)) {
+ //背景绘制
+ tabIndicator.draw(canvas)
+ }
+ }
+
+ /**保持位置不变*/
+ fun Canvas.holdLocation(action: () -> Unit) {
+ translate(scrollX.toFloat(), scrollY.toFloat())
+ action()
+ translate(-scrollX.toFloat(), -scrollY.toFloat())
+ }
+
+ override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
+ return super.drawChild(canvas, child, drawingTime)
+ }
+
+ override fun verifyDrawable(who: Drawable): Boolean {
+ return super.verifyDrawable(who) ||
+ who == tabIndicator /*||
+ who == tabBorder ||
+ who == tabDivider ||
+ who == tabConvexBackgroundDrawable*/ /*||
+ who == tabBadge 防止循环绘制*/
+ }
+
+ //
+
+ //
+
+ //所有child的总宽度, 不包含parent的padding
+ var _childAllWidthSum = 0
+
+ //最大的凸起高度
+ var _maxConvexHeight = 0
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+
+ if (dslSelector.dslSelectIndex < 0) {
+ //还没有选中
+ setCurrentItem(tabDefaultIndex)
+ }
+
+ if (isHorizontal()) {
+ measureHorizontal(widthMeasureSpec, heightMeasureSpec)
+ } else {
+ measureVertical(widthMeasureSpec, heightMeasureSpec)
+ }
+ }
+
+ fun measureHorizontal(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ dslSelector.updateVisibleList()
+
+ val visibleChildList = dslSelector.visibleViewList
+ val visibleChildCount = visibleChildList.size
+
+ //控制最小大小
+ val tabMinHeight = if (suggestedMinimumHeight > 0) {
+ suggestedMinimumHeight
+ } else {
+ itemDefaultHeight
+ }
+
+ if (visibleChildCount == 0) {
+ setMeasuredDimension(
+ getDefaultSize(suggestedMinimumWidth, widthMeasureSpec),
+ getDefaultSize(tabMinHeight, heightMeasureSpec)
+ )
+ return
+ }
+
+ //super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ var widthSize = MeasureSpec.getSize(widthMeasureSpec)
+ val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+ var heightSize = MeasureSpec.getSize(heightMeasureSpec)
+ val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+
+ _maxConvexHeight = 0
+
+ var childWidthSpec: Int = -1
+
+ //记录child最大的height, 用来实现tabLayout wrap_content, 包括突出的大小
+ var childMaxHeight = tabMinHeight //child最大的高度
+
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ if (heightSize == 0) {
+ heightSize = Int.MAX_VALUE
+ }
+ }
+
+ if (widthMode == MeasureSpec.UNSPECIFIED) {
+ if (widthSize == 0) {
+ widthSize = Int.MAX_VALUE
+ }
+ }
+
+ //分割线需要排除的宽度
+ val dividerWidthExclude =
+ if (drawDivider) tabDivider?.run { dividerWidth + dividerMarginLeft + dividerMarginRight }
+ ?: 0 else 0
+
+ //智能等宽判断
+ if (itemAutoEquWidth) {
+ var childMaxWidth = 0 //所有child宽度总和
+ visibleChildList.forEachIndexed { index, child ->
+ val lp: LayoutParams = child.layoutParams as LayoutParams
+ measureChild(child, widthMeasureSpec, heightMeasureSpec)
+ childMaxWidth += lp.marginStart + lp.marginEnd + child.measuredWidth
+
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true) {
+ childMaxWidth += dividerWidthExclude
+ }
+ if (tabDivider?.haveAfterDivider(index, visibleChildList.size) == true) {
+ childMaxWidth += dividerWidthExclude
+ }
+ }
+ }
+
+ itemIsEquWidth = childMaxWidth <= widthSize
+ }
+
+ itemEquWidthCountRange?.let {
+ itemIsEquWidth = it.contains(visibleChildCount)
+ }
+
+ //等宽时, child宽度的测量模式
+ val childEquWidthSpec = if (itemIsEquWidth) {
+ exactlyMeasure(
+ if (itemWidth > 0) {
+ itemWidth
+ } else {
+ var excludeWidth = paddingStart + paddingEnd
+ visibleChildList.forEachIndexed { index, child ->
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(
+ index,
+ visibleChildList.size
+ ) == true
+ ) {
+ excludeWidth += dividerWidthExclude
+ }
+ if (tabDivider?.haveAfterDivider(
+ index,
+ visibleChildList.size
+ ) == true
+ ) {
+ excludeWidth += dividerWidthExclude
+ }
+ }
+ val lp = child.layoutParams as LayoutParams
+ excludeWidth += lp.marginStart + lp.marginEnd
+ }
+ (widthSize - excludeWidth) / visibleChildCount
+ }
+ )
+ } else {
+ -1
+ }
+
+ //...end
+
+ _childAllWidthSum = 0
+
+ //没有设置weight属性的child宽度总和, 用于计算剩余空间
+ var allChildUsedWidth = 0
+
+ fun measureChild(childView: View, heightSpec: Int? = null) {
+ val lp = childView.layoutParams as LayoutParams
+
+ //child高度测量模式
+ var childHeightSpec: Int = -1
+
+ val widthHeight = calcLayoutWidthHeight(
+ lp.layoutWidth, lp.layoutHeight,
+ widthSize, heightSize, 0, 0
+ )
+
+ if (heightMode == MeasureSpec.EXACTLY) {
+ //固定高度
+ childHeightSpec =
+ exactlyMeasure(heightSize - paddingTop - paddingBottom - lp.topMargin - lp.bottomMargin)
+ } else {
+ if (widthHeight[1] > 0) {
+ heightSize = widthHeight[1]
+ childHeightSpec = exactlyMeasure(heightSize)
+ heightSize += paddingTop + paddingBottom
+ } else {
+ childHeightSpec = if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {
+ exactlyMeasure(tabMinHeight)
+ } else {
+ atmostMeasure(Int.MAX_VALUE)
+ }
+ }
+ }
+
+ val childConvexHeight = lp.layoutConvexHeight
+
+ //...end
+
+ //计算宽度测量模式
+ childWidthSpec //no op
+
+ if (heightSpec != null) {
+ childView.measure(childWidthSpec, heightSpec)
+ } else {
+ childView.measure(childWidthSpec, childHeightSpec)
+ }
+ if (childConvexHeight > 0) {
+ _maxConvexHeight = max(_maxConvexHeight, childConvexHeight)
+ //需要凸起
+ val spec = exactlyMeasure(childView.measuredHeight + childConvexHeight)
+ childView.measure(childWidthSpec, spec)
+ }
+ childMaxHeight = max(childMaxHeight, childView.measuredHeight)
+ }
+
+ visibleChildList.forEachIndexed { index, childView ->
+ val lp = childView.layoutParams as LayoutParams
+ var childUsedWidth = 0
+ if (lp.weight < 0) {
+ val widthHeight = calcLayoutWidthHeight(
+ lp.layoutWidth, lp.layoutHeight,
+ widthSize, heightSize, 0, 0
+ )
+
+ //计算宽度测量模式
+ childWidthSpec = when {
+ itemIsEquWidth -> childEquWidthSpec
+ widthHeight[0] > 0 -> exactlyMeasure(widthHeight[0])
+ lp.width == ViewGroup.LayoutParams.MATCH_PARENT -> exactlyMeasure(widthSize - paddingStart - paddingEnd)
+ lp.width > 0 -> exactlyMeasure(lp.width)
+ else -> atmostMeasure(widthSize - paddingStart - paddingEnd)
+ }
+
+ measureChild(childView)
+
+ childUsedWidth = childView.measuredWidth + lp.marginStart + lp.marginEnd
+ } else {
+ childUsedWidth = lp.marginStart + lp.marginEnd
+ }
+
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true) {
+ childUsedWidth += dividerWidthExclude
+ }
+ if (tabDivider?.haveAfterDivider(index, visibleChildList.size) == true) {
+ childUsedWidth += dividerWidthExclude
+ }
+ }
+
+ childMaxHeight = max(childMaxHeight, childView.measuredHeight)
+ allChildUsedWidth += childUsedWidth
+ _childAllWidthSum += childUsedWidth
+ }
+
+ //剩余空间
+ val spaceSize = widthSize - allChildUsedWidth
+
+ //计算weight
+ visibleChildList.forEach { childView ->
+ val lp = childView.layoutParams as LayoutParams
+ if (lp.weight > 0) {
+ val widthHeight = calcLayoutWidthHeight(
+ lp.layoutWidth, lp.layoutHeight,
+ widthSize, heightSize, 0, 0
+ )
+
+ //计算宽度测量模式
+ childWidthSpec = when {
+ itemIsEquWidth -> childEquWidthSpec
+ spaceSize > 0 -> exactlyMeasure(spaceSize * lp.weight)
+ widthHeight[0] > 0 -> exactlyMeasure(allChildUsedWidth)
+ lp.width == ViewGroup.LayoutParams.MATCH_PARENT -> exactlyMeasure(widthSize - paddingStart - paddingEnd)
+ lp.width > 0 -> exactlyMeasure(lp.width)
+ else -> atmostMeasure(widthSize - paddingStart - paddingEnd)
+ }
+
+ measureChild(childView)
+
+ childMaxHeight = max(childMaxHeight, childView.measuredHeight)
+
+ //上面已经处理了分割线和margin的距离了
+ _childAllWidthSum += childView.measuredWidth
+ }
+ }
+ //...end
+
+ if (heightMode == MeasureSpec.AT_MOST) {
+ //wrap_content 情况下, 重新测量所有子view
+ val childHeightSpec = exactlyMeasure(
+ max(
+ childMaxHeight - _maxConvexHeight,
+ suggestedMinimumHeight - paddingTop - paddingBottom
+ )
+ )
+ visibleChildList.forEach { childView ->
+ measureChild(childView, childHeightSpec)
+ }
+ }
+
+ if (widthMode != MeasureSpec.EXACTLY) {
+ widthSize = min(_childAllWidthSum + paddingStart + paddingEnd, widthSize)
+ }
+
+ if (visibleChildList.isEmpty()) {
+ heightSize = if (suggestedMinimumHeight > 0) {
+ suggestedMinimumHeight
+ } else {
+ itemDefaultHeight
+ }
+ } else if (heightMode != MeasureSpec.EXACTLY) {
+ heightSize = max(
+ childMaxHeight - _maxConvexHeight + paddingTop + paddingBottom,
+ suggestedMinimumHeight
+ )
+ }
+
+ setMeasuredDimension(widthSize, heightSize + _maxConvexHeight)
+ }
+
+ fun measureVertical(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ dslSelector.updateVisibleList()
+
+ val visibleChildList = dslSelector.visibleViewList
+ val visibleChildCount = visibleChildList.size
+
+ if (visibleChildCount == 0) {
+ setMeasuredDimension(
+ getDefaultSize(
+ if (suggestedMinimumHeight > 0) {
+ suggestedMinimumHeight
+ } else {
+ itemDefaultHeight
+ }, widthMeasureSpec
+ ),
+ getDefaultSize(suggestedMinimumHeight, heightMeasureSpec)
+ )
+ return
+ }
+
+ //super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ var widthSize = MeasureSpec.getSize(widthMeasureSpec)
+ val widthMode = MeasureSpec.getMode(widthMeasureSpec)
+ var heightSize = MeasureSpec.getSize(heightMeasureSpec)
+ val heightMode = MeasureSpec.getMode(heightMeasureSpec)
+
+ _maxConvexHeight = 0
+
+ //child高度测量模式
+ var childHeightSpec: Int = -1
+ var childWidthSpec: Int = -1
+
+ if (heightMode == MeasureSpec.UNSPECIFIED) {
+ if (heightSize == 0) {
+ heightSize = Int.MAX_VALUE
+ }
+ }
+
+ if (widthMode == MeasureSpec.EXACTLY) {
+ //固定宽度
+ childWidthSpec = exactlyMeasure(widthSize - paddingStart - paddingEnd)
+ } else if (widthMode == MeasureSpec.UNSPECIFIED) {
+ if (widthSize == 0) {
+ widthSize = Int.MAX_VALUE
+ }
+ }
+
+ //分割线需要排除的宽度
+ val dividerHeightExclude =
+ if (drawDivider) tabDivider?.run { dividerHeight + dividerMarginTop + dividerMarginBottom }
+ ?: 0 else 0
+
+ //智能等宽判断
+ if (itemAutoEquWidth) {
+ var childMaxHeight = 0 //所有child高度总和
+ visibleChildList.forEachIndexed { index, child ->
+ val lp: LayoutParams = child.layoutParams as LayoutParams
+ measureChild(child, widthMeasureSpec, heightMeasureSpec)
+ childMaxHeight += lp.topMargin + lp.bottomMargin + child.measuredHeight
+
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true) {
+ childMaxHeight += dividerHeightExclude
+ }
+ if (tabDivider?.haveAfterDivider(index, visibleChildList.size) == true) {
+ childMaxHeight += dividerHeightExclude
+ }
+ }
+ }
+
+ itemIsEquWidth = childMaxHeight <= heightSize
+ }
+
+ itemEquWidthCountRange?.let {
+ itemIsEquWidth = it.contains(visibleChildCount)
+ }
+
+ //等宽时, child高度的测量模式
+ val childEquHeightSpec = if (itemIsEquWidth) {
+ exactlyMeasure(
+ if (itemWidth > 0) {
+ itemWidth
+ } else {
+ var excludeHeight = paddingTop + paddingBottom
+ visibleChildList.forEachIndexed { index, child ->
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true
+ ) {
+ excludeHeight += dividerHeightExclude
+ }
+ if (tabDivider?.haveAfterDivider(index, visibleChildList.size) == true
+ ) {
+ excludeHeight += dividerHeightExclude
+ }
+ }
+ val lp = child.layoutParams as LayoutParams
+ excludeHeight += lp.topMargin + lp.bottomMargin
+ }
+ (heightSize - excludeHeight) / visibleChildCount
+ }
+ )
+ } else {
+ -1
+ }
+
+ //...end
+
+ _childAllWidthSum = 0
+
+ var wrapContentWidth = false
+
+ //没有设置weight属性的child宽度总和, 用于计算剩余空间
+ var allChildUsedHeight = 0
+
+ fun measureChild(childView: View) {
+ val lp = childView.layoutParams as LayoutParams
+
+ //纵向布局, 不支持横向margin支持
+ lp.marginStart = 0
+ lp.marginEnd = 0
+
+ val childConvexHeight = lp.layoutConvexHeight
+ _maxConvexHeight = max(_maxConvexHeight, childConvexHeight)
+
+ val widthHeight = calcLayoutWidthHeight(
+ lp.layoutWidth, lp.layoutHeight,
+ widthSize, heightSize, 0, 0
+ )
+
+ //计算宽度测量模式
+ wrapContentWidth = false
+ if (childWidthSpec == -1) {
+ if (widthHeight[0] > 0) {
+ widthSize = widthHeight[0]
+ childWidthSpec = exactlyMeasure(widthSize)
+ widthSize += paddingStart + paddingEnd
+ }
+ }
+
+ if (childWidthSpec == -1) {
+ if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+
+ widthSize = if (suggestedMinimumWidth > 0) {
+ suggestedMinimumWidth
+ } else {
+ itemDefaultHeight
+ }
+
+ childWidthSpec = exactlyMeasure(widthSize)
+
+ widthSize += paddingStart + paddingEnd
+ } else {
+ childWidthSpec = atmostMeasure(widthSize)
+ wrapContentWidth = true
+ }
+ }
+ //...end
+
+ //计算高度测量模式
+ childHeightSpec //no op
+
+ if (childConvexHeight > 0) {
+ //需要凸起
+ val childConvexWidthSpec = MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(childWidthSpec) + childConvexHeight,
+ MeasureSpec.getMode(childWidthSpec)
+ )
+ childView.measure(childConvexWidthSpec, childHeightSpec)
+ } else {
+ childView.measure(childWidthSpec, childHeightSpec)
+ }
+
+ if (wrapContentWidth) {
+ widthSize = childView.measuredWidth
+ childWidthSpec = exactlyMeasure(widthSize)
+ widthSize += paddingStart + paddingEnd
+ }
+ }
+
+ visibleChildList.forEachIndexed { index, childView ->
+ val lp = childView.layoutParams as LayoutParams
+ var childUsedHeight = 0
+ if (lp.weight < 0) {
+ val widthHeight = calcLayoutWidthHeight(
+ lp.layoutWidth, lp.layoutHeight,
+ widthSize, heightSize, 0, 0
+ )
+
+ //计算高度测量模式
+ childHeightSpec = when {
+ itemIsEquWidth -> childEquHeightSpec
+ widthHeight[1] > 0 -> exactlyMeasure(widthHeight[1])
+ lp.height == ViewGroup.LayoutParams.MATCH_PARENT -> exactlyMeasure(heightSize - paddingTop - paddingBottom)
+ lp.height > 0 -> exactlyMeasure(lp.height)
+ else -> atmostMeasure(heightSize - paddingTop - paddingBottom)
+ }
+
+ measureChild(childView)
+
+ childUsedHeight = childView.measuredHeight + lp.topMargin + lp.bottomMargin
+ } else {
+ childUsedHeight = lp.topMargin + lp.bottomMargin
+ }
+
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true) {
+ childUsedHeight += dividerHeightExclude
+ }
+ if (tabDivider?.haveAfterDivider(index, visibleChildList.size) == true) {
+ childUsedHeight += dividerHeightExclude
+ }
+ }
+
+ allChildUsedHeight += childUsedHeight
+ _childAllWidthSum += childUsedHeight
+ }
+
+ //剩余空间
+ val spaceSize = heightSize - allChildUsedHeight
+
+ //计算weight
+ visibleChildList.forEach { childView ->
+ val lp = childView.layoutParams as LayoutParams
+ if (lp.weight > 0) {
+ val widthHeight = calcLayoutWidthHeight(
+ lp.layoutWidth, lp.layoutHeight,
+ widthSize, heightSize, 0, 0
+ )
+
+ //计算高度测量模式
+ childHeightSpec = when {
+ itemIsEquWidth -> childEquHeightSpec
+ spaceSize > 0 -> exactlyMeasure(spaceSize * lp.weight)
+ widthHeight[1] > 0 -> exactlyMeasure(allChildUsedHeight)
+ lp.height == ViewGroup.LayoutParams.MATCH_PARENT -> exactlyMeasure(heightSize - paddingTop - paddingBottom)
+ lp.height > 0 -> exactlyMeasure(lp.height)
+ else -> atmostMeasure(heightSize - paddingTop - paddingBottom)
+ }
+
+ measureChild(childView)
+
+ //上面已经处理了分割线和margin的距离了
+ _childAllWidthSum += childView.measuredHeight
+ }
+ }
+ //...end
+
+ if (heightMode != MeasureSpec.EXACTLY) {
+ heightSize = min(_childAllWidthSum + paddingTop + paddingBottom, heightSize)
+ }
+
+ if (visibleChildList.isEmpty()) {
+ widthSize = if (suggestedMinimumWidth > 0) {
+ suggestedMinimumWidth
+ } else {
+ itemDefaultHeight
+ }
+ }
+
+ setMeasuredDimension(widthSize + _maxConvexHeight, heightSize)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ if (isHorizontal()) {
+ layoutHorizontal(changed, l, t, r, b)
+ } else {
+ layoutVertical(changed, l, t, r, b)
+ }
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+
+ //check
+ restoreScroll()
+
+ if (dslSelector.dslSelectIndex < 0) {
+ //还没有选中
+ setCurrentItem(tabDefaultIndex)
+ } else {
+ if (_overScroller.isFinished) {
+ _scrollToTarget(dslSelector.dslSelectIndex, layoutScrollAnim)
+ }
+ }
+ }
+
+ val isLayoutRtl: Boolean
+ get() = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL
+
+ var _layoutDirection: Int = -1
+
+ //API 17
+ override fun onRtlPropertiesChanged(layoutDirection: Int) {
+ super.onRtlPropertiesChanged(layoutDirection)
+
+ if (layoutDirection != _layoutDirection) {
+ _layoutDirection = layoutDirection
+ if (orientation == LinearLayout.HORIZONTAL) {
+ updateTabLayout() //更新样式
+ requestLayout() //重新布局
+ }
+ }
+ }
+
+ fun layoutHorizontal(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ val isRtl = isLayoutRtl
+
+ var left = paddingStart
+ var right = measuredWidth - paddingEnd
+
+ var childBottom = measuredHeight - paddingBottom
+
+ val dividerExclude = if (drawDivider) tabDivider?.run {
+ dividerWidth + dividerMarginLeft + dividerMarginRight
+ } ?: 0 else 0
+
+ val visibleChildList = dslSelector.visibleViewList
+ visibleChildList.forEachIndexed { index, childView ->
+
+ val lp = childView.layoutParams as LayoutParams
+ val verticalGravity = lp.gravity and Gravity.VERTICAL_GRAVITY_MASK
+
+ if (isRtl) {
+ right -= lp.marginEnd
+ } else {
+ left += lp.marginStart
+ }
+
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true) {
+
+ if (isRtl) {
+ right -= dividerExclude
+ } else {
+ left += dividerExclude
+ }
+ }
+ }
+
+ childBottom = when (verticalGravity) {
+ Gravity.CENTER_VERTICAL -> measuredHeight - paddingBottom -
+ ((measuredHeight - paddingTop - paddingBottom - _maxConvexHeight) / 2 -
+ childView.measuredHeight / 2)
+
+ Gravity.BOTTOM -> measuredHeight - paddingBottom
+ else -> paddingTop + lp.topMargin + childView.measuredHeight
+ }
+
+ if (isRtl) {
+ childView.layout(
+ right - childView.measuredWidth,
+ childBottom - childView.measuredHeight,
+ right,
+ childBottom
+ )
+ right -= childView.measuredWidth + lp.marginStart
+ } else {
+ childView.layout(
+ left,
+ childBottom - childView.measuredHeight,
+ left + childView.measuredWidth,
+ childBottom
+ )
+ left += childView.measuredWidth + lp.marginEnd
+ }
+ }
+
+ //check
+ restoreScroll()
+
+ if (dslSelector.dslSelectIndex < 0) {
+ //还没有选中
+ setCurrentItem(tabDefaultIndex)
+ } else {
+ if (_overScroller.isFinished) {
+ _scrollToTarget(dslSelector.dslSelectIndex, layoutScrollAnim)
+ }
+ }
+ }
+
+ fun layoutVertical(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ var top = paddingTop
+ var childLeft = paddingStart
+
+ val dividerExclude =
+ if (drawDivider) tabDivider?.run { dividerHeight + dividerMarginTop + dividerMarginBottom }
+ ?: 0 else 0
+
+ val visibleChildList = dslSelector.visibleViewList
+ visibleChildList.forEachIndexed { index, childView ->
+
+ val lp = childView.layoutParams as LayoutParams
+ val layoutDirection = 0
+ val absoluteGravity = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection)
+ val horizontalGravity = absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK
+
+ top += lp.topMargin
+
+ if (drawDivider) {
+ if (tabDivider?.haveBeforeDivider(index, visibleChildList.size) == true) {
+ top += dividerExclude
+ }
+ }
+
+ childLeft = when (horizontalGravity) {
+ Gravity.CENTER_HORIZONTAL -> paddingStart + ((measuredWidth - paddingStart - paddingEnd - _maxConvexHeight) / 2 -
+ childView.measuredWidth / 2)
+
+ Gravity.RIGHT -> measuredWidth - paddingRight - childView.measuredWidth - lp.rightMargin
+ else -> paddingLeft + lp.leftMargin
+ }
+
+ /*默认水平居中显示*/
+ childView.layout(
+ childLeft,
+ top,
+ childLeft + childView.measuredWidth,
+ top + childView.measuredHeight
+ )
+
+ top += childView.measuredHeight + lp.bottomMargin
+ }
+ }
+
+ /**是否是横向布局*/
+ fun isHorizontal() = orientation.isHorizontal()
+
+ //
+
+ //
+
+ override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams {
+ return LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER
+ )
+ }
+
+ override fun generateLayoutParams(attrs: AttributeSet?): ViewGroup.LayoutParams {
+ return LayoutParams(context, attrs)
+ }
+
+ override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
+ return p?.run { LayoutParams(p) } ?: generateDefaultLayoutParams()
+ }
+
+ class LayoutParams : FrameLayout.LayoutParams {
+
+ /**
+ * 支持格式0.3pw 0.5ph, 表示[parent]的多少倍数
+ * */
+ var layoutWidth: String? = null
+ var layoutHeight: String? = null
+
+ /**凸出的高度*/
+ var layoutConvexHeight: Int = 0
+
+ /**
+ * 宽高[WRAP_CONTENT]时, 内容view的定位索引
+ * [TabIndicator.indicatorContentIndex]
+ * */
+ var indicatorContentIndex = -1
+ var indicatorContentId = View.NO_ID
+
+ /**[com.angcyo.tablayout.DslTabLayoutConfig.getOnGetTextStyleView]*/
+ var contentTextViewIndex = -1
+
+ /**[com.angcyo.tablayout.DslTabLayoutConfig.getTabTextViewId]*/
+ var contentTextViewId = View.NO_ID
+
+ /**[com.angcyo.tablayout.DslTabLayoutConfig.getOnGetIcoStyleView]*/
+ var contentIconViewIndex = -1
+
+ /**[com.angcyo.tablayout.DslTabLayoutConfig.getTabIconViewId]*/
+ var contentIconViewId = View.NO_ID
+
+ /**
+ * 剩余空间占比, 1f表示占满剩余空间, 0.5f表示使用剩余空间的0.5倍
+ * [android.widget.LinearLayout.LayoutParams.weight]*/
+ var weight: Float = -1f
+
+ /**突出需要绘制的Drawable
+ * [com.angcyo.tablayout.DslTabHighlight.highlightDrawable]*/
+ var highlightDrawable: Drawable? = null
+
+ constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
+ val a = c.obtainStyledAttributes(attrs, R.styleable.DslTabLayout_Layout)
+ layoutWidth = a.getString(R.styleable.DslTabLayout_Layout_layout_tab_width)
+ layoutHeight = a.getString(R.styleable.DslTabLayout_Layout_layout_tab_height)
+ layoutConvexHeight =
+ a.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_Layout_layout_tab_convex_height,
+ layoutConvexHeight
+ )
+ indicatorContentIndex = a.getInt(
+ R.styleable.DslTabLayout_Layout_layout_tab_indicator_content_index,
+ indicatorContentIndex
+ )
+ indicatorContentId = a.getResourceId(
+ R.styleable.DslTabLayout_Layout_layout_tab_indicator_content_id,
+ indicatorContentId
+ )
+ weight = a.getFloat(R.styleable.DslTabLayout_Layout_layout_tab_weight, weight)
+ highlightDrawable =
+ a.getDrawable(R.styleable.DslTabLayout_Layout_layout_highlight_drawable)
+
+ contentTextViewIndex = a.getInt(
+ R.styleable.DslTabLayout_Layout_layout_tab_text_view_index,
+ contentTextViewIndex
+ )
+ contentIconViewIndex = a.getInt(
+ R.styleable.DslTabLayout_Layout_layout_tab_text_view_index,
+ contentIconViewIndex
+ )
+ contentTextViewId = a.getResourceId(
+ R.styleable.DslTabLayout_Layout_layout_tab_text_view_id,
+ contentTextViewId
+ )
+ contentIconViewId = a.getResourceId(
+ R.styleable.DslTabLayout_Layout_layout_tab_icon_view_id,
+ contentIconViewIndex
+ )
+
+ a.recycle()
+
+ if (gravity == UNSPECIFIED_GRAVITY) {
+ gravity = if (layoutConvexHeight > 0) {
+ Gravity.BOTTOM
+ } else {
+ Gravity.CENTER
+ }
+ }
+ }
+
+ constructor(source: ViewGroup.LayoutParams) : super(source) {
+ if (source is LayoutParams) {
+ this.layoutWidth = source.layoutWidth
+ this.layoutHeight = source.layoutHeight
+ this.layoutConvexHeight = source.layoutConvexHeight
+ this.weight = source.weight
+ this.highlightDrawable = source.highlightDrawable
+ }
+ }
+
+ constructor(width: Int, height: Int) : super(width, height)
+
+ constructor(width: Int, height: Int, gravity: Int) : super(width, height, gravity)
+ }
+
+ //
+
+ //
+
+ //滚动支持
+ val _overScroller: OverScroller by lazy {
+ OverScroller(context)
+ }
+
+ //手势检测
+ val _gestureDetector: GestureDetectorCompat by lazy {
+ GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() {
+ override fun onFling(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ if (isHorizontal()) {
+ val absX = abs(velocityX)
+ if (absX > _minFlingVelocity) {
+ onFlingChange(velocityX)
+ }
+ } else {
+ val absY = abs(velocityY)
+ if (absY > _minFlingVelocity) {
+ onFlingChange(velocityY)
+ }
+ }
+
+ return true
+ }
+
+ override fun onScroll(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ var handle = false
+ if (isHorizontal()) {
+ val absX = abs(distanceX)
+ if (absX > _touchSlop) {
+ handle = onScrollChange(distanceX)
+ }
+ } else {
+ val absY = abs(distanceY)
+ if (absY > _touchSlop) {
+ handle = onScrollChange(distanceY)
+ }
+ }
+ return handle
+ }
+ })
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ var intercept = false
+ if (needScroll) {
+ if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
+ _overScroller.abortAnimation()
+ _scrollAnimator.cancel()
+ }
+ if (isEnabled) {
+ intercept = super.onInterceptTouchEvent(ev) || _gestureDetector.onTouchEvent(ev)
+ }
+ } else {
+ if (isEnabled) {
+ intercept = super.onInterceptTouchEvent(ev)
+ }
+ }
+ return if (isEnabled) {
+ if (itemEnableSelector) {
+ intercept
+ } else {
+ true
+ }
+ } else {
+ false
+ }
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (isEnabled) {
+ if (needScroll) {
+ _gestureDetector.onTouchEvent(event)
+ if (event.actionMasked == MotionEvent.ACTION_CANCEL ||
+ event.actionMasked == MotionEvent.ACTION_UP
+ ) {
+ parent.requestDisallowInterceptTouchEvent(false)
+ } else if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ _overScroller.abortAnimation()
+ }
+ return true
+ } else {
+ return (isEnabled && super.onTouchEvent(event))
+ }
+ } else {
+ return false
+ }
+ }
+
+ /**是否需要滚动*/
+ val needScroll: Boolean
+ get() = if (tabEnableSelectorMode) true else {
+ if (isHorizontal()) {
+ if (isLayoutRtl) {
+ minScrollX < 0
+ } else {
+ maxScrollX > 0
+ }
+ } else {
+ maxScrollY > 0
+ }
+ }
+
+ /**[parent]宽度外的滚动距离*/
+ val maxScrollX: Int
+ get() = if (isLayoutRtl && isHorizontal()) {
+ if (tabEnableSelectorMode) viewDrawWidth / 2 else 0
+ } else {
+ max(
+ maxWidth - measuredWidth + if (tabEnableSelectorMode) viewDrawWidth / 2 else 0,
+ 0
+ )
+ }
+
+ val maxScrollY: Int
+ get() = max(
+ maxHeight - measuredHeight + if (tabEnableSelectorMode) viewDrawHeight / 2 else 0,
+ 0
+ )
+
+ /**最小滚动的值*/
+ val minScrollX: Int
+ get() = if (isLayoutRtl && isHorizontal()) {
+ min(
+ -(maxWidth - measuredWidth + if (tabEnableSelectorMode) viewDrawWidth / 2 else 0),
+ 0
+ )
+ } else {
+ if (tabEnableSelectorMode) -viewDrawWidth / 2 else 0
+ }
+
+ val minScrollY: Int
+ get() = if (tabEnableSelectorMode) -viewDrawHeight / 2 else 0
+
+ /**view最大的宽度*/
+ val maxWidth: Int
+ get() = _childAllWidthSum + paddingStart + paddingEnd
+
+ val maxHeight: Int
+ get() = _childAllWidthSum + paddingTop + paddingBottom
+
+ open fun onFlingChange(velocity: Float /*瞬时值*/) {
+ if (needScroll) {
+
+ //速率小于0 , 手指向左滑动
+ //速率大于0 , 手指向右滑动
+
+ if (tabEnableSelectorMode) {
+ if (isHorizontal() && isLayoutRtl) {
+ if (velocity < 0) {
+ setCurrentItem(dslSelector.dslSelectIndex - 1)
+ } else if (velocity > 0) {
+ setCurrentItem(dslSelector.dslSelectIndex + 1)
+ }
+ } else {
+ if (velocity < 0) {
+ setCurrentItem(dslSelector.dslSelectIndex + 1)
+ } else if (velocity > 0) {
+ setCurrentItem(dslSelector.dslSelectIndex - 1)
+ }
+ }
+ } else {
+ if (isHorizontal()) {
+ if (isLayoutRtl) {
+ startFling(-velocity.toInt(), minScrollX, 0)
+ } else {
+ startFling(-velocity.toInt(), 0, maxScrollX)
+ }
+ } else {
+ startFling(-velocity.toInt(), 0, maxHeight)
+ }
+ }
+ }
+ }
+
+ fun startFling(velocity: Int, min: Int, max: Int) {
+
+ fun velocity(velocity: Int): Int {
+ return if (velocity > 0) {
+ clamp(velocity, _minFlingVelocity, _maxFlingVelocity)
+ } else {
+ clamp(velocity, -_maxFlingVelocity, -_minFlingVelocity)
+ }
+ }
+
+ val v = velocity(velocity)
+
+ _overScroller.abortAnimation()
+
+ if (isHorizontal()) {
+ _overScroller.fling(
+ scrollX,
+ scrollY,
+ v,
+ 0,
+ min,
+ max,
+ 0,
+ 0,
+ measuredWidth,
+ 0
+ )
+ } else {
+ _overScroller.fling(
+ scrollX,
+ scrollY,
+ 0,
+ v,
+ 0,
+ 0,
+ min,
+ max,
+ 0,
+ measuredHeight
+ )
+ }
+ postInvalidate()
+ }
+
+ fun startScroll(dv: Int) {
+ _overScroller.abortAnimation()
+ if (isHorizontal()) {
+ _overScroller.startScroll(scrollX, scrollY, dv, 0, scrollAnimDuration)
+ } else {
+ _overScroller.startScroll(scrollX, scrollY, 0, dv, scrollAnimDuration)
+ }
+ ViewCompat.postInvalidateOnAnimation(this)
+ }
+
+ /**检查是否需要重置滚动的位置*/
+ fun restoreScroll() {
+ if (itemIsEquWidth || !needScroll) {
+ if (scrollX != 0 || scrollY != 0) {
+ scrollTo(0, 0)
+ }
+ }
+ }
+
+ open fun onScrollChange(distance: Float): Boolean {
+ if (needScroll) {
+
+ //distance小于0 , 手指向右滑动
+ //distance大于0 , 手指向左滑动
+
+ parent.requestDisallowInterceptTouchEvent(true)
+
+ if (tabEnableSelectorMode) {
+ //滑动选择模式下, 不响应scroll事件
+ } else {
+ if (isHorizontal()) {
+ scrollBy(distance.toInt(), 0)
+ } else {
+ scrollBy(0, distance.toInt())
+ }
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun scrollTo(x: Int, y: Int) {
+ if (isHorizontal()) {
+ when {
+ x > maxScrollX -> super.scrollTo(maxScrollX, 0)
+ x < minScrollX -> super.scrollTo(minScrollX, 0)
+ else -> super.scrollTo(x, 0)
+ }
+ } else {
+ when {
+ y > maxScrollY -> super.scrollTo(0, maxScrollY)
+ y < minScrollY -> super.scrollTo(0, minScrollY)
+ else -> super.scrollTo(0, y)
+ }
+ }
+ }
+
+ override fun computeScroll() {
+ if (_overScroller.computeScrollOffset()) {
+ scrollTo(_overScroller.currX, _overScroller.currY)
+ invalidate()
+ if (_overScroller.currX < minScrollX || _overScroller.currX > maxScrollX) {
+ _overScroller.abortAnimation()
+ }
+ }
+ }
+
+ fun _getViewTargetX(): Int {
+ return when (tabIndicator.indicatorGravity) {
+ DslTabIndicator.INDICATOR_GRAVITY_START -> paddingStart
+ DslTabIndicator.INDICATOR_GRAVITY_END -> measuredWidth - paddingEnd
+ else -> paddingStart + viewDrawWidth / 2
+ }
+ }
+
+ fun _getViewTargetY(): Int {
+ return when (tabIndicator.indicatorGravity) {
+ DslTabIndicator.INDICATOR_GRAVITY_START -> paddingTop
+ DslTabIndicator.INDICATOR_GRAVITY_END -> measuredHeight - paddingBottom
+ else -> paddingTop + viewDrawHeight / 2
+ }
+ }
+
+ /**将[index]位置显示在TabLayout的中心*/
+ fun _scrollToTarget(index: Int, scrollAnim: Boolean) {
+ if (!needScroll) {
+ return
+ }
+
+ dslSelector.visibleViewList.getOrNull(index)?.let {
+ if (!ViewCompat.isLaidOut(it)) {
+ //没有布局
+ return
+ }
+ }
+
+ val dv = if (isHorizontal()) {
+ val childTargetX = tabIndicator.getChildTargetX(index)
+ val viewDrawTargetX = _getViewTargetX()
+ when {
+ tabEnableSelectorMode -> {
+ val viewCenterX = measuredWidth / 2
+ childTargetX - viewCenterX - scrollX
+ }
+
+ isLayoutRtl -> {
+ if (childTargetX < viewDrawTargetX) {
+ childTargetX - viewDrawTargetX - scrollX
+ } else {
+ -scrollX
+ }
+ }
+
+ else -> {
+ if (childTargetX > viewDrawTargetX) {
+ childTargetX - viewDrawTargetX - scrollX
+ } else {
+ -scrollX
+ }
+ }
+ }
+ } else {
+ //竖向
+ val childTargetY = tabIndicator.getChildTargetY(index)
+ val viewDrawTargetY = _getViewTargetY()
+ when {
+ tabEnableSelectorMode -> {
+ val viewCenterY = measuredHeight / 2
+ childTargetY - viewCenterY - scrollY
+ }
+
+ childTargetY > viewDrawTargetY -> {
+ childTargetY - viewDrawTargetY - scrollY
+ }
+
+ else -> {
+ if (tabIndicator.indicatorGravity == DslTabIndicator.INDICATOR_GRAVITY_END &&
+ childTargetY < viewDrawTargetY
+ ) {
+ childTargetY - viewDrawTargetY - scrollY
+ } else {
+ -scrollY
+ }
+ }
+ }
+ }
+
+ if (isHorizontal()) {
+ if (isInEditMode || !scrollAnim) {
+ _overScroller.abortAnimation()
+ scrollBy(dv, 0)
+ } else {
+ startScroll(dv)
+ }
+ } else {
+ if (isInEditMode || !scrollAnim) {
+ _overScroller.abortAnimation()
+ scrollBy(0, dv)
+ } else {
+ startScroll(dv)
+ }
+ }
+ }
+
+ //
+
+ //
+
+ val _scrollAnimator: ValueAnimator by lazy {
+ ValueAnimator().apply {
+ interpolator = LinearInterpolator()
+ duration = tabIndicatorAnimationDuration
+ addUpdateListener {
+ _onAnimateValue(it.animatedValue as Float)
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationCancel(animation: Animator) {
+ _onAnimateValue(1f)
+ onAnimationEnd(animation)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ _onAnimateEnd()
+ }
+ })
+ }
+ }
+
+ val isAnimatorStart: Boolean
+ get() = _scrollAnimator.isStarted
+
+ fun _animateToItem(fromIndex: Int, toIndex: Int) {
+ if (toIndex == fromIndex) {
+ return
+ }
+
+ //取消之前的动画
+ _scrollAnimator.cancel()
+
+ if (!tabIndicator.indicatorAnim) {
+ //不需要动画
+ _onAnimateEnd()
+ return
+ }
+
+ if (fromIndex < 0) {
+ //从一个不存在的位置, 到目标位置
+ tabIndicator.currentIndex = toIndex
+ } else {
+ tabIndicator.currentIndex = fromIndex
+ }
+ tabIndicator._targetIndex = toIndex
+
+ if (isInEditMode) {
+ tabIndicator.currentIndex = toIndex
+ return
+ }
+
+ if (tabIndicator.currentIndex == tabIndicator._targetIndex) {
+ return
+ }
+ //"_animateToItem ${tabIndicator.currentIndex} ${tabIndicator._targetIndex}".loge()
+ _scrollAnimator.setFloatValues(tabIndicator.positionOffset, 1f)
+ _scrollAnimator.start()
+ }
+
+ fun _onAnimateValue(value: Float) {
+ tabIndicator.positionOffset = value
+ tabLayoutConfig?.onPageIndexScrolled(
+ tabIndicator.currentIndex,
+ tabIndicator._targetIndex,
+ value
+ )
+ tabLayoutConfig?.let {
+ dslSelector.visibleViewList.apply {
+ val targetView = getOrNull(tabIndicator._targetIndex)
+ if (targetView != null) {
+ it.onPageViewScrolled(
+ getOrNull(tabIndicator.currentIndex),
+ targetView,
+ value
+ )
+ }
+ }
+ }
+ }
+
+ fun _onAnimateEnd() {
+ tabIndicator.currentIndex = dslSelector.dslSelectIndex
+ tabIndicator._targetIndex = tabIndicator.currentIndex
+ tabIndicator.positionOffset = 0f
+ //结束_viewPager的滚动动画, 系统没有直接结束的api, 固用此方法代替.
+ //_viewPager?.setCurrentItem(tabIndicator.currentIndex, false)
+ }
+
+ //
+
+ //
+
+ var _viewPagerDelegate: ViewPagerDelegate? = null
+ var _viewPagerScrollState = 0
+
+ fun onPageScrollStateChanged(state: Int) {
+ //"$state".logi()
+ _viewPagerScrollState = state
+ if (state == ViewPagerDelegate.SCROLL_STATE_IDLE) {
+ _onAnimateEnd()
+ dslSelector.updateStyle()
+ }
+ }
+
+ fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+ if (isAnimatorStart) {
+ //动画已经开始了
+ return
+ }
+
+ val currentItem = _viewPagerDelegate?.onGetCurrentItem() ?: 0
+ //"$currentItem:$position $positionOffset $positionOffsetPixels state:$_viewPagerScrollState".logw()
+
+ if (position < currentItem) {
+ //Page 目标在左
+ if (_viewPagerScrollState == ViewPagerDelegate.SCROLL_STATE_DRAGGING) {
+ tabIndicator.currentIndex = position + 1
+ tabIndicator._targetIndex = position
+ }
+ _onAnimateValue(1 - positionOffset)
+ } else {
+ //Page 目标在右
+ if (_viewPagerScrollState == ViewPagerDelegate.SCROLL_STATE_DRAGGING) {
+ tabIndicator.currentIndex = position
+ tabIndicator._targetIndex = position + 1
+ }
+ _onAnimateValue(positionOffset)
+ }
+ }
+
+ fun onPageSelected(position: Int) {
+ setCurrentItem(position, true, false)
+ }
+
+ //
+
+ //
+
+ override fun onRestoreInstanceState(state: Parcelable?) {
+ if (state is Bundle) {
+ val oldState: Parcelable? = state.getParcelable("old")
+ super.onRestoreInstanceState(oldState)
+
+ tabDefaultIndex = state.getInt("defaultIndex", tabDefaultIndex)
+ val currentItemIndex = state.getInt("currentIndex", -1)
+ dslSelector.dslSelectIndex = -1
+ if (currentItemIndex > 0) {
+ setCurrentItem(currentItemIndex, true, false)
+ }
+ } else {
+ super.onRestoreInstanceState(state)
+ }
+ }
+
+ override fun onSaveInstanceState(): Parcelable? {
+ val state = super.onSaveInstanceState()
+ val bundle = Bundle()
+ bundle.putParcelable("old", state)
+ bundle.putInt("defaultIndex", tabDefaultIndex)
+ bundle.putInt("currentIndex", currentItemIndex)
+ return bundle
+ }
+
+ //
+}
+
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayoutConfig.kt b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayoutConfig.kt
new file mode 100644
index 000000000..3d4a48dec
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayoutConfig.kt
@@ -0,0 +1,526 @@
+package com.angcyo.tablayout
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Typeface
+import android.util.AttributeSet
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.annotation.IdRes
+import com.angcyo.tablayout.DslTabIndicator.Companion.NO_COLOR
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/26
+ * Copyright (c) 2019 ShenZhen O&M Cloud Co., Ltd. All rights reserved.
+ */
+open class DslTabLayoutConfig(val tabLayout: DslTabLayout) : DslSelectorConfig() {
+
+ /**是否开启文本颜色*/
+ var tabEnableTextColor = true
+ set(value) {
+ field = value
+ if (field) {
+ tabEnableIcoColor = true
+ }
+ }
+
+ /**是否开启颜色渐变效果*/
+ var tabEnableGradientColor = false
+ set(value) {
+ field = value
+ if (field) {
+ tabEnableIcoGradientColor = true
+ }
+ }
+
+ /**是否激活指示器的颜色渐变效果*/
+ var tabEnableIndicatorGradientColor = false
+
+ /**选中的文本颜色*/
+ var tabSelectColor: Int = Color.WHITE //Color.parseColor("#333333")
+
+ /**未选中的文本颜色*/
+ var tabDeselectColor: Int = Color.parseColor("#999999")
+
+ /**是否开启Bold, 文本加粗*/
+ var tabEnableTextBold = false
+
+ /**是否使用粗体字体的方式设置粗体, 否则使用[Paint.FAKE_BOLD_TEXT_FLAG]
+ * 需要先激活[tabEnableTextBold]*/
+ var tabUseTypefaceBold = false
+
+ /**是否开启图标颜色*/
+ var tabEnableIcoColor = true
+
+ /**是否开启图标颜色渐变效果*/
+ var tabEnableIcoGradientColor = false
+
+ /**选中的图标颜色*/
+ var tabIcoSelectColor: Int = NO_COLOR
+ get() {
+ return if (field == NO_COLOR) tabSelectColor else field
+ }
+
+ /**未选中的图标颜色*/
+ var tabIcoDeselectColor: Int = NO_COLOR
+ get() {
+ return if (field == NO_COLOR) tabDeselectColor else field
+ }
+
+ /**是否开启scale渐变效果*/
+ var tabEnableGradientScale = false
+
+ /**最小缩放的比例*/
+ var tabMinScale = 0.8f
+
+ /**最大缩放的比例*/
+ var tabMaxScale = 1.2f
+
+ /**是否开启字体大小渐变效果*/
+ var tabEnableGradientTextSize = true
+
+ /**tab中文本字体未选中时的字体大小, >0时激活*/
+ var tabTextMinSize = -1f
+
+ /**tab中文本字体选中时的字体大小, >0时激活*/
+ var tabTextMaxSize = -1f
+
+ /**渐变效果实现的回调*/
+ var tabGradientCallback = TabGradientCallback()
+
+ /**指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上.
+ * 如果指定的控件不存在, 控件会降权至[ItemView]*/
+ @IdRes
+ var tabTextViewId: Int = View.NO_ID
+
+ /**指定图标控件的id*/
+ @IdRes
+ var tabIconViewId: Int = View.NO_ID
+
+ /**返回用于配置文本样式的控件*/
+ var onGetTextStyleView: (itemView: View, index: Int) -> TextView? = { itemView, _ ->
+ if (tabTextViewId == View.NO_ID) {
+ var tv: TextView? = if (itemView is TextView) itemView else null
+
+ if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
+ itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
+ if (it is TextView) {
+ tv = it
+ }
+ }
+ }
+
+ if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
+ itemView.findViewById(tabLayout.tabIndicator.indicatorContentId)?.let {
+ if (it is TextView) {
+ tv = it
+ }
+ }
+ }
+
+ val lp = itemView.layoutParams
+ if (lp is DslTabLayout.LayoutParams) {
+ if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
+ itemView.getChildOrNull(lp.indicatorContentIndex)?.let {
+ if (it is TextView) {
+ tv = it
+ }
+ }
+ }
+
+ if (lp.indicatorContentId != View.NO_ID) {
+ itemView.findViewById(lp.indicatorContentId)?.let {
+ if (it is TextView) {
+ tv = it
+ }
+ }
+ }
+
+ if (lp.contentTextViewIndex != -1 && itemView is ViewGroup) {
+ itemView.getChildOrNull(lp.contentTextViewIndex)?.let {
+ if (it is TextView) {
+ tv = it
+ }
+ }
+ }
+
+ if (lp.contentTextViewId != View.NO_ID) {
+ itemView.findViewById(lp.contentTextViewId)?.let {
+ if (it is TextView) {
+ tv = it
+ }
+ }
+ }
+ }
+ tv
+ } else {
+ itemView.findViewById(tabTextViewId)
+ }
+ }
+
+ /**返回用于配置ico样式的控件*/
+ var onGetIcoStyleView: (itemView: View, index: Int) -> View? = { itemView, _ ->
+ if (tabIconViewId == View.NO_ID) {
+ var iv: View? = itemView
+
+ if (tabLayout.tabIndicator.indicatorContentIndex != -1) {
+ itemView.getChildOrNull(tabLayout.tabIndicator.indicatorContentIndex)?.let {
+ iv = it
+ }
+ }
+
+ if (tabLayout.tabIndicator.indicatorContentId != View.NO_ID) {
+ itemView.findViewById(tabLayout.tabIndicator.indicatorContentId)?.let {
+ iv = it
+ }
+ }
+
+ val lp = itemView.layoutParams
+ if (lp is DslTabLayout.LayoutParams) {
+ if (lp.indicatorContentIndex != -1 && itemView is ViewGroup) {
+ iv = itemView.getChildOrNull(lp.indicatorContentIndex)
+ }
+
+ if (lp.indicatorContentId != View.NO_ID) {
+ itemView.findViewById(lp.indicatorContentId)?.let {
+ iv = it
+ }
+ }
+
+ if (lp.contentIconViewIndex != -1 && itemView is ViewGroup) {
+ iv = itemView.getChildOrNull(lp.contentIconViewIndex)
+ }
+
+ if (lp.contentIconViewId != View.NO_ID) {
+ itemView.findViewById(lp.contentIconViewId)?.let {
+ iv = it
+ }
+ }
+ }
+ iv
+ } else {
+ itemView.findViewById(tabIconViewId)
+ }
+ }
+
+ /**获取渐变结束时,指示器的颜色.*/
+ var onGetGradientIndicatorColor: (fromIndex: Int, toIndex: Int, positionOffset: Float) -> Int =
+ { fromIndex, toIndex, positionOffset ->
+ tabLayout.tabIndicator.indicatorColor
+ }
+
+ init {
+ onStyleItemView = { itemView, index, select ->
+ onUpdateItemStyle(itemView, index, select)
+ }
+ onSelectIndexChange = { fromIndex, selectIndexList, reselect, fromUser ->
+ val toIndex = selectIndexList.last()
+ tabLayout._viewPagerDelegate?.onSetCurrentItem(fromIndex, toIndex, reselect, fromUser)
+ }
+ }
+
+ /**xml属性读取*/
+ open fun initAttribute(context: Context, attributeSet: AttributeSet? = null) {
+ val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.DslTabLayout)
+
+ tabSelectColor =
+ typedArray.getColor(R.styleable.DslTabLayout_tab_select_color, tabSelectColor)
+ tabDeselectColor =
+ typedArray.getColor(
+ R.styleable.DslTabLayout_tab_deselect_color,
+ tabDeselectColor
+ )
+ tabIcoSelectColor =
+ typedArray.getColor(R.styleable.DslTabLayout_tab_ico_select_color, NO_COLOR)
+ tabIcoDeselectColor =
+ typedArray.getColor(R.styleable.DslTabLayout_tab_ico_deselect_color, NO_COLOR)
+
+ tabEnableTextColor = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_text_color,
+ tabEnableTextColor
+ )
+ tabEnableIndicatorGradientColor = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_indicator_gradient_color,
+ tabEnableIndicatorGradientColor
+ )
+ tabEnableGradientColor = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_gradient_color,
+ tabEnableGradientColor
+ )
+ tabEnableIcoColor = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_ico_color,
+ tabEnableIcoColor
+ )
+ tabEnableIcoGradientColor = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_ico_gradient_color,
+ tabEnableIcoGradientColor
+ )
+
+ tabEnableTextBold = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_text_bold,
+ tabEnableTextBold
+ )
+
+ tabUseTypefaceBold = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_use_typeface_bold,
+ tabUseTypefaceBold
+ )
+
+ tabEnableGradientScale = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_gradient_scale,
+ tabEnableGradientScale
+ )
+ tabMinScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_min_scale, tabMinScale)
+ tabMaxScale = typedArray.getFloat(R.styleable.DslTabLayout_tab_max_scale, tabMaxScale)
+
+ tabEnableGradientTextSize = typedArray.getBoolean(
+ R.styleable.DslTabLayout_tab_enable_gradient_text_size,
+ tabEnableGradientTextSize
+ )
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_min_size)) {
+ tabTextMinSize = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_text_min_size,
+ tabTextMinSize.toInt()
+ ).toFloat()
+ }
+ if (typedArray.hasValue(R.styleable.DslTabLayout_tab_text_max_size)) {
+ tabTextMaxSize = typedArray.getDimensionPixelOffset(
+ R.styleable.DslTabLayout_tab_text_max_size,
+ tabTextMaxSize.toInt()
+ ).toFloat()
+ }
+
+ tabTextViewId =
+ typedArray.getResourceId(R.styleable.DslTabLayout_tab_text_view_id, tabTextViewId)
+ tabIconViewId =
+ typedArray.getResourceId(R.styleable.DslTabLayout_tab_icon_view_id, tabIconViewId)
+
+ typedArray.recycle()
+ }
+
+ /**更新item的样式*/
+ open fun onUpdateItemStyle(itemView: View, index: Int, select: Boolean) {
+ //"$itemView\n$index\n$select".logw()
+
+ (onGetTextStyleView(itemView, index))?.apply {
+ //文本加粗
+ paint?.apply {
+ if (tabEnableTextBold && select) {
+ //设置粗体
+ if (tabUseTypefaceBold) {
+ typeface = Typeface.defaultFromStyle(Typeface.BOLD)
+ } else {
+ flags = flags or Paint.FAKE_BOLD_TEXT_FLAG
+ isFakeBoldText = true
+ }
+ } else {
+ //取消粗体
+ if (tabUseTypefaceBold) {
+ typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
+ } else {
+ flags = flags and Paint.FAKE_BOLD_TEXT_FLAG.inv()
+ isFakeBoldText = false
+ }
+ }
+ }
+
+ if (tabEnableTextColor) {
+ //文本颜色
+ setTextColor(if (select) tabSelectColor else tabDeselectColor)
+ }
+
+ if (tabTextMaxSize > 0 || tabTextMinSize > 0) {
+ //文本字体大小
+ val minTextSize = min(tabTextMinSize, tabTextMaxSize)
+ val maxTextSize = max(tabTextMinSize, tabTextMaxSize)
+ setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ if (select) maxTextSize else minTextSize
+ )
+ }
+ }
+
+ if (tabEnableIcoColor) {
+ onGetIcoStyleView(itemView, index)?.apply {
+ _updateIcoColor(this, if (select) tabIcoSelectColor else tabIcoDeselectColor)
+ }
+ }
+
+ if (tabEnableGradientScale) {
+ itemView.scaleX = if (select) tabMaxScale else tabMinScale
+ itemView.scaleY = if (select) tabMaxScale else tabMinScale
+ }
+
+ if (tabLayout.drawBorder) {
+ tabLayout.tabBorder?.updateItemBackground(tabLayout, itemView, index, select)
+ }
+ }
+
+ /**
+ * [DslTabLayout]滚动时回调.
+ * */
+ open fun onPageIndexScrolled(fromIndex: Int, toIndex: Int, positionOffset: Float) {
+
+ }
+
+ /**
+ * [onPageIndexScrolled]
+ * */
+ open fun onPageViewScrolled(fromView: View?, toView: View, positionOffset: Float) {
+ //"$fromView\n$toView\n$positionOffset".logi()
+
+ if (fromView != toView) {
+
+ val fromIndex = tabLayout.tabIndicator.currentIndex
+ val toIndex = tabLayout.tabIndicator._targetIndex
+
+ if (tabEnableIndicatorGradientColor) {
+ val startColor = onGetGradientIndicatorColor(fromIndex, fromIndex, 0f)
+ val endColor = onGetGradientIndicatorColor(fromIndex, toIndex, positionOffset)
+
+ tabLayout.tabIndicator.indicatorColor =
+ evaluateColor(positionOffset, startColor, endColor)
+ }
+
+ if (tabEnableGradientColor) {
+ //文本渐变
+ fromView?.apply {
+ _gradientColor(
+ onGetTextStyleView(this, fromIndex),
+ tabSelectColor,
+ tabDeselectColor,
+ positionOffset
+ )
+ }
+ _gradientColor(
+ onGetTextStyleView(toView, toIndex),
+ tabDeselectColor,
+ tabSelectColor,
+ positionOffset
+ )
+ }
+
+ if (tabEnableIcoGradientColor) {
+ //图标渐变
+ fromView?.apply {
+ _gradientIcoColor(
+ onGetIcoStyleView(this, fromIndex),
+ tabIcoSelectColor,
+ tabIcoDeselectColor,
+ positionOffset
+ )
+ }
+
+ _gradientIcoColor(
+ onGetIcoStyleView(toView, toIndex),
+ tabIcoDeselectColor,
+ tabIcoSelectColor,
+ positionOffset
+ )
+ }
+
+ if (tabEnableGradientScale) {
+ //scale渐变
+ _gradientScale(fromView, tabMaxScale, tabMinScale, positionOffset)
+ _gradientScale(toView, tabMinScale, tabMaxScale, positionOffset)
+ }
+
+ if (tabEnableGradientTextSize &&
+ tabTextMaxSize > 0 &&
+ tabTextMinSize > 0 &&
+ tabTextMinSize != tabTextMaxSize
+ ) {
+
+ //文本字体大小渐变
+ _gradientTextSize(
+ fromView?.run { onGetTextStyleView(this, fromIndex) },
+ tabTextMaxSize,
+ tabTextMinSize,
+ positionOffset
+ )
+ _gradientTextSize(
+ onGetTextStyleView(toView, toIndex),
+ tabTextMinSize,
+ tabTextMaxSize,
+ positionOffset
+ )
+
+ if (toIndex == tabLayout.dslSelector.visibleViewList.lastIndex || toIndex == 0) {
+ tabLayout._scrollToTarget(toIndex, false)
+ }
+ }
+ }
+ }
+
+ open fun _gradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
+ tabGradientCallback.onGradientColor(view, startColor, endColor, percent)
+ }
+
+ open fun _gradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
+ tabGradientCallback.onGradientIcoColor(view, startColor, endColor, percent)
+ }
+
+ open fun _gradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
+ tabGradientCallback.onGradientScale(view, startScale, endScale, percent)
+ }
+
+ open fun _gradientTextSize(
+ view: TextView?,
+ startTextSize: Float,
+ endTextSize: Float,
+ percent: Float
+ ) {
+ tabGradientCallback.onGradientTextSize(view, startTextSize, endTextSize, percent)
+ }
+
+ open fun _updateIcoColor(view: View?, color: Int) {
+ tabGradientCallback.onUpdateIcoColor(view, color)
+ }
+}
+
+open class TabGradientCallback {
+
+ open fun onGradientColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
+ (view as? TextView)?.apply {
+ setTextColor(evaluateColor(percent, startColor, endColor))
+ }
+ }
+
+ open fun onGradientIcoColor(view: View?, startColor: Int, endColor: Int, percent: Float) {
+ onUpdateIcoColor(view, evaluateColor(percent, startColor, endColor))
+ }
+
+ open fun onUpdateIcoColor(view: View?, color: Int) {
+ view?.tintDrawableColor(color)
+ }
+
+ open fun onGradientScale(view: View?, startScale: Float, endScale: Float, percent: Float) {
+ view?.apply {
+ (startScale + (endScale - startScale) * percent).let {
+ scaleX = it
+ scaleY = it
+ }
+ }
+ }
+
+ open fun onGradientTextSize(
+ view: TextView?,
+ startTextSize: Float,
+ endTextSize: Float,
+ percent: Float
+ ) {
+ view?.apply {
+ setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ (startTextSize + (endTextSize - startTextSize) * percent)
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/ITabIndicatorDraw.kt b/TabLayout/src/main/java/com/angcyo/tablayout/ITabIndicatorDraw.kt
new file mode 100644
index 000000000..1b7f74926
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/ITabIndicatorDraw.kt
@@ -0,0 +1,22 @@
+package com.angcyo.tablayout
+
+import android.graphics.Canvas
+
+/**
+ * 用来实现[DslTabIndicator]的自绘
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2022/02/21
+ * Copyright (c) 2020 ShenZhen Wayto Ltd. All rights reserved.
+ */
+interface ITabIndicatorDraw {
+
+ /**绘制指示器
+ * [positionOffset] 页面偏移量*/
+ fun onDrawTabIndicator(
+ tabIndicator: DslTabIndicator,
+ canvas: Canvas,
+ positionOffset: Float
+ )
+
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt b/TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
new file mode 100644
index 000000000..2d040a90e
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
@@ -0,0 +1,334 @@
+package com.angcyo.tablayout
+
+import android.app.Activity
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.text.TextUtils
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.annotation.LayoutRes
+import androidx.core.graphics.drawable.DrawableCompat
+import androidx.core.math.MathUtils
+
+/**
+ *
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/11/23
+ */
+internal val dpi: Int
+ get() = dp.toInt()
+
+internal val dp: Float
+ get() = Resources.getSystem().displayMetrics.density
+
+internal val View.dpi: Int
+ get() = context.resources.displayMetrics.density.toInt()
+
+internal val View.screenWidth: Int
+ get() = context.resources.displayMetrics.widthPixels
+
+internal val View.screenHeight: Int
+ get() = context.resources.displayMetrics.heightPixels
+
+internal val View.viewDrawWidth: Int
+ get() = measuredWidth - paddingLeft - paddingRight
+
+internal val View.viewDrawHeight: Int
+ get() = measuredHeight - paddingTop - paddingBottom
+
+/**Match_Parent*/
+internal fun exactlyMeasure(size: Int): Int =
+ View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.EXACTLY)
+
+internal fun exactlyMeasure(size: Float): Int = exactlyMeasure(size.toInt())
+
+/**Wrap_Content*/
+internal fun atmostMeasure(size: Int): Int =
+ View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.AT_MOST)
+
+internal fun Int.have(value: Int): Boolean = if (this == 0 || value == 0) {
+ false
+} else if (this == 0 && value == 0) {
+ true
+} else {
+ ((this > 0 && value > 0) || (this < 0 && value < 0)) && this and value == value
+}
+
+internal fun Int.remove(value: Int): Int = this and value.inv()
+
+internal fun clamp(value: Float, min: Float, max: Float): Float {
+ if (value < min) {
+ return min
+ } else if (value > max) {
+ return max
+ }
+ return value
+}
+
+internal fun clamp(value: Int, min: Int, max: Int): Int {
+ if (value < min) {
+ return min
+ } else if (value > max) {
+ return max
+ }
+ return value
+}
+
+internal fun Any.logi() {
+ Log.i("DslTabLayout", "$this")
+}
+
+internal fun Any.logw() {
+ Log.w("DslTabLayout", "$this")
+}
+
+internal fun Any.loge() {
+ Log.e("DslTabLayout", "$this")
+}
+
+internal fun View.calcLayoutWidthHeight(
+ rLayoutWidth: String?, rLayoutHeight: String?,
+ parentWidth: Int, parentHeight: Int,
+ rLayoutWidthExclude: Int = 0, rLayoutHeightExclude: Int = 0
+): IntArray {
+ val size = intArrayOf(-1, -1)
+ if (TextUtils.isEmpty(rLayoutWidth) && TextUtils.isEmpty(rLayoutHeight)) {
+ return size
+ }
+ if (!TextUtils.isEmpty(rLayoutWidth)) {
+ if (rLayoutWidth!!.contains("sw", true)) {
+ val ratio = rLayoutWidth.replace("sw", "", true).toFloatOrNull()
+ ratio?.let {
+ size[0] = (ratio * (screenWidth - rLayoutWidthExclude)).toInt()
+ }
+ } else if (rLayoutWidth!!.contains("pw", true)) {
+ val ratio = rLayoutWidth.replace("pw", "", true).toFloatOrNull()
+ ratio?.let {
+ size[0] = (ratio * (parentWidth - rLayoutWidthExclude)).toInt()
+ }
+ }
+ }
+ if (!TextUtils.isEmpty(rLayoutHeight)) {
+ if (rLayoutHeight!!.contains("sh", true)) {
+ val ratio = rLayoutHeight.replace("sh", "", true).toFloatOrNull()
+ ratio?.let {
+ size[1] = (ratio * (screenHeight - rLayoutHeightExclude)).toInt()
+ }
+ } else if (rLayoutHeight!!.contains("ph", true)) {
+ val ratio = rLayoutHeight.replace("ph", "", true).toFloatOrNull()
+ ratio?.let {
+ size[1] = (ratio * (parentHeight - rLayoutHeightExclude)).toInt()
+ }
+ }
+ }
+ return size
+}
+
+internal fun evaluateColor(fraction: Float /*0-1*/, startColor: Int, endColor: Int): Int {
+ val fr = MathUtils.clamp(fraction, 0f, 1f)
+ val startA = startColor shr 24 and 0xff
+ val startR = startColor shr 16 and 0xff
+ val startG = startColor shr 8 and 0xff
+ val startB = startColor and 0xff
+ val endA = endColor shr 24 and 0xff
+ val endR = endColor shr 16 and 0xff
+ val endG = endColor shr 8 and 0xff
+ val endB = endColor and 0xff
+ return startA + (fr * (endA - startA)).toInt() shl 24 or
+ (startR + (fr * (endR - startR)).toInt() shl 16) or
+ (startG + (fr * (endG - startG)).toInt() shl 8) or
+ startB + (fr * (endB - startB)).toInt()
+}
+
+internal fun Drawable?.tintDrawableColor(color: Int): Drawable? {
+
+ if (this == null) {
+ return this
+ }
+
+ val wrappedDrawable =
+ DrawableCompat.wrap(this).mutate()
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ DrawableCompat.setTint(wrappedDrawable, color)
+ } else {
+ wrappedDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ }
+
+ return wrappedDrawable
+}
+
+internal fun View?.tintDrawableColor(color: Int) {
+ when (this) {
+ is TextView -> {
+ val drawables = arrayOfNulls(4)
+ compoundDrawables.forEachIndexed { index, drawable ->
+ drawables[index] = drawable?.tintDrawableColor(color)
+ }
+ setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3])
+ }
+ is ImageView -> {
+ setImageDrawable(drawable?.tintDrawableColor(color))
+ }
+ }
+}
+
+internal fun Paint?.textWidth(text: String?): Float {
+ if (TextUtils.isEmpty(text)) {
+ return 0f
+ }
+ return this?.run {
+ measureText(text)
+ } ?: 0f
+}
+
+internal fun Paint?.textHeight(): Float = this?.run { descent() - ascent() } ?: 0f
+
+internal fun View.getChildOrNull(index: Int): View? {
+ return if (this is ViewGroup) {
+ return if (index in 0 until childCount) {
+ getChildAt(index)
+ } else {
+ null
+ }
+ } else {
+ this
+ }
+}
+
+/**获取[View]在指定[parent]中的矩形坐标*/
+internal fun View.getLocationInParent(parentView: View? = null, result: Rect = Rect()): Rect {
+ val parent: View? = parentView ?: (parent as? View)
+
+ if (parent == null) {
+ getViewRect(result)
+ } else {
+ result.set(0, 0, 0, 0)
+ if (this != parent) {
+ fun doIt(view: View, parent: View, rect: Rect) {
+ val viewParent = view.parent
+ if (viewParent is View) {
+ rect.left += view.left
+ rect.top += view.top
+ if (viewParent != parent) {
+ doIt(viewParent, parent, rect)
+ }
+ }
+ }
+ doIt(this, parent, result)
+ }
+ result.right = result.left + this.measuredWidth
+ result.bottom = result.top + this.measuredHeight
+ }
+
+ return result
+}
+
+/**
+ * 获取View, 相对于手机屏幕的矩形
+ * */
+internal fun View.getViewRect(result: Rect = Rect()): Rect {
+ var offsetX = 0
+ var offsetY = 0
+
+ //横屏, 并且显示了虚拟导航栏的时候. 需要左边偏移
+ //只计算一次
+ (context as? Activity)?.let {
+ it.window.decorView.getGlobalVisibleRect(result)
+ if (result.width() > result.height()) {
+ //横屏了
+ offsetX = navBarHeight(it)
+ }
+ }
+
+ return getViewRect(offsetX, offsetY, result)
+}
+
+/**
+ * 获取View, 相对于手机屏幕的矩形, 带皮阿尼一
+ * */
+internal fun View.getViewRect(offsetX: Int, offsetY: Int, result: Rect = Rect()): Rect {
+ //可见位置的坐标, 超出屏幕的距离会被剃掉
+ //getGlobalVisibleRect(r)
+ val r2 = IntArray(2)
+ //val r3 = IntArray(2)
+ //相对于屏幕的坐标
+ getLocationOnScreen(r2)
+ //相对于窗口的坐标
+ //getLocationInWindow(r3)
+
+ val left = r2[0] + offsetX
+ val top = r2[1] + offsetY
+
+ result.set(left, top, left + measuredWidth, top + measuredHeight)
+ return result
+}
+
+
+/**
+ * 导航栏的高度(如果显示了)
+ */
+internal fun navBarHeight(context: Context): Int {
+ var result = 0
+
+ if (context is Activity) {
+ val decorRect = Rect()
+ val windowRect = Rect()
+
+ context.window.decorView.getGlobalVisibleRect(decorRect)
+ context.window.findViewById(Window.ID_ANDROID_CONTENT)
+ .getGlobalVisibleRect(windowRect)
+
+ if (decorRect.width() > decorRect.height()) {
+ //横屏
+ result = decorRect.width() - windowRect.width()
+ } else {
+ //竖屏
+ result = decorRect.bottom - windowRect.bottom
+ }
+ }
+
+ return result
+}
+
+fun Collection<*>?.size() = this?.size ?: 0
+
+/**判断2个列表中的数据是否改变过*/
+internal fun List?.isChange(other: List?): Boolean {
+ if (this.size() != other.size()) {
+ return true
+ }
+ this?.forEachIndexed { index, t ->
+ if (t != other?.getOrNull(index)) {
+ return true
+ }
+ }
+ return false
+}
+
+fun Int.isHorizontal() = this == LinearLayout.HORIZONTAL
+
+fun Int.isVertical() = this == LinearLayout.VERTICAL
+
+internal fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = true): View {
+ if (layoutId == -1) {
+ return this
+ }
+ val rootView = LayoutInflater.from(context).inflate(layoutId, this, false)
+ if (attachToRoot) {
+ addView(rootView)
+ }
+ return rootView
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/java/com/angcyo/tablayout/ViewPagerDelegate.kt b/TabLayout/src/main/java/com/angcyo/tablayout/ViewPagerDelegate.kt
new file mode 100644
index 000000000..a40df8500
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/ViewPagerDelegate.kt
@@ -0,0 +1,21 @@
+package com.angcyo.tablayout
+
+/**
+ * 不依赖ViewPager和ViewPager2
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/12/14
+ */
+interface ViewPagerDelegate {
+ companion object {
+ const val SCROLL_STATE_IDLE = 0
+ const val SCROLL_STATE_DRAGGING = 1
+ const val SCROLL_STATE_SETTLING = 2
+ }
+
+ /**获取当前页面索引*/
+ fun onGetCurrentItem(): Int
+
+ /**设置当前的页面*/
+ fun onSetCurrentItem(fromIndex: Int, toIndex: Int, reselect: Boolean, fromUser: Boolean)
+}
\ No newline at end of file
diff --git a/TabLayout/src/main/res/values/attr_dsl_tab_layout.xml b/TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
new file mode 100644
index 000000000..423d215c8
--- /dev/null
+++ b/TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
@@ -0,0 +1,299 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ViewPager2Delegate/.gitignore b/ViewPager2Delegate/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/ViewPager2Delegate/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/ViewPager2Delegate/build.gradle b/ViewPager2Delegate/build.gradle
new file mode 100644
index 000000000..de4729b6a
--- /dev/null
+++ b/ViewPager2Delegate/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion rootProject.ext.android.compileSdkVersion
+ buildToolsVersion rootProject.ext.android.buildToolsVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.android.minSdkVersion
+ targetSdkVersion rootProject.ext.android.targetSdkVersion
+ versionCode rootProject.ext.android.versionCode
+ versionName rootProject.ext.android.versionName
+ manifestPlaceholders = rootProject.ext.manifestPlaceholders
+
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+}
+
+dependencies {
+ //https://github.com/angcyo/DslTabLayout
+ implementation project(':TabLayout')
+ //https://mvnrepository.com/artifact/androidx.viewpager2/viewpager2
+ implementation 'androidx.viewpager2:viewpager2:1.0.0'
+}
+
+//apply from: "$gradleHost/master/publish.gradle"
\ No newline at end of file
diff --git a/ViewPager2Delegate/consumer-rules.pro b/ViewPager2Delegate/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/ViewPager2Delegate/proguard-rules.pro b/ViewPager2Delegate/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/ViewPager2Delegate/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/ViewPager2Delegate/src/main/AndroidManifest.xml b/ViewPager2Delegate/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..f55a84030
--- /dev/null
+++ b/ViewPager2Delegate/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
diff --git a/ViewPager2Delegate/src/main/java/com/angcyo/tablayout/delegate2/ViewPager2Delegate.kt b/ViewPager2Delegate/src/main/java/com/angcyo/tablayout/delegate2/ViewPager2Delegate.kt
new file mode 100644
index 000000000..98e72cb85
--- /dev/null
+++ b/ViewPager2Delegate/src/main/java/com/angcyo/tablayout/delegate2/ViewPager2Delegate.kt
@@ -0,0 +1,66 @@
+package com.angcyo.tablayout.delegate2
+
+import androidx.viewpager2.widget.ViewPager2
+import com.angcyo.tablayout.DslTabLayout
+import com.angcyo.tablayout.ViewPagerDelegate
+import kotlin.math.absoluteValue
+
+/**
+ * 兼容[ViewPager2]
+ * Email:angcyo@126.com
+ * @author angcyo
+ * @date 2019/12/14
+ */
+open class ViewPager2Delegate(
+ val viewPager: ViewPager2,
+ val dslTabLayout: DslTabLayout?,
+ val forceSmoothScroll: Boolean? = null
+) : ViewPager2.OnPageChangeCallback(), ViewPagerDelegate {
+
+ companion object {
+
+ /**
+ * [forceSmoothScroll] 为 null, 只有切换左右page时, 才有VP的动画, 否则没有.
+ * */
+ fun install(
+ viewPager: ViewPager2,
+ dslTabLayout: DslTabLayout?,
+ forceSmoothScroll: Boolean? = null
+ ): ViewPager2Delegate {
+ return ViewPager2Delegate(viewPager, dslTabLayout, forceSmoothScroll)
+ }
+ }
+
+ init {
+ viewPager.registerOnPageChangeCallback(this)
+ dslTabLayout?.setupViewPager(this)
+ }
+
+ override fun onGetCurrentItem(): Int {
+ return viewPager.currentItem
+ }
+
+ override fun onSetCurrentItem(
+ fromIndex: Int,
+ toIndex: Int,
+ reselect: Boolean,
+ fromUser: Boolean
+ ) {
+ if (fromUser) {
+ val smoothScroll = forceSmoothScroll ?: ((toIndex - fromIndex).absoluteValue <= 1)
+ viewPager.setCurrentItem(toIndex, smoothScroll)
+ }
+ }
+
+ override fun onPageScrollStateChanged(state: Int) {
+ dslTabLayout?.onPageScrollStateChanged(state)
+ }
+
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+ dslTabLayout?.onPageScrolled(position, positionOffset, positionOffsetPixels)
+ }
+
+ override fun onPageSelected(position: Int) {
+ dslTabLayout?.onPageSelected(position)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c6e47c1a3..b52c66ce2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -186,6 +186,15 @@
+
+
+
diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml
index c1c7b4e46..84cb45982 100644
--- a/common/src/main/AndroidManifest.xml
+++ b/common/src/main/AndroidManifest.xml
@@ -22,15 +22,7 @@
-
-
-
+