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'
|
||||
|
||||
}
|