{
+ ProtoAdapter_Transform() {
+ super(FieldEncoding.LENGTH_DELIMITED, Transform.class);
+ }
+
+ @Override
+ public int encodedSize(Transform value) {
+ return (value.a != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(1, value.a) : 0)
+ + (value.b != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(2, value.b) : 0)
+ + (value.c != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(3, value.c) : 0)
+ + (value.d != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(4, value.d) : 0)
+ + (value.tx != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(5, value.tx) : 0)
+ + (value.ty != null ? ProtoAdapter.FLOAT.encodedSizeWithTag(6, value.ty) : 0)
+ + value.unknownFields().size();
+ }
+
+ @Override
+ public void encode(ProtoWriter writer, Transform value) throws IOException {
+ if (value.a != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 1, value.a);
+ if (value.b != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 2, value.b);
+ if (value.c != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 3, value.c);
+ if (value.d != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 4, value.d);
+ if (value.tx != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 5, value.tx);
+ if (value.ty != null) ProtoAdapter.FLOAT.encodeWithTag(writer, 6, value.ty);
+ writer.writeBytes(value.unknownFields());
+ }
+
+ @Override
+ public Transform decode(ProtoReader reader) throws IOException {
+ Builder builder = new Builder();
+ long token = reader.beginMessage();
+ for (int tag; (tag = reader.nextTag()) != -1;) {
+ switch (tag) {
+ case 1: builder.a(ProtoAdapter.FLOAT.decode(reader)); break;
+ case 2: builder.b(ProtoAdapter.FLOAT.decode(reader)); break;
+ case 3: builder.c(ProtoAdapter.FLOAT.decode(reader)); break;
+ case 4: builder.d(ProtoAdapter.FLOAT.decode(reader)); break;
+ case 5: builder.tx(ProtoAdapter.FLOAT.decode(reader)); break;
+ case 6: builder.ty(ProtoAdapter.FLOAT.decode(reader)); break;
+ default: {
+ FieldEncoding fieldEncoding = reader.peekFieldEncoding();
+ Object value = fieldEncoding.rawProtoAdapter().decode(reader);
+ builder.addUnknownField(tag, fieldEncoding, value);
+ }
+ }
+ }
+ reader.endMessage(token);
+ return builder.build();
+ }
+
+ @Override
+ public Transform redact(Transform value) {
+ Builder builder = value.newBuilder();
+ builder.clearUnknownFields();
+ return builder.build();
+ }
+ }
+}
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/Pools.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/Pools.kt
new file mode 100644
index 000000000..7382ab84b
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/Pools.kt
@@ -0,0 +1,102 @@
+package com.opensource.svgaplayer.utils
+
+/**
+ * Helper class for creating pools of objects. An example use looks like this:
+ *
+ * public class MyPooledClass {
+ *
+ * private static final SynchronizedPool sPool =
+ * new SynchronizedPool(10);
+ *
+ * public static MyPooledClass obtain() {
+ * MyPooledClass instance = sPool.acquire();
+ * return (instance != null) ? instance : new MyPooledClass();
+ * }
+ *
+ * public void recycle() {
+ * // Clear state if needed.
+ * sPool.release(this);
+ * }
+ *
+ * . . .
+ * }
+ *
+ *
+ */
+class Pools private constructor() {
+
+ /**
+ * Interface for managing a pool of objects.
+ *
+ * @param The pooled type.
+ */
+ interface Pool {
+ /**
+ * @return An instance from the pool if such, null otherwise.
+ */
+ fun acquire(): T?
+
+ /**
+ * Release an instance to the pool.
+ *
+ * @param instance The instance to release.
+ * @return Whether the instance was put in the pool.
+ *
+ * @throws IllegalStateException If the instance is already in the pool.
+ */
+ fun release(instance: T): Boolean
+ }
+
+ /**
+ * Simple (non-synchronized) pool of objects.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ *
+ * @param The pooled type.
+ */
+ open class SimplePool(maxPoolSize: Int) : Pool {
+ private val mPool: Array
+ private var mPoolSize = 0
+
+ init {
+ require(maxPoolSize > 0) { "The max pool size must be > 0" }
+ mPool = arrayOfNulls(maxPoolSize)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun acquire(): T? {
+ if (mPoolSize > 0) {
+ val lastPooledIndex = mPoolSize - 1
+ val instance = mPool[lastPooledIndex] as T?
+ mPool[lastPooledIndex] = null
+ mPoolSize--
+ return instance
+ }
+ return null
+ }
+
+ override fun release(instance: T): Boolean {
+ check(!isInPool(instance)) { "Already in the pool!" }
+ if (mPoolSize < mPool.size) {
+ mPool[mPoolSize] = instance
+ mPoolSize++
+ return true
+ }
+ return false
+ }
+
+ private fun isInPool(instance: T): Boolean {
+ for (i in 0 until mPoolSize) {
+ if (mPool[i] === instance) {
+ return true
+ }
+ }
+ return false
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/SVGAScaleInfo.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/SVGAScaleInfo.kt
new file mode 100644
index 000000000..792abc266
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/SVGAScaleInfo.kt
@@ -0,0 +1,146 @@
+package com.opensource.svgaplayer.utils
+
+import android.widget.ImageView
+
+/**
+ * Created by ubt on 2018/1/19.
+ */
+class SVGAScaleInfo {
+
+ var tranFx : Float = 0.0f
+ var tranFy : Float = 0.0f
+ var scaleFx : Float = 1.0f
+ var scaleFy : Float = 1.0f
+ var ratio = 1.0f
+ var ratioX = false
+
+ private fun resetVar(){
+ tranFx = 0.0f
+ tranFy = 0.0f
+ scaleFx = 1.0f
+ scaleFy = 1.0f
+ ratio = 1.0f
+ ratioX = false
+ }
+
+ fun performScaleType(canvasWidth : Float, canvasHeight: Float, videoWidth : Float, videoHeight : Float, scaleType: ImageView.ScaleType) {
+ if (canvasWidth == 0.0f || canvasHeight == 0.0f || videoWidth == 0.0f || videoHeight == 0.0f) {
+ return
+ }
+
+ resetVar()
+ val canW_vidW_f = (canvasWidth - videoWidth) / 2.0f
+ val canH_vidH_f = (canvasHeight - videoHeight) / 2.0f
+
+ val videoRatio = videoWidth / videoHeight
+ val canvasRatio = canvasWidth / canvasHeight
+
+ val canH_d_vidH = canvasHeight / videoHeight
+ val canW_d_vidW = canvasWidth / videoWidth
+
+ when (scaleType) {
+ ImageView.ScaleType.CENTER -> {
+ tranFx = canW_vidW_f
+ tranFy = canH_vidH_f
+ }
+ ImageView.ScaleType.CENTER_CROP -> {
+ if (videoRatio > canvasRatio) {
+ ratio = canH_d_vidH
+ ratioX = false
+ scaleFx = canH_d_vidH
+ scaleFy = canH_d_vidH
+ tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
+ }
+ else {
+ ratio = canW_d_vidW
+ ratioX = true
+ scaleFx = canW_d_vidW
+ scaleFy = canW_d_vidW
+ tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
+ }
+ }
+ ImageView.ScaleType.CENTER_INSIDE -> {
+ if (videoWidth < canvasWidth && videoHeight < canvasHeight) {
+ tranFx = canW_vidW_f
+ tranFy = canH_vidH_f
+ }
+ else {
+ if (videoRatio > canvasRatio) {
+ ratio = canW_d_vidW
+ ratioX = true
+ scaleFx = canW_d_vidW
+ scaleFy = canW_d_vidW
+ tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
+
+ }
+ else {
+ ratio = canH_d_vidH
+ ratioX = false
+ scaleFx = canH_d_vidH
+ scaleFy = canH_d_vidH
+ tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
+ }
+ }
+ }
+ ImageView.ScaleType.FIT_CENTER -> {
+ if (videoRatio > canvasRatio) {
+ ratio = canW_d_vidW
+ ratioX = true
+ scaleFx = canW_d_vidW
+ scaleFy = canW_d_vidW
+ tranFy = (canvasHeight - videoHeight * (canW_d_vidW)) / 2.0f
+ }
+ else {
+ ratio = canH_d_vidH
+ ratioX = false
+ scaleFx = canH_d_vidH
+ scaleFy = canH_d_vidH
+ tranFx = (canvasWidth - videoWidth * (canH_d_vidH)) / 2.0f
+ }
+ }
+ ImageView.ScaleType.FIT_START -> {
+ if (videoRatio > canvasRatio) {
+ ratio = canW_d_vidW
+ ratioX = true
+ scaleFx = canW_d_vidW
+ scaleFy = canW_d_vidW
+ }
+ else {
+ ratio = canH_d_vidH
+ ratioX = false
+ scaleFx = canH_d_vidH
+ scaleFy = canH_d_vidH
+ }
+ }
+ ImageView.ScaleType.FIT_END -> {
+ if (videoRatio > canvasRatio) {
+ ratio = canW_d_vidW
+ ratioX = true
+ scaleFx = canW_d_vidW
+ scaleFy = canW_d_vidW
+ tranFy= canvasHeight - videoHeight * (canW_d_vidW)
+ }
+ else {
+ ratio = canH_d_vidH
+ ratioX = false
+ scaleFx = canH_d_vidH
+ scaleFy = canH_d_vidH
+ tranFx = canvasWidth - videoWidth * (canH_d_vidH)
+ }
+ }
+ ImageView.ScaleType.FIT_XY -> {
+ ratio = Math.max(canW_d_vidW, canH_d_vidH)
+ ratioX = canW_d_vidW > canH_d_vidH
+ scaleFx = canW_d_vidW
+ scaleFy = canH_d_vidH
+ }
+ else -> {
+ ratio = canW_d_vidW
+ ratioX = true
+ scaleFx = canW_d_vidW
+ scaleFy = canW_d_vidW
+ }
+ }
+ }
+
+}
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/SVGAStructs.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/SVGAStructs.kt
new file mode 100644
index 000000000..f87b30db0
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/SVGAStructs.kt
@@ -0,0 +1,11 @@
+package com.opensource.svgaplayer.utils
+
+/**
+ * Created by cuiminghui on 2017/3/29.
+ */
+
+class SVGAPoint(val x: Float, val y: Float, val value: Float)
+
+class SVGARect(val x: Double, val y: Double, val width: Double, val height: Double)
+
+class SVGARange(val location: Int, val length: Int)
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/DefaultLogCat.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/DefaultLogCat.kt
new file mode 100644
index 000000000..33200b0e1
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/DefaultLogCat.kt
@@ -0,0 +1,28 @@
+package com.opensource.svgaplayer.utils.log
+
+import android.util.Log
+
+/**
+ * 内部默认 ILogger 接口实现
+ */
+class DefaultLogCat : ILogger {
+ override fun verbose(tag: String, msg: String) {
+ Log.v(tag, msg)
+ }
+
+ override fun info(tag: String, msg: String) {
+ Log.i(tag, msg)
+ }
+
+ override fun debug(tag: String, msg: String) {
+ Log.d(tag, msg)
+ }
+
+ override fun warn(tag: String, msg: String) {
+ Log.w(tag, msg)
+ }
+
+ override fun error(tag: String, msg: String?, error: Throwable?) {
+ Log.e(tag, msg, error)
+ }
+}
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/ILogger.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/ILogger.kt
new file mode 100644
index 000000000..ad935104c
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/ILogger.kt
@@ -0,0 +1,12 @@
+package com.opensource.svgaplayer.utils.log
+
+/**
+ * log 外部接管接口
+ **/
+interface ILogger {
+ fun verbose(tag: String, msg: String)
+ fun info(tag: String, msg: String)
+ fun debug(tag: String, msg: String)
+ fun warn(tag: String, msg: String)
+ fun error(tag: String, msg: String?, error: Throwable?)
+}
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/LogUtils.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/LogUtils.kt
new file mode 100644
index 000000000..60c67f9c2
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/LogUtils.kt
@@ -0,0 +1,57 @@
+package com.opensource.svgaplayer.utils.log
+
+/**
+ * 日志输出
+ */
+internal object LogUtils {
+ private const val TAG = "SVGALog"
+
+ fun verbose(tag: String = TAG, msg: String) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.verbose(tag, msg)
+ }
+
+ fun info(tag: String = TAG, msg: String) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.info(tag, msg)
+ }
+
+ fun debug(tag: String = TAG, msg: String) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.debug(tag, msg)
+ }
+
+ fun warn(tag: String = TAG, msg: String) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.warn(tag, msg)
+ }
+
+ fun error(tag: String = TAG, msg: String) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.error(tag, msg, null)
+ }
+
+ fun error(tag: String, error: Throwable) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.error(tag, error.message, error)
+ }
+
+ fun error(tag: String = TAG, msg: String, error: Throwable) {
+ if (!SVGALogger.isLogEnabled()) {
+ return
+ }
+ SVGALogger.getSVGALogger()?.error(tag, msg, error)
+ }
+}
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/SVGALogger.kt b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/SVGALogger.kt
new file mode 100644
index 000000000..5767c6385
--- /dev/null
+++ b/SVGAlibrary/src/main/java/com/opensource/svgaplayer/utils/log/SVGALogger.kt
@@ -0,0 +1,40 @@
+package com.opensource.svgaplayer.utils.log
+
+/**
+ * SVGA logger 配置管理
+ **/
+object SVGALogger {
+
+ private var mLogger: ILogger? = DefaultLogCat()
+ private var isLogEnabled = false
+
+ /**
+ * log 接管注入
+ */
+ fun injectSVGALoggerImp(logImp: ILogger): SVGALogger {
+ mLogger = logImp
+ return this
+ }
+
+ /**
+ * 设置是否开启 log
+ */
+ fun setLogEnabled(isEnabled: Boolean): SVGALogger {
+ isLogEnabled = isEnabled
+ return this
+ }
+
+ /**
+ * 获取当前 ILogger 实现类
+ */
+ fun getSVGALogger(): ILogger? {
+ return mLogger
+ }
+
+ /**
+ * 是否开启 log
+ */
+ fun isLogEnabled(): Boolean {
+ return isLogEnabled
+ }
+}
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/res/values/attrs.xml b/SVGAlibrary/src/main/res/values/attrs.xml
new file mode 100644
index 000000000..471c34453
--- /dev/null
+++ b/SVGAlibrary/src/main/res/values/attrs.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SVGAlibrary/src/main/res/values/strings.xml b/SVGAlibrary/src/main/res/values/strings.xml
new file mode 100644
index 000000000..475f55349
--- /dev/null
+++ b/SVGAlibrary/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ SVGAPlayer
+
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..d915fd1b2
--- /dev/null
+++ b/TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
@@ -0,0 +1,2042 @@
+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
+ }
+
+ //
+}
\ No newline at end of file
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/common/build.gradle b/common/build.gradle
index 39c0f0dd1..664aa81d8 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -107,6 +107,7 @@ dependencies {
//svga播放器
//api rootProject.ext.dependencies["SVGAPlayer"]
+ // api project(path:':SVGAlibrary')//svga
implementation 'com.squareup.wire:wire-runtime:4.4.1'
//七牛云存储
@@ -237,4 +238,7 @@ dependencies {
api 'com.google.android.exoplayer:exoplayer-core:2.18.2@aar'
api rootProject.ext.dependencies["blank-utilcode"]
+
+ //下标切换器 https://github.com/angcyo/DslTabLayout
+ api project(':TabLayout')
}
diff --git a/common/src/main/assets/gift_wall_gift_info_light.svga b/common/src/main/assets/gift_wall_gift_info_light.svga
new file mode 100644
index 0000000000000000000000000000000000000000..6965e8c724596912f06f113e9e7f3c863edb2416
GIT binary patch
literal 1145793
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