diff --git a/picasso/src/main/java/com/squareup/picasso3/Picasso.kt b/picasso/src/main/java/com/squareup/picasso3/Picasso.kt index b9a5abca68..50f1077534 100644 --- a/picasso/src/main/java/com/squareup/picasso3/Picasso.kt +++ b/picasso/src/main/java/com/squareup/picasso3/Picasso.kt @@ -97,6 +97,8 @@ class Picasso internal constructor( @get:JvmName("-targetToDeferredRequestCreator") internal val targetToDeferredRequestCreator = mutableMapOf() + private val targetToSizeResolver = mutableMapOf() + @get:JvmName("-shutdown") @set:JvmName("-shutdown") internal var shutdown = false @@ -135,6 +137,11 @@ class Picasso internal constructor( for (i in deferredRequestCreators.indices) { deferredRequestCreators[i].cancel() } + + val sizeResolvers = targetToSizeResolver.values.toList() + for (i in sizeResolvers.indices) { + sizeResolvers[i].onCancel() + } } /** Cancel any existing requests for the specified target [ImageView]. */ @@ -181,6 +188,10 @@ class Picasso internal constructor( deferredRequestCreator.cancel() } } + + targetToSizeResolver.values.forEach { sizeResolver -> + // TODO: Work out how to cancel size resolvers by tag. + } } @OnLifecycleEvent(ON_STOP) @@ -200,6 +211,8 @@ class Picasso internal constructor( dispatcher.dispatchPauseTag(tag) } } + + // TODO: Work how to to pause size resolvers. } /** @@ -230,6 +243,8 @@ class Picasso internal constructor( dispatcher.dispatchResumeTag(tag) } } + + // TODO: Work out how to resume size resolvers. } /** @@ -386,6 +401,14 @@ class Picasso internal constructor( return nextRequest } + internal fun resolveSize(sizeResolver: SizeResolver, target: Any, callback: (Size) -> Unit) { + if (targetToSizeResolver.containsKey(target)) { + cancelExistingRequest(target) + } + targetToSizeResolver[target] = sizeResolver + sizeResolver.resolve(callback) + } + @JvmName("-defer") internal fun defer(view: ImageView, request: DeferredRequestCreator) { // If there is already a deferred request, cancel it. @@ -517,6 +540,7 @@ class Picasso internal constructor( action.cancel() dispatcher.dispatchCancel(action) } + targetToSizeResolver.remove(target) if (target is ImageView) { val deferredRequestCreator = targetToDeferredRequestCreator.remove(target) deferredRequestCreator?.cancel() diff --git a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt index cb7edb7a30..95a470b358 100644 --- a/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt +++ b/picasso/src/main/java/com/squareup/picasso3/RequestCreator.kt @@ -58,6 +58,7 @@ class RequestCreator internal constructor( @DrawableRes private var errorResId = 0 private var placeholderDrawable: Drawable? = null private var errorDrawable: Drawable? = null + private var sizeResolver: SizeResolver? = null /** Internal use only. Used by [DeferredRequestCreator]. */ @get:JvmName("-tag") @@ -68,6 +69,12 @@ class RequestCreator internal constructor( check(!picasso.shutdown) { "Picasso instance already shut down. Cannot submit new requests." } } + fun sizeResolver(sizeResolver: SizeResolver): RequestCreator { + check(!deferred) { "Use only one of fit() or sizeResolver()." } + this.sizeResolver = sizeResolver + return this + } + /** * Explicitly opt-out to having a placeholder set when calling [into]. * @@ -433,6 +440,17 @@ class RequestCreator internal constructor( return } + if (sizeResolver != null) { + picasso.resolveSize(sizeResolver!!, target) { (width, height) -> + resize(width, height) + loadIntoTarget(target, started) + } + } else { + loadIntoTarget(target, started) + } + } + + private fun loadIntoTarget(target: BitmapTarget, started: Long) { val request = createRequest(started) if (shouldReadFromMemoryCache(request.memoryPolicy)) { val bitmap = picasso.quickMemoryCacheCheck(request.key) @@ -556,11 +574,18 @@ class RequestCreator internal constructor( setPlaceholder(target, getPlaceholderDrawable()) } picasso.defer(target, DeferredRequestCreator(this, target, callback)) + picasso.resolveSize(ViewSizeResolver(target), target) { (width, height) -> + data.resize(width, height) + loadIntoImageView(target, started, callback) + } return } - data.resize(width, height) } + loadIntoImageView(target, started, callback) + } + + private fun loadIntoImageView(target: ImageView, started: Long, callback: Callback? = null) { val request = createRequest(started) if (shouldReadFromMemoryCache(request.memoryPolicy)) { diff --git a/picasso/src/main/java/com/squareup/picasso3/ViewSizeResolver.kt b/picasso/src/main/java/com/squareup/picasso3/ViewSizeResolver.kt new file mode 100644 index 0000000000..c0f2d8ac4b --- /dev/null +++ b/picasso/src/main/java/com/squareup/picasso3/ViewSizeResolver.kt @@ -0,0 +1,74 @@ +package com.squareup.picasso3 + +import android.view.View +import android.view.ViewTreeObserver +import androidx.annotation.Px + +interface SizeResolver { + fun resolve(listener: (Size) -> Unit) + fun onCancel() +} + +data class Size(@Px val width: Int, @Px val height: Int) + +class ViewSizeResolver( + private val target: View, +) : SizeResolver, ViewTreeObserver.OnPreDrawListener, View.OnAttachStateChangeListener { + + private var listener: ((Size) -> Unit)? = null + + override fun resolve(listener: (Size) -> Unit) { + val startingWidth = target.width + val startingHeight = target.height + + if (startingWidth > 0 && startingHeight > 0) { + listener(Size(startingWidth, startingHeight)) + return + } + + this.listener = listener + target.addOnAttachStateChangeListener(this) + // Only add the pre-draw listener if the view is already attached. + // See: https://github.com/square/picasso/issues/1321 + if (target.windowToken != null) { + onViewAttachedToWindow(target) + } + } + + override fun onCancel() { + listener = null + target.removeOnAttachStateChangeListener(this) + val vto = target.viewTreeObserver + if (vto.isAlive) { + vto.removeOnPreDrawListener(this) + } + } + + override fun onPreDraw(): Boolean { + val vto = target.viewTreeObserver + if (!vto.isAlive) { + return true + } + + val width = target.width + val height = target.height + + if (width <= 0 || height <= 0) { + return true + } + + target.removeOnAttachStateChangeListener(this) + vto.removeOnPreDrawListener(this) + listener?.invoke(Size(width, height)) + listener = null + return true + } + + override fun onViewAttachedToWindow(view: View) { + view.viewTreeObserver.addOnPreDrawListener(this) + } + + override fun onViewDetachedFromWindow(view: View) { + view.viewTreeObserver.removeOnPreDrawListener(this) + } +}