@@ -16,26 +16,80 @@ import android.widget.ImageView
1616import androidx.core.animation.doOnEnd
1717import androidx.core.animation.doOnStart
1818import androidx.core.view.updateLayoutParams
19+ import java.lang.Exception
1920import java.util.*
2021import kotlin.math.max
2122import kotlin.math.min
2223import kotlin.random.Random
2324
2425class CloudView : FrameLayout {
2526 constructor (context: Context ) : super (context)
26- constructor (context: Context , attrs: AttributeSet ? ) : super (context, attrs)
27- constructor (context: Context , attrs: AttributeSet ? , defStyleAttr: Int ) : super (context, attrs, defStyleAttr)
27+
28+ constructor (context: Context , attrs: AttributeSet ? ) : super (context, attrs) {
29+ if (attrs != null ) saveAttrs(attrs)
30+ }
31+
32+ constructor (context: Context , attrs: AttributeSet ? , defStyleAttr: Int ) : super (context, attrs, defStyleAttr) {
33+ if (attrs != null ) saveAttrs(attrs, defStyleAttr)
34+ }
2835
2936 private var isDrawn = false
3037 private var isAnimating = false
31- private var requestedSizeRange = 300 .. 500
38+ private var requestedSizeRange = DEFAULT_MINIMUM_CLOUD_SIZE .. DEFAULT_MAXIMUM_CLOUD_SIZE
3239 private val imageViews = HashSet <ImageView >()
3340
3441 private var imageResId: Int? = R .drawable.ic_cloud
3542 private var imageBitmap: Bitmap ? = null
3643 private var imageDrawable: Drawable ? = null
3744
3845
46+ private fun saveAttrs (attrs : AttributeSet ? , defStyleAttr : Int = 0) {
47+ context.theme.obtainStyledAttributes(
48+ attrs, R .styleable.CloudView , defStyleAttr, 0 ).apply {
49+ try {
50+ if (getBoolean(R .styleable.CloudView_isAnimating , true )) startAnimation()
51+
52+ getResourceId(R .styleable.CloudView_cloudImageSrc , - 1 ).apply {
53+ if (this != - 1 ) imageResId = this
54+ }
55+
56+ getDimensionPixelSize(R .styleable.CloudView_minCloudSize , DEFAULT_MINIMUM_CLOUD_SIZE ).apply {
57+ minCloudSize = this
58+ }
59+
60+ getDimensionPixelSize(R .styleable.CloudView_maxCloudSize , DEFAULT_MAXIMUM_CLOUD_SIZE ).apply {
61+ maxCloudSize = this
62+ }
63+
64+ getInteger(R .styleable.CloudView_cloudCount , DEFAULT_CLOUD_COUNT ).apply {
65+ cloudCount = this
66+ }
67+
68+ getInteger(R .styleable.CloudView_basePassTimeMs , DEFAULT_PASS_TIME_MS ).apply {
69+ basePassTimeMs = this
70+ }
71+
72+ getInteger(R .styleable.CloudView_passTimeVarianceMs , DEFAULT_PASS_TIME_VARIANCE_MS ).apply {
73+ passTimeVarianceMs = this
74+ }
75+
76+ getBoolean(R .styleable.CloudView_fadeInEnabled , false ).apply {
77+ isFadeInEnabled = this
78+ }
79+
80+ getInteger(R .styleable.CloudView_fadeInTimeMs , DEFAULT_FADE_IN_TIME_MS ).apply {
81+
82+ }
83+
84+ getColor(R .styleable.CloudView_skyColor , Color .parseColor(" #03A9F4" )).apply {
85+ setBackgroundColor(this )
86+ }
87+ } finally {
88+ recycle()
89+ }
90+ }
91+ }
92+
3993 /* *
4094 * Sets all values to their defaults. This is not necessary as a first step if no values have
4195 * been changed.
@@ -47,16 +101,16 @@ class CloudView : FrameLayout {
47101 imageResId = R .drawable.ic_cloud
48102 imageBitmap = null
49103 imageDrawable = null
50- requestedSizeRange = 300 .. 500
51- setCloudCount(10 )
104+ requestedSizeRange = DEFAULT_MINIMUM_CLOUD_SIZE .. DEFAULT_MAXIMUM_CLOUD_SIZE
105+ setCloudCount(DEFAULT_CLOUD_COUNT )
52106
53107 if (wasAnimating) startAnimation()
54108 }
55109
56110 /* *
57111 * The maximum number of clouds seen in the view at once. Note clouds are redrawn on change.
58112 */
59- var cloudCount: Int = 10
113+ var cloudCount: Int = DEFAULT_CLOUD_COUNT
60114 set(value) {
61115 field = value
62116 if (isDrawn) respawnClouds()
@@ -118,7 +172,7 @@ class CloudView : FrameLayout {
118172 * will pass the entire view in basePassTimeMs + [passTimeVarianceMs]. Note that clouds currently
119173 * crossing the view are not updated.
120174 */
121- var basePassTimeMs: Int = 10000
175+ var basePassTimeMs: Int = DEFAULT_PASS_TIME_MS
122176 set(value) {
123177 if (value <= 0 ) throw IllegalArgumentException ()
124178 else field = value
@@ -129,7 +183,7 @@ class CloudView : FrameLayout {
129183 * entire view. A variance of 0 means all clouds will move at the same speed. Note that clouds
130184 * currently crossing the view are not updated.
131185 */
132- var passTimeVarianceMs: Int = 2000
186+ var passTimeVarianceMs: Int = DEFAULT_PASS_TIME_VARIANCE_MS
133187 set(value) {
134188 if (value < 0 ) throw IllegalArgumentException ()
135189 else field = value
@@ -147,7 +201,6 @@ class CloudView : FrameLayout {
147201 var isFadeInEnabled = false
148202
149203 init {
150- setBackgroundColor(Color .TRANSPARENT )
151204 viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver .OnGlobalLayoutListener {
152205 override fun onGlobalLayout () {
153206 if (isDrawn) {
@@ -294,11 +347,31 @@ class CloudView : FrameLayout {
294347 }
295348 }
296349
350+ /* *
351+ * The time taken for a cloud to fade in when entering the view. Note that [isFadeInEnabled] must
352+ * be true in order to enable this effect. Exceeding [basePassTimeMs] or [passTimeVarianceMs] may
353+ * cause clouds to cross the entire view before fully fading in.
354+ */
355+ var fadeInTimeMs: Int = DEFAULT_FADE_IN_TIME_MS
356+ set(value) {
357+ if (value < 0 ) throw IllegalArgumentException ()
358+
359+ field = value
360+
361+ if (value > basePassTimeMs) {
362+ Log .i(TAG , " setFadeIntTimeMs(): Warning: Fade in time (${value} ms) exceeds base " +
363+ " pass time (${basePassTimeMs} ms). Clouds may cross the entire view before fully " +
364+ " fading in." )
365+ }
366+ }
367+
297368 private fun animateCloud (cloud : ImageView ) {
298369 cloud.animation = null
299370
300- cloud.x = width.toFloat()
301- cloud.y = height * Random .nextFloat()
371+ val cloudHeight = cloud.layoutParams.width.toFloat()
372+
373+ cloud.x = width.toFloat() - paddingRight
374+ cloud.y = paddingTop + ((height - paddingBottom - paddingTop - cloudHeight) * Random .nextFloat())
302375
303376 val cloudWidth = cloud.layoutParams.width.toFloat()
304377
@@ -310,7 +383,7 @@ class CloudView : FrameLayout {
310383 doOnStart {
311384 if (isFadeInEnabled) {
312385 with (AlphaAnimation (0.0f , 1.0f )) {
313- this .duration = 1000
386+ this .duration = fadeInTimeMs.toLong()
314387 cloud.startAnimation(this )
315388 }
316389 }
@@ -352,5 +425,11 @@ class CloudView : FrameLayout {
352425
353426 companion object {
354427 private const val TAG = " GGG CloudView"
428+ const val DEFAULT_PASS_TIME_MS = 10000
429+ const val DEFAULT_PASS_TIME_VARIANCE_MS = 2000
430+ const val DEFAULT_MINIMUM_CLOUD_SIZE = 300
431+ const val DEFAULT_MAXIMUM_CLOUD_SIZE = 500
432+ const val DEFAULT_CLOUD_COUNT = 10
433+ const val DEFAULT_FADE_IN_TIME_MS = 1000
355434 }
356435}
0 commit comments