增加漫画单独配置

更新界面
This commit is contained in:
HapeLee committed 2025-08-02 12:44:25 +08:00
1 parent 22f34161ab
commit 3c5df88027
44 files changed
+798 -1059

No files matched your search

@@ -1,445 +0,0 @@
package io.legato.kazusa.ui.widget.image
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.TextPaint
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.widget.AppCompatImageView
import io.legato.kazusa.R
import io.legato.kazusa.utils.getCompatColor
import io.legato.kazusa.utils.printOnDebug
import io.legato.kazusa.utils.spToPx
import kotlin.math.min
import kotlin.math.pow
import androidx.core.graphics.createBitmap
import io.legato.kazusa.utils.themeColor
import androidx.core.content.withStyledAttributes
@Suppress("unused", "MemberVisibilityCanBePrivate")
class CircleImageView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : AppCompatImageView(context, attrs) {
private val mDrawableRect = RectF()
private val mBorderRect = RectF()
private val mShaderMatrix = Matrix()
private val mBitmapPaint = Paint()
private val mBorderPaint = Paint()
private val mCircleBackgroundPaint = Paint()
private val textPaint by lazy {
val textPaint = TextPaint()
textPaint.isAntiAlias = true
textPaint.textAlign = Paint.Align.CENTER
textPaint
}
private var mBorderColor = DEFAULT_BORDER_COLOR
private var mBorderWidth = DEFAULT_BORDER_WIDTH
private var mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR
private var mBitmap: Bitmap? = null
private var mBitmapShader: BitmapShader? = null
private var mBitmapWidth: Int = 0
private var mBitmapHeight: Int = 0
private var mDrawableRadius: Float = 0.toFloat()
private var mBorderRadius: Float = 0.toFloat()
private var mColorFilter: ColorFilter? = null
private var mReady: Boolean = false
private var mSetupPending: Boolean = false
private var mBorderOverlay: Boolean = false
var isDisableCircularTransformation: Boolean = false
set(disableCircularTransformation) {
if (field == disableCircularTransformation) {
return
}
field = disableCircularTransformation
initializeBitmap()
}
var borderColor: Int
get() = mBorderColor
set(@ColorInt borderColor) {
if (borderColor == mBorderColor) {
return
}
mBorderColor = borderColor
mBorderPaint.color = mBorderColor
invalidate()
}
var circleBackgroundColor: Int
get() = mCircleBackgroundColor
set(@ColorInt circleBackgroundColor) {
if (circleBackgroundColor == mCircleBackgroundColor) {
return
}
mCircleBackgroundColor = circleBackgroundColor
mCircleBackgroundPaint.color = circleBackgroundColor
invalidate()
}
var borderWidth: Int
get() = mBorderWidth
set(borderWidth) {
if (borderWidth == mBorderWidth) {
return
}
mBorderWidth = borderWidth
setup()
}
var isBorderOverlay: Boolean
get() = mBorderOverlay
set(borderOverlay) {
if (borderOverlay == mBorderOverlay) {
return
}
mBorderOverlay = borderOverlay
setup()
}
private var text: String? = null
private var textColor = context.themeColor(com.google.android.material.R.attr.colorOnSurface)
private var textBold = false
var isInView = false
init {
context.withStyledAttributes(attrs, R.styleable.CircleImageView) {
mBorderWidth =
getDimensionPixelSize(
R.styleable.CircleImageView_civ_border_width,
DEFAULT_BORDER_WIDTH
)
mBorderColor =
getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR)
mBorderOverlay =
getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)
mCircleBackgroundColor =
getColor(
R.styleable.CircleImageView_civ_circle_background_color,
DEFAULT_CIRCLE_BACKGROUND_COLOR
)
text = getString(R.styleable.CircleImageView_text)
contentDescription = text
if (hasValue(R.styleable.CircleImageView_textColor)) {
textColor = getColor(
R.styleable.CircleImageView_textColor,
context.themeColor(com.google.android.material.R.attr.colorOnSurface)
)
}
}
mReady = true
if (mSetupPending) {
setup()
mSetupPending = false
}
}
override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
if (adjustViewBounds) {
throw IllegalArgumentException("adjustViewBounds not supported.")
}
}
override fun onDraw(canvas: Canvas) {
if (isDisableCircularTransformation) {
super.onDraw(canvas)
return
}
if (mBitmap == null) {
return
}
if (mCircleBackgroundColor != Color.TRANSPARENT) {
canvas.drawCircle(
mDrawableRect.centerX(),
mDrawableRect.centerY(),
mDrawableRadius,
mCircleBackgroundPaint
)
}
canvas.drawCircle(
mDrawableRect.centerX(),
mDrawableRect.centerY(),
mDrawableRadius,
mBitmapPaint
)
if (mBorderWidth > 0) {
canvas.drawCircle(
mBorderRect.centerX(),
mBorderRect.centerY(),
mBorderRadius,
mBorderPaint
)
}
drawText(canvas)
}
private fun drawText(canvas: Canvas) {
text?.let {
textPaint.color = textColor
textPaint.isFakeBoldText = textBold
textPaint.textSize = 15f.spToPx()
val fm = textPaint.fontMetrics
canvas.drawText(
it,
width * 0.5f,
(height * 0.5f + (fm.bottom - fm.top) * 0.5f - fm.bottom),
textPaint
)
}
}
fun setText(text: String?) {
this.text = text
contentDescription = text
invalidate()
}
fun setTextColor(@ColorInt textColor: Int) {
this.textColor = textColor
invalidate()
}
fun setTextBold(bold: Boolean) {
this.textBold = bold
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
setup()
}
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
super.setPadding(left, top, right, bottom)
setup()
}
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
super.setPaddingRelative(start, top, end, bottom)
setup()
}
fun setCircleBackgroundColorResource(@ColorRes circleBackgroundRes: Int) {
circleBackgroundColor = context.getCompatColor(circleBackgroundRes)
}
override fun setImageBitmap(bm: Bitmap) {
super.setImageBitmap(bm)
initializeBitmap()
}
override fun setImageDrawable(drawable: Drawable?) {
super.setImageDrawable(drawable)
initializeBitmap()
}
override fun setImageResource(@DrawableRes resId: Int) {
super.setImageResource(resId)
initializeBitmap()
}
override fun setImageURI(uri: Uri?) {
super.setImageURI(uri)
initializeBitmap()
}
override fun setColorFilter(cf: ColorFilter) {
if (cf === mColorFilter) {
return
}
mColorFilter = cf
applyColorFilter()
invalidate()
}
override fun getColorFilter(): ColorFilter? {
return mColorFilter
}
private fun applyColorFilter() {
mBitmapPaint.colorFilter = mColorFilter
}
private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
if (drawable == null) {
return null
}
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
return try {
val bitmap: Bitmap = if (drawable is ColorDrawable) {
createBitmap(COLOR_DRAWABLE_DIMENSION, COLOR_DRAWABLE_DIMENSION, BITMAP_CONFIG)
} else {
createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, BITMAP_CONFIG)
}
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
bitmap
} catch (e: Exception) {
e.printOnDebug()
null
}
}
private fun initializeBitmap() {
mBitmap = if (isDisableCircularTransformation) {
null
} else {
getBitmapFromDrawable(drawable)
}
setup()
}
private fun setup() {
if (!mReady) {
mSetupPending = true
return
}
if (width == 0 && height == 0) {
return
}
if (mBitmap == null) {
invalidate()
return
}
mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
mBitmapPaint.isAntiAlias = true
mBitmapPaint.shader = mBitmapShader
mBorderPaint.style = Paint.Style.STROKE
mBorderPaint.isAntiAlias = true
mBorderPaint.color = mBorderColor
mBorderPaint.strokeWidth = mBorderWidth.toFloat()
mCircleBackgroundPaint.style = Paint.Style.FILL
mCircleBackgroundPaint.isAntiAlias = true
mCircleBackgroundPaint.color = mCircleBackgroundColor
mBitmapHeight = mBitmap!!.height
mBitmapWidth = mBitmap!!.width
mBorderRect.set(calculateBounds())
mBorderRadius =
min(
(mBorderRect.height() - mBorderWidth) / 2.0f,
(mBorderRect.width() - mBorderWidth) / 2.0f
)
mDrawableRect.set(mBorderRect)
if (!mBorderOverlay && mBorderWidth > 0) {
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f)
}
mDrawableRadius = min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f)
applyColorFilter()
updateShaderMatrix()
invalidate()
}
private fun calculateBounds(): RectF {
val availableWidth = width - paddingLeft - paddingRight
val availableHeight = height - paddingTop - paddingBottom
val sideLength = min(availableWidth, availableHeight)
val left = paddingLeft + (availableWidth - sideLength) / 2f
val top = paddingTop + (availableHeight - sideLength) / 2f
return RectF(left, top, left + sideLength, top + sideLength)
}
private fun updateShaderMatrix() {
val scale: Float
var dx = 0f
var dy = 0f
mShaderMatrix.set(null)
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / mBitmapHeight.toFloat()
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f
} else {
scale = mDrawableRect.width() / mBitmapWidth.toFloat()
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f
}
mShaderMatrix.setScale(scale, scale)
mShaderMatrix.postTranslate(
(dx + 0.5f).toInt() + mDrawableRect.left,
(dy + 0.5f).toInt() + mDrawableRect.top
)
mBitmapShader!!.setLocalMatrix(mShaderMatrix)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isInView = (inTouchableArea(event.x, event.y))
}
}
return super.onTouchEvent(event)
}
private fun inTouchableArea(x: Float, y: Float): Boolean {
return (x - mBorderRect.centerX()).toDouble()
.pow(2.0) + (y - mBorderRect.centerY()).toDouble()
.pow(2.0) <= mBorderRadius.toDouble().pow(2.0)
}
private inner class OutlineProvider : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
val bounds = Rect()
mBorderRect.roundOut(bounds)
outline.setRoundRect(bounds, bounds.width() / 2.0f)
}
}
companion object {
private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
private const val COLOR_DRAWABLE_DIMENSION = 2
private const val DEFAULT_BORDER_WIDTH = 0
private const val DEFAULT_BORDER_COLOR = Color.BLACK
private const val DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT
private const val DEFAULT_BORDER_OVERLAY = false
}
}