Compare commits
No commits in common. "master" and "develop" have entirely different histories.
15
.gitignore
vendored
@ -1,15 +0,0 @@
|
|||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea/caches
|
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/navEditor.xml
|
|
||||||
/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
.externalNativeBuild
|
|
||||||
.cxx
|
|
||||||
local.properties
|
|
1
TabLayout/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,27 +0,0 @@
|
|||||||
apply plugin: 'com.android.library'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 33
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion 28
|
|
||||||
targetSdkVersion 33
|
|
||||||
|
|
||||||
consumerProguardFiles 'consumer-rules.pro'
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
namespace 'com.angcyo.tablayout'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'androidx.core:core-ktx:1.7.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
//apply from: "$gradleHost/master/publish.gradle"
|
|
21
TabLayout/proguard-rules.pro
vendored
@ -1,21 +0,0 @@
|
|||||||
# 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
|
|
@ -1 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@ -1,200 +0,0 @@
|
|||||||
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="基类方法">
|
|
||||||
}
|
|
@ -1,275 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,306 +0,0 @@
|
|||||||
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()!!
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,215 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,438 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,222 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
@ -1,279 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,153 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,931 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,526 +0,0 @@
|
|||||||
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
@ -1,334 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
<?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
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,31 +0,0 @@
|
|||||||
apply plugin: 'com.android.library'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdkVersion 33
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion 28
|
|
||||||
targetSdkVersion 33
|
|
||||||
|
|
||||||
consumerProguardFiles 'consumer-rules.pro'
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
namespace 'com.angcyo.tablayout.delegate2'
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
21
ViewPager2Delegate/proguard-rules.pro
vendored
@ -1,21 +0,0 @@
|
|||||||
# 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
|
|
@ -1 +0,0 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@ -1,66 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
1
app/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,74 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.application'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
signingConfigs {
|
|
||||||
debug {
|
|
||||||
storeFile file('C:\\Users\\58381\\Documents\\AndroidSign\\yutou.jks')
|
|
||||||
storePassword '34864394'
|
|
||||||
keyAlias 'yutou'
|
|
||||||
keyPassword '34864394'
|
|
||||||
}
|
|
||||||
yutou {
|
|
||||||
storeFile file('C:\\Users\\58381\\Documents\\AndroidSign\\yutou.jks')
|
|
||||||
storePassword '34864394'
|
|
||||||
keyAlias 'yutou'
|
|
||||||
keyPassword '34864394'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileSdkVersion 34
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
applicationId "com.yutou.passmanage"
|
|
||||||
minSdkVersion 28
|
|
||||||
targetSdkVersion 34
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.3"
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
signingConfig signingConfigs.yutou
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
namespace 'com.yutou.passmanage'
|
|
||||||
buildFeatures {
|
|
||||||
viewBinding true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
|
||||||
implementation 'androidx.navigation:navigation-fragment:2.7.7'
|
|
||||||
implementation 'androidx.navigation:navigation-ui:2.7.7'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4'
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
|
||||||
|
|
||||||
implementation 'com.alibaba.fastjson2:fastjson2:2.0.52'
|
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:2.6.1"
|
|
||||||
annotationProcessor "androidx.room:room-compiler:2.6.1"
|
|
||||||
implementation "androidx.biometric:biometric:1.1.0"
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
|
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
|
|
||||||
|
|
||||||
api project(path:':TabLayout')
|
|
||||||
api project(path:':ViewPager2Delegate')
|
|
||||||
api project(path:':netlibs')
|
|
||||||
|
|
||||||
}
|
|
21
app/proguard-rules.pro
vendored
@ -1,21 +0,0 @@
|
|||||||
# 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
|
|
@ -1,26 +0,0 @@
|
|||||||
package com.yutou.passmanage;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
|
||||||
assertEquals("com.yutou.passmanage", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true"
|
|
||||||
android:icon="@mipmap/app_icon"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
|
||||||
android:supportsRtl="true"
|
|
||||||
android:theme="@style/Theme.MyPassworldManage"
|
|
||||||
android:usesCleartextTraffic="true" >
|
|
||||||
<activity
|
|
||||||
android:name=".MainActivity"
|
|
||||||
android:exported="true" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
|
Before Width: | Height: | Size: 5.2 KiB |
@ -1,265 +0,0 @@
|
|||||||
package com.yutou.passmanage.Adapters;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import com.yutou.netlibs.http.HttpCallback;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPassword;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPasswordDao;
|
|
||||||
import com.yutou.passmanage.Interfaces.NetworkInterface;
|
|
||||||
import com.yutou.passmanage.NetUtils.NetTools;
|
|
||||||
import com.yutou.passmanage.R;
|
|
||||||
import com.yutou.passmanage.Tools.NetworkTool;
|
|
||||||
import com.yutou.passmanage.Tools.ResTools;
|
|
||||||
import com.yutou.passmanage.Tools.RoomDatabaseManager;
|
|
||||||
import com.yutou.passmanage.bean.BaseBean;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.yutou.passmanage.Tools.RoomDatabaseManager.addPassword;
|
|
||||||
|
|
||||||
public class PassWordListAdapter extends RecyclerView.Adapter<PassWordListAdapter.MyViewHolder> {
|
|
||||||
private Context context;
|
|
||||||
private List<ToolsPassword> list;
|
|
||||||
ToolsPasswordDao dao;
|
|
||||||
|
|
||||||
public PassWordListAdapter(Context context, List<ToolsPassword> list) {
|
|
||||||
this.context = context;
|
|
||||||
this.list = list;
|
|
||||||
this.dao = RoomDatabaseManager.build(context).passwordDao();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setList(List<ToolsPassword> list) {
|
|
||||||
this.list = list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.recycler_list, parent, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
|
|
||||||
ToolsPassword data = list.get(holder.getAdapterPosition());
|
|
||||||
String account = ResTools.getString(context, R.string.account) + ":" + data.getAccount();
|
|
||||||
String password = ResTools.getString(context, R.string.password) + ":" + data.getPassword();
|
|
||||||
holder.name.setText(data.getTitle());
|
|
||||||
holder.user.setText(account);
|
|
||||||
holder.password.setText(password);
|
|
||||||
holder.copy.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
NetTools.getApi().getPassword(data.getId()).enqueue(new HttpCallback<String>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(int code, String status, String response) {
|
|
||||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
||||||
ClipData clip = ClipData.newPlainText("", response);
|
|
||||||
clipboard.setPrimaryClip(clip);
|
|
||||||
Toast.makeText(context, ResTools.getString(context, R.string.toast_copy_password), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable throwable) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
holder.item_layout.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
NetTools.getApi().getPassword(data.getId()).enqueue(new HttpCallback<String>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(int code, String status, String response) {
|
|
||||||
data.setPassword(response);
|
|
||||||
showDialog(data, holder.getAdapterPosition());
|
|
||||||
save(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable throwable) {
|
|
||||||
new Thread(() -> {
|
|
||||||
ToolsPassword queried = dao.queryPassword(data.getId());
|
|
||||||
if (queried != null) {
|
|
||||||
new Handler(Looper.getMainLooper())
|
|
||||||
.post(() -> showDialog(data, holder.getAdapterPosition()));
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(ToolsPassword pwd) {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
ToolsPassword queried = dao.queryPassword(pwd.getId());
|
|
||||||
if (queried == null) {
|
|
||||||
dao.insert(pwd);
|
|
||||||
} else {
|
|
||||||
dao.update(pwd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog removeDialog = null;
|
|
||||||
|
|
||||||
private void showDialog(ToolsPassword data, int position) {
|
|
||||||
View view = LayoutInflater.from(context.getApplicationContext()).inflate(R.layout.dialog_add_passworld, null);
|
|
||||||
Button delete = view.findViewById(R.id.delete);
|
|
||||||
EditText title, account, password, info;
|
|
||||||
title = view.findViewById(R.id.title);
|
|
||||||
account = view.findViewById(R.id.account);
|
|
||||||
password = view.findViewById(R.id.myPassword);
|
|
||||||
info = view.findViewById(R.id.info);
|
|
||||||
title.setText(data.getTitle());
|
|
||||||
account.setText(data.getAccount());
|
|
||||||
password.setText(data.getPassword());
|
|
||||||
info.setText(data.getInfo());
|
|
||||||
delete.setVisibility(View.VISIBLE);
|
|
||||||
delete.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(context)
|
|
||||||
.setTitle("确认删除操作")
|
|
||||||
.setMessage("确定删除这条密码吗?本操作不可反悔")
|
|
||||||
.setPositiveButton("删除", new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
NetTools.getApi().removePassword(data.getId()).enqueue(new HttpCallback<BaseBean>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(int code, String status, BaseBean response) {
|
|
||||||
remove(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable throwable) {
|
|
||||||
remove(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove(boolean isError) {
|
|
||||||
if (isError) {
|
|
||||||
if (data.isUpload()) {
|
|
||||||
data.setRemove(true);
|
|
||||||
dao.update(data);
|
|
||||||
} else {
|
|
||||||
dao.delete(data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dao.delete(data);
|
|
||||||
}
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
handler.post(() -> {
|
|
||||||
dialog.dismiss();
|
|
||||||
removeDialog.dismiss();
|
|
||||||
Toast.makeText(context, "删除成功", Toast.LENGTH_LONG).show();
|
|
||||||
list.remove(position);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton("取消", (dialog13, which) -> dialog13.dismiss()).create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
removeDialog = new AlertDialog.Builder(context)
|
|
||||||
.setTitle("查看信息")
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton("更新", (dialog1, which) -> {
|
|
||||||
addPassword(title.getText().toString(), account.getText().toString(), password.getText().toString(), info.getText().toString(), data.getId(), false, new NetworkInterface() {
|
|
||||||
@Override
|
|
||||||
public void httpGetData(Object data, int state) {
|
|
||||||
addDatabase(title.getText().toString(),
|
|
||||||
account.getText().toString(),
|
|
||||||
password.getText().toString(),
|
|
||||||
info.getText().toString(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void httpError(Exception e) {
|
|
||||||
addDatabase(title.getText().toString(),
|
|
||||||
account.getText().toString(),
|
|
||||||
password.getText().toString(),
|
|
||||||
info.getText().toString(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDatabase(String title1, String account1, String password1, String info, boolean upload) {
|
|
||||||
new Thread(() -> {
|
|
||||||
data.setAccount(account1);
|
|
||||||
data.setTitle(title1);
|
|
||||||
data.setPassword(password1);
|
|
||||||
data.setInfo(info);
|
|
||||||
data.setUpload(upload);
|
|
||||||
dao.update(data);
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
handler.post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Toast.makeText(context, ResTools.getString(context, R.string.toast_update), Toast.LENGTH_LONG).show();
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialog1.dismiss();
|
|
||||||
})
|
|
||||||
.setNegativeButton("取消", (dialog12, which) -> dialog12.dismiss()).create();
|
|
||||||
removeDialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return list.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class MyViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
ImageButton icon;
|
|
||||||
TextView name, user, password;
|
|
||||||
Button copy;
|
|
||||||
LinearLayout item_layout;
|
|
||||||
|
|
||||||
public MyViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
icon = itemView.findViewById(R.id.icon);
|
|
||||||
name = itemView.findViewById(R.id.name);
|
|
||||||
user = itemView.findViewById(R.id.user);
|
|
||||||
password = itemView.findViewById(R.id.password);
|
|
||||||
copy = itemView.findViewById(R.id.copy);
|
|
||||||
item_layout = itemView.findViewById(R.id.item_layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.yutou.passmanage.Datas;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
public class AppData {
|
|
||||||
public static Handler handler=new Handler(Looper.getMainLooper());
|
|
||||||
public static String key;
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
package com.yutou.passmanage.Datas;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo;
|
|
||||||
import androidx.room.Entity;
|
|
||||||
import androidx.room.Ignore;
|
|
||||||
import androidx.room.PrimaryKey;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.annotation.JSONField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tools_password
|
|
||||||
* @author
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
public class ToolsPassword{
|
|
||||||
@PrimaryKey
|
|
||||||
private Integer id;
|
|
||||||
@ColumnInfo
|
|
||||||
private String title;
|
|
||||||
@ColumnInfo
|
|
||||||
@JSONField(name = "username")
|
|
||||||
private String account;
|
|
||||||
@ColumnInfo
|
|
||||||
private String password;
|
|
||||||
@Ignore
|
|
||||||
private String url;
|
|
||||||
@ColumnInfo
|
|
||||||
private String info;
|
|
||||||
@ColumnInfo
|
|
||||||
private Integer type;
|
|
||||||
@Ignore
|
|
||||||
private Integer uid;
|
|
||||||
@ColumnInfo
|
|
||||||
private boolean upload;
|
|
||||||
@ColumnInfo
|
|
||||||
private boolean remove;
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccount() {
|
|
||||||
return account;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccount(String account) {
|
|
||||||
this.account = account;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Integer getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(Integer type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getUid() {
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUid(Integer uid) {
|
|
||||||
this.uid = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpload() {
|
|
||||||
return upload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpload(boolean upload) {
|
|
||||||
this.upload = upload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRemove() {
|
|
||||||
return remove;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRemove(boolean remove) {
|
|
||||||
this.remove = remove;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ToolsPassword{" +
|
|
||||||
"id=" + id +
|
|
||||||
", title='" + title + '\'' +
|
|
||||||
", account='" + account + '\'' +
|
|
||||||
", password='" + password + '\'' +
|
|
||||||
", url='" + url + '\'' +
|
|
||||||
", info='" + info + '\'' +
|
|
||||||
", type=" + type +
|
|
||||||
", uid=" + uid +
|
|
||||||
", upload=" + upload +
|
|
||||||
", remove=" + remove +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.yutou.passmanage.Datas;
|
|
||||||
|
|
||||||
import androidx.room.Dao;
|
|
||||||
import androidx.room.Delete;
|
|
||||||
import androidx.room.Insert;
|
|
||||||
import androidx.room.Query;
|
|
||||||
import androidx.room.Update;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
public interface ToolsPasswordDao {
|
|
||||||
@Query("select * from ToolsPassword")
|
|
||||||
List<ToolsPassword> getAllAndRemove();
|
|
||||||
@Query("select * from ToolsPassword where remove=0")
|
|
||||||
List<ToolsPassword> getAll();
|
|
||||||
@Query("select * from ToolsPassword where title=:title and account=:account and password=:password and info=:info")
|
|
||||||
ToolsPassword isExist(String title,String account,String password,String info);
|
|
||||||
@Query("select * from ToolsPassword where id=:id")
|
|
||||||
ToolsPassword queryPassword(int id);
|
|
||||||
@Query("select * from ToolsPassword where title like :title and remove=0")
|
|
||||||
List<ToolsPassword> queryPassword(String title);
|
|
||||||
@Insert
|
|
||||||
void insert(ToolsPassword password);
|
|
||||||
@Insert
|
|
||||||
void insertAll(List<ToolsPassword> list);
|
|
||||||
@Delete
|
|
||||||
void delete(ToolsPassword password);
|
|
||||||
@Update
|
|
||||||
void update(ToolsPassword password);
|
|
||||||
@Query("DELETE FROM ToolsPassword")
|
|
||||||
void clear();
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package com.yutou.passmanage.Datas;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tools_password_type
|
|
||||||
* @author
|
|
||||||
*/
|
|
||||||
public class ToolsPasswordType {
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
private Integer uid;
|
|
||||||
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getUid() {
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUid(Integer uid) {
|
|
||||||
this.uid = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package com.yutou.passmanage.Interfaces;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载接口
|
|
||||||
*/
|
|
||||||
public abstract class DownloadInterface {
|
|
||||||
/**
|
|
||||||
* 下载完成
|
|
||||||
* @param oldJar
|
|
||||||
*/
|
|
||||||
public abstract void onDownloadOver(File oldJar);
|
|
||||||
public void onError(Exception e){};
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.yutou.passmanage.Interfaces;
|
|
||||||
|
|
||||||
public abstract class NetworkInterface<T> {
|
|
||||||
/**
|
|
||||||
* 请求成功
|
|
||||||
* @param data 请求参数
|
|
||||||
* @param state http状态
|
|
||||||
*/
|
|
||||||
public void httpGetData(T data, int state){}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求异常
|
|
||||||
* @param e 异常
|
|
||||||
*/
|
|
||||||
public void httpError(Exception e){}
|
|
||||||
}
|
|
@ -1,339 +0,0 @@
|
|||||||
package com.yutou.passmanage;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import com.angcyo.tablayout.DslTabLayout;
|
|
||||||
import com.angcyo.tablayout.DslTabLayoutConfig;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
import com.yutou.netlibs.http.HttpCallback;
|
|
||||||
import com.yutou.passmanage.Adapters.PassWordListAdapter;
|
|
||||||
import com.yutou.passmanage.Datas.AppData;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPassword;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPasswordDao;
|
|
||||||
import com.yutou.passmanage.Interfaces.NetworkInterface;
|
|
||||||
import com.yutou.passmanage.NetUtils.NetTools;
|
|
||||||
import com.yutou.passmanage.Tools.NetworkTool;
|
|
||||||
import com.yutou.passmanage.Tools.RoomDatabaseManager;
|
|
||||||
import com.yutou.passmanage.Tools.Tools;
|
|
||||||
import com.yutou.passmanage.bean.BaseBean;
|
|
||||||
import com.yutou.passmanage.bean.PasswordBean;
|
|
||||||
import com.yutou.passmanage.bean.PasswordTypeBean;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.biometric.BiometricManager;
|
|
||||||
import androidx.biometric.BiometricPrompt;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK;
|
|
||||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
|
||||||
import static com.yutou.passmanage.Tools.RoomDatabaseManager.addPassword;
|
|
||||||
|
|
||||||
import kotlin.Unit;
|
|
||||||
import kotlin.jvm.functions.Function1;
|
|
||||||
import kotlin.jvm.functions.Function4;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
|
||||||
EditText search;
|
|
||||||
ImageView clearSearch;
|
|
||||||
RecyclerView passList;
|
|
||||||
FloatingActionButton floatButton;
|
|
||||||
PassWordListAdapter adapter;
|
|
||||||
TextView authError;
|
|
||||||
DslTabLayout tabLayout;
|
|
||||||
Handler handler = new Handler(Looper.getMainLooper());
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
initView();
|
|
||||||
auth();
|
|
||||||
}
|
|
||||||
|
|
||||||
BiometricPrompt prompt;
|
|
||||||
|
|
||||||
void auth() {
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
init();
|
|
||||||
authError.setVisibility(View.GONE);
|
|
||||||
passList.setVisibility(View.VISIBLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
BiometricManager manager = BiometricManager.from(this);
|
|
||||||
if (manager.canAuthenticate(BIOMETRIC_WEAK | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
|
||||||
|
|
||||||
prompt = new BiometricPrompt(this, ContextCompat.getMainExecutor(this), new BiometricPrompt.AuthenticationCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
|
||||||
super.onAuthenticationSucceeded(result);
|
|
||||||
Toast.makeText(MainActivity.this, "验证成功", Toast.LENGTH_LONG).show();
|
|
||||||
init();
|
|
||||||
authError.setVisibility(View.GONE);
|
|
||||||
passList.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed();
|
|
||||||
Toast.makeText(MainActivity.this, "验证失败,已退出", Toast.LENGTH_LONG).show();
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
|
||||||
super.onAuthenticationError(errorCode, errString);
|
|
||||||
System.out.println("errorCode = " + errorCode + ", errString = " + errString);
|
|
||||||
if (errorCode == 10) {
|
|
||||||
authError.setText("未授权,点击授权");
|
|
||||||
authError.setVisibility(View.VISIBLE);
|
|
||||||
passList.setVisibility(View.GONE);
|
|
||||||
} else if (errorCode == 13) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
|
|
||||||
.setTitle("验证指纹")
|
|
||||||
.setSubtitle("使用指纹来解锁应用")
|
|
||||||
.setAllowedAuthenticators(BIOMETRIC_WEAK | DEVICE_CREDENTIAL)
|
|
||||||
.build();
|
|
||||||
prompt.authenticate(promptInfo);
|
|
||||||
System.out.println("调用指纹");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Toast.makeText(this, "必须在支持指纹的设备上使用", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void exit() {
|
|
||||||
MainActivity.this.finish();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void initView() {
|
|
||||||
dao = RoomDatabaseManager.build(this).passwordDao();
|
|
||||||
adapter = new PassWordListAdapter(this, new ArrayList<>());
|
|
||||||
search = findViewById(R.id.search);
|
|
||||||
authError = findViewById(R.id.goneText);
|
|
||||||
clearSearch = findViewById(R.id.clearSearch);
|
|
||||||
passList = findViewById(R.id.passList);
|
|
||||||
floatButton = findViewById(R.id.floatButton);
|
|
||||||
tabLayout = findViewById(R.id.tabLayout);
|
|
||||||
floatButton.setOnClickListener(v -> showAddPasswordDialog());
|
|
||||||
passList.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
passList.setAdapter(adapter);
|
|
||||||
clearSearch.setOnClickListener(v -> {
|
|
||||||
search.setText("");
|
|
||||||
new Thread(this::showData).start();
|
|
||||||
});
|
|
||||||
authError.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
auth();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
search.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
new Thread(() -> {
|
|
||||||
List<ToolsPassword> list;
|
|
||||||
if (Tools.isEmpty(s.toString())) {
|
|
||||||
list = dao.getAll();
|
|
||||||
} else {
|
|
||||||
list = dao.queryPassword("%" + s.toString() + "%");
|
|
||||||
}
|
|
||||||
handler.post(() -> {
|
|
||||||
adapter.setList(list);
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
});
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tabLayout.configTabLayoutConfig(new Function1<DslTabLayoutConfig, Unit>() {
|
|
||||||
@Override
|
|
||||||
public Unit invoke(DslTabLayoutConfig dslTabLayoutConfig) {
|
|
||||||
dslTabLayoutConfig.setOnSelectItemView(new Function4<View, Integer, Boolean, Boolean, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean invoke(View view, Integer integer, Boolean aBoolean, Boolean aBoolean2) {
|
|
||||||
if (aBoolean) {
|
|
||||||
PasswordTypeBean typeBean = (PasswordTypeBean) view.getTag();
|
|
||||||
NetTools.getApi().getTypePassword(typeBean.getId()).enqueue(new HttpCallback<List<ToolsPassword>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(int code, String status, List<ToolsPassword> response) {
|
|
||||||
setShow(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable throwable) {
|
|
||||||
throwable.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void showAddPasswordDialog() {
|
|
||||||
View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.dialog_add_passworld, null);
|
|
||||||
EditText title, account, password, info;
|
|
||||||
title = view.findViewById(R.id.title);
|
|
||||||
account = view.findViewById(R.id.account);
|
|
||||||
password = view.findViewById(R.id.myPassword);
|
|
||||||
info = view.findViewById(R.id.info);
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
|
|
||||||
.setTitle("添加账号")
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton("保存", (dialog1, which) -> {
|
|
||||||
addPassword(title.getText().toString(), account.getText().toString(), password.getText().toString(), info.getText().toString(), -1, true, new NetworkInterface() {
|
|
||||||
@Override
|
|
||||||
public void httpGetData(Object data, int state) {
|
|
||||||
addDatabase(title.getText().toString(),
|
|
||||||
account.getText().toString(),
|
|
||||||
password.getText().toString(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void httpError(Exception e) {
|
|
||||||
addDatabase(title.getText().toString(),
|
|
||||||
account.getText().toString(),
|
|
||||||
password.getText().toString(),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addDatabase(String title1, String account1, String password1, boolean upload) {
|
|
||||||
new Thread(() -> {
|
|
||||||
ToolsPassword psd = new ToolsPassword();
|
|
||||||
psd.setTitle(title1);
|
|
||||||
psd.setAccount(account1);
|
|
||||||
psd.setPassword(password1);
|
|
||||||
psd.setUpload(upload);
|
|
||||||
dao.insert(psd);
|
|
||||||
showData();
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
dialog1.dismiss();
|
|
||||||
})
|
|
||||||
.setNegativeButton("取消", (dialog12, which) -> dialog12.dismiss()).create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void init() {
|
|
||||||
AppData.key = Tools.getConfig("key", this);
|
|
||||||
if (Tools.isEmpty(AppData.key)) {
|
|
||||||
EditText key = new EditText(this);
|
|
||||||
key.setHint("激活码");
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
|
||||||
.setTitle("输入激活码")
|
|
||||||
.setView(key)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton("确定", (dialog1, which) -> {
|
|
||||||
String k = key.getText().toString().trim();
|
|
||||||
if (Tools.isEmpty(k)) {
|
|
||||||
Toast.makeText(MainActivity.this, "激活码不能为空", Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Tools.setConfig("key", k, MainActivity.this);
|
|
||||||
AppData.key = k;
|
|
||||||
dialog1.dismiss();
|
|
||||||
initData();
|
|
||||||
}).create();
|
|
||||||
dialog.show();
|
|
||||||
} else {
|
|
||||||
initData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolsPasswordDao dao;
|
|
||||||
|
|
||||||
void initData() {
|
|
||||||
NetTools.getApi().getAllTypePassword().enqueue(new HttpCallback<List<PasswordTypeBean>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(int code, String status, List<PasswordTypeBean> response) {
|
|
||||||
for (PasswordTypeBean bean : response) {
|
|
||||||
Button tab = new Button(MainActivity.this);
|
|
||||||
tab.setText(bean.getTitle());
|
|
||||||
tab.setTag(bean);
|
|
||||||
tabLayout.addView(tab);
|
|
||||||
}
|
|
||||||
showLocalData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable throwable) {
|
|
||||||
showLocalData();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
new Thread(() -> {
|
|
||||||
if (dao.getAll().isEmpty()) {
|
|
||||||
authError.setText("列表为空,点击右下角+新增配置");
|
|
||||||
authError.setVisibility(View.VISIBLE);
|
|
||||||
passList.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showLocalData() {
|
|
||||||
showData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setShow(List<ToolsPassword> data) {
|
|
||||||
adapter.setList(data);
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
void showData() {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
List<ToolsPassword> list = dao.getAll();
|
|
||||||
handler.post(() -> {
|
|
||||||
adapter.setList(list);
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package com.yutou.passmanage.NetUtils;
|
|
||||||
|
|
||||||
import com.yutou.netlibs.http.HttpBody;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPassword;
|
|
||||||
import com.yutou.passmanage.bean.BaseBean;
|
|
||||||
import com.yutou.passmanage.bean.PasswordTypeBean;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.http.Field;
|
|
||||||
import retrofit2.http.FormUrlEncoded;
|
|
||||||
import retrofit2.http.GET;
|
|
||||||
import retrofit2.http.POST;
|
|
||||||
import retrofit2.http.Query;
|
|
||||||
|
|
||||||
public interface NetApi {
|
|
||||||
@GET("/tools/password/type/get/list.do")
|
|
||||||
Call<HttpBody<List<PasswordTypeBean>>> getAllTypePassword();
|
|
||||||
@GET("/tools/password/get/list.do")
|
|
||||||
Call<HttpBody<List<ToolsPassword>>> getTypePassword(
|
|
||||||
@Query("type") int type
|
|
||||||
);
|
|
||||||
|
|
||||||
@GET("/tools/password/get/password.do")
|
|
||||||
Call<HttpBody<String>> getPassword(
|
|
||||||
@Query("id") int id
|
|
||||||
);
|
|
||||||
|
|
||||||
@FormUrlEncoded
|
|
||||||
@POST("/tools/password/set/add.do")
|
|
||||||
Call<HttpBody<BaseBean>> addPassword(@Field("title") String title,
|
|
||||||
@Field("account") String account,
|
|
||||||
@Field("password") String password,
|
|
||||||
@Field("info") String info);
|
|
||||||
|
|
||||||
@FormUrlEncoded
|
|
||||||
@POST("/tools/password/set/update.do")
|
|
||||||
Call<HttpBody<BaseBean>> updatePassword(@Field("id") int id,
|
|
||||||
@Field("title") String title,
|
|
||||||
@Field("account") String account,
|
|
||||||
@Field("password") String password,
|
|
||||||
@Field("info") String info);
|
|
||||||
@FormUrlEncoded
|
|
||||||
@POST("/tools/password/set/remove.do")
|
|
||||||
Call<HttpBody<BaseBean>> removePassword(@Field("id") int id);
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package com.yutou.passmanage.NetUtils;
|
|
||||||
|
|
||||||
import com.yutou.netlibs.http.API;
|
|
||||||
import com.yutou.netlibs.http.BaseAPI;
|
|
||||||
import com.yutou.passmanage.Datas.AppData;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
public class NetTools extends API<NetApi> {
|
|
||||||
private NetTools(String url,HashMap<String,String> map) {
|
|
||||||
super(url,map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetApi getApi(){
|
|
||||||
HashMap<String,String> map=new HashMap<>();
|
|
||||||
map.put("token", AppData.key);
|
|
||||||
return new NetTools("https://tools.yutou233.cn",map).createAPI(NetApi.class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package com.yutou.passmanage.Tools;
|
|
||||||
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONException;
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import com.yutou.passmanage.Datas.AppData;
|
|
||||||
import com.yutou.passmanage.Interfaces.NetworkInterface;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
|
||||||
public class NetworkTool {
|
|
||||||
|
|
||||||
private final static String TAG = NetworkTool.class.getSimpleName();
|
|
||||||
|
|
||||||
public static class NetworkAPI {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private String getExtUa() {
|
|
||||||
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toGetSplice(JSONObject json) {
|
|
||||||
try {
|
|
||||||
json.put("token", AppData.key);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
StringBuilder string = new StringBuilder();
|
|
||||||
Set<String> keys = json.keySet();
|
|
||||||
for (String key : keys) {
|
|
||||||
try {
|
|
||||||
string.append("&").append(key).append("=").append(URLEncoder.encode(json.getString(key), "UTF-8"));
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
try {
|
|
||||||
string.append("&").append(URLEncoder.encode(key, "UTF-8")).append("=");
|
|
||||||
// string += "&" + key + "=";
|
|
||||||
} catch (Exception e1) {
|
|
||||||
string.append("&").append(key).append("=");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string = new StringBuilder(string.substring(1, string.length()).replaceAll(" ", ""));
|
|
||||||
return string.toString();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.yutou.passmanage.Tools;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
public class ResTools {
|
|
||||||
public static String getString(Context context,int id){
|
|
||||||
return context.getResources().getString(id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
package com.yutou.passmanage.Tools;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.room.Database;
|
|
||||||
import androidx.room.DatabaseConfiguration;
|
|
||||||
import androidx.room.InvalidationTracker;
|
|
||||||
import androidx.room.Room;
|
|
||||||
import androidx.room.RoomDatabase;
|
|
||||||
import androidx.room.migration.Migration;
|
|
||||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
|
||||||
import androidx.sqlite.db.SupportSQLiteOpenHelper;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
|
||||||
import com.yutou.netlibs.http.HttpBody;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPassword;
|
|
||||||
import com.yutou.passmanage.Datas.ToolsPasswordDao;
|
|
||||||
import com.yutou.passmanage.Interfaces.NetworkInterface;
|
|
||||||
import com.yutou.passmanage.NetUtils.NetTools;
|
|
||||||
import com.yutou.passmanage.bean.BaseBean;
|
|
||||||
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.HttpException;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
@Database(entities = {ToolsPassword.class}, version = 2,exportSchema = false)
|
|
||||||
public abstract class RoomDatabaseManager extends RoomDatabase {
|
|
||||||
public abstract ToolsPasswordDao passwordDao();
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected InvalidationTracker createInvalidationTracker() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearAllTables() {
|
|
||||||
|
|
||||||
}
|
|
||||||
private static final Migration update1to2=new Migration(1,2) {
|
|
||||||
@Override
|
|
||||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
|
||||||
database.execSQL("ALTER TABLE ToolsPassword ADD COLUMN `info` ` TEXT` ");
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static RoomDatabaseManager build(Context context) {
|
|
||||||
return Room.databaseBuilder(context.getApplicationContext(), RoomDatabaseManager.class, "password.db")
|
|
||||||
.addMigrations(update1to2)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
public static void addPassword(String title, String account, String password,String info,int id,boolean isAdd, NetworkInterface networkInterface) {
|
|
||||||
JSONObject json = new JSONObject();
|
|
||||||
json.put("title", title);
|
|
||||||
json.put("username", account);
|
|
||||||
json.put("password", password);
|
|
||||||
json.put("url", "");
|
|
||||||
json.put("info", info);
|
|
||||||
json.put("type", "-1");
|
|
||||||
if(id!=-1){
|
|
||||||
json.put("id",id);
|
|
||||||
}
|
|
||||||
if(isAdd) {
|
|
||||||
NetTools.getApi().addPassword(title,account,password,info).enqueue(new retrofit2.Callback<HttpBody<BaseBean>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(Call<HttpBody<BaseBean>> call, Response<HttpBody<BaseBean>> response) {
|
|
||||||
networkInterface.httpGetData(response.body().getData(), response.body().getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<HttpBody<BaseBean>> call, Throwable t) {
|
|
||||||
networkInterface.httpError(new RuntimeException(t.getMessage()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
NetTools.getApi().updatePassword(id,title,account,password,info).enqueue(new retrofit2.Callback<HttpBody<BaseBean>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(Call<HttpBody<BaseBean>> call, Response<HttpBody<BaseBean>> response) {
|
|
||||||
networkInterface.httpGetData(response.body().getData(), response.body().getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Call<HttpBody<BaseBean>> call, Throwable t) {
|
|
||||||
networkInterface.httpError(new RuntimeException(t.getMessage()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package com.yutou.passmanage.Tools;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Tools {
|
|
||||||
public static boolean isEmpty(String str){
|
|
||||||
if(str==null|| str.trim().isEmpty()){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public static PackageInfo getPackageInfo(String packageName, Context context){
|
|
||||||
List<PackageInfo> list=context.getPackageManager().getInstalledPackages(0);
|
|
||||||
if(list!=null){
|
|
||||||
for (PackageInfo packageInfo : list) {
|
|
||||||
if(packageInfo.packageName.equals(packageName)){
|
|
||||||
return packageInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public static List<PackageInfo> getAllPackageInfo(Context context){
|
|
||||||
return context.getPackageManager().getInstalledPackages(0);
|
|
||||||
}
|
|
||||||
public static void setConfig(String key,String value,Context context){
|
|
||||||
SharedPreferences preferences=context.getSharedPreferences("config.cfg",Context.MODE_PRIVATE);
|
|
||||||
preferences.edit().putString(key,value).apply();
|
|
||||||
}
|
|
||||||
public static String getConfig(String key,Context context){
|
|
||||||
SharedPreferences preferences=context.getSharedPreferences("config.cfg",Context.MODE_PRIVATE);
|
|
||||||
return preferences.getString(key,null);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package com.yutou.passmanage.bean;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class BaseBean implements Serializable {
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
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 +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
|
||||||
</vector>
|
|
Before Width: | Height: | Size: 683 B |
@ -1,170 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
|
@ -1,76 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/search"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ems="10"
|
|
||||||
android:gravity="start|top"
|
|
||||||
android:hint="搜索"
|
|
||||||
android:inputType="textMultiLine"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/clearSearch"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/clearSearch"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:src="@drawable/ic_close"
|
|
||||||
app:layout_constraintEnd_toEndOf="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
|
|
||||||
android:id="@+id/passList"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/tabLayout"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/search"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/goneText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text=""
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/floatButton"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="32dp"
|
|
||||||
android:layout_marginBottom="32dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:contentDescription="add"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:srcCompat="@android:drawable/ic_input_add" />
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,117 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginEnd="15dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/title" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ems="10"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
android:hint="@string/title" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/account" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/account"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="@dimen/edit_layout_height"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ems="10"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
android:hint="@string/account"
|
|
||||||
android:autofillHints="name" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/password" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/myPassword"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="@dimen/edit_layout_height"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ems="10"
|
|
||||||
android:hint="@string/password"
|
|
||||||
android:autofillHints="name"
|
|
||||||
android:inputType="textPersonName"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/textView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:text="@string/info" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/info"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ems="10"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:gravity="start|top"
|
|
||||||
android:hint="这里可以填写备注信息"
|
|
||||||
android:inputType="textMultiLine"
|
|
||||||
android:minLines="5" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/delete"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="删除" />
|
|
||||||
</LinearLayout>
|
|
@ -1,73 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/item_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/icon"
|
|
||||||
android:layout_width="60dp"
|
|
||||||
android:layout_height="60dp"
|
|
||||||
android:layout_weight="0"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:srcCompat="@drawable/ic_launcher_foreground" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:layout_marginEnd="10dp"
|
|
||||||
android:layout_weight="2"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="TextView"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/user"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center|start"
|
|
||||||
android:gravity="right"
|
|
||||||
android:text="TextView" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="start|center"
|
|
||||||
android:gravity="right"
|
|
||||||
android:text="TextView" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/copy"
|
|
||||||
android:layout_width="70dp"
|
|
||||||
android:layout_height="60dp"
|
|
||||||
android:layout_weight="0.1"
|
|
||||||
android:text="复制" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/divider2"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="?android:attr/listDivider" />
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/app_icon_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/app_icon_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/app_icon_background"/>
|
|
||||||
<foreground android:drawable="@mipmap/app_icon_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 973 B |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 16 KiB |
@ -1,21 +0,0 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.MyPassworldManage" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
||||||
<!-- Primary brand color. -->
|
|
||||||
<item name="colorPrimary">@color/purple_200</item>
|
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/black</item>
|
|
||||||
<!-- Secondary brand color. -->
|
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
|
||||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Base.Theme.MyPassworldManage" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Customize your dark theme here. -->
|
|
||||||
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="app_icon_background">#639BFF</color>
|
|
||||||
</resources>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
|
||||||
<color name="purple_500">#FF6200EE</color>
|
|
||||||
<color name="purple_700">#FF3700B3</color>
|
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
|
||||||
<color name="teal_700">#FF018786</color>
|
|
||||||
<color name="black">#FF000000</color>
|
|
||||||
<color name="white">#FFFFFFFF</color>
|
|
||||||
</resources>
|
|
@ -1,56 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">密码管理器</string>
|
|
||||||
<string name="password">密码</string>
|
|
||||||
<string name="account">账号</string>
|
|
||||||
<string name="title">标题</string>
|
|
||||||
<string name="info">备注</string>
|
|
||||||
<string name="name">名字</string>
|
|
||||||
<string name="toast_copy_password">已复制密码</string>
|
|
||||||
<string name="toast_update">更新成功</string>
|
|
||||||
<!-- Strings used for fragments for navigation -->
|
|
||||||
<string name="first_fragment_label">First Fragment</string>
|
|
||||||
<string name="second_fragment_label">Second Fragment</string>
|
|
||||||
<string name="next">Next</string>
|
|
||||||
<string name="previous">Previous</string>
|
|
||||||
|
|
||||||
<string name="lorem_ipsum">
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
|
|
||||||
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
|
|
||||||
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
|
|
||||||
litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
|
|
||||||
diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
|
|
||||||
ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
|
|
||||||
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
|
|
||||||
egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
|
|
||||||
neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
|
|
||||||
fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
|
|
||||||
molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
|
|
||||||
bibendum, vel congue leo egestas.\n\n
|
|
||||||
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
|
|
||||||
amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
|
|
||||||
molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
|
|
||||||
interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
|
|
||||||
lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
|
|
||||||
in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
|
|
||||||
est.\n\n
|
|
||||||
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
|
|
||||||
Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
|
|
||||||
non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
|
|
||||||
eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
|
|
||||||
quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
|
|
||||||
ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
|
|
||||||
placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
|
|
||||||
convallis.\n\n
|
|
||||||
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
|
|
||||||
malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
|
|
||||||
gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
|
|
||||||
libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
|
|
||||||
sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
|
|
||||||
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
|
|
||||||
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
|
|
||||||
</string>
|
|
||||||
<string name="title_activity_main2">MainActivity2</string>
|
|
||||||
<string name="title_home">Home</string>
|
|
||||||
<string name="title_dashboard">Dashboard</string>
|
|
||||||
<string name="title_notifications">Notifications</string>
|
|
||||||
</resources>
|
|
@ -1,21 +0,0 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.MyPassworldManage" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
||||||
<!-- Primary brand color. -->
|
|
||||||
<item name="colorPrimary">@color/purple_500</item>
|
|
||||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
||||||
<item name="colorOnPrimary">@color/white</item>
|
|
||||||
<!-- Secondary brand color. -->
|
|
||||||
<item name="colorSecondary">@color/teal_200</item>
|
|
||||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
|
||||||
<!-- Status bar color. -->
|
|
||||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Base.Theme.MyPassworldManage" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Customize your light theme here. -->
|
|
||||||
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
|
|
||||||
</style>
|
|
||||||
</resources>
|
|
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<dimen name="edit_layout_height">48dp</dimen>
|
|
||||||
</resources>
|
|
@ -1,17 +0,0 @@
|
|||||||
package com.yutou.passmanage;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
}
|
|
24
build.gradle
@ -1,24 +0,0 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:8.5.2'
|
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
|
||||||
// in the individual module build.gradle files
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task clean(type: Delete) {
|
|
||||||
delete rootProject.buildDir
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
# Project-wide Gradle settings.
|
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
|
||||||
# Android operating system, and which are packaged with your app"s APK
|
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
||||||
android.useAndroidX=true
|
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
|
||||||
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
|
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
|
||||||
android.nonTransitiveRClass=false
|
|
||||||
android.nonFinalResIds=false
|
|
6
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +0,0 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
|
||||||
distributionPath=wrapper/dists
|
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
|
||||||
networkTimeout=10000
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
|
244
gradlew
vendored
@ -1,244 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
#
|
|
||||||
# Copyright © 2015-2021 the original authors.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
|
||||||
#
|
|
||||||
# Important for running:
|
|
||||||
#
|
|
||||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
|
||||||
# noncompliant, but you have some other compliant shell such as ksh or
|
|
||||||
# bash, then to run this script, type that shell name before the whole
|
|
||||||
# command line, like:
|
|
||||||
#
|
|
||||||
# ksh Gradle
|
|
||||||
#
|
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
|
||||||
# requires all of these POSIX shell features:
|
|
||||||
# * functions;
|
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
|
||||||
#
|
|
||||||
# Important for patching:
|
|
||||||
#
|
|
||||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
|
||||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
|
||||||
#
|
|
||||||
# The "traditional" practice of packing multiple parameters into a
|
|
||||||
# space-separated string is a well documented source of bugs and security
|
|
||||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
|
||||||
# options in "$@", and eventually passing that to Java.
|
|
||||||
#
|
|
||||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
|
||||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
|
||||||
# see the in-line comments for details.
|
|
||||||
#
|
|
||||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
|
||||||
# Darwin, MinGW, and NonStop.
|
|
||||||
#
|
|
||||||
# (3) This script is generated from the Groovy template
|
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
|
||||||
# within the Gradle project.
|
|
||||||
#
|
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
|
||||||
app_path=$0
|
|
||||||
|
|
||||||
# Need this for daisy-chained symlinks.
|
|
||||||
while
|
|
||||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
|
||||||
[ -h "$app_path" ]
|
|
||||||
do
|
|
||||||
ls=$( ls -ld "$app_path" )
|
|
||||||
link=${ls#*' -> '}
|
|
||||||
case $link in #(
|
|
||||||
/*) app_path=$link ;; #(
|
|
||||||
*) app_path=$APP_HOME$link ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# This is normally unused
|
|
||||||
# shellcheck disable=SC2034
|
|
||||||
APP_BASE_NAME=${0##*/}
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
|
||||||
MAX_FD=maximum
|
|
||||||
|
|
||||||
warn () {
|
|
||||||
echo "$*"
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
die () {
|
|
||||||
echo
|
|
||||||
echo "$*"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
|
||||||
cygwin=false
|
|
||||||
msys=false
|
|
||||||
darwin=false
|
|
||||||
nonstop=false
|
|
||||||
case "$( uname )" in #(
|
|
||||||
CYGWIN* ) cygwin=true ;; #(
|
|
||||||
Darwin* ) darwin=true ;; #(
|
|
||||||
MSYS* | MINGW* ) msys=true ;; #(
|
|
||||||
NONSTOP* ) nonstop=true ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
|
||||||
else
|
|
||||||
JAVACMD=$JAVA_HOME/bin/java
|
|
||||||
fi
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD=java
|
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
location of your Java installation."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|
||||||
case $MAX_FD in #(
|
|
||||||
max*)
|
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC3045
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
|
||||||
warn "Could not query maximum file descriptor limit"
|
|
||||||
esac
|
|
||||||
case $MAX_FD in #(
|
|
||||||
'' | soft) :;; #(
|
|
||||||
*)
|
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC3045
|
|
||||||
ulimit -n "$MAX_FD" ||
|
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command, stacking in reverse order:
|
|
||||||
# * args from the command line
|
|
||||||
# * the main class name
|
|
||||||
# * -classpath
|
|
||||||
# * -D...appname settings
|
|
||||||
# * --module-path (only if needed)
|
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if "$cygwin" || "$msys" ; then
|
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
|
||||||
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
for arg do
|
|
||||||
if
|
|
||||||
case $arg in #(
|
|
||||||
-*) false ;; # don't mess with options #(
|
|
||||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
|
||||||
[ -e "$t" ] ;; #(
|
|
||||||
*) false ;;
|
|
||||||
esac
|
|
||||||
then
|
|
||||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
|
||||||
fi
|
|
||||||
# Roll the args list around exactly as many times as the number of
|
|
||||||
# args, so each arg winds up back in the position where it started, but
|
|
||||||
# possibly modified.
|
|
||||||
#
|
|
||||||
# NB: a `for` loop captures its iteration list before it begins, so
|
|
||||||
# changing the positional parameters here affects neither the number of
|
|
||||||
# iterations, nor the values presented in `arg`.
|
|
||||||
shift # remove old arg
|
|
||||||
set -- "$@" "$arg" # push replacement arg
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
|
||||||
|
|
||||||
set -- \
|
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
|
||||||
-classpath "$CLASSPATH" \
|
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
|
||||||
#
|
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
|
||||||
#
|
|
||||||
# In Bash we could simply go:
|
|
||||||
#
|
|
||||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
|
||||||
# set -- "${ARGS[@]}" "$@"
|
|
||||||
#
|
|
||||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
|
||||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
|
||||||
# character that might be a shell metacharacter, then use eval to reverse
|
|
||||||
# that process (while maintaining the separation between arguments), and wrap
|
|
||||||
# the whole thing up as a single "set" statement.
|
|
||||||
#
|
|
||||||
# This will of course break if any of these variables contains a newline or
|
|
||||||
# an unmatched quote.
|
|
||||||
#
|
|
||||||
|
|
||||||
eval "set -- $(
|
|
||||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
|
||||||
xargs -n1 |
|
|
||||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
|
||||||
tr '\n' ' '
|
|
||||||
)" '"$@"'
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
|
92
gradlew.bat
vendored
@ -1,92 +0,0 @@
|
|||||||
@rem
|
|
||||||
@rem Copyright 2015 the original author or authors.
|
|
||||||
@rem
|
|
||||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
@rem you may not use this file except in compliance with the License.
|
|
||||||
@rem You may obtain a copy of the License at
|
|
||||||
@rem
|
|
||||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@rem
|
|
||||||
@rem Unless required by applicable law or agreed to in writing, software
|
|
||||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
@rem See the License for the specific language governing permissions and
|
|
||||||
@rem limitations under the License.
|
|
||||||
@rem
|
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
|
||||||
@rem ##########################################################################
|
|
||||||
@rem
|
|
||||||
@rem Gradle startup script for Windows
|
|
||||||
@rem
|
|
||||||
@rem ##########################################################################
|
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
|
||||||
set APP_HOME=%DIRNAME%
|
|
||||||
|
|
||||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
|
||||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
|
||||||
|
|
||||||
@rem Find java.exe
|
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
|
||||||
echo.
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
|
||||||
echo location of your Java installation.
|
|
||||||
|
|
||||||
goto fail
|
|
||||||
|
|
||||||
:execute
|
|
||||||
@rem Setup the command line
|
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
|
||||||
|
|
||||||
:end
|
|
||||||
@rem End local scope for the variables with windows NT shell
|
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
1
netlibs/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,34 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'com.android.library'
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'com.yutou.netlibs'
|
|
||||||
compileSdk 34
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk 28
|
|
||||||
targetSdk 34
|
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
api 'com.squareup.okhttp3:okhttp:3.14.9'
|
|
||||||
api 'com.squareup.retrofit2:retrofit:2.3.0'
|
|
||||||
api 'com.alibaba.fastjson2:fastjson2:2.0.52'
|
|
||||||
|
|
||||||
}
|
|