增加漫画单独配置
更新界面
This commit is contained in:
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
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user