Files
EhViewer/app/src/main/java/com/hippo/widget/LoadImageView.kt
T
2023-02-04 10:43:28 +08:00

225 lines
6.7 KiB
Kotlin

/*
* Copyright 2015-2016 Hippo Seven
*
* 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
*
* http://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.
*/
package com.hippo.widget
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.TransitionDrawable
import android.util.AttributeSet
import android.view.View
import androidx.annotation.IntDef
import androidx.core.content.ContextCompat
import coil.imageLoader
import coil.request.Disposable
import coil.request.ImageRequest
import coil.size.Size
import com.hippo.drawable.PreciselyClipDrawable
import com.hippo.ehviewer.R
open class LoadImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FixedAspectImageView(context, attrs, defStyleAttr), View.OnClickListener,
View.OnLongClickListener {
private var mRequest: ImageRequest? = null
private var mOffsetX = Integer.MIN_VALUE
private var mOffsetY = Integer.MIN_VALUE
private var mClipWidth = Integer.MIN_VALUE
private var mClipHeight = Integer.MIN_VALUE
private var mRetryType: Int = 0
private var mFailed: Boolean = false
private var task: Disposable? = null
private val imageDrawable: Drawable?
get() {
var drawable = drawable
if (drawable is TransitionDrawable) {
if (drawable.numberOfLayers == 2) {
drawable = drawable.getDrawable(1)
}
}
if (drawable is PreciselyClipDrawable) {
drawable = drawable.wrappedDrawable
}
return drawable
}
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.LoadImageView, defStyleAttr, 0)
setRetryType(a.getInt(R.styleable.LoadImageView_retryType, 0))
a.recycle()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (mFailed) onFailure()
else task?.run { if (isDisposed) reload() }
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
task?.dispose()
clearDrawable()
}
private fun clearDrawable() {
setImageDrawable(null)
}
private fun clearRetry() {
if (mRetryType == RETRY_TYPE_CLICK) {
setOnClickListener(null)
isClickable = false
} else if (mRetryType == RETRY_TYPE_LONG_CLICK) {
setOnLongClickListener(null)
isLongClickable = false
}
}
private fun setRetryType(@RetryType retryType: Int) {
if (mRetryType != retryType) {
val oldRetryType = mRetryType
mRetryType = retryType
if (mFailed) {
if (oldRetryType == RETRY_TYPE_CLICK) {
setOnClickListener(null)
isClickable = false
} else if (oldRetryType == RETRY_TYPE_LONG_CLICK) {
setOnLongClickListener(null)
isLongClickable = false
}
if (retryType == RETRY_TYPE_CLICK) {
setOnClickListener(this)
} else if (retryType == RETRY_TYPE_LONG_CLICK) {
setOnLongClickListener(this)
}
}
}
}
fun setClip(offsetX: Int, offsetY: Int, clipWidth: Int, clipHeight: Int) {
mOffsetX = offsetX
mOffsetY = offsetY
mClipWidth = clipWidth
mClipHeight = clipHeight
}
fun resetClip() {
mOffsetX = Integer.MIN_VALUE
mOffsetY = Integer.MIN_VALUE
mClipWidth = Integer.MIN_VALUE
mClipHeight = Integer.MIN_VALUE
}
@JvmOverloads
fun load(url: String, isLoaded: Boolean = false) {
if ((mRequest?.data as? String?) == url)
return
mFailed = false
clearRetry()
task?.dispose()
mRequest =
ImageRequest.Builder(context).data(url).crossfade(false)
.size(Size.ORIGINAL).target(
{ onWait() },
{ onFailure() },
{ onGetValue(it, isLoaded) }
).build().also { load(it) }
}
fun load(request: ImageRequest) {
task = context.imageLoader.enqueue(request)
}
private fun reload() {
mRequest?.let { load(it) }
}
private fun onWait() {
mFailed = false
clearDrawable()
}
private fun onGetValue(drawable: Drawable, isLoaded: Boolean) {
var drawable = drawable
clearDrawable()
if (Integer.MIN_VALUE != mOffsetX) {
drawable = PreciselyClipDrawable(drawable, mOffsetX, mOffsetY, mClipWidth, mClipHeight)
}
onPreSetImageDrawable(drawable, true)
if (isShown && !isLoaded) {
val layers = arrayOfNulls<Drawable>(2)
layers[0] = ColorDrawable(Color.TRANSPARENT)
layers[1] = drawable
val transitionDrawable = TransitionDrawable(layers)
setImageDrawable(transitionDrawable)
transitionDrawable.startTransition(300)
} else {
setImageDrawable(drawable)
}
}
fun onFailure() {
mFailed = true
clearDrawable()
val drawable = ContextCompat.getDrawable(context, R.drawable.image_failed)
onPreSetImageDrawable(drawable, true)
setImageDrawable(drawable)
when (mRetryType) {
RETRY_TYPE_CLICK -> {
setOnClickListener(this)
}
RETRY_TYPE_LONG_CLICK -> {
setOnLongClickListener(this)
}
else -> {
mRequest = null
}
}
}
override fun onClick(v: View) {
reload()
}
override fun onLongClick(v: View): Boolean {
reload()
return true
}
open fun onPreSetImageDrawable(drawable: Drawable?, isTarget: Boolean) {}
open fun onPreSetImageResource(resId: Int, isTarget: Boolean) {}
@IntDef(RETRY_TYPE_NONE, RETRY_TYPE_CLICK, RETRY_TYPE_LONG_CLICK)
@Retention(AnnotationRetention.SOURCE)
private annotation class RetryType
companion object {
const val RETRY_TYPE_NONE = 0
const val RETRY_TYPE_CLICK = 1
const val RETRY_TYPE_LONG_CLICK = 2
}
}