Skip to content

Instantly share code, notes, and snippets.

@KirkBushman
Created April 5, 2018 19:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save KirkBushman/c9a912f63165827cd247691946c6bf1e to your computer and use it in GitHub Desktop.
Save KirkBushman/c9a912f63165827cd247691946c6bf1e to your computer and use it in GitHub Desktop.
Progress loader on Gifs
class GlideLoader(private val imageView: ImageView, private val progressBar: ProgressBar) {
fun load(url: String, options: RequestOptions) {
onConnecting()
ProgressAppGlideModule.expect(url, object : ProgressAppGlideModule.UIonProgressListener {
override fun onProgress(bytesRead: Long, expectedLength: Long) {
progressBar.setProgress((100 * bytesRead / expectedLength).toInt())
}
override fun getGranularityPercentage(): Float {
return 1.0f
}
})
Glide.with(imageView.context)
.asGif()
.load(url)
.apply(options.skipMemoryCache(true))
.listener(object : RequestListener<GifDrawable> {
override fun onResourceReady(resource: GifDrawable?, model: Any?, target: Target<GifDrawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
ProgressAppGlideModule.forget(url)
onFinished()
Log.d("Glide gif loader", "onResourceReady($resource, $model, $target, $dataSource, $isFirstResource)")
return false
}
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<GifDrawable>?, isFirstResource: Boolean): Boolean {
ProgressAppGlideModule.forget(url)
onFinished()
Log.d("Glide gif loader", "onException($e, $model, $target, $isFirstResource)")
return false
}
})
.into(imageView)
}
private fun onConnecting() {
progressBar.visibility = View.VISIBLE
}
private fun onFinished() {
progressBar.visibility = View.GONE
imageView.visibility = View.VISIBLE
}
}
class MediaZoomGifActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_activity_layout)
...
val url = intent.getStringExtra(MEDIA_URL)
val options = RequestOptions().priority(Priority.HIGH)
GlideLoader(media_gif_imageview, progress_bar).load(newUrl, options)
}
}
@GlideModule
class ProgressAppGlideModule : LibraryGlideModule() {
companion object {
fun forget(url: String) {
ProgressAppGlideModule.DispatchingProgressListener.forget(url)
}
fun expect(url: String, listener: ProgressAppGlideModule.UIonProgressListener) {
ProgressAppGlideModule.DispatchingProgressListener.expect(url, listener)
}
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
super.registerComponents(context, glide, registry)
val client = OkHttpClient.Builder()
.addNetworkInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
val listener = DispatchingProgressListener()
return response.newBuilder()
.body(OkHttpProgressResponseBody(request.url(), response.body()!!, listener))
.build()
}
})
.build()
registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client))
}
private interface ResponseProgressListener {
fun update(url: HttpUrl, bytesRead: Long, contentLength: Long)
}
interface UIonProgressListener {
fun onProgress(bytesRead: Long, expectedLength: Long)
fun getGranularityPercentage(): Float
}
private class DispatchingProgressListener : ProgressAppGlideModule.ResponseProgressListener {
companion object {
private val LISTENERS = WeakHashMap<String, UIonProgressListener>()
private val PROGRESSES = WeakHashMap<String, Long>()
fun forget(url: String) {
LISTENERS.remove(url)
PROGRESSES.remove(url)
}
fun expect(url: String, listener: UIonProgressListener) {
LISTENERS[url] = listener
}
}
private val handler = Handler(Looper.getMainLooper())
override fun update(url: HttpUrl, bytesRead: Long, contentLength: Long) {
val key = url.toString()
val listener = LISTENERS[key] ?: return
if(contentLength <= bytesRead) {
forget(key)
}
if(needsDispatch(key, bytesRead, contentLength, listener.getGranularityPercentage())) {
handler.post {
listener.onProgress(bytesRead, contentLength)
}
}
}
private fun needsDispatch(key: String, current: Long, total: Long, granularity: Float): Boolean {
if(granularity == 0f || current == 0L || total == current) {
return true
}
val percent = 100f * current / total
val currentProgress = (percent / granularity).toLong()
val lastProgress = PROGRESSES[key]
return if(lastProgress == null || currentProgress != lastProgress) {
PROGRESSES[key] = currentProgress
true
} else {
false
}
}
}
private class OkHttpProgressResponseBody(
private val url: HttpUrl,
private val responseBody: ResponseBody,
private val progressListener: ResponseProgressListener) : ResponseBody() {
private var bufferedSource: BufferedSource? = null
override fun contentType(): MediaType? {
return responseBody.contentType()
}
override fun contentLength(): Long {
return responseBody.contentLength()
}
override fun source(): BufferedSource {
if(bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()))
}
return bufferedSource!!
}
private fun source(source: Source): Source {
return object : ForwardingSource(source) {
var totalBytesRead = 0L
override fun read(sink: Buffer?, byteCount: Long): Long {
val bytesRead = super.read(sink!!, byteCount)
val fullLength = responseBody.contentLength()
if(bytesRead == -1L) {
totalBytesRead = fullLength
} else {
totalBytesRead += bytesRead
}
progressListener.update(url, totalBytesRead, fullLength)
return bytesRead
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment