/library/src/main/java/com/cunoraz/gifview/library/GifView.kt

https://github.com/Cutta/GifView · Kotlin · 227 lines · 156 code · 35 blank · 36 comment · 28 complexity · dc7bafa2ccf9339bdcab136bfa80665d MD5 · raw file

  1. package com.cunoraz.gifview.library
  2. import android.annotation.SuppressLint
  3. import android.content.Context
  4. import android.graphics.Canvas
  5. import android.graphics.Movie
  6. import android.os.Build
  7. import android.util.AttributeSet
  8. import android.view.View
  9. /**
  10. * Created by Cuneyt on 4.10.2015.
  11. * Updated by Cuneyt on 02.04.2019.
  12. * Gifview
  13. */
  14. class GifView @JvmOverloads
  15. constructor(context: Context,
  16. attrs: AttributeSet? = null,
  17. defStyle: Int = R.styleable.CustomTheme_gifViewStyle)
  18. : View(context, attrs, defStyle) {
  19. private var movieMovieResourceId: Int = 0
  20. private var movie: Movie? = null
  21. private var movieStart: Long = 0
  22. private var currentAnimationTime: Int = 0
  23. private var movieLeft: Float = 0F
  24. private var movieTop: Float = 0F
  25. private var movieScale: Float = 0F
  26. private var movieMeasuredMovieWidth: Int = 0
  27. private var movieMeasuredMovieHeight: Int = 0
  28. @Volatile
  29. var isPaused: Boolean = false
  30. private var isVisible = true
  31. var gifResource: Int
  32. get() = this.movieMovieResourceId
  33. set(movieResourceId) {
  34. this.movieMovieResourceId = movieResourceId
  35. movie = Movie.decodeStream(resources.openRawResource(movieMovieResourceId))
  36. requestLayout()
  37. }
  38. val isPlaying: Boolean
  39. get() = !this.isPaused
  40. init {
  41. setViewAttributes(context, attrs, defStyle)
  42. }
  43. @SuppressLint("NewApi")
  44. private fun setViewAttributes(context: Context, attrs: AttributeSet?, defStyle: Int) {
  45. setLayerType(View.LAYER_TYPE_SOFTWARE, null)
  46. val array = context.obtainStyledAttributes(attrs,
  47. R.styleable.GifView, defStyle, R.style.Widget_GifView)
  48. //-1 is default value
  49. movieMovieResourceId = array.getResourceId(R.styleable.GifView_gif, -1)
  50. isPaused = array.getBoolean(R.styleable.GifView_paused, false)
  51. array.recycle()
  52. if (movieMovieResourceId != -1) {
  53. movie = Movie.decodeStream(resources.openRawResource(movieMovieResourceId))
  54. }
  55. }
  56. fun play() {
  57. if (this.isPaused) {
  58. this.isPaused = false
  59. /**
  60. * Calculate new movie start time, so that it resumes from the same
  61. * frame.
  62. */
  63. movieStart = android.os.SystemClock.uptimeMillis() - currentAnimationTime
  64. invalidate()
  65. }
  66. }
  67. fun pause() {
  68. if (!this.isPaused) {
  69. this.isPaused = true
  70. invalidate()
  71. }
  72. }
  73. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  74. if (movie != null) {
  75. val movieWidth = movie!!.width()
  76. val movieHeight = movie!!.height()
  77. /*
  78. * Calculate horizontal scaling
  79. */
  80. var scaleH = 1f
  81. val measureModeWidth = View.MeasureSpec.getMode(widthMeasureSpec)
  82. if (measureModeWidth != View.MeasureSpec.UNSPECIFIED) {
  83. val maximumWidth = View.MeasureSpec.getSize(widthMeasureSpec)
  84. if (movieWidth > maximumWidth) {
  85. scaleH = movieWidth.toFloat() / maximumWidth.toFloat()
  86. }
  87. }
  88. /*
  89. * calculate vertical scaling
  90. */
  91. var scaleW = 1f
  92. val measureModeHeight = View.MeasureSpec.getMode(heightMeasureSpec)
  93. if (measureModeHeight != View.MeasureSpec.UNSPECIFIED) {
  94. val maximumHeight = View.MeasureSpec.getSize(heightMeasureSpec)
  95. if (movieHeight > maximumHeight) {
  96. scaleW = movieHeight.toFloat() / maximumHeight.toFloat()
  97. }
  98. }
  99. /*
  100. * calculate overall scale
  101. */
  102. movieScale = 1f / Math.max(scaleH, scaleW)
  103. movieMeasuredMovieWidth = (movieWidth * movieScale).toInt()
  104. movieMeasuredMovieHeight = (movieHeight * movieScale).toInt()
  105. setMeasuredDimension(movieMeasuredMovieWidth, movieMeasuredMovieHeight)
  106. } else {
  107. /*
  108. * No movie set, just set minimum available size.
  109. */
  110. setMeasuredDimension(suggestedMinimumWidth, suggestedMinimumHeight)
  111. }
  112. }
  113. override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
  114. super.onLayout(changed, left, top, right, bottom)
  115. /*
  116. * Calculate movieLeft / movieTop for drawing in center
  117. */
  118. movieLeft = (width - movieMeasuredMovieWidth) / 2f
  119. movieTop = (height - movieMeasuredMovieHeight) / 2f
  120. isVisible = visibility == View.VISIBLE
  121. }
  122. override fun onDraw(canvas: Canvas) {
  123. if (movie != null) {
  124. if (!isPaused) {
  125. updateAnimationTime()
  126. drawMovieFrame(canvas)
  127. invalidateView()
  128. } else {
  129. drawMovieFrame(canvas)
  130. }
  131. }
  132. }
  133. /**
  134. * Invalidates view only if it is isVisible.
  135. * <br></br>
  136. * [.postInvalidateOnAnimation] is used for Jelly Bean and higher.
  137. */
  138. @SuppressLint("NewApi")
  139. private fun invalidateView() {
  140. if (isVisible) {
  141. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  142. postInvalidateOnAnimation()
  143. } else {
  144. invalidate()
  145. }
  146. }
  147. }
  148. /**
  149. * Calculate current animation time
  150. */
  151. private fun updateAnimationTime() {
  152. val now = android.os.SystemClock.uptimeMillis()
  153. if (movieStart == 0L) {
  154. movieStart = now
  155. }
  156. var duration = movie!!.duration()
  157. if (duration == 0) {
  158. duration = DEFAULT_MOVIE_VIEW_DURATION
  159. }
  160. currentAnimationTime = ((now - movieStart) % duration).toInt()
  161. }
  162. /**
  163. * Draw current GIF frame
  164. */
  165. private fun drawMovieFrame(canvas: Canvas) {
  166. movie!!.setTime(currentAnimationTime)
  167. canvas.save()
  168. canvas.scale(movieScale, movieScale)
  169. movie!!.draw(canvas, movieLeft / movieScale, movieTop / movieScale)
  170. canvas.restore()
  171. }
  172. @SuppressLint("NewApi")
  173. override fun onScreenStateChanged(screenState: Int) {
  174. super.onScreenStateChanged(screenState)
  175. isVisible = screenState == View.SCREEN_STATE_ON
  176. invalidateView()
  177. }
  178. @SuppressLint("NewApi")
  179. override fun onVisibilityChanged(changedView: View, visibility: Int) {
  180. super.onVisibilityChanged(changedView, visibility)
  181. isVisible = visibility == View.VISIBLE
  182. invalidateView()
  183. }
  184. override fun onWindowVisibilityChanged(visibility: Int) {
  185. super.onWindowVisibilityChanged(visibility)
  186. isVisible = visibility == View.VISIBLE
  187. invalidateView()
  188. }
  189. companion object {
  190. private const val DEFAULT_MOVIE_VIEW_DURATION = 1000
  191. }
  192. }