From 2b70d7c3c44a8a5eb2c087ab2108c293e122b2df Mon Sep 17 00:00:00 2001 From: Evgeny Shishkin Date: Sun, 25 Dec 2016 20:58:51 +0300 Subject: [PATCH] Support new attributes `image`, `angle`, `speed` --- .../src/main/res/drawable-hdpi/snowflake.png | Bin 0 -> 1212 bytes .../src/main/res/drawable-mdpi/snowflake.png | Bin 0 -> 716 bytes .../src/main/res/drawable-xhdpi/snowflake.png | Bin 0 -> 1821 bytes .../main/res/drawable-xxhdpi/snowflake.png | Bin 0 -> 3294 bytes .../main/res/drawable-xxxhdpi/snowflake.png | Bin 0 -> 4980 bytes .../src/main/res/layout/activity_snowfall.xml | 14 +-- .../com/jetradarmobile/snowfall/Drawables.kt | 42 +++++++++ .../com/jetradarmobile/snowfall/Randomizer.kt | 49 +++++++++++ .../jetradarmobile/snowfall/SnowfallView.kt | 62 +++++++------ .../com/jetradarmobile/snowfall/Snowflake.kt | 83 ++++++++++++------ snowfall/src/main/res/values/attrs.xml | 8 +- 11 files changed, 191 insertions(+), 67 deletions(-) create mode 100644 snowfall-sample/src/main/res/drawable-hdpi/snowflake.png create mode 100644 snowfall-sample/src/main/res/drawable-mdpi/snowflake.png create mode 100644 snowfall-sample/src/main/res/drawable-xhdpi/snowflake.png create mode 100644 snowfall-sample/src/main/res/drawable-xxhdpi/snowflake.png create mode 100644 snowfall-sample/src/main/res/drawable-xxxhdpi/snowflake.png create mode 100644 snowfall/src/main/java/com/jetradarmobile/snowfall/Drawables.kt create mode 100644 snowfall/src/main/java/com/jetradarmobile/snowfall/Randomizer.kt diff --git a/snowfall-sample/src/main/res/drawable-hdpi/snowflake.png b/snowfall-sample/src/main/res/drawable-hdpi/snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..7a9b05c6f413ce3b2dee88c54c9973ef28d761cc GIT binary patch literal 1212 zcmV;t1Vj6YP)4n zZZH3%-0%0>Rk_>kcE8%c+Pf@p6?on|HJ^2+KSA#Q25csud%ldOS8?sJ`CpxieBRf< z`2Ps?;Z$H-o+#7#*)vg_RD*ddIhw@)PVhZsYW%MXKVXKjd{)w z=FjB=`m{6&uJx0B1+-3QL+60lZ+71~38M4h*&zN{J}3fm12rXbHK{A0Em-`n!2sen zsVgAok8Y2+wcP5M3UF54VwU?XPe9|jBvTOIF8#P`Q*suN8m(DkLcc9_=D7fvRyo#} zP)|TBh`2$C8i4s>=})_FPaYKqk}Xdo&lH?9FfQf{>;|+3z<+wWGf>Z@h=PjH@0b3# zD*%vzO&GpsF%{?P>0yrol}|R9Wf#|%pS%NTe=dPnP$u;Au00{EX^rI(O@H6Khi~76 zK!W&XDFJA`*`N*}5lhZ6Zu8yJAb^>UX*r`!Xt~EEKsFVAjn(I~!272pa2f=_v`h4Tn1-gN5)RPp7=yMK7&xoQYRQELsdL4)CM#fG)7IT($_ zXl%{4miHtArUHxIH=Z%=*TVtqU;ha-Z&4G46_Bhj z3&OW#-?Pd~#QSK)vSRv(vlC1`Ir`D<`(Tfa<#!{~QDiCrq9_5=bE^?F5RI(UfT|Zv z6I!7Q$e76T9SAZtN%r_4SgN^3J*#H+NI=fjX*z0IQ}Jg|eUHDjH6e;ksD4lQu!HfF z1}@O$Za==|mOm$wpAB|{*lWokluP>PsNw`@v1m#OrVCZ?yc4}@sg^h`>8v2#M=4mp7A3@Aa8 z6mT=@FFinwU#_^CXwIJv)S&VK0kaNt#;)zhb8Jrk*`WfepkiP=VYf9waf)kLBeq6t zwZ!yI$(eC$`H-gxwK;4}6fhYDyB)xQoE00y+S<+odMifUA#VZAqMpHI2pE&00o4dl zy4*vm)kVbEBgZeq$MWLII@qm5g@{7ndODD^S-!X_NAtBA%sJ+W@8-wGbWVS~iJdSL zs0P^}p33cue%^nz9$=jX)cWaviI2_y a===@vtTwb8k936q00005>yM3@&{FQNw(!~NuJFf z9{$5Fm&-xg;c!Se;<27rFFl*^FyNTCxPbba@=;-&1ji{ipd-PSHbFaYrT>KdDI=+c$9CI!@8siZYa_78#rdg<{59Is)78sM+ z8~~zz#)-2t_tzJIKJ;$$i{U`+=G8@8acb^T+q${0000YK4HTh~EbDTP z?W`Gi`SK6Q>+9>wvoS9(FR#z`pY1(u{H*Z2_WB9YnlvqZk$_xr!BBEn9+YHlCkN#rW;VXR@73FG7gBCndOSbgj{^>Xst zX9EuWA4}O{%l-1H=k(DHoWY)f&3!{#^S`^T#ikErKm)x(yB3jp$oWtn5*VkiiKd+* zF!@=-8eELczaju^y4)|nFOA%%z$k#>0o_)~I2<&3@O92Q_1lUL{_c-mq+N0{(DLoZLi2VqNsi7a<#5)4x>0AMq z|NT+}|FiVpO9MFbi=7FG0}te{OYQe`mb>F}ramR{j({#hYGhF0f4uoWEQ^E_x7RJr z8<_q|*#a;U|9xBTXXGh$1Km!**O|U{Q}3SBBLV>rlv(+aary{YPo1FiSppOviM|2g z-~iknl?TIzaz!BUYz=`8a3}xMigg=3EBW>0*9$?QdOE<%v#&4ki9PY;xCw#thuyG} zr>zMKOegQu0Y)SOY&r7OWcf4#;y?ne7h$cb-u4N?RB!c>S?-&E5Evh?%_LA+!wqw4 zk+4QaFDf?$JOKniF#uC*z1lB_)lXb5OEw93{k$Fo-uCvN8lJW1V^HAvP9A}umqq}| z2rPGt;i=zzA8l)Oz|I;m7{nqJiEd%rj|gZ`qeulXz*)q)p>kJfpIBf!Rm0V(;j`Tc zpI8Szf^zlV;FsM^d>DeH!7MN|U=yg*ck*W|rl$Jk9s~j;=3tNlOnv~xfpbGi!~)H# zHRJ9H1zr;xcm(JK+C$!cF5s48nU_KRoz_W5`b=ei#(#c!MBQqbE zz_HV}_fWpxKHwPDC_n*p!kuyi$_Yq=r!}}6VDrzXT21pKFmdViP5`_Th4y+rKt?7% z;ByFnN^5|C@A(vulsL8STbOq4uav*#Od#fEoWY&fS zK`^qBD@U(1fB;j!IyB$D+^inqWB0pe6QK9TEnuyPO!oP5&->O0*z2b115CB@&T3E2 zj9cq9buHgy-as){a?F8nBAyX-LiYVCS@Zn^%^Cn}HEF#fp_krMop7hl>c^HFX}$P| zOaeA3_QakcfB+o<%{2kIdUgWNU*X*|@GP)q=siz5UtAsyu)XlHhkW&KYO*4RR>0TD zFybT-@UHpxk@7REhOPcg#y$X%m_W^#M9d3=y!MK*Tl!xuonQ$-7(^5UYaKYg8iTqK zgT(A15@|iBMnWE69-SoQw`Q%08a{g`5x5K977eoOG-?if!`+HFOYD(3l;1ic^=jRI zp!EneJLC4YOadj;(Avpcvq>MT)20S@11utH#ntk&{JG~DA~J77E}ewbZ0YH=-UM#n zLC>UQO~e3VQy;M$L=ccC2MGP@Gl2j@Nx)c7R30Lcs4k*xy4-u&1nxsSxlqI8@oUcZ z1Ol(dAi~;LL-`~iS#iC9XUlJYg^r&5cXANuYTka3c9D}L00vmBpHXk!+ASE^N4?y= zp1}B5paSg%+(u8T!$Ih(ZiV0EAW&e~3fWG^Kx%+f>sJa%z1ZsH$x$0nd&%sppI&MC z&8=s(hkZw2+(b9fAWg-9B2Fvd;!^`HSACX0MV?qEo{hjcf7WjQ9f2}#AGQWGKtMXB z$cXooYH!{EFahD$>kP`eJl4VJi?nrh%Y82o37k?j1dz=)Ai(qV8)!J-)v#m|D7Tym zxV@SG)?xhl5rK+XLjVG{LG1Y%HQ3(FE55>q6g&46uXgA3w2#W0^AUmaH9>$itdlpy zx?iN_9#`I}&RYJJh_~0AS$9>lW1l538`M~gkBp%#5i94)-$@sL$R!cr7vo=w)B8}? z-wow;`fBpoxgY4PT%eLipw97Q{pk5<_^mgfDKG(^z#=kZfnf8O`%pfUb5|UYyWrJg z+(O#{Zq15K5YxARUW?tyv(vsyye&RW;9Pt`=$vw_zZKL`_zCAPwIEd%M0>Sl00000 LNkvXXu0mjfuq%JE literal 0 HcmV?d00001 diff --git a/snowfall-sample/src/main/res/drawable-xxhdpi/snowflake.png b/snowfall-sample/src/main/res/drawable-xxhdpi/snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..3851e9c21c40d750acfb1b71acc8d37364aa1b77 GIT binary patch literal 3294 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 literal 0 HcmV?d00001 diff --git a/snowfall-sample/src/main/res/drawable-xxxhdpi/snowflake.png b/snowfall-sample/src/main/res/drawable-xxxhdpi/snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..6933d11ced797eab57db6a73726312279c7c67ce GIT binary patch literal 4980 zcmV-)6N~JLP)1Q{6N8U2HRr zJi`~->+ZeH-rVWRYvY+cuH_U<>y17AoBeJCL!N`qKEr?ej@QvYUqSa=)i-i}#VhF1 zCv)Yme&c`_TXmc>-P2xt^{Xeo#>#n%o*O+>aCzmdbyk1YE|wOX_T)|*rxjk_86Gb^ zhsn>NKJxP41D+RmllCJ$Y}m0caW0ib1vuYR2H ze0qv_1A+f#1gj0ouHNLyC-fXxb@Rh$FY7SGa z^3(X?<<33l!2EILPb|C3k&CCkSb8(xFRxs{*#N@R4JY3zvge>+NjR(_h`Ee-*XqV; zxb&>kE2m#OU5q`5+T~Vj5A?CVj-UNU+M^dRn_=f?r+aGob7`Q_$6tRKCAdpD@jhMDhR6Kham)t@-*p8S~; z-+&r2+ko*JYy)insW;lrgT;frRbIJ(n$?g)@mCG|*N+(6Fyc%;+e+|ASQ;MpNS70T z4w~I%xQPEj-XxBkUPbBR)k}xdFUiddSl)HB0UPJj25XT9#b-Bk`%SJG3Co^!+4RJez_ z&E#kE?ahw|t1g@5(v4m2;S+V* zi(f8X4Bme1hvZ!sP?NOg^EEU5@OZ`ORx!EkhQ!B7s>Po+zrnN1sjgwQ7oBgPB(6F* zvtE0ZD+l(}B^+MB@{cbYByE1CH6^Al?W@f0Rr4R4-IE9FR}oG*YBY2;s@a|!^kv{; zq#@9j!TtA#o|xB?x&)`%UMW^Nap-E#OOXes$8ht!;fSR0H*=d@(Dfv4(P!uH{J$aE=nv8eD*baVdD~d`a3v39=(7zZNvOo zbG74WKAU#PhRzO~nqA&zUIqy7aPTSNv^9rWJ-E6S$&NpH<(EUZ@W|{(EPoGP`?%)< z+T>atoDQL3i=Uluzlnu=6^Y4F%j=1-aq5B2-pq^b& zHQ^P5+mm09Y?xeel4j2y`N`z$*gY3eaz)b(i$90j;M2wO?b+&YLw^s{!V_m#nAU3Y0&0Hh z8foMFd5|{McsRQS%kEnM%NA3Y-#*zJbivqD=2O>j>1xn8zG)4zVe>7bms{U}UOUy6 z>l;FXs7@J=0{Nnawr)LfJ zo(E>*6<1qb1B4q)tG_v7^8)NGUoNgeXK&^u*1q+H)4pNx>5FLSU1FnkX#_?$$j#g( zJWjcMeNCTD)Hsp`Al6lBAQEk_s7CKYXi@N`Y{*{$jK#Kt}1Osv{zvFfGQzPf~o!@|`d z<##dghMV4r+ebO@cr#xOI4pcKcf0!S4?mUt0zQ5LDMZpgy}y0!+pG;W(9)<{>0UW7 zHjPWi>u~V$!qWD_Z*N?}Pa1}&lx_9I;>hpSpsrk;YD{ju^vc0u$Dh5ZSv?%N@#=Gf z_Fmd64aeP^qgRQTF3wl{)inpKW_7dE_9?cR`@Z~D8h*I?`vEsCrVib}!r%lBOD4`P zuexf(*FN*Bb5py~N2Cqz)g1)ChTCtg5l)L8NjXQcd$974>d)A;@2UGV=lAjYsVTa_ zH8k4t1Z2BBi|;kls|M!RPWb#4RE|33;8lk$X4OY~=GW&&xkz9A`4msAT(1q%6I^Y0 zoMLz|y;UnLu6`#5x9%3jZ%IX`wlSY>b(s7kJ!{xEf7)T!H2xHO8NQsOVZq6)DQDt4 z;l{_zzd^k(tcg|DrfM{N<@0MS?ux;!^^(@SYkR2qT~Z(E@7m`}T|-&L6GT=GYmm=q z!zT?n{S*D$B{qa~@mo6ns?YMAujSj5-MPyXYl&q~J#$yPI*0n`732V9)nRk^hT2oz z!NtQSS3I1Kf3(jQzDw5J{F&T76Faik!9C);jLAQ$e~1~-HR(W8uXYDK)7R?%J;WNE zUb53+-`3~RH{jZ4>yYEF_?aKy0m(e5r^o{_vFyp0mxT8Orf1%njeEBIDiZS#EcVpd z@OZP1{opjb{WtTgoL#`#04Kgyo3UxHJia)Ir%AGE2bh;K^KkP!PG>u@25oiqoxPt1hk% zDR&MXCVsTaFHen|a`p|lnRtURn>n)^Sn*l%><|-Y&u>8a@L4lL3^x#`L2 zBIEOCJX`K+yi^zetP2UaQ$b~Gq$TapEfs z_idY;&E0FmeSLBI2O7*Cbh(?k`}8Ue1J|+-f!E8l>$e!)aA`f`_nL_()`G3Lz2)64 zM;D+7p&N9AfVGK@+Kkp1emR@D$5-CulwVz%!14I=??droG{F~3vs*`4b>h%?N8IpY z`7?J*z3eyfY2$p6qYL;#8o(?#?b)26@JY>1f4xj~X1+L|CgQ7vrOVH!t?+bsde#lY zCHYg&IzL%2e*W$s1OD6jMP?H{ZLo%i?(0c<3fbbGSc~mjL6-}6>9klltqEL-dfM%=>Ue4K*34(GQqHPZ-l_l01t`{r8g1a& z`FI_KCM$>EMRYN0`-adx@#zh^VD$j2K8)nku&LSkvqrgF_|y6F>7(3gOpSY&oL#_d zW){*w!|2K8`zrR7%k_Hd32vDDf8SP(^3~w0+Xbk{rY&)ru&3l#BYzHf+UoLet+~e$ zx4tK@z3G;C_~c&f{Q{Q10}>h@q6QiUTa0dCapf6yE8e^SKkw{W9WlIWsF~lM8}#J@ z>@_ciuO>ONVOE2~J$i@3<5njvHWxg}PUGDrXWxLcWE-?e_VjH$2cr%wZU?~0#wk8E ze2Q4L;nih}@yS}_2*)9lTaC$&vy+#PHFpW$=iYMxHLGGxu7k+0DNlM&wWpl$`_+Ia z$Ld?aK6?7g-@;diJp1h18J6xJe1?o;t#&?b$-T8U%5!pd0X3{DyJ7L!L~eKoHZ1%a zZ|Duz06G{<4SYQQybL~>y|fR!I^uD%^W)Jl`Efi9^8%^|3x`d++*yC4>~a|MdXI3V z&o1DIyJBq!*Qg=V2G<{Dw*UQ)Ll7Efb>Pjs*l=}SL_D?FL{9szS}e)NA?caVo~wF$ z!ms+cbpe`cDA{_OSiN?Dc}TJRske}3o&vDWHUI8yu9V z3qPtqYgCJ_-e&H59BCLHnK`r0BlgcqFMd9H0X0uIBuyJu1A`mp9IO~#L*C5&?}Yu~ zz2|4G>fw@^&z?hP!{*xDU0@`gtrrX46JPIqxOn@d;jPVlO%5;M%Im11Xn<&nz^#T3 z$ALKn&7WWMnIk4gzSV_n=0)Pr+3iDH$+W}d)Ca8L!tIwYH=E9e&wcY@M{9#`%i#rF zNwZNh4^v}eU4nSc(>QeE(Ectsh@9$mF#eSGpsQ8Aa*EMM5&&)y!f+v8z<^8&8Cjx_Db zCG!B`4(kwoBWS;ZXsZEc_pfJpvoAH0{w3RR+LzA8W#1At%9;6Wy%e9X&G^Omg#VPx zgFaK>Y~yhHY|TTvgq}`)$jP6zda}!%es=lvnbeawTW-Uwb~p`Nb2ezl(&h8-mRB#J zjWlOqnlMa0sd+KBgBD{q_;iQxw;_qoFTc9^>wv0LjU%l_weefkEv`6TYDe?~ul#;H%f)jsfQhpRC8{PNfj z$wL=#rLqPZ02;fe-r)1u{VTZ~JckssNEgAzBk|J>L2TOjG;V$Hr&JdvKcDUb*!?16 z$4i&9g`SglUBKL3Q#I!Bnr*N%25-=8QeOOtvFBjql$Txp9C{r2tHh_{WvAs#UiMn+ zDgV_AsF`_SjW`U9Ozmmvp!SZ}Hz4jk7hvy}I@R6Gi!HwPCcX+@`KvuIea7(lWZFNG zJ3n8&fVQa-{yeb843mcUZimgo@Ow3F<}O`EwI@GYU6O_u+XFW~aHr&}H*005%ZZ=A z>-o3xQ{cdP*9FYdHANeu!}LV22WZIhr`-j?tH(Zj8gaS=ylL}SsW?i@xj@fM84`{~{H**>)W^JlHO&$RE`;ehqd3pgl$ImCLX z{P}8HFQIz4WF9#CC>D25y7NPwYeT=Y%;U80xPXSDn+`>j8RPeKiFX;{Gq=3HSvaup zboh~mU6XgzxK{r#)?f~UkANTlryr#>%sE7{iHo&=y8P)M4F+%GUF5{7b+0ZreU#Te zUt*8^@|nrs@eNS_>=5DfdI4ebk1nA2tbt2l;XCm0vF6O!HT$U6HU87Fp2(*QyeY8* zr#)%xp1APzo3{P)%giV3ZQU%dqq)bl?Kk~tpY^g{!0MMb@o5*oTR;11xp#x_dIin> z&jw)NGnPFs0=Dw19Y25SYq81u)9t^PSJ0n6h(D`=`Lum|58JEh-V2|7-Saem;(YgO y + app:snowflakeAngleMax="5" + app:snowflakeSizeMin="6dp" + app:snowflakeSizeMax="24dp" + app:snowflakeSpeedMin="4" + app:snowflakeSpeedMax="8" + app:snowflakeFadingEnabled="true" + app:snowflakeImage="@drawable/snowflake"/> \ No newline at end of file diff --git a/snowfall/src/main/java/com/jetradarmobile/snowfall/Drawables.kt b/snowfall/src/main/java/com/jetradarmobile/snowfall/Drawables.kt new file mode 100644 index 0000000..20ecbaf --- /dev/null +++ b/snowfall/src/main/java/com/jetradarmobile/snowfall/Drawables.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 JetRadar + * + * 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.jetradarmobile.snowfall + +import android.annotation.TargetApi +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.VectorDrawable +import android.os.Build + +internal fun Drawable.toBitmap(): Bitmap { + return when (this) { + is BitmapDrawable -> bitmap + is VectorDrawable -> toBitmap() + else -> throw IllegalArgumentException("Unsupported drawable type") + } +} + +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +internal fun VectorDrawable.toBitmap(): Bitmap { + val bitmap = Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + setBounds(0, 0, canvas.width, canvas.height) + draw(canvas) + return bitmap +} \ No newline at end of file diff --git a/snowfall/src/main/java/com/jetradarmobile/snowfall/Randomizer.kt b/snowfall/src/main/java/com/jetradarmobile/snowfall/Randomizer.kt new file mode 100644 index 0000000..c25ff79 --- /dev/null +++ b/snowfall/src/main/java/com/jetradarmobile/snowfall/Randomizer.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 JetRadar + * + * 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.jetradarmobile.snowfall + +import java.lang.Math.abs +import java.util.Random + +internal class Randomizer { + private val random by lazy { Random() } + + fun randomDouble(max: Int): Double { + return random.nextDouble() * (max + 1) + } + + fun randomInt(min: Int, max: Int, gaussian: Boolean = false): Int { + return randomInt(max - min, gaussian) + min + } + + fun randomInt(max: Int, gaussian: Boolean = false): Int { + if (gaussian) { + return (abs(randomGaussian()) * (max + 1)).toInt() + } else { + return random.nextInt(max + 1) + } + } + + fun randomGaussian(): Double { + val gaussian = random.nextGaussian() / 3 // more 99% of instances in range (-1, 1) + return if (gaussian > -1 && gaussian < 1) gaussian else randomGaussian() + } + + fun randomSignum(): Int { + return if (random.nextBoolean()) 1 else -1 + } +} \ No newline at end of file diff --git a/snowfall/src/main/java/com/jetradarmobile/snowfall/SnowfallView.kt b/snowfall/src/main/java/com/jetradarmobile/snowfall/SnowfallView.kt index e0338fa..700ecf0 100644 --- a/snowfall/src/main/java/com/jetradarmobile/snowfall/SnowfallView.kt +++ b/snowfall/src/main/java/com/jetradarmobile/snowfall/SnowfallView.kt @@ -17,87 +17,85 @@ package com.jetradarmobile.snowfall import android.content.Context +import android.graphics.Bitmap import android.graphics.Canvas -import android.graphics.Point import android.util.AttributeSet import android.view.View import java.util.ArrayList -import java.util.Random class SnowfallView(context: Context, attrs: AttributeSet) : View(context, attrs) { - private val DEFAULT_SNOWFLAKES_NUM = 150 + private val DEFAULT_SNOWFLAKES_NUM = 200 private val DEFAULT_SNOWFLAKE_ALPHA_MIN = 150 private val DEFAULT_SNOWFLAKE_ALPHA_MAX = 250 + private val DEFAULT_SNOWFLAKE_ANGLE_MAX = 10 private val DEFAULT_SNOWFLAKE_SIZE_MIN_IN_DP = 2 private val DEFAULT_SNOWFLAKE_SIZE_MAX_IN_DP = 8 + private val DEFAULT_SNOWFLAKE_SPEED_MIN = 2 + private val DEFAULT_SNOWFLAKE_SPEED_MAX = 8 private val DEFAULT_SNOWFLAKE_FADING_ENABLED = false private val snowflakesNum: Int + private val snowflakeImage: Bitmap? private val snowflakeAlphaMin: Int private val snowflakeAlphaMax: Int + private val snowflakeAngleMax: Int private val snowflakeSizeMinInPx: Int private val snowflakeSizeMaxInPx: Int + private val snowflakeSpeedMin: Int + private val snowflakeSpeedMax: Int private val snowflakeFadingEnabled: Boolean private val snowflakes: MutableList - private val updateThread by lazy { UpdateThread() } + private val updateSnowflakesThread: UpdateSnowflakesThread init { - updateThread.start() - val a = context.obtainStyledAttributes(attrs, R.styleable.SnowfallView) snowflakesNum = a.getInt(R.styleable.SnowfallView_snowflakesNum, DEFAULT_SNOWFLAKES_NUM) + snowflakeImage = a.getDrawable(R.styleable.SnowfallView_snowflakeImage)?.toBitmap() snowflakeAlphaMin = a.getInt(R.styleable.SnowfallView_snowflakeAlphaMin, DEFAULT_SNOWFLAKE_ALPHA_MIN) snowflakeAlphaMax = a.getInt(R.styleable.SnowfallView_snowflakeAlphaMax, DEFAULT_SNOWFLAKE_ALPHA_MAX) + snowflakeAngleMax = a.getInt(R.styleable.SnowfallView_snowflakeAngleMax, DEFAULT_SNOWFLAKE_ANGLE_MAX) snowflakeSizeMinInPx = a.getDimensionPixelSize(R.styleable.SnowfallView_snowflakeSizeMin, dpToPx(DEFAULT_SNOWFLAKE_SIZE_MIN_IN_DP)) snowflakeSizeMaxInPx = a.getDimensionPixelSize(R.styleable.SnowfallView_snowflakeSizeMax, dpToPx(DEFAULT_SNOWFLAKE_SIZE_MAX_IN_DP)) + snowflakeSpeedMin = a.getInt(R.styleable.SnowfallView_snowflakeSpeedMin, DEFAULT_SNOWFLAKE_SPEED_MIN) + snowflakeSpeedMax = a.getInt(R.styleable.SnowfallView_snowflakeSpeedMax, DEFAULT_SNOWFLAKE_SPEED_MAX) snowflakeFadingEnabled = a.getBoolean(R.styleable.SnowfallView_snowflakeFadingEnabled, DEFAULT_SNOWFLAKE_FADING_ENABLED) a.recycle() snowflakes = ArrayList(snowflakesNum) - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - if (w != oldw || h != oldh) { - snowflakes.clear() - snowflakes.addAll(Array(snowflakesNum, { createSnowflake() })) - } - } - private fun createSnowflake(): Snowflake { - val position = Point(random(width), random(height)) - val size = random(snowflakeSizeMinInPx, snowflakeSizeMaxInPx) - val speed = size / 3 - val angle = 0F - val alpha = random(snowflakeAlphaMin, snowflakeAlphaMax) - return Snowflake(position = position, size = size, speed = speed, angle = angle, alpha = alpha, fadingEnabled = snowflakeFadingEnabled) - } - - private fun random(min: Int, max: Int): Int { - return random(max - min + 1) + min - } - - private fun random(max: Int): Int { - return Random().nextInt(max) + updateSnowflakesThread = UpdateSnowflakesThread() + updateSnowflakesThread.start() } private fun dpToPx(dp: Int): Int { return (dp * resources.displayMetrics.density).toInt() } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + snowflakes.clear() + val snowflakeParams = Snowflake.Params( + parentWidth = width, parentHeight = height, image = snowflakeImage, + alphaMin = snowflakeAlphaMin, alphaMax = snowflakeAlphaMax, angleMax = snowflakeAngleMax, + sizeMinInPx = snowflakeSizeMinInPx, sizeMaxInPx = snowflakeSizeMaxInPx, + speedMin = snowflakeSpeedMin, speedMax = snowflakeSpeedMax, + fadingEnabled = snowflakeFadingEnabled) + snowflakes.addAll(Array(snowflakesNum, { Snowflake(snowflakeParams) })) + } + override fun onDraw(canvas: Canvas) { super.onDraw(canvas) snowflakes.forEach { it.draw(canvas) } } - private inner class UpdateThread : Thread() { + private inner class UpdateSnowflakesThread : Thread() { private val FPS = 10L override fun run() { while (true) { try { Thread.sleep(FPS) } catch (ignored: InterruptedException) {} - snowflakes.forEach { it.update(width, height) } + snowflakes.forEach { it.update() } postInvalidateOnAnimation() } } diff --git a/snowfall/src/main/java/com/jetradarmobile/snowfall/Snowflake.kt b/snowfall/src/main/java/com/jetradarmobile/snowfall/Snowflake.kt index f6e7e74..9f8177f 100644 --- a/snowfall/src/main/java/com/jetradarmobile/snowfall/Snowflake.kt +++ b/snowfall/src/main/java/com/jetradarmobile/snowfall/Snowflake.kt @@ -16,58 +16,85 @@ package com.jetradarmobile.snowfall +import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Paint.Style -import android.graphics.Point -import java.util.Random +import java.lang.Math.cos +import java.lang.Math.sin +import java.lang.Math.toRadians + +internal class Snowflake(val params: Params) { + private var size: Int = 0 + private var alpha: Int = 255 + private var bitmap: Bitmap? = null + private var speedX: Double = 0.0 + private var speedY: Double = 0.0 + private var positionX: Double = 0.0 + private var positionY: Double = 0.0 -internal class Snowflake(val position: Point, val size: Int, val speed: Int, val angle: Float, val alpha: Int, val fadingEnabled: Boolean) { - private var speedX: Int = 0 - private var speedY: Int = 0 private val paint by lazy { Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.rgb(255, 255, 255) - alpha = this@Snowflake.alpha style = Style.FILL } } + private val randomizer by lazy { Randomizer() } init { - calculateSpeed() + init() } - fun update(width: Int, height: Int) { - var x = position.x + speedX - var y = position.y + speedY - if (y > height) { - calculateSpeed() - x = Random().nextInt(width) - y = -size - 1 + private fun init() { + size = randomizer.randomInt(params.sizeMinInPx, params.sizeMaxInPx, gaussian = true) + if (params.image != null) { + bitmap = Bitmap.createScaledBitmap(params.image, size, size, false) } - if (fadingEnabled) { - paint.alpha = (alpha * ((height.toFloat() - y.toFloat()) / (height.toFloat()))).toInt() - } + val speed = ((size - params.sizeMinInPx).toFloat() / (params.sizeMaxInPx - params.sizeMinInPx) * + (params.speedMax - params.speedMin) + params.speedMin) + val angle = toRadians(randomizer.randomDouble(params.angleMax) * randomizer.randomSignum()) + speedX = speed * sin(angle) + speedY = speed * cos(angle) + + alpha = randomizer.randomInt(params.alphaMin, params.alphaMax) + paint.alpha = alpha - position.set(x, y) + positionX = randomizer.randomDouble(params.parentWidth) + positionY = randomizer.randomDouble(params.parentHeight) } - fun calculateSpeed() { - speedX = (speed * Math.sin(angle())).toInt() - speedY = (speed * Math.cos(angle())).toInt() - if (speedY == 0) { - speedY++ + fun update() { + positionX += speedX + positionY += speedY + if (positionY > params.parentHeight) { + init() + positionY = -size.toDouble() + } + if (params.fadingEnabled) { + paint.alpha = (alpha * ((params.parentHeight - positionY).toFloat() / params.parentHeight)).toInt() } } fun draw(canvas: Canvas) { - canvas.drawCircle(position.x.toFloat(), position.y.toFloat(), size.toFloat(), paint) + if (bitmap != null) { + canvas.drawBitmap(bitmap, positionX.toFloat(), positionY.toFloat(), paint) + } else { + canvas.drawCircle(positionX.toFloat(), positionY.toFloat(), size.toFloat(), paint) + } } - private fun angle(): Double { - val angle = Random().nextInt(30) - 15 - return Math.toRadians(angle.toDouble()) - } + data class Params( + val parentWidth: Int, + val parentHeight: Int, + val image: Bitmap?, + val alphaMin: Int, + val alphaMax: Int, + val angleMax: Int, + val sizeMinInPx: Int, + val sizeMaxInPx: Int, + val speedMin: Int, + val speedMax: Int, + val fadingEnabled: Boolean) } \ No newline at end of file diff --git a/snowfall/src/main/res/values/attrs.xml b/snowfall/src/main/res/values/attrs.xml index 53fd64f..d8ef1f5 100644 --- a/snowfall/src/main/res/values/attrs.xml +++ b/snowfall/src/main/res/values/attrs.xml @@ -19,10 +19,14 @@ - - + + + + + +