update
This commit is contained in:
parent
d305c7809c
commit
9e889a2f14
1
TabLayout/.gitignore
vendored
Normal file
1
TabLayout/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
29
TabLayout/build.gradle
Normal file
29
TabLayout/build.gradle
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 33
|
||||||
|
buildToolsVersion "30.0.2"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 28
|
||||||
|
targetSdkVersion 33
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.2"
|
||||||
|
|
||||||
|
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"
|
0
TabLayout/consumer-rules.pro
Normal file
0
TabLayout/consumer-rules.pro
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal file
21
TabLayout/proguard-rules.pro
vendored
Normal file
@ -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
|
2
TabLayout/src/main/AndroidManifest.xml
Normal file
2
TabLayout/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.angcyo.tablayout" />
|
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
200
TabLayout/src/main/java/com/angcyo/tablayout/AbsDslDrawable.kt
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="View相关方法">
|
||||||
|
|
||||||
|
/**附着的[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
|
||||||
|
|
||||||
|
//</editor-fold desc="View相关方法">
|
||||||
|
|
||||||
|
//<editor-fold desc="基类方法">
|
||||||
|
|
||||||
|
/**核心方法, 绘制*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold desc="基类方法">
|
||||||
|
}
|
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
275
TabLayout/src/main/java/com/angcyo/tablayout/DslBadgeDrawable.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="圆角相关配置">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold desc="圆角相关配置">
|
||||||
|
|
||||||
|
//<editor-fold desc="传递属性">
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
//</editor-fold desc="传递属性">
|
||||||
|
}
|
||||||
|
|
||||||
|
@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()!!
|
||||||
|
}
|
||||||
|
}
|
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
215
TabLayout/src/main/java/com/angcyo/tablayout/DslGravity.kt
Normal file
@ -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
|
||||||
|
}
|
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
438
TabLayout/src/main/java/com/angcyo/tablayout/DslSelector.kt
Normal file
@ -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<View> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中的索引列表
|
||||||
|
* */
|
||||||
|
val selectorIndexList: MutableList<Int> = mutableListOf()
|
||||||
|
get() {
|
||||||
|
field.clear()
|
||||||
|
visibleViewList.forEachIndexed { index, view ->
|
||||||
|
if (view.isSe()) {
|
||||||
|
field.add(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中的View列表
|
||||||
|
* */
|
||||||
|
val selectorViewList: MutableList<View> = 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<View> {
|
||||||
|
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<Int>,
|
||||||
|
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<View>, reselect: Boolean, fromUser: Boolean) -> Unit =
|
||||||
|
{ _, _, _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中改变回调
|
||||||
|
* [onSelectViewChange]
|
||||||
|
* @param fromIndex 单选模式下有效, 表示之前选中的[View], 在可见性[child]列表中的索引
|
||||||
|
* */
|
||||||
|
var onSelectIndexChange: (fromIndex: Int, selectIndexList: List<Int>, 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)
|
||||||
|
}
|
||||||
|
}
|
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
222
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBadge.kt
Normal file
@ -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
|
||||||
|
)
|
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
279
TabLayout/src/main/java/com/angcyo/tablayout/DslTabBorder.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
153
TabLayout/src/main/java/com/angcyo/tablayout/DslTabDivider.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
118
TabLayout/src/main/java/com/angcyo/tablayout/DslTabHighlight.kt
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
931
TabLayout/src/main/java/com/angcyo/tablayout/DslTabIndicator.kt
Normal file
@ -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
|
||||||
|
}
|
2043
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
2043
TabLayout/src/main/java/com/angcyo/tablayout/DslTabLayout.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -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<View>(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<View>(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<View>(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<View>(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<View>(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<View>(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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
334
TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
Normal file
334
TabLayout/src/main/java/com/angcyo/tablayout/LibEx.kt
Normal file
@ -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<Drawable?>(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<View>(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 <T> List<T>?.isChange(other: List<T>?): 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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
299
TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
Normal file
299
TabLayout/src/main/res/values/attr_dsl_tab_layout.xml
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<declare-styleable name="DslTabLayout">
|
||||||
|
<!--Item是否等宽-->
|
||||||
|
<attr name="tab_item_is_equ_width" format="boolean" />
|
||||||
|
<!--当子Item数量大于等于指定数量时,开启等宽,此属性优先级最高-->
|
||||||
|
<attr name="tab_item_equ_width_count" format="integer" />
|
||||||
|
<!--[~3] 小于等于3个 [3~] 大于等于3个 [3~5] 3<= <=5 -->
|
||||||
|
<attr name="tab_item_equ_width_count_range" format="string" />
|
||||||
|
<!--智能判断Item是否等宽, 如果所有子项, 未撑满tab时, 开启等宽模式.此属性会覆盖[tab_item_is_equ_width]-->
|
||||||
|
<attr name="tab_item_auto_equ_width" format="boolean" />
|
||||||
|
<!--默认选中的索引值-->
|
||||||
|
<attr name="tab_default_index" format="integer" />
|
||||||
|
<!--等宽模式下, 指定item的宽度. 不指定则平分-->
|
||||||
|
<attr name="tab_item_width" format="dimension" />
|
||||||
|
<!--在TabLayout wrap_content时, child match_parent时的高度-->
|
||||||
|
<attr name="tab_item_default_height" format="dimension" />
|
||||||
|
<!--是否绘制边框-->
|
||||||
|
<attr name="tab_draw_border" format="boolean" />
|
||||||
|
<!--是否绘制分割线-->
|
||||||
|
<attr name="tab_draw_divider" format="boolean" />
|
||||||
|
<!--是否绘制指示器-->
|
||||||
|
<attr name="tab_draw_indicator" format="boolean" />
|
||||||
|
<!--高凸模式下的背景drawable-->
|
||||||
|
<attr name="tab_convex_background" format="reference|color" />
|
||||||
|
<!--是否激活滑动选择模式-->
|
||||||
|
<attr name="tab_enable_selector_mode" format="boolean" />
|
||||||
|
<!--方向-->
|
||||||
|
<attr name="tab_orientation" format="enum">
|
||||||
|
<enum name="VERTICAL" value="1" />
|
||||||
|
<enum name="HORIZONTAL" value="0" />
|
||||||
|
</attr>
|
||||||
|
<attr name="tab_layout_scroll_anim" format="boolean" />
|
||||||
|
<attr name="tab_scroll_anim_duration" format="integer" />
|
||||||
|
<!--预览的布局id-->
|
||||||
|
<attr name="tab_preview_item_layout_id" format="reference" />
|
||||||
|
<!--预览的布局数量-->
|
||||||
|
<attr name="tab_preview_item_count" format="integer" />
|
||||||
|
|
||||||
|
<!--indicator 指示器相关属性-->
|
||||||
|
|
||||||
|
<!--强制指定指示器的Drawable-->
|
||||||
|
<attr name="tab_indicator_drawable" format="reference" />
|
||||||
|
<!--强制指定Drawable的颜色-->
|
||||||
|
<attr name="tab_indicator_color" format="color" />
|
||||||
|
<!--指示器的绘制类型, 可以使用[STYLE_TOP|STYLE_FOREGROUND]组合配置-->
|
||||||
|
<attr name="tab_indicator_style" format="flags">
|
||||||
|
<!--不绘制-->
|
||||||
|
<flag name="STYLE_NONE" value="0" />
|
||||||
|
<flag name="STYLE_TOP" value="0x1" />
|
||||||
|
<flag name="STYLE_BOTTOM" value="0x2" />
|
||||||
|
<flag name="STYLE_CENTER" value="0x4" />
|
||||||
|
<!--前景绘制-->
|
||||||
|
<flag name="STYLE_FOREGROUND" value="0x1000" />
|
||||||
|
</attr>
|
||||||
|
<!--指示器的重力-->
|
||||||
|
<attr name="tab_indicator_gravity" format="enum">
|
||||||
|
<!--指示器靠左显示-->
|
||||||
|
<enum name="GRAVITY_START" value="0x1" />
|
||||||
|
<!--指示器靠右显示-->
|
||||||
|
<enum name="GRAVITY_END" value="0x2" />
|
||||||
|
<!--指示器居中显示-->
|
||||||
|
<enum name="GRAVITY_CENTER" value="0x4" />
|
||||||
|
</attr>
|
||||||
|
<!--是否激活流式效果, ViewPager在滚动时, 指示器的宽度由小变大,再由大变小-->
|
||||||
|
<attr name="tab_indicator_enable_flow" format="boolean" />
|
||||||
|
<!--闪现效果-->
|
||||||
|
<attr name="tab_indicator_enable_flash" format="boolean" />
|
||||||
|
<!--闪现效果使用clip处理-->
|
||||||
|
<attr name="tab_indicator_enable_flash_clip" format="boolean" />
|
||||||
|
<!--tab child的索引相差多少值时, 才开启flow效果-->
|
||||||
|
<attr name="tab_indicator_flow_step" format="integer" />
|
||||||
|
<!--指示器的宽度-->
|
||||||
|
<attr name="tab_indicator_width" format="dimension|flags">
|
||||||
|
<flag name="WRAP_CONTENT" value="-2" />
|
||||||
|
<flag name="MATCH_PARENT" value="-1" />
|
||||||
|
</attr>
|
||||||
|
<!--宽度的补偿-->
|
||||||
|
<attr name="tab_indicator_width_offset" format="dimension" />
|
||||||
|
<!--同上-->
|
||||||
|
<attr name="tab_indicator_height" format="dimension|flags">
|
||||||
|
<flag name="WRAP_CONTENT" value="-2" />
|
||||||
|
<flag name="MATCH_PARENT" value="-1" />
|
||||||
|
</attr>
|
||||||
|
<!--同上-->
|
||||||
|
<attr name="tab_indicator_height_offset" format="dimension" />
|
||||||
|
<!--x轴的补偿-->
|
||||||
|
<attr name="tab_indicator_x_offset" format="dimension" />
|
||||||
|
<!--y轴的补偿, 会根据[tab_indicator_style]的类型, 自动取负值.-->
|
||||||
|
<attr name="tab_indicator_y_offset" format="dimension" />
|
||||||
|
<!--指示器child的锚点索引, 用来辅助定位计算指示器的宽度,高度,中点坐标-->
|
||||||
|
<attr name="tab_indicator_content_index" format="integer" />
|
||||||
|
<!--指示器child的锚点控件id, 用来辅助定位计算指示器的宽度,高度,中点坐标-->
|
||||||
|
<attr name="tab_indicator_content_id" format="reference" />
|
||||||
|
<!--切换指示器时, 是否需要动画的支持-->
|
||||||
|
<attr name="tab_indicator_anim" format="boolean" />
|
||||||
|
|
||||||
|
<!--请参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
|
||||||
|
<attr name="tab_indicator_shape" format="enum">
|
||||||
|
<enum name="RECTANGLE" value="0" />
|
||||||
|
<enum name="OVAL" value="1" />
|
||||||
|
<enum name="LINE" value="2" />
|
||||||
|
<enum name="RING" value="3" />
|
||||||
|
</attr>
|
||||||
|
<attr name="tab_indicator_solid_color" format="color" />
|
||||||
|
<attr name="tab_indicator_stroke_color" format="color" />
|
||||||
|
<attr name="tab_indicator_stroke_width" format="dimension" />
|
||||||
|
<attr name="tab_indicator_dash_width" format="dimension" />
|
||||||
|
<attr name="tab_indicator_dash_gap" format="dimension" />
|
||||||
|
<attr name="tab_indicator_radius" format="dimension" />
|
||||||
|
<attr name="tab_indicator_radii" format="string" />
|
||||||
|
<attr name="tab_indicator_gradient_colors" format="string" />
|
||||||
|
<attr name="tab_indicator_gradient_start_color" format="color" />
|
||||||
|
<attr name="tab_indicator_gradient_end_color" format="color" />
|
||||||
|
<attr name="tab_indicator_ignore_child_padding" format="boolean" />
|
||||||
|
<!--end...-->
|
||||||
|
|
||||||
|
<!--TabLayoutConfig 相关属性-->
|
||||||
|
<!--item选中时 文本的颜色-->
|
||||||
|
<attr name="tab_select_color" format="color" />
|
||||||
|
<!--item未选中时 文本的颜色-->
|
||||||
|
<attr name="tab_deselect_color" format="color" />
|
||||||
|
<!--选中时 图标(Drawable)的颜色, 默认是文本的颜色-->
|
||||||
|
<attr name="tab_ico_select_color" format="color" />
|
||||||
|
<!--未选中时 图标(Drawable)的颜色, 默认是文本的颜色-->
|
||||||
|
<attr name="tab_ico_deselect_color" format="color" />
|
||||||
|
<!--是否激活自动设置文本的颜色-->
|
||||||
|
<attr name="tab_enable_text_color" format="boolean" />
|
||||||
|
<!--是否激活自动设置图标的颜色-->
|
||||||
|
<attr name="tab_enable_ico_color" format="boolean" />
|
||||||
|
<!--是否激活文本变粗-->
|
||||||
|
<attr name="tab_enable_text_bold" format="boolean" />
|
||||||
|
<!--是否使用字体的方式设置变粗效果, 需要先开启[tab_enable_text_bold]-->
|
||||||
|
<attr name="tab_use_typeface_bold" format="boolean" />
|
||||||
|
<!--是否激活文本颜色渐变-->
|
||||||
|
<attr name="tab_enable_gradient_color" format="boolean" />
|
||||||
|
<!--是否激活指示器的颜色渐变效果-->
|
||||||
|
<attr name="tab_enable_indicator_gradient_color" format="boolean" />
|
||||||
|
<!--是否激活图标颜色渐变-->
|
||||||
|
<attr name="tab_enable_ico_gradient_color" format="boolean" />
|
||||||
|
<!--是否激活缩放渐变-->
|
||||||
|
<attr name="tab_enable_gradient_scale" format="boolean" />
|
||||||
|
<!--缩放渐变的最小值-->
|
||||||
|
<attr name="tab_min_scale" format="float" />
|
||||||
|
<!--缩放渐变的最大值-->
|
||||||
|
<attr name="tab_max_scale" format="float" />
|
||||||
|
<!--是否激活文本大小渐变-->
|
||||||
|
<attr name="tab_enable_gradient_text_size" format="boolean" />
|
||||||
|
<!--文本字体大小最小值-->
|
||||||
|
<attr name="tab_text_min_size" format="dimension" />
|
||||||
|
<!--文本字体大小最大值-->
|
||||||
|
<attr name="tab_text_max_size" format="dimension" />
|
||||||
|
<!--指定文本控件的id, 所有文本属性改变, 将会发生在这个控件上, 如果指定的控件不存在, 控件会降权至[ItemView]-->
|
||||||
|
<attr name="tab_text_view_id" format="reference" />
|
||||||
|
<!--指定图标控件的id, 同上-->
|
||||||
|
<attr name="tab_icon_view_id" format="reference" />
|
||||||
|
<!--end...-->
|
||||||
|
|
||||||
|
<!--Divider 分割线相关属性-->
|
||||||
|
<!--强制指定分割线的Drawable-->
|
||||||
|
<attr name="tab_divider_drawable" format="reference" />
|
||||||
|
<!--参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
|
||||||
|
<attr name="tab_divider_stroke_color" format="color" />
|
||||||
|
<attr name="tab_divider_solid_color" format="color" />
|
||||||
|
<attr name="tab_divider_stroke_width" format="dimension" />
|
||||||
|
<attr name="tab_divider_radius_size" format="dimension" />
|
||||||
|
<!--分割线margin距离-->
|
||||||
|
<attr name="tab_divider_margin_left" format="dimension" />
|
||||||
|
<attr name="tab_divider_margin_right" format="dimension" />
|
||||||
|
<attr name="tab_divider_margin_top" format="dimension" />
|
||||||
|
<attr name="tab_divider_margin_bottom" format="dimension" />
|
||||||
|
<!--分割线的宽度-->
|
||||||
|
<attr name="tab_divider_width" format="dimension" />
|
||||||
|
<attr name="tab_divider_height" format="dimension" />
|
||||||
|
<!--分割线显示的位置-->
|
||||||
|
<attr name="tab_divider_show_mode" format="flags">
|
||||||
|
<flag name="SHOW_DIVIDER_BEGINNING" value="1" />
|
||||||
|
<flag name="SHOW_DIVIDER_MIDDLE" value="2" />
|
||||||
|
<flag name="SHOW_DIVIDER_END" value="4" />
|
||||||
|
</attr>
|
||||||
|
<!--end...-->
|
||||||
|
|
||||||
|
<!--Border 边框相关属性-->
|
||||||
|
<!--强制指定边框的Drawable-->
|
||||||
|
<attr name="tab_border_drawable" format="reference" />
|
||||||
|
<!--参考[GradientDrawable](https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable)-->
|
||||||
|
<attr name="tab_border_stroke_color" format="color" />
|
||||||
|
<attr name="tab_border_solid_color" format="color" />
|
||||||
|
<attr name="tab_border_stroke_width" format="dimension" />
|
||||||
|
<attr name="tab_border_radius_size" format="dimension" />
|
||||||
|
<!--边框是否要负责绘制item的背景, 可以自动根据边框的圆角自动配置给item-->
|
||||||
|
<attr name="tab_border_draw_item_background" format="boolean" />
|
||||||
|
<!--高度补偿-->
|
||||||
|
<attr name="tab_border_item_background_height_offset" format="dimension" />
|
||||||
|
<!--宽度补偿-->
|
||||||
|
<attr name="tab_border_item_background_width_offset" format="dimension" />
|
||||||
|
<attr name="tab_border_keep_item_radius" format="boolean" />
|
||||||
|
<attr name="tab_border_item_background_solid_color" format="color" />
|
||||||
|
<attr name="tab_border_item_background_solid_disable_color" format="color" />
|
||||||
|
<attr name="tab_border_item_background_gradient_start_color" format="color" />
|
||||||
|
<attr name="tab_border_item_background_gradient_end_color" format="color" />
|
||||||
|
<!--end...-->
|
||||||
|
|
||||||
|
<!--Badge 角标相关属性-->
|
||||||
|
<!--是否绘制角标-->
|
||||||
|
<attr name="tab_draw_badge" format="boolean" />
|
||||||
|
<!--角标的背景填充颜色-->
|
||||||
|
<attr name="tab_badge_solid_color" format="color" />
|
||||||
|
<!--角标文本的颜色-->
|
||||||
|
<attr name="tab_badge_text_color" format="color" />
|
||||||
|
<!--圆点状态时的半径大小-->
|
||||||
|
<attr name="tab_badge_circle_radius" format="dimension" />
|
||||||
|
<!--角标的圆角半径-->
|
||||||
|
<attr name="tab_badge_radius" format="dimension" />
|
||||||
|
<!--角标重力-->
|
||||||
|
<attr name="tab_badge_gravity" format="flags">
|
||||||
|
<flag name="top" value="0x30" />
|
||||||
|
<flag name="bottom" value="0x50" />
|
||||||
|
<flag name="left" value="0x03" />
|
||||||
|
<flag name="right" value="0x05" />
|
||||||
|
<flag name="center_vertical" value="0x10" />
|
||||||
|
<flag name="center_horizontal" value="0x01" />
|
||||||
|
<flag name="center" value="0x11" />
|
||||||
|
</attr>
|
||||||
|
<!--x轴方向的偏移量, 会根据[Gravity]自动取负值-->
|
||||||
|
<attr name="tab_badge_offset_x" format="dimension" />
|
||||||
|
<!--同上-->
|
||||||
|
<attr name="tab_badge_offset_y" format="dimension" />
|
||||||
|
<!--参考[View]的padding属性-->
|
||||||
|
<attr name="tab_badge_padding_left" format="dimension" />
|
||||||
|
<attr name="tab_badge_padding_right" format="dimension" />
|
||||||
|
<attr name="tab_badge_padding_top" format="dimension" />
|
||||||
|
<attr name="tab_badge_padding_bottom" format="dimension" />
|
||||||
|
<!--角标的文本内容, 多用于xml预览-->
|
||||||
|
<attr name="tab_badge_text" format="string" />
|
||||||
|
<!--角标的文本字体大小-->
|
||||||
|
<attr name="tab_badge_text_size" format="dimension" />
|
||||||
|
|
||||||
|
<!--角标[tab_badge_gravity]定位锚点-->
|
||||||
|
<attr name="tab_badge_anchor_child_index" format="integer" />
|
||||||
|
<!--是否要忽略锚点view的padding-->
|
||||||
|
<attr name="tab_badge_ignore_child_padding" format="boolean" />
|
||||||
|
<!--角标圆形状态下的单独配置的偏移-->
|
||||||
|
<attr name="tab_badge_circle_offset_x" format="dimension" />
|
||||||
|
<!--角标圆形状态下的单独配置的偏移-->
|
||||||
|
<attr name="tab_badge_circle_offset_y" format="dimension" />
|
||||||
|
|
||||||
|
<attr name="tab_badge_stroke_color" format="color" />
|
||||||
|
<attr name="tab_badge_stroke_width" format="dimension" />
|
||||||
|
|
||||||
|
<attr name="tab_badge_min_width" format="dimension|flags">
|
||||||
|
<flag name="WRAP_HEIGHT" value="-1" />
|
||||||
|
<flag name="NONE" value="-2" />
|
||||||
|
</attr>
|
||||||
|
<attr name="tab_badge_min_height" format="dimension">
|
||||||
|
<flag name="NONE" value="-2" />
|
||||||
|
</attr>
|
||||||
|
|
||||||
|
<!--Highlight 突出相关属性-->
|
||||||
|
<attr name="tab_draw_highlight" format="boolean" />
|
||||||
|
<attr name="tab_highlight_drawable" format="reference" />
|
||||||
|
<attr name="tab_highlight_width_offset" format="dimension" />
|
||||||
|
<attr name="tab_highlight_height_offset" format="dimension" />
|
||||||
|
<!--突出的宽度-->
|
||||||
|
<attr name="tab_highlight_width" format="dimension|flags">
|
||||||
|
<flag name="WRAP_CONTENT" value="-2" />
|
||||||
|
<flag name="MATCH_PARENT" value="-1" />
|
||||||
|
</attr>
|
||||||
|
<attr name="tab_highlight_height" format="dimension|flags">
|
||||||
|
<flag name="WRAP_CONTENT" value="-2" />
|
||||||
|
<flag name="MATCH_PARENT" value="-1" />
|
||||||
|
</attr>
|
||||||
|
<!--end...-->
|
||||||
|
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
<declare-styleable name="DslTabLayout_Layout">
|
||||||
|
<!--支持按比例设置宽度, sw屏幕宽度 pw父宽度, 0.5sw:屏幕宽度的0.5倍, 0.3pw:父宽度的0.3倍-->
|
||||||
|
<attr name="layout_tab_width" format="string" />
|
||||||
|
<!--同上 sh, ph-->
|
||||||
|
<attr name="layout_tab_height" format="string" />
|
||||||
|
<!--高凸模式, 需要高凸的高度-->
|
||||||
|
<attr name="layout_tab_convex_height" format="dimension" />
|
||||||
|
<!--单独指定child的锚点索引-->
|
||||||
|
<attr name="layout_tab_indicator_content_index" format="integer" />
|
||||||
|
<!--单独指定child的锚点控件的id-->
|
||||||
|
<attr name="layout_tab_indicator_content_id" format="reference" />
|
||||||
|
<!--android.widget.LinearLayout.LayoutParams.weight 剩余空间所占比例-->
|
||||||
|
<attr name="layout_tab_weight" format="float" />
|
||||||
|
<!--单独配置的drawable, 可以覆盖tab中的配置-->
|
||||||
|
<attr name="layout_highlight_drawable" format="reference" />
|
||||||
|
|
||||||
|
<attr name="layout_tab_text_view_index" format="integer" />
|
||||||
|
<attr name="layout_tab_icon_view_index" format="integer" />
|
||||||
|
<attr name="layout_tab_text_view_id" format="reference" />
|
||||||
|
<attr name="layout_tab_icon_view_id" format="reference" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
1
ViewPager2Delegate/.gitignore
vendored
Normal file
1
ViewPager2Delegate/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
33
ViewPager2Delegate/build.gradle
Normal file
33
ViewPager2Delegate/build.gradle
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 33
|
||||||
|
buildToolsVersion "30.0.2"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 28
|
||||||
|
targetSdkVersion 33
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.2"
|
||||||
|
|
||||||
|
consumerProguardFiles 'consumer-rules.pro'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
//https://github.com/angcyo/DslTabLayout
|
||||||
|
implementation project(':TabLayout')
|
||||||
|
//https://mvnrepository.com/artifact/androidx.viewpager2/viewpager2
|
||||||
|
implementation 'androidx.viewpager2:viewpager2:1.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
//apply from: "$gradleHost/master/publish.gradle"
|
0
ViewPager2Delegate/consumer-rules.pro
Normal file
0
ViewPager2Delegate/consumer-rules.pro
Normal file
21
ViewPager2Delegate/proguard-rules.pro
vendored
Normal file
21
ViewPager2Delegate/proguard-rules.pro
vendored
Normal file
@ -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
|
2
ViewPager2Delegate/src/main/AndroidManifest.xml
Normal file
2
ViewPager2Delegate/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.angcyo.tablayout.delegate2" />
|
@ -0,0 +1,66 @@
|
|||||||
|
package com.angcyo.tablayout.delegate2
|
||||||
|
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import com.angcyo.tablayout.DslTabLayout
|
||||||
|
import com.angcyo.tablayout.ViewPagerDelegate
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兼容[ViewPager2]
|
||||||
|
* Email:angcyo@126.com
|
||||||
|
* @author angcyo
|
||||||
|
* @date 2019/12/14
|
||||||
|
*/
|
||||||
|
open class ViewPager2Delegate(
|
||||||
|
val viewPager: ViewPager2,
|
||||||
|
val dslTabLayout: DslTabLayout?,
|
||||||
|
val forceSmoothScroll: Boolean? = null
|
||||||
|
) : ViewPager2.OnPageChangeCallback(), ViewPagerDelegate {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [forceSmoothScroll] 为 null, 只有切换左右page时, 才有VP的动画, 否则没有.
|
||||||
|
* */
|
||||||
|
fun install(
|
||||||
|
viewPager: ViewPager2,
|
||||||
|
dslTabLayout: DslTabLayout?,
|
||||||
|
forceSmoothScroll: Boolean? = null
|
||||||
|
): ViewPager2Delegate {
|
||||||
|
return ViewPager2Delegate(viewPager, dslTabLayout, forceSmoothScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewPager.registerOnPageChangeCallback(this)
|
||||||
|
dslTabLayout?.setupViewPager(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetCurrentItem(): Int {
|
||||||
|
return viewPager.currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSetCurrentItem(
|
||||||
|
fromIndex: Int,
|
||||||
|
toIndex: Int,
|
||||||
|
reselect: Boolean,
|
||||||
|
fromUser: Boolean
|
||||||
|
) {
|
||||||
|
if (fromUser) {
|
||||||
|
val smoothScroll = forceSmoothScroll ?: ((toIndex - fromIndex).absoluteValue <= 1)
|
||||||
|
viewPager.setCurrentItem(toIndex, smoothScroll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
|
dslTabLayout?.onPageScrollStateChanged(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||||
|
dslTabLayout?.onPageScrolled(position, positionOffset, positionOffsetPixels)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
dslTabLayout?.onPageSelected(position)
|
||||||
|
}
|
||||||
|
}
|
@ -5,25 +5,25 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
debug {
|
debug {
|
||||||
storeFile file('D:\\AndroidKeys\\yutou.jks')
|
storeFile file('C:\\Users\\58381\\Documents\\AndroidSign\\yutou.jks')
|
||||||
storePassword '34864394'
|
storePassword '34864394'
|
||||||
keyAlias 'yutou'
|
keyAlias 'yutou'
|
||||||
keyPassword '34864394'
|
keyPassword '34864394'
|
||||||
}
|
}
|
||||||
yutou {
|
yutou {
|
||||||
storeFile file('D:\\AndroidKeys\\yutou.jks')
|
storeFile file('C:\\Users\\58381\\Documents\\AndroidSign\\yutou.jks')
|
||||||
storePassword '34864394'
|
storePassword '34864394'
|
||||||
keyAlias 'yutou'
|
keyAlias 'yutou'
|
||||||
keyPassword '34864394'
|
keyPassword '34864394'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileSdkVersion 30
|
compileSdkVersion 33
|
||||||
buildToolsVersion "30.0.2"
|
buildToolsVersion "30.0.2"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.yutou.passmanage"
|
applicationId "com.yutou.passmanage"
|
||||||
minSdkVersion 23
|
minSdkVersion 28
|
||||||
targetSdkVersion 30
|
targetSdkVersion 33
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.2"
|
versionName "1.2"
|
||||||
|
|
||||||
@ -59,5 +59,6 @@ dependencies {
|
|||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
implementation "androidx.biometric:biometric:1.1.0"
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
|
api project(path:':TabLayout')
|
||||||
|
api project(path:':ViewPager2Delegate')
|
||||||
}
|
}
|
@ -11,7 +11,8 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:theme="@style/Theme.MyPassworldManage">
|
android:theme="@style/Theme.MyPassworldManage">
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
package com.yutou.passmanage.Interfaces;
|
package com.yutou.passmanage.Interfaces;
|
||||||
|
|
||||||
public interface NetworkInterface {
|
public abstract class NetworkInterface<T> {
|
||||||
/**
|
/**
|
||||||
* 请求成功
|
* 请求成功
|
||||||
* @param data 请求参数
|
* @param data 请求参数
|
||||||
* @param state http状态
|
* @param state http状态
|
||||||
*/
|
*/
|
||||||
void httpGetData(Object data, int state);
|
public void httpGetData(T data, int state){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求异常
|
* 请求异常
|
||||||
* @param e 异常
|
* @param e 异常
|
||||||
*/
|
*/
|
||||||
void httpError(Exception e);
|
public void httpError(Exception e){}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@ -16,6 +18,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONArray;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.angcyo.tablayout.DslTabLayout;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.yutou.passmanage.Adapters.PassWordListAdapter;
|
import com.yutou.passmanage.Adapters.PassWordListAdapter;
|
||||||
import com.yutou.passmanage.Datas.AppData;
|
import com.yutou.passmanage.Datas.AppData;
|
||||||
@ -25,6 +28,8 @@ import com.yutou.passmanage.Interfaces.NetworkInterface;
|
|||||||
import com.yutou.passmanage.Tools.NetworkTool;
|
import com.yutou.passmanage.Tools.NetworkTool;
|
||||||
import com.yutou.passmanage.Tools.RoomDatabaseManager;
|
import com.yutou.passmanage.Tools.RoomDatabaseManager;
|
||||||
import com.yutou.passmanage.Tools.Tools;
|
import com.yutou.passmanage.Tools.Tools;
|
||||||
|
import com.yutou.passmanage.bean.PasswordBean;
|
||||||
|
import com.yutou.passmanage.bean.PasswordTypeBean;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -49,6 +54,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
FloatingActionButton floatButton;
|
FloatingActionButton floatButton;
|
||||||
PassWordListAdapter adapter;
|
PassWordListAdapter adapter;
|
||||||
TextView authError;
|
TextView authError;
|
||||||
|
DslTabLayout tabLayout;
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -63,6 +69,12 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
BiometricPrompt prompt;
|
BiometricPrompt prompt;
|
||||||
|
|
||||||
void auth() {
|
void auth() {
|
||||||
|
if(true){
|
||||||
|
init();
|
||||||
|
authError.setVisibility(View.GONE);
|
||||||
|
passList.setVisibility(View.VISIBLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
BiometricManager manager = BiometricManager.from(this);
|
BiometricManager manager = BiometricManager.from(this);
|
||||||
if (manager.canAuthenticate(BIOMETRIC_WEAK | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
if (manager.canAuthenticate(BIOMETRIC_WEAK | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||||
|
|
||||||
@ -122,6 +134,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
clearSearch = findViewById(R.id.clearSearch);
|
clearSearch = findViewById(R.id.clearSearch);
|
||||||
passList = findViewById(R.id.passList);
|
passList = findViewById(R.id.passList);
|
||||||
floatButton = findViewById(R.id.floatButton);
|
floatButton = findViewById(R.id.floatButton);
|
||||||
|
tabLayout = findViewById(R.id.tabLayout);
|
||||||
floatButton.setOnClickListener(v -> showAddPasswordDialog());
|
floatButton.setOnClickListener(v -> showAddPasswordDialog());
|
||||||
passList.setLayoutManager(new LinearLayoutManager(this));
|
passList.setLayoutManager(new LinearLayoutManager(this));
|
||||||
passList.setAdapter(adapter);
|
passList.setAdapter(adapter);
|
||||||
@ -241,7 +254,29 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
ToolsPasswordDao dao;
|
ToolsPasswordDao dao;
|
||||||
|
|
||||||
void initData() {
|
void initData() {
|
||||||
|
|
||||||
|
NetworkTool.httpGet(NetworkTool.NetworkAPI.PASSWORD_TYPE_ALL, new JSONObject(), new NetworkInterface<JSONObject>() {
|
||||||
|
@Override
|
||||||
|
public void httpGetData(JSONObject json, int state) {
|
||||||
|
List<PasswordTypeBean> data = JSONArray.parseArray(json.getJSONArray("data").toJSONString(), PasswordTypeBean.class);
|
||||||
|
for (PasswordTypeBean bean : data) {
|
||||||
|
Button tab = new Button(MainActivity.this);
|
||||||
|
tab.setOnClickListener(view -> {
|
||||||
|
|
||||||
|
});
|
||||||
|
tab.setText(bean.getTitle());
|
||||||
|
tab.setTag(bean);
|
||||||
|
tabLayout.addView(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void httpError(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
|
|
||||||
List<ToolsPassword> list = dao.getAllAndRemove();
|
List<ToolsPassword> list = dao.getAllAndRemove();
|
||||||
if (list.size() == 0) {
|
if (list.size() == 0) {
|
||||||
authError.setText("列表为空,点击右下角+新增配置");
|
authError.setText("列表为空,点击右下角+新增配置");
|
||||||
@ -303,11 +338,11 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkTool.httpGet(NetworkTool.NetworkAPI.PASSWORD_ALL, new JSONObject(), new NetworkInterface() {
|
NetworkTool.httpGet(NetworkTool.NetworkAPI.PASSWORD_ALL, new JSONObject(), new NetworkInterface<JSONObject>() {
|
||||||
@Override
|
@Override
|
||||||
public void httpGetData(Object data, int state) {
|
public void httpGetData(JSONObject json, int state) {
|
||||||
data = ((String) data).replace("desc", "info");
|
// data = ((String) data).replace("desc", "info");
|
||||||
JSONObject json = JSONObject.parseObject((String) data);
|
// JSONObject json = JSONObject.parseObject((String) data);
|
||||||
if (json.getInteger("code") == 0) {
|
if (json.getInteger("code") == 0) {
|
||||||
List<ToolsPassword> list = JSONArray.parseArray(json.getJSONArray("data").toJSONString(), ToolsPassword.class);
|
List<ToolsPassword> list = JSONArray.parseArray(json.getJSONArray("data").toJSONString(), ToolsPassword.class);
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
|
@ -3,6 +3,8 @@ package com.yutou.passmanage.Tools;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
import com.alibaba.fastjson.JSONException;
|
import com.alibaba.fastjson.JSONException;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.yutou.passmanage.Datas.AppData;
|
import com.yutou.passmanage.Datas.AppData;
|
||||||
@ -13,9 +15,12 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
@ -36,7 +41,7 @@ public class NetworkTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void httpGet(String url, JSONObject body, NetworkInterface networkInterface) {
|
public static <T> void httpGet(String url, JSONObject body, NetworkInterface<T> networkInterface) {
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -60,15 +65,26 @@ public class NetworkTool {
|
|||||||
public void run() {
|
public void run() {
|
||||||
if (networkInterface != null) {
|
if (networkInterface != null) {
|
||||||
try {
|
try {
|
||||||
networkInterface.httpGetData(str.toString(), connection.getResponseCode());
|
JSONObject json=JSONObject.parseObject(str.toString());
|
||||||
} catch (IOException e) {
|
Type[] types = ((ParameterizedType) networkInterface.getClass().getGenericSuperclass()).getActualTypeArguments();
|
||||||
|
if(types.length==0){
|
||||||
|
networkInterface.httpGetData((T) JSONObject.parseObject(str.toString()),connection.getResponseCode());
|
||||||
|
}else {
|
||||||
|
Type type=types[0];
|
||||||
|
if (type.getTypeName().contains("JSON")) {
|
||||||
|
networkInterface.httpGetData((T) JSONObject.parseObject(str.toString()), connection.getResponseCode());
|
||||||
|
}else {
|
||||||
|
networkInterface.httpGetData(JSONObject.parseObject(json.getJSONObject("data").toJSONString(), type), connection.getResponseCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
//Log.i(TAG + "[" + url + "]", "body:" + str + " (" + connection.getResponseCode() + ")");
|
Log.i(TAG + "[" + url + "]", "body:" + str + " (" + connection.getResponseCode() + ")");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
AppData.handler.post(new Runnable() {
|
AppData.handler.post(new Runnable() {
|
||||||
@ -142,7 +158,7 @@ public class NetworkTool {
|
|||||||
}
|
}
|
||||||
final String finalStr = str.toString();
|
final String finalStr = str.toString();
|
||||||
|
|
||||||
// Log.i(TAG + "[" + url + "?" + toGetSplice(body) + "]", "body:" + str + " (" + connection.getResponseCode() + ")");
|
Log.i(TAG + "[" + url + "?" + toGetSplice(body) + "]", "body:" + str + " (" + connection.getResponseCode() + ")");
|
||||||
AppData.handler.post(new Runnable() {
|
AppData.handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.yutou.passmanage.bean;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class BaseBean implements Serializable {
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.yutou.passmanage.bean;
|
||||||
|
|
||||||
|
public class PasswordBean extends BaseBean{
|
||||||
|
private int id;
|
||||||
|
private String title;
|
||||||
|
private String titlePinyin;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String url;
|
||||||
|
private String info;
|
||||||
|
private int type;
|
||||||
|
private int uid;
|
||||||
|
|
||||||
|
public PasswordBean() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitlePinyin() {
|
||||||
|
return titlePinyin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitlePinyin(String titlePinyin) {
|
||||||
|
this.titlePinyin = titlePinyin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInfo() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfo(String info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(int type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUid(int uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.yutou.passmanage.bean;
|
||||||
|
|
||||||
|
public class PasswordTypeBean extends BaseBean{
|
||||||
|
private int id;
|
||||||
|
private String title;
|
||||||
|
private int uid;
|
||||||
|
|
||||||
|
public PasswordTypeBean() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUid() {
|
||||||
|
return uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUid(int uid) {
|
||||||
|
this.uid = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PasswordTypeBean{" +
|
||||||
|
"id=" + id +
|
||||||
|
", title='" + title + '\'' +
|
||||||
|
", uid=" + uid +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -29,16 +29,24 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.angcyo.tablayout.DslTabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
</com.angcyo.tablayout.DslTabLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/passList"
|
android:id="@+id/passList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@+id/tabLayout"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/search" >
|
app:layout_constraintTop_toBottomOf="@+id/search"/>
|
||||||
</androidx.recyclerview.widget.RecyclerView>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/goneText"
|
android:id="@+id/goneText"
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_marginStart="20dp"
|
android:layout_marginStart="20dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/account" />
|
android:text="@string/title" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/title"
|
android:id="@+id/title"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:inputType="textPersonName"
|
android:inputType="textPersonName"
|
||||||
android:hint="@string/account" />
|
android:hint="@string/title" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<string name="app_name">密码管理器</string>
|
<string name="app_name">密码管理器</string>
|
||||||
<string name="password">密码</string>
|
<string name="password">密码</string>
|
||||||
<string name="account">账号</string>
|
<string name="account">账号</string>
|
||||||
|
<string name="title">标题</string>
|
||||||
<string name="info">备注</string>
|
<string name="info">备注</string>
|
||||||
<string name="name">名字</string>
|
<string name="name">名字</string>
|
||||||
<string name="toast_copy_password">已复制密码</string>
|
<string name="toast_copy_password">已复制密码</string>
|
||||||
|
@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.0.0'
|
classpath 'com.android.tools.build:gradle:7.0.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20"
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
|
@ -17,3 +17,8 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
||||||
|
systemProp.http.proxyHost=127.0.0.1
|
||||||
|
systemProp.https.proxyHost=127.0.0.1
|
||||||
|
systemProp.https.proxyPort=7980
|
||||||
|
systemProp.http.proxyPort=7890
|
||||||
|
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
@ -1,2 +1,4 @@
|
|||||||
include ':app'
|
include ':app'
|
||||||
|
include ':ViewPager2Delegate'
|
||||||
|
include ':TabLayout'
|
||||||
rootProject.name = "MyPassworldManage"
|
rootProject.name = "MyPassworldManage"
|
Loading…
Reference in New Issue
Block a user