From 907f6fd44500c8060f217dfa85648aee87f168f1 Mon Sep 17 00:00:00 2001
From: drake
-
+
rY-Y9lTE zqP|8OB`f3{5rTc@GAmczpC}#<+7kvN3&6RnRJII4oruYr?*SYRlYY2ju8Z&G@QNO{ z7w;7qk_@aCJEKKb2EcM3?>68*yLe2i4|l~pl5Cr6=hwIIf+i)+`bC!dmkgXgVT`42$i1M~{$xAc*%Y&xYkGXKvr&nE zj{RK;lgF1Clj3^CH>W?@hP4(#d6jnA_U0-{18x}8W;^9ntHBskRIFH?J$N{q>#1om zl(w>Rd*&C~jSX2foV=kfB(bzUYu=oXoUX1|X9?4TN{K>1-^}R1fUHj6q*-o&C~Muh zL#IbMeqpp|7gD-1@gcjVp?_X8e$!|zJ3}O`P38TJRv%!Yqm{dG_dO%evha!2PPZOh z<<{BMFoxuG%JagyyH9YnwzqE`HVAWA(@)X?9wMJzUT$fBLK|tJ|L1a@Hi}jw9h4 zg$^v?*G0l>5d=H&Z*Z>EgCZH7f GA83f(+(CB8^5%@!h!Jm!5ef?QvJ0*xKRW=5M&}299Bodd zZcPy*r{RfCsoR5p4%pEezZ+V=pwTp=(R}|IEf9)+^HRK+^BI-3RJ>*co57`z`kti- zmDdI!(BTLfzlr`^tAK?qnSs2f#m#;)Y@jr{i#4Ejy7x>fhc lyRv{P1=Sl zM@X6<7zX<~GJE$~PPc1wLrh%hP?6bifZoPxr&?Vb@B8g>DCs5-?MA`va0(vwlQV{h zpXF)`3Xx`rj8v*n%S8nyYF8NzVP+j%?YZSpUm>j42C=?!nXsT~56wK*ZhR37)Uq7Y z!@&u0Fzs;C8o>acEb4e2BK`K%8fw~?JrakMSULR* IkKDt#XgH5N HT2kuuO1}?%QQbbkba9&z&HM7DbVRA@25{ zwzeNwI%Ud{TzPA;K~~_Z?PzQ;(I@dD&*JSHrIpm`?`Uz&aG{K_)LG5PpRk>F=9lZK zZY1{hC(c2k;<>toPI#O{ND$fKWU9D8>}OYv8U};aR?K^ax58=H%^{b#u9w;^&fDJ6 z (B2x6ZI1EF&(iToO+(VUU5amA1qr)iZ^6PV6VH-zD)7Rx`)(B| z=aH1d#p*pKGq@FD&4w#2!cXK0S`TiouB)1(9};d`5@?pIy4~Lo2dy8x4s~|zv$M0O z3M?r&tf!UQMs79-0AsqHNWR3ek3YKgE|4vK;d+uL{P5+SzFw=)(pYZaJ=WQ6+hp;) zL1Ct7pw|vVlVrCMv}hsp%T~G-w!gviqL#7nUVpKAJI_nzt~Vw^-QI0DXo!WRN|m_l zR9S7kMXUim2+mc}%k$v~4=!tiq0B#3-bVM;C;SgWt|w7XDs!YQ(MsQnZiJI$W##b~ zo5AI1T{N7M_Vx?`Ya9k$7TqL1#}lX_g$NY#no|Rf&Jsl+xZENf%=m&ecsRzmHhLsv z@U>!N@%~@fu ) z!j;i+&8)ZZf{$Hj1f|HT;MNSogMH@3b-Sbk8L%WLvQK7t?5cadxy32LV 2^+#dOEx~5%zhs0n|XLPf(WWGdQ)x=QuXmEUqyADcUR+vr_qpUf K26IJq5U&0!945QR!axz1|TDEY$jD7LX0z-$*S^IKOnVnC91MQV0f z{_L@g*wWxFIFAVi+hx*~(Z&vr%PJ=k*(}PxLHFxsTD|IMtEEfRSyKF0gRno%f)q*m z^s4PxL(!ke-xM!bTu;|xKhw{Hju&b@&o$aW1T))Z_9J;}W?z`PF&6e16A%(%v#`i* ze-hG0hU;CZmnB2LA5(^Z!w3hsTIC2-sC~&qi}^-?qu*C2Fon7R&36>RHQX1d@rm{( zauqn;P9I#-4q|VzapB%W1FDr~W)ko=T84w$MI$d5zYPq@^_KUj`F2jqoSc+n4nFI; zo(zGht($RN?6?hUN{ioQGHKae4 9<`1r@@%57y&mRrF`q3PK zl1xaqYXfV1Zo-A0O%ioEudsm{+n5z~AlWkZRKKU{Q4jy0IbWCFf1Z5Cmwn+7_pqXB z0~M _ljrm-68oE;onS zcb~7Aq4Zbeo%f3LCQ!EU+x98F{W;Txq|aNib;s50RTo6IWTU~zi^FUf3?{7bfz;rV zYv&{N#l<-6*qR=J>s^9Uctn)w{ZL0dPv{pIg?JzIQE0 ~dI;h{<0#7T8s!7dpVHs9wiHO|Yp9QFH1ZtW#B5ur&0w$80n>fEQ^_7-& zcqE1#EZ(7B8rlTJW(k%=C2BCyG>%4aPI=^;WC)9?R7wPegp=qU(GS9Ge3HcCki5WC zv5_-)FzslP$mrxL_Od*2hJx-^(>P3K;;cYU)u;huR&?TZ3jGHAE&EWwgnOSkos%p= zHy2}OIE(r>$dG6ihi~jV#L 8*06ELn`mRVDcs?${=BsLOqxwS`>mDM3fFCZIinm=TvNJuqKqGAT2W2_d$ zkY@v>V@y$s!5dggtk4(79P|xurtk_x{8jE=;UzFK(>0=Jce^C+OS_}mG(wRZ4DIXu z9XKeAtwvs+5Qn0pVm4e)b}Ywq8?nyUeJ8!{hoN&tf7}w@(qCcAGS+nYY<@P2eIjt2 zqQV9K;l&ky%$UA=Z(aX&@la!y>9f?u`rQm#dGBEzR;x!zhev*EGioWb$Z)B#fZO_~ zq0V#VsI2DtU5e?#1)b6idx~gR+)SwF$}&D%Dp(3XjXuj>DHL5g_`x_J8T+feM_r%_ zac?4$XNEH0PRhg88N(|{VDtDn)^mS@5c(_qLzH*3k|BF49n2sP zlM5_W1cLAc4jb==#7Z&(`^}O{vlm~{FvY81yzdawBk($<=4kEPk<*`q07YVyazKwr ztE2oae*upmu23Zk4p1nv5xYe!G$N9-Hdc*CT_Arp^gEbi_C2NRrwx|#Nr@4qiz_9< z3l42-evU&x5bH)mLI;Mdt6VWtEOS+9mQpRhPho^(Qe&w9?Isx2tR4Wjgh>$6>w{Nq zC(U7Z716;gW_&!}Mh}P$aN7X%VOR!)cQH4 zAAO3SLB9=#&+UBX0%|OUlRbqw4kexBU!P5|e)~kH+deXD2eaQ3 >Tq|YKtwzKVJ4k%WR1R)`}9=V?aIE@vo&(ji|;vBW2nFASnEP! z177GDDL1Pv|33ev%}l!Qi{tMPY7Ep8;ubS)uxCP(&H?O=6At<@(1v;w!Vq@3#J`ha zTrp7 9sLrx^2wOUFd=RtR^R pEGx6MP1)FDgH`dZ!PB*UG%#FcAGyNS|2n?r;F}; z7bOxXhAtFBFSib iw7o9#W9dnDIsC6Ja7Pu^#hl5CGT-%E zRFb}MjKU<(xztXLa0KI!D~o^ZzK_l-*y8?H{wskM3 3AI9!)P7G|;c zmFdr+$d0R|iFSwUoi_;Vp#&|(^mdu`fl5+oX;Ex$P+bezl!9FJKrB$k_~BfYhW6*) z+o(1~`?LPRPtRh;t|({A qrL&+?2R3vuwj{oyA=XAS1d%$}V$8 zQ9_FCE~6pNZq{ _T^F&-V`;x$h|_)xUM)K IfrF=zetI_)?i!VRigLZ&K>~UF<$zSEq|NPP6 zd%f@#Hzfeg5sr0^hSa{&On$1-Sb%#30)knqUK9#SqiS{pdZXSFSKO1+Mw0b%`Ieq) zJpr-1o$)$P67vvEAiGc1{eI+_RB^+=L?$wo#%pMt^UL4{mb1qc)3D|CWf$tFh2ymO zM}ziS|KAt+XN_cbnd5lV9%8CvpZWmzWYDc=)A`YCy)IDDt?TWOl?!&WaQ4-ehSz3t zB5$24ot^JZXMX1XDo=fB3{;mQIaQpJ$Z`DYzAwGtt{I6E2=5ZO@0G30H_53zcK>*Z z+^%77QKX+BT~v^WkPnjc7Yf!&d+tW(!20GiySVLNH(f3tL@^R|k*>^|=CLKl$w*-k z<=W{_aNc(R&aUNhJ*s_Sm|msTU^>*Jf}cr?8Y0Maj;zz)O#CIHhUzGHDJw7|mF@^p z`-m8;t(t2M=4QJOr@s!!3{$) lmMZK z%T)@7EDpsAd_tH3!P7LeQoLcl-si?j^YAXu)z>6an!;DE>Zqdy52AUnkK-RrI2 yAr4rrLw%GP%2y<_1!qB$p(v z=8~Fam~ED#JL=grZcAd4#z}%@wo`sFsYONhw{v&2kwx_Ho^Uc00Edtcr)tf=6qn_- zTkMv #*ijLZ @ T$kJ(*qgmwBj=>VpepJFkf+-NmkT!@}dF zzQO~$?kyLL!h2iEU#6 r0Xsx<^X>zA!}k{eK-(_?vNGkCO5OCT7heJ5jO1 zvI1HY N#Y*{`<9OmQFMu3IXyNXi^{*6i5~lQaP@)jlLB5Cmi(K;YFner(51o;W zRr|kt0Xn_sNTRMn^PAOpdKzs)q6Ujv?e @4LN!{*&LP18(_d@6|tl~?Y6Gb(jc<$Yly7lV)t5HE~ z#|IC7ST(`?e#24>2^LAZp&tj8JF6PE3YZ=eishncD=Q-JX mRqr!x)I$!HA&Qpo5KcutwCED`>Jgjm>TUFrj6GI2_1q*x`V=_9W#WY{KRppoqal zA;CqssVw-dbG&zSY2=m8F=6C%t3glq0<%~M8UO@IRD62HNv#L%X7V1yV zUDXa!ofweiG0)ZS@+UFg%&VTqN@wq%Thr%vXQX`q>j4tU`O|Fz!9*D>h*(?_ndMLe z-q#Air6&?zCY4H$lKdob)17l_^07H!`dh;O z1z3HpGkVcZwjG^-nI>=!i4)h>T0~8vNhQ{Hq{3_i61fy
?jumZ$Z&dDwu z&{kKol`FnV?$9#Wuq*e#v *V>gqJSPd?^AC}6Z8>bNTxIj;R3n4OEn|| zbz7w+O5PrWE~d8OP&8gezMXVd8MJ>$XEQ@H=*(`v?>p_t=&RgyIl=_0X)xK&nn;cu zC-{=l8~8f!8D{BrY45W*s=deu`73|&W;zTd#3NECg=J>bf$wp;X_OJ5640IXR4cJ2 zDEgBs`$z|!Cdg?hqh9)lM#wFhtShM?DhQ`~K7Q&^i1sy&k}!PfmwAN{%8oYwLEE11 z#V5foAY|+;?VwY1j`GcS%1Fy##a+PVJ~R0OYj~Mxv=zMF*LtyS^Q{5ZKP1$)!wCg< zK?i7Z5e^Au3)mr{8q~WU=9p9H$&LvoxB?S9w9_=48T{R4yZ{*+fNsaP9YRd|O*$~w zST&|p)cs^R;`9e2fZ6=+yB3FA2wd134G3PRDRn%6>YGVlYMePU@y!}F07va$P1;)V zz*q{EeI!K$)!U{ZvYC)3Ct0-fH+@0q!OZ9v;Z4Oxflr6H3<7FpD9Di!{Ts3i{>`Y) zP1Qc~- D{1H(xdtJ?8+cvz{J3)`nUbOG`IGsuRF>^C>W-W4J|EV=XJ&Lm3fq( zb|g_YBdM|_R=Nj4CntHFsWqXgk#KNCsw>9%LM&c88|10At292OgIDZ7X yQvQlj_^E^xM#5CoFULitl%RAz`T> z@J8gj=YBP%3zC-Or?Ex~UW&^spTP0Zcbw4k%#Kl>bcjfc`e{Wr;#zpjj)NUMx^ia) zr(TiBA4SX3u@#!yNu=ZA>v@QOb?9DGelMM4BlRk9J16n@+&H~xKd*(|C*SXBUAu{I zUX8t=tf%H!!v6E-HLb{e`5dyHR@G$WxX7M6lzI$eS20!hd#?2!4$wKp<1t_f{N1^- zb8G?b$#Us)Yam(YmKYvb2{y*Ta3`UuwceC$O0HDl$jaMimbmeG!Y*TeU?p-aF{~mM zRLN08Fc z0b+pM23P7+#pnS0dx85kOu|0M*N~|Z+0kQ^xU $P zce$JQoV@4H_xr25RhJ)AHC21{>eaiu*IGSKKge>Y_5nz_Z|u&`E8cB4Gw8i;xd0i= zw+1fX6ctXuj-G7T*{r2@tD?xTfqPz@J&&EpFVk)eOqX>=)zci28s{_5c+uH+MeTrV zW`ZY5P3>@eWM!@IE)gu{62|TqwRFy!CW@h5ZA=ITy1gkh%cj3Mg<@$h#tD-Ad8-39 z6S2sLi)RfWM6EMhepHfRNTI(5n}#mr3MhKP7_Oweh~{=7kfq IN$e(p|EKa#AuSCY`CHk8hd7Gp)w$7Tc%FmSm+BfIS~nBk^u9>34o;*rz>X z+T`x))XYi_n?ch{0LjK(P6K&sj&ZuQ26oF%^Z?BzzD0dGn#Kt-#fcl)7Ny5JbjR`x z!Pu^|SOIID(C`lOIAOp=Q~9K(HT$KK820p<%3BP>Rf~@wV-L;&5U9b|v|k}XFcNdC zL=<8h17fm 2kuL}GzMmtzrNJSEK&=#^w)3UkmXL9LQ~IT9f8Y@MbOYo zd0QHVr{`^ONlWWh(vo8FG)4~K3~{M0IvZ@%0?1)Aa%{RxhpF`1no;>k ;P6=<<54=1sHAJca}?6CvIt6Wo^D?}g-P2G!9=s7 zTx(as`_G0tuaD!%p$y$8g<4Pf+}H*yR?)d_Rfxb-bx37-asA+W6rgYk)Abi^Fa>HS zB>a44UMMS>LBLfSL3RdUzfNZ#J8)3TZ77Az5 DvK#$koHwph{C=04RW6N5SQ^4g9E&8DCP(itJoau*kxQ z^4Mll1%~B?WTAa9IVNn_gBS`$G1<@FEnF_VYwvbQ^$lR1^cMe?wxk`~@pST%2Xp}Z zQ2F%h^h{Tl!l#q(?-=w8YWQEjO9?D>=uAhQSB4kWzul&e<}7*1bQahAdOza^jfih+ zUel<}416x88}-`hwdl##6b{WVrS`7u?D{>WW~;0m^yn`3$usZVaFLhf`%ivSRH6Ik z>FE)3d19OP>xTzLQG)U!>dWIF3MP;0va+8VGw7%UWW1uOl*v)f0*V*^K+D!>RGfZ` z;f%+T6T;3Ds(yXvKmtAB>j=# zXgzV*0#OwKmJ9{eJ*=wl(XU* zSpFZsk@Z8TKw6q3<(vP(?W_Q3-Q-mGvD^PJcxg0%c54OcXutnuyP9~w^uc(Sh4cWN zTWRFyj0|u%iQY+zor{>87%- zaBUhmIL^Twa8j;uP~}idcq|`a2c)istmElY-g;0CDt P cHJ0i64h(*HVWBu$oLQD5g>ZuXJow}C*B3s;j{i3+iO$= zu} {=(+9s2bk#CJHY)> z*ExWIE!7cHuCZS@$V%q7Nkj#OwQ7LW4kC79wHtmU@y9$qGI1iJ0xI$I*S=6-U!*Z` zWj)9t&W`iPQ*+OgI-(td&`s*Uh%}g-uV#Hh#fJKFm$+1y^CgMraL8dlxCS2t+vjvE zwGF{D+O7KXKo3)II 4qrpnoG{5GO98qD2sUKJ9PuP2>|yKz0)ot@WuSx%nF zY{J53IJgds7Xa%`P&vhPsKacd6H$i8iInk&c-3VV?Pi98a5B2`OJe*0>Hg{O>*cB_ z4R7#dlYt1 gmeg=;yNu&l(abFxRqv^PAMRp-o3c{$O7~VjJxXIr_+Nn$H)qu4w zy?9t_Dh3}P-_BGsVL2NcItGSB%YCi5{Q*5jEn#a3Q0i%2r)Pr#^kZgdg1;@BpXr6f zE_qz=IqS^-q?QO{ZcCzfRbol!Q4HT)S}oZSwl^>Bk~tJ7`kl&Det$kha6-b(GUjsl z1@KE+Odtoaa4RP;ClaCM^_=a91{77V>Ez{0vYpwxI^nzcT#+KxNj~pRV0@a{d=pv7 zo}WGVUN}9PtC|d1{|kfu@F>au2X)ATz5i9_Ko PPT(H|*t7+RL4gqW`(EOmVIA(Z| zS4VXe(#64P#z2v?lM=EPJ>!U4#j}p+xoPE_S+kOw7AC$U(%1V2U{mYqP$(3#8T`bB zjy^{Fn8*O2PZF5^bN-}rWxCqsv5>Iv&qVvO!29aSmw8to53dz|=m5#F`i}x+e}pn( zYaFNcoI;44h)oYBE6>wgAXZnP7989{Kc6$i-m|b|{X$@j%V$`9#`}Um{n|vrPz!C< z>RQQCdRaMUghV*CP$p(3lS!}Q!=TjtCxFQS-%ix>9+&3pSe=G6{=n{6awGGPw7isV zjN-W)c%B6{tU;t?ZAq*WhRznTly5oTo3*Y$))87PQ%9Jtls>iHGo2>+CRG~Zx$R}q zKcyTUU(vMj-LDwq_a9)85?o3&^Pc~t1=^{kb4v&uKS-3_M08rF&iKK-eq?Tg>~{u` zBCLSci8mEJ0{($s_x%*%f^Wp~OJDg_?C;aWu-h0ofX5^H59m&+ e>#z)vphlUakWIlhEqcHZ}ovayZ zqmx|t>h?O@GXGf)9PS^u+)mdgl#A*2>kEK=|IYsqI^rxuo?z%{7naX)AdRXsi0wi) zy+ +egMx){d0P?bhUe--9SB+I(UQP{? zuI8n{g8MiVl&b9Ug_ClsG`E;f%9YMH_pb;vAV1_1_J49oT5$rCq3r!!4g?xK^mr5( zsIj^}h8yl(6>iM22v?cKYnIlu@Oth&zVYDtIGMrdRn6Fuf+BPu$>X6PygTn)q>HFB zC9TwugY52^VWvAMn-Gu6(aS`wsabR5rh}rI+f$g|p<_fze!=ZI@aQqlwr2!dU38WY zQifmx$^hwA-rS@LRZva4%b5-~?EaE;Uo@~|y6@>J(^d9|ckG9AWkLFX%zDAqiux%- z`EP80V*78C@tPtl-JV9Ztr{`O8k$+&(#ftRisO4wo?r@A*orN5Jpx!wq&nJJW4b35 z8ypJ(#BGA5N`gc7v7@T0>V&HiS-FrP`xes%h@9t$7n`v)2m8zWpuoj}60+VsegXck zAAhMW-EYQSeYMPNT-ujc9$Z?At7e+LI}s-4)7MpX?~f-CAw93Tl3(0?ZNqqfe1M`f zBh28(TPVBaPkKp8z3u~Qi<+P9`sOv{fcZsrre!44qSI%a;3A9j(aBP)srka0j{{cQ zyeN}CU#(3sgA3D3Yi;JI=1v(6aam9Evip|{%_BQm_$aj6f$o=PD&TY@J`9u}!WB!B zUP&Dp2Zx9MTp}o`G#b~d4ib&V5YbW0-JKF3P|H!L(dXctik)%U!CF~0t?=;k_j$jb zu8LfrDed20HG(!}_N;yB%N*s L$p6|T5F_Y~SV(bFd zRb4dfwK GFr$aPj{ofgF#9TiqnD~M9(hxH!3k&Q z)7h}tOCmcQ;>tdHd zT+A6zq2T&W5=d6CQdf RjA4Q~b;{nhFVC575cxBEE zJ)iM2#JHI(!LE6^S;2v7q2BmIIHack_F$7Qpd^A7=0un@jwsfNqb`#oGVe=(>5WFH zB`0*#ni#1Nd19oN2c?BI@so`^IWb3s6Gj5aqgl6FezFUm6Rt*U>jIa0QP2S*rDe%G zR)wz3b$wJZ!W)OEvv_%ghK5Izn1G0w`QkSM@)h~=jYl!<$GV+~hz5+LWKS|xT3?AK zSTGbIFbuKv%G(9=0%@c)4!F)>43;SO;#0@_QCA7`jF$~Q(rx<6_{MOUKpj 2u1O}I3{6Ua4f%MTaI-2bbXQ({=u=mAR!#a=l+ntuMGzm5s(XN7nm8hy z?%aMcH`g|CTdYJ7>hPjM1XWv2b^z68d`#x&3q|(*EY=Iml(;STtxXrruh4J;&oy%p zC5!v&6xPQ|)0S7Gzdi=S=9FJf()Z3ZxW3$NI^%3mDu3wEx_IDdLS29DIzXN*V8A^S zC#!__uC2}O KNv52%O~R3Ubd+J91r%%{Vt*!0kbryK8(2 z0Z+c@e W@27cqC_dDga$?hb~>4ho#N9!ZucZ2zl1^L3D{1xRr{J_?My$_H_R{z1LlZ!_O zXaWNYg9Oc)U8i;~v;7gynS<%3tbT*ouYn5Uh-pV}(Vqfa9fEyRsPKhTfc;W7tF04; zsN)ndmu@e*L2v}IAi`RSYz@~l@I+AL8N9c_GQ^(q$ew%ZmmzR~m7AWdGW~Ugs%H5s zmtM)>1ZCHZ1}lSzX+4dNmA5n=7I-h>LFq3O&avjm)(0d? AN7l3tr0}>QUz8o_Iv1=|xv-wCp8Ub~zI)O7=^`DCnz*`wg{{TeM*Be3 z`khY82V&IV*LG9EvW#N fi>GLm;-kVw`Vs%~|ul7V-a?vdUJGrVt zvcRxz9shU-poGvvSwTFGIx2L&!AyUqIseu(t*AeZ>7v`fKb^iCs@Ryz({2A;l {mYR-`}3sK9?C!1bl*#nKG=D@*my{x0iW>+cH3dK#pRlM6zg; z;Iq{uu?E9AY6#Wj;qgncTdpJ%+5Irb*7wg*0w+R-eU^LFLmG;JjU1pW>gcEowA+lW ze)85PS~c46XlT@I&_C0%zuBOObrvb%7qu(tX7anP(})DhZV}2Nj-owr6$rLm6YQ|v zG2P>aNSr1o3g_b%X4c!Of@27ptBo&c>8IP>5?yMIW#NmKxpQU>hM-0Ns}RRyBKEEV z8^II34piW{C0qY+Au`` yKFK I4sP!K z3&BxO=%$*>?EIuc$+uw1 {3aiCtw_jGmf_8VKy?^Yn8j=H^)`HsxgJ*-J8zqyA+D(0uo zGR^iUou&upPKB1M?_x8}gty%*RQ0Wdt;*z{YV^lgLTgf6Yb^fgHS*q-Ty_zOLY3xb zkCY-|>#&b>C+#tOBCmP~*g0EgozD7AMa5Yg#T&FUwtrWgCi8-^O6hb4GjB%tFFW$# z1pC18mxK9kH$EuvdCb^2?2QH{sn7>r@2ixIt$HFP6mmAPg)mjLTFXgYX{=)#CzT0B z#d|jgKg=DS*&VM!kL69%Ub4CsUEva!X)I5hG%fDIA %wbw4`?cq50e$_KEl@|9q*rMx@g^@sTi}pdHBJ}F$4++U3)Ldm`BK*_`Cs5v z7OYS|nPX}Aa??q0-C(Ogid7VWX@GK_E&?IM3W8tuq-T)nTtOOD1Zs{Vyw?Z=jZT*F zsx40itJosEawU#M+@;oh&U7Npu$pbZaTm{x+<0${to;-;l{YLpa=c0!=G+h!_B%T` z*2*iik7tabN|{qQ4!4(*$S7YMtuoxkO}RBVGdIYx+KV&ofiG}dw>`St+ps;}zCq4n zYj>+}u{-`m;i~{nZ|M(q=}U0vlL@wsB-gpBC1z2y1mm5xl#M^PJ^s#Jx^*2k@OgZX zGmXy*ftB9nda`R(*o7#{NHiCozDkm9qH*jDBTAC8Yi2U(jZZ8sWw98qtWFmqX&E*) zJbSFriB5%G`N)^uu(8>0geH1z&
Rn{fEO0TB<$% zDy=>8Y!b^eA$A~U(#SKY|K;T&(PmboR3znKCNFCiHoJPDJ_}T@JdsjDH0g0r_29_p zu8#9GAuA~%54TRAsJdm!rYh@K-~F3BC*y$>vu(jM 1p-8@(%>pDHx15nM1Oma)93@n-n$Q1wI> zEB4Niq6KueitJ8jBdt>HTp *7tID@teIDVuZqpq7O(_olH^gJ7kt z$qF^~^xYJW_VFf1PxpmJxlJrBR;;}IWz``r5z(D_t$~+iACH;z0k`>9??(z7W_1Cj zsT;=kq~IltxT$izj@CCauuJ(C()HB+-J5(*L(c_|pB0dM7Auh+kvYrdwdE?W^Fd!L z7XhfIWyg;uiwzIgS%bGwA@!QfF-7KJB@?T+ZnP56s0-?@f}u-D!DIG6RJ;Wp#!lg> ziAIHyy|L_TDXKNatHmj~2 5IhQqN+oZ*;j+!UX8j(9~anv)e1iSMT4Ke^hox zQEfdPj-4JU%91K{cCpmzOQm22jH}`3a^&Kxm?b({t@pY)iB#nT4PL5W{oBD_XDKX; zMgzB`XLYn3GyQo^u7^?5bApxu{ZjgH^Y5On+k#b_O;~HfTj~1x<*oG@H|d9Q@uGZ; zJ074WwL&Ks9j&m)y_Qs72_ZFwMsB62rFUNDi?>zV2hW`pxwC{lu09ljs{?PgPJ4wP z-?4#Ut2a-osYD@B5tNMmsk|IX(#IQcKcO@$Y_WxN9IAuPKet2jq}?M=;rARYdT071 zg5KhA<*=yZV5PV#BFqgeQb*gS{o(KQ!jd!xn+$V!_uq*4rCD@oT%wxWmg~?W?lxUI zL)}*pzAlo5UnRmKlOs~#>QBy#E#~N*1C%Sqzu+#T>Bp~E4|93*(AYPMX4Y=p`8Fo$ zzYE=?_%_}6Q9iB@*F 0?WPwSQMIH>gl|haY33L%4w;-&mkJT z`O4rJuE=RK>UMef;Xw8~>{Q>!{lk`)E%DhIMG&l~&|~Px({4LrY*<#DS@-tzSqfa> zdJxK; PDK&Uq_A;~g*GgRgV_JA! zQRwZ1*rOA*G~TqHC1oj@^jurW7_Wgmq 4Q}Vr>da9&Oe5Hfb2%(hILk= !r1NAaEwSH>g_#71~rBR=fBXdW9NrdZmq90gX-l<^>-! zaOrpFV`O*Nz8s;B%zG%-r$)zmol8 IM{FD9s+o+Yy4YUC gB>b{ftBm1=5F zfl_LH`URu>b8;p21ClDznPN>A18G{9%J&9*ERoE6F>>n((LXsI90i2s;iB {umvfRqfe#g z|42x0_-^dUSo8rF8j_5ryo1pc+^J*6ojIF*C&g*A6!*Gjt2^B``9^gr&@-)ko*;_y zFq_-PGrs{cd&Nz{`MXXn)$b9qls@2t{vu w8Fv?dpj}IqT~ZU z2x-hzi3D^MloO#DwZr`1qT6*)0dYbd=SI!e&S6f-=9|gJdnXi%*Y+MkX(pdqMG7>a z(7};SE|WbnII9!za_il9XlU}4Z065ljrsET*FoI~qXu?A#|h?Paun?EjA9GbPt99e z%<2YA^_hjMWDE6bHqe%omAy6BS~8sGD;Pvli?JD^Kf81+)9DW+jSrbVnJt`86NdKD zB1Rhpcm&LCm7PdYJKd36_O_^7nq0BCt$U8nS1J#c(@-hKa?GIH)L8f2<=%DU`|vi$ z1C_Hgxd9JBoq$Kk2tuj@Db Q5UwCQ0qGHjqs`(^Jzv{ zTgtur@g8|LZH}gFtByx;s7(GuG8oung3D9OT=c-l*8+`vkRFvu$bvjQPX;Ui=Z~1H z{ym)2@x-V>39+@~Zwo6mgC5V*dWU243! p|^KVIZ^ z_qndB{urVq^9TtgH1Uydh50GHoxhkbYI*UbLh4BKly!(2b$K4SsFwm wpd zeC%LgU+j>nJ^t?iss(c-Z#yA)F)OkBUzv}P zt^uLffg^thp!4f|{;|HX_toJ)N&t#^0YbebEdQefAcUM%Y{AL-{15-XiopS)@K-$l zX#MaJ3An)9Mh|oHzlwbVBEoDwn)KgUWdO~|XN9?+|5fZeJK(Z*vRMDl`v1)Fe~US^ zP({4J7315r@H451J7ay{e_yjNHOqIKj>hb|V>h8pbGYpXW *RVVuE zS1QIRftZb2YK^Y7@IeqT0`|`cePx^!6C2uGPs9%AmiT|7#W#l<+tgWEzbvV1`O=kp zo@2{bnQZ+O>R;gE>eVE(=VQ;W-h|kB`}cVyEe!Js52$tVNGT<^2ifVSu2iVTLLWzO z;fvwTS*gB6yc^xy<@d`t{|(N_&rez)d^Jxqew}KOoFZD1QQ=& ! z9oYYBy&&LQUtixmB{aEwh#A}|-)S5$IImJMo@TZY)`_7?|L=3+zR6!&k|Pha#j}+5 zBVo3qOYKV8J%2+)j0FXb7lQ-vcj8R_d3fsO+9{OhqLwJ;S3v8>n`V{2g6bv3B<7$v z+Qi%0>*p8!$ol4JZY^_5Y@7J0A%ByNq|}e)Lu0A@9C6K~CRDgMEUxdrD+A0&jSr-B z^e&sfGk*r5pnKYGGEV((I0JA)$Bvx-H^A%Au`gzZ)NAnmzT99HU{icWUiWWmvIWk) z4f%!Uf3>8-{tDbL#rbXjO>Z;QK%$XXMkxJFG*se1jt{V(3H+;dy%dnF5;Jo${%R%` zfQ$l1U#!RaJFR^fkegVAr3C-3#upt(i8$>JWdB;35Xf&JXefmGZw|zHhJ8UDu*~~+ s+8?Yyj;uSu- z>% literal 0 HcmV?d00001 diff --git a/docs/image/preview.png b/docs/img/preview.png similarity index 100% rename from docs/image/preview.png rename to docs/img/preview.png diff --git a/docs/index.md b/docs/index.md index 1078df3c8..1c4f3916e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,14 @@ -本框架在不影响RecyclerView的任何函数组件使用基础上开发. 本框架也将一直保持维护状态 +本框架在不影响RecyclerView的任何函数组件使用基础上开发, 本项目将一直保持社区维护
-STAR/分享可以让更多人参与到本开源项目, 点击文档右上角小铅笔可直接修订文档 ↗
+STAR/分享可以让更多人参与到本开源项目
+!!! note "前言" + - 更详细的用法可阅读方法注释 + - 下载源码可参考示例项目 + ## 创建一个简单的列表 ```kotlin @@ -12,16 +16,12 @@ rv.linear().setup { addType(R.layout.item_simple) }.models = getData() ``` +文档提及的`rv`为RecyclerView简称, Model即`数据类/bean/pojo` -这里出现的`BRV`关键词即本框架, `RV`即RecyclerView简称. `setup`函数也只是简化创建BindingAdapter对象 - -## 列表填充数据的4种方式 - -BRV支持4种方式, 灵活使用, 这里提及的Model就等同于数据类/JavaBean/POJO - - +
+BRV支持以下4种数据绑定方式, 请根据使用场景选择 -### 1. 函数回调 +## 函数回调 在`onBind`函数中填充数据 @@ -38,43 +38,30 @@ rv.linear().setup { -### 2. 实现接口 +## 实现接口 + +为Model实现接口`ItemBind`, 然后在`onBind`中进行赋值数据 -通过为Model实现接口`ItemBind`, 实现函数`onBind`, 在该函数中填充数据; 这种方式在很多框架中被应用, 例如BRVAH, 但是我不推荐这种视图在Model中绑定的方式, 因为Model应当只存储数据和计算逻辑, 不应包含任何UI +!!! warning "耦合" + 不推荐, 这会导致业务逻辑和视图耦合, 不方便应用迭代
+ 但这种方式在很多框架中被使用, 例如`BRVAH`, 可以方便你从其他框架迁移到BRV ```kotlin class SimpleModel(var name: String = "BRV") : ItemBind { override fun onBind(holder: BindingAdapter.BindingViewHolder) { - val appName = holder.context.getString(R.string.app_name) - holder.findView(R.id.tv_simple).text = appName + itemPosition - - // 如果存在多种数据类型, 请使用holder.getModelOrNull()或者if来判断itemViewType类型, 避免取值类型转换错误 - val data = holder.getModel() - - when (holder.itemViewType) { - R.layout.item_simple_text -> { - val binding = holder.getBinding () - val data = holder.getModel -() - binding.tv.text = data.name - } - } + holder.findView (R.id.tv_simple).text = holder.layoutPosition.toString() } } ``` -### 3. ViewBinding - +## ViewBinding +要求开启ViewBinding, 上面实现接口方式也可以使用`getBinding()` ```kotlin rv.linear().setup { addType (R.layout.item_simple) - - onCreate { - getBinding ().tvName.text = "文本内容" - } - onBind { - val binding = getBinding () // 使用ViewBinding/DataBinding都可以使用本方法 + val binding = getBinding () // ViewBinding/DataBinding都支持 val data = holder.getModel () } }.models = getData() @@ -82,16 +69,19 @@ rv.linear().setup { -### 4. DataBinding +## DataBinding + +通过DataBinding数据绑定可以自动填充数据 -通过DataBinding数据绑定形式自动填充数据, 推荐, 这是代码量最少最灵活的一种方式. +!!! success "推荐" + 这是最简洁优雅/最安全的一种绑定数据方式 -#### a. 启用DataBinding +### 1. 启用DataBinding -第一步在module中的`build.gradle`文件中开启DataBinding框架 +首先在module中的`build.gradle`中开启DataBinding ```groovy -apply plugin: "kotlin-kapt" // kapt插件用于生成dataBinding +apply plugin: "kotlin-kapt" // 必要 android { /.../ @@ -99,7 +89,7 @@ android { } ``` -#### b. 布局声明变量 +### 2. 布局声明变量 第二步, 在Item的布局文件中声明变量, 然后绑定变量到视图控件上 @@ -113,64 +103,38 @@ android { type="com.drake.brv.sample.model.SimpleModel" /> - - + android:layout_height="100dp" /> ``` 选中行是DataBinding使用方法 -#### c. 初始化全局ID - +### 3. 自动绑定 +DataBinding会根据你配置的`modelId`自动绑定models中数据到xml中 ```kotlin class App : Application() { - override fun onCreate() { - super.onCreate() - - // 初始化BindingAdapter的默认绑定ID, 如果不使用DataBinding并不需要初始化 + // ... BRV.modelId = BR.m } } ``` -> rv是一个列表. 里面的models是一个list集合, 每个元素对应一个item. dataBinding会根据你`赋值的Id`自动绑定models中元素到xml中赋值- - - -- -
- -1. 注意要先在某个布局或Item布局声明``布局中的变量`name="m"`, `BR.m`才能被生成, 一旦声明`BRV.model = BR.m`你的所有BRV使用的item布局都得使用`name="m"`来声明数据模型, 否则会无法自动绑定
+1. 注意要先在某个布局中声明``布局中的变量`m`(推荐命名), `BR.m`才能被生成
一旦声明`BRV.model = BR.m`你的所有BRV使用的item布局都得使用`name="m"`来声明数据模型, 否则无法自动绑定 1. 导包注意导入你所在module的BR, 这样所有使用该Id来声明数据模型的布局都会被BRV自动绑定数据
-1. 如果依然没有生成请`make project`(即图中绿色小锤子图标)
+1. 如果没有生成请`make project`(小锤子)
-1. m(m是model的简称)可以是任何其他的名称, model或者sb都可以, 比如你`name="data"`, 那么你就应该使用BR.data
-BR.data和Android中常见的`R.id.data`都属于Id常量, 本质上都是Int值. 你可以点击查看BR.m源码
- -#### d. 构建列表 - -这种方式创建列表无需处理数据 +### 4. 构建列表 ```kotlin rv.linear().setup { @@ -178,8 +142,15 @@ rv.linear().setup { }.models = getData() ``` -别看文档中DataBinding方式复杂, 实际代码量最少, 同时最解耦 +!!! warning "组件化项目" + 多Module组件化项目要求主Module及使用DataBinding的Module都开启DataBinding及Kapt, + 否则抛出NoClassDefFoundError找不到BR类 -> 使用DataBinding可以复制或者引用我的常用自定义属性: [DataBindingComponent.kt](https://github.com/liangjingkanji/Engine/blob/master/engine/src/main/java/com/drake/engine/databinding/DataBindingComponent.kt)
-> 如果你想更加了解DataBinding请阅读[DataBinding最全使用说明](https://juejin.cn/post/6844903549223059463/) +!!! quote "DataBinding" + 使用DataBinding可以复制或者引用我的常用自定义属性: [DataBindingComponent.kt](https://github.com/liangjingkanji/Engine/blob/master/engine/src/main/java/com/drake/engine/databinding/DataBindingComponent.kt)
+ 如果想更了解DataBinding可以阅读一篇文章: [DataBinding最全使用说明](https://juejin.cn/post/6844903549223059463/) +--- +[下载Apk](https://github.com/liangjingkanji/BRV/releases/latest/download/brv-sample.apk){ .md-button } +[下载源码](https://github.com/liangjingkanji/BRV.git){ .md-button } +[示例代码](https://github1s.com/liangjingkanji/BRV/blob/HEAD/sample/src/main/java/com/drake/brv/sample/ui/fragment/SimpleFragment.kt){ .md-button } diff --git a/docs/issues.md b/docs/issues.md index ab16129c2..250aef96d 100644 --- a/docs/issues.md +++ b/docs/issues.md @@ -1,4 +1,6 @@ -## 常见问题 +BRV基于RecyclerView的组件进行扩展完善, 不影响原有功能可自行扩展实现 + +
- [java 上可以用吗?](https://github.com/liangjingkanji/BRV/issues/180) - [由dataclass导致的分组折叠崩溃](https://github.com/liangjingkanji/BRV/issues/79) @@ -17,4 +19,11 @@ - [上拉加载多语言的问题](https://github.com/liangjingkanji/BRV/issues/212) - [第一页加载项没有占满整个页面时,自动加载第二页](https://github.com/liangjingkanji/BRV/issues/206) - [上拉加载时会把顶部布局顶出去](https://github.com/liangjingkanji/BRV/issues/192) -- [请教侧滑背景实现思路](https://github.com/liangjingkanji/BRV/issues/158) \ No newline at end of file +- [请教侧滑背景实现思路](https://github.com/liangjingkanji/BRV/issues/158) + +## 开发者交流 + +- [反馈问题](https://github.com/liangjingkanji/BRV/issues) +- [其他开发者提及问题](https://github.com/liangjingkanji/BRV/issues) +- [交流社区](https://github.com/liangjingkanji/BRV/discussions) +- \ No newline at end of file diff --git a/docs/lifecycle.md b/docs/lifecycle.md index 796534a35..43f77e4af 100644 --- a/docs/lifecycle.md +++ b/docs/lifecycle.md @@ -1,21 +1,35 @@ -## 生命周期 +!!! question "rv生命周期是什么" -首先要知道的RecyclerView(以下简称rv)的基础知识 - -1. onCreateViewHolder (创建视图) 调用次数为屏幕同时可展示的Item数量, 对视图的频繁操作优先考虑此回调中进行! -1. onBindViewHolder (绑定数据) 在每次Item被显示到屏幕上时回调, 故会在快速滑动列表的时候反复调用! 建议不要在里面进行耗时操作
-比如建议使用[Serialze](https://github.com/liangjingkanji/Serialize)取代SharePreference等耗时存储 + 1. onCreateViewHolder (创建视图)
+ 调用次数为屏幕同时可展示的Item数量, 对视图的频繁操作优先考虑此回调中进行 + 2. onBindViewHolder (绑定数据)
+ 每次Item被显示到屏幕上时回调, 故会在快速滑动列表的时候反复调用! 不建议耗时操作
而在BRV中简化了这两个函数 | 函数 | 描述 | |-|-| -| onCreate | 对应Adapter的`onCreateViewHolder`函数回调 | -| onBind | 对应Adapter的`onBindViewHolder`函数回调 | -| onBindViewHolders | 一个`onBindViewHolder`监听器的集合, 一般用于其他框架来监听扩展, 使用者一般不需要使用 | +| onCreate | 对应Adapter的`onCreateViewHolder`函数 | +| onBind | 对应Adapter的`onBindViewHolder`函数 | +| onBindViewHolders | 一个`onBindViewHolder`监听器的集合, 提供给开发者扩展功能使用 | + +```kotlin +rv.linear().setup { + addType(R.layout.item_simple) + + onCreate { + // 一般在这里配置事件监听 + } + + onBind { + val binding = getBinding () // 使用ViewBinding/DataBinding都可以使用本方法 + val data = holder.getModel () + } +}.models = getData() +``` -## 注意 +## 重写 BindingAdapter是`open class` 可以被继承重写, 任何没有提供的函数回调可以通过继承或者匿名类实现
diff --git a/docs/multi-type.md b/docs/multi-type.md index 2f1d65d22..abaaffefb 100644 --- a/docs/multi-type.md +++ b/docs/multi-type.md @@ -1,6 +1,6 @@ 在BRV中创建多类型非常简单, 通过多次调用`addType()`来添加多个类型即可 -## 添加多类型 +## 添加类型 ### 多对多 @@ -40,31 +40,16 @@ rv.linear().setup { ``` 当前`addType`的大括号内的`this`就是你指定的泛型, 所以我们直接通过`Model.age`来判断返回不同的多类型 -### 接口实现 +### 接口类型 -接口类型是可以addType一个类型, 然后由可以添加N个其子类作为models的数据. 接口具体实现由不同的子类不同实现 +当你`addType`添加一个接口类型, 那么Models中添加的其子类也会被识别为该类型 -示例 +但当你指定的泛型为抽象类/普通类, 将无法识别为其子类, 请使用`addInterfaceType()`来替代 -```kotlin -interface BaseInterfaceModel { - var text: String -} - -data class InterfaceModel1(override var text: String) : BaseInterfaceModel - -data class InterfaceModel2(val otherData: Int, override var text: String) : BaseInterfaceModel - -data class InterfaceModel3(val otherText: String) : BaseInterfaceModel { - override var text: String = otherText -} -``` - -构建示例数据 +示例数据 ```kotlin private fun getData(): List { - // 在Model中也可以绑定数据 return List(3) { InterfaceModel1("item $it") } + List(3) { InterfaceModel2(it, "item ${3 + it}") } + List(3) { InterfaceModel3("item ${6 + it}") } @@ -74,75 +59,54 @@ private fun getData(): List { 声明列表 ```kotlin - binding.rv.linear().setup { - addType (R.layout.item_interface_type) - R.id.item.onClick { - toast("点击文本") - } - }.models = getData() +binding.rv.linear().setup { + addType (R.layout.item_interface_type) +}.models = getData() ``` -这里只是演示简单的文本, 具体可以编写更加复杂的业务逻辑 +仅简单演示, 实际可以让`BaseInterfaceModel`子类分别实现不同业务逻辑 ## 区分类型 -上面章节提到每个Item类型的数据类型, 所以我们取数据类来进行业务逻辑时需要根据不同类型做不同处理 +每个类型的数据和视图可能不同, 所以需要根据不同类型做不同处理 +!!! Failure "类型不匹配" + 如果多类型列表不区分类型进行`getBinding()`或者`getModel()`会导致类型转换失败抛出异常 -根据`itemViewType`区分类型 -```kotlin -rv.linear().setup { - addType (R.layout.item_simple) - addType (R.layout.item_simple) - onBind { - when(itemViewType) { - R.layout.item_simple -> { - getBinding ().tvName.text = "文本内容" - } - R.layout.item_simple_2 -> { - getBinding ().tvName.text = "类型2-文本内容" - } +=== "根据`itemViewType`区分类型" + ```kotlin + when(itemViewType) { + R.layout.item_simple -> { + getBinding ().tvName.text = "文本内容" } - } -}.models = getData() -``` - -根据`getBinding()`区分类型 -```kotlin -rv.linear().setup { - addType (R.layout.item_simple) - addType (R.layout.item_simple) - onBind { - when (val viewBinding = getBinding ()) { - is ItemSimpleBinding -> { - viewBinding.tvName.text = "文本内容" - } - is ItemComplexBinding -> { - viewBinding.tvName.text = "类型2-文本内容" - } + R.layout.item_simple_2 -> { + getBinding ().tvName.text = "类型2-文本内容" } } -}.models = getData() -``` + ``` -根据`getModel()`区分类型 -```kotlin -rv.linear().setup { - addType (R.layout.item_simple) - addType (R.layout.item_simple) - onBind { - when (val model = getModel ()) { - is ChatModel -> { - model.input = "消息内容" - model.notifyChange() - } - is CommentModel -> { - model.input = "评论内容" - model.notifyChange() - } +=== "根据`getBindingOrNull()`区分类型" + ```kotlin + getBindingOrNull ()?.run { + tvSimple.text = layoutPosition.toString() + } + getBindingOrNull ()?.run { + tvContent.text = layoutPosition.toString() + } + ``` + +=== "根据`getModel()`区分类型" + ```kotlin + when (val model = getModel ()) { + is ChatModel -> { + model.input = "消息内容" + model.notifyChange() + } + is CommentModel -> { + model.input = "评论内容" + model.notifyChange() } } -}.models = getData() -``` \ No newline at end of file + ``` \ No newline at end of file diff --git a/docs/performance.md b/docs/performance.md index 86e0151ec..f1def5ae1 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -1,19 +1,21 @@ -首先希望你已经阅读过rv基础的[生命周期](lifecycle.md), 正常情况下列表并不使用本章的优化方法也不会卡顿 +首先希望你已经了解过rv的[生命周期](lifecycle.md), 正常情况下不使用本章的优化方案也不会卡顿 -> BRV并没有对RecyclerView存在卡顿影响, 这里介绍的都是RecyclerView的优化点, 未提及的请自行搜索或补充 +!!! note "前言" + BRV对RecyclerView不存在卡顿影响, 这里介绍的都是RecyclerView的优化点, 未提及的请搜索或补充 -性能优化主要是避免滑动列表时频繁耗时操作, 而onBind就是滑动列表过程中频繁触发的回调 ## 主线程读写数据 +1. 性能优化主要是避免滑动列表时频繁耗时操作, 而onBind就是滑动列表过程中频繁触发的回调 +2. SharePreference(简称sp)是Android自带的键值存储工具, 其在主线程读写本地数据耗时明显
-SharePreference(简称sp)是Android自带的键值存储工具, 虽然允许方便地在主线程读写本地数据但是性能堪忧
-如果你在onBind里面读取体积较大的sp会导致列表卡顿甚至引起应用ANR +
+所以当onBind里面读取文件存储体积过大的sp数据会导致列表滑动卡顿甚至引起应用ANR -网上可能大部分推荐使用MMKV来取代, 但我推荐基于MMKV封装的更高效/方便的[Serialze](https://github.com/liangjingkanji/Serialize)来取代 +网上大部分可能推荐MMKV来取代sp, 但我推荐基于MMKV的性能/使用更好的[Serialze](https://github.com/liangjingkanji/Serialize)来取代 ## 嵌套列表 -在使用rv嵌套rv时应当在onCreate回调中为内嵌的rv设置视图(使用`rv.setup`), 这是为了避免同一类型反复创建rv导致内存消耗. 而嵌套的rv数据可以在onBind中绑定数据, 使用`rv.models` +rv嵌套时尽量在`onCreate`中创建视图, 因为在OnBind中会反复创建rv导致内存消耗 ```kotlin rv.linear().setup { @@ -33,9 +35,7 @@ rv.linear().setup { } ``` -由于被嵌套的rv是无法复用item, 所以建议使用recycledViewPool来复用, 具体请搜索关键词 - -如果被嵌套的列表非常简单其实也无需考虑其复用优化, 甚至你直接使用addView动态添加可能会比嵌套列表更简单 +被嵌套rv是无法和父级rv共享item复用池的, 如果子父列表类型相同建议使用`recycledViewPool`指定相同复用池, 详细请自行搜索 ## 视图添加/删除 diff --git a/docs/refresh-data.md b/docs/refresh-data.md index ebb30ffef..3cf668334 100644 --- a/docs/refresh-data.md +++ b/docs/refresh-data.md @@ -1,132 +1,87 @@ +!!! question "RV如何更新列表" + 列表数据来源于集合, 任何更新方法都是基于下面的封装 -BRV没有自定义RecyclerView, 所以RV如何操作数据BRV就如何操作数据. 对于RV基础不了解的可以阅读本章后网上搜索 + 1. 操作集合 + 2. 调用Adapter的`notifyXX`等方法通知RV更新视图 -你要知道的是RV操作数据需要两个步骤 -1. 更新集合(删除或者添加元素) -2. 调用对应的notify**()方法更新列表 +BindingAdapter/RV新增以下扩展函数操作数据 -但是BRV的`models/addData`赋值会自动notifyDataChanged(), 无需调用更新列表. 如果不想自动更新列表可以使用BindingAdapter的`_data`字段 - -> BRV的数据集合无论是`models`或`addData()`都是添加的`List(任意对象数据集合)`. 所以只要是一个集合即可映射出一个列表
-> 如果数据不满足一个集合条件(或任何数据上的问题), 请自己处理下数据 +| 函数| 动画 | 描述 | +|-|-|-| +| models | 无动画 | 设置集合, 会`notifyDataChanged()` | +| setDifferModels | 有动画 | 设置集合, 使用`DiffUtil.calculateDiff`来决定`notifyXX()`更新视图 | +| addModels | 可选动画 | 添加/插入集合, 会`notifyDataChanged()` | +| _data | 无动画 | 对应列表的集合对象, 需手动通知更新 | ## 添加数据 -使用BRV自带的两个方法添加数据会自动刷新UI - -```kotlin -rv.models = dataList // 自动使用 notifyDataSetChanged -rv.addModels(newDataList) // 自动使用 notifyItemRangeInserted, 当然也可以禁止动画 -binding.rv.addModels(newDataList, index = 3) // 在索引3后面添加数据 -``` - -代码示例 ```kotlin binding.rv.linear().setup { addType(R.layout.item_simple) - onBind { - findView (R.id.tv_simple).text = getModel ().name - } }.models = getData() -binding.rv.addModels(data, index = 3) // 添加数据 -binding.rv.models = newDataList // 覆盖原来的列表 +binding.rv.addModels(data, index = 3) // 插入到第四个 +binding.rv.models = newDataList // 覆盖列表 ``` - - -> 切记Java/Kotlin的引用类型是传址, 如果这两个方法操作的数据集都是同一个会导致问题 - 自己添加自己. 这种属于基本的语法问题 +!!! failure "同一个集合对象反复添加" + 引用类型是传址, 反复添加同一个集合对象, 并不会更新列表, 这属于基本语法知识 ## 删除数据 -BRV操作数据和RV没有任何区别(毕竟只是自定义Adapter), 删除全部数据可以为models赋值null - -如果删除指定数据, 比如删除第二条 +比如删除第3条 ```kotlin -rv.mutable.removeAt(2) // 先删除数据 -rv.bindingAdapter.notifyItemRemoved(2) // 然后刷新列表 +binding.rv.mutable.removeAt(2) // 删除数据 +binding.rv.bindingAdapter.notifyItemRemoved(2) // 通知更新 +// binding.rv.bindingAdapter.notifyDataChanged() 通知更新但无动画 ``` - -如果你删除N条或者添加/插入N条, 请调用对应的`notifyItem**()`方法, 具体方法你可以查看本章末尾或者网上搜索 - -## 对比数据刷新 -BRV可以根据新的数据集合和旧的数据集合对比判断来自动使用刷新动画 - +批量删除 ```kotlin -rv.setDifferModels(getRandomData()) +binding.rv.mutable.run { + removeAt(1) + removeAt(2) + removeAt(3) +} +binding.rv.bindingAdapter.notifyItemRangeRemoved(1, 3) // 通知更新 +// binding.rv.bindingAdapter.notifyDataChanged() 通知更新但无动画 ``` -该方法内部使用Android自带工具`DiffUtil`进行数据对比刷新, 但是支持异步/同步线程对比 +## 对比刷新 +新旧数据对比然后自动局部更新并展示对应动画 + ```kotlin -fun setDifferModels(newModels: List ?, detectMoves: Boolean = true, commitCallback: Runnable? = null) +binding.rv.setDifferModels(getRandomData()) ``` -> 数据对比默认使用`equals`方法对比, 你可以为数据手动实现equals方法来修改对比逻辑. 推荐定义数据为 data class, 因其会根据构造参数自动生成equals -如果需要完全自定义对比数据的判断逻辑就实现`ItemDifferCallback`接口 +内部使用官方`DiffUtil`进行数据对比刷新, 并支持子线程, 自定义请实现`ItemDifferCallback` ```kotlin hl_lines="3" rv.linear().setup { addType (R.layout.item_simple) - itemDifferCallback = object : ItemDifferCallback { - override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean { - return if (oldItem is SimpleModel && newItem is SimpleModel) { - oldItem.name == newItem.name - } else super.areContentsTheSame(oldItem, newItem) - } - } - // ... + itemDifferCallback = MyItemDifferCallback() }.models = getRandomData(true) ``` -使用`setDifferModels`对比刷新时, 相同item刷新有白屏动画这是因为`getChangePayload`返回null, 随便返回一个对象即可关闭 - -```kotlin -rv.linear().setup { - // ... - itemDifferCallback = object : ItemDifferCallback { - override fun getChangePayload(oldItem: Any, newItem: Any): Any? { - return true - } - } -}.models = getRandomData(true) -``` +- [白屏动画](https://github.com/liangjingkanji/BRV/blob/5269ef245e7f312a0077194611f1c2aded647a3c/brv/src/main/java/com/drake/brv/listener/ItemDifferCallback.kt#L63) +- [自定义对比条件](https://github.com/liangjingkanji/BRV/blob/5269ef245e7f312a0077194611f1c2aded647a3c/brv/src/main/java/com/drake/brv/listener/ItemDifferCallback.kt#L48) ## 局部刷新 -局部刷新某个或者批量Item的内容, 我们可以使用到两种方式 +指定刷新某个Item的数据 -1. `notifyItemChanged`等方法, 这个上面提过 -2. DataBinding本身就支持这个特性 (推荐此方法), 性能最高/方便. Demo中的[选择模式](https://github.com/liangjingkanji/BRV/blob/master/sample/src/main/java/com/drake/brv/sample/ui/fragment/CheckModeFragment.kt)就是如此实现 +1. 使用`notifyXX()`方法 +2. 使用DataBinding双向绑定特性, 数据变化会自动更新视图 -
-BRV支持DataBinding绑定数据, DataBinding的数据模型如果继承Observable就可以自动更新UI +!!! success "简单高效" + DataBinding 零代码, 最小范围(高性能), 不考虑索引 -```kotlin -data class CheckModel(var checked: Boolean = false, var visibility: Boolean = false) : BaseObservable() -``` - -可以自动更新UI的字段类型, 这样可以不用整个数据模型继承BaseObservable - -- LiveData -- ObservableField + 扩展阅读: [DataBinding最全使用说明 ](https://juejin.cn/post/6844903549223059463) -自动更新LiveData字段要求先为DataBinding配置生命周期, 因为liveData观察者需要lifecycleOwner -```kotlin -binding.lifecycleOwner = this -``` +## 通知更新 -> 以上属于DataBinding使用基础, 更多DataBinding使用方法请阅读: [DataBinding最全使用说明 ](https://juejin.cn/post/6844903549223059463) - -## 刷新方法 - -这里介绍的属于RecyclerView官方方法, BRV的`BindingAdapter`继承`RecyclerView.Adapter`, 自然拥有父类的数据刷新方法. -由于很多开发者常问此需求, 故统一介绍下 - -```kotlin -class BindingAdapter : RecyclerView.Adapter() -``` +`BindingAdapter`继承`RecyclerView.Adapter`, 拥有父类的通知更新方法 | 刷新方法 | 描述 | |-|-| @@ -139,15 +94,5 @@ class BindingAdapter : RecyclerView.Adapter () | notifyItemRangeInserted | 指定范围内Item插入 | | notifyItemRangeRemoved | 指定范围内删除 | -具体区别可以搜索: `RecycleView局部刷新`
-要注意的是这些方法都是刷新UI, 如果你列表数据并没有发生改变那么刷新是无效的 - -```kotlin -rv.linear().setup { - addType(R.layout.item_simple) -}.models = dataList - -dataList.add(SimpleModel()) - -rv.bindingAdapter.notifyItemInserted(dataList.size) // 最后的位置有插入一个新的Item -``` +!!! warning "更新无效" + 这些方法都是通知更新视图, 如果你列表数据并没有发生改变那么是无效的 \ No newline at end of file diff --git a/docs/refresh.md b/docs/refresh.md index f0ebc1aa0..70bd68d11 100644 --- a/docs/refresh.md +++ b/docs/refresh.md @@ -146,6 +146,7 @@ PageRefreshLayout内嵌`StateLayout`同时具备显示缺省页的能力 | showError | 显示错误缺省页 | | showContent | 显示内容页 | +如果你使用`showXX()`方法更改缺省页状态, 并不需要再调用`finishXX()`等方法 #### 全局缺省页配置 @@ -215,7 +216,7 @@ StateConfig.apply { 前面提到 PageRefreshLayout 支持自动分页加载, 自动分页不需要你调用`rv.models`函数去设置数据, 使用`addData`即可 -```kotlin hl_lines="8" +```kotlin hl_lines="5" // 下拉刷新和上拉加载都会执行onRefresh, 除非另外设置onLoadMore pageLayout.onRefresh { scope { diff --git a/docs/swipe.md b/docs/swipe.md index cfc2f5b35..cc705a2ee 100644 --- a/docs/swipe.md +++ b/docs/swipe.md @@ -7,22 +7,11 @@ data class SwipeModel(override var itemOrientationSwipe: Int = ItemOrientation.ALL) : ItemSwipe ``` -> 注意如果你的数据模型被Gson反序列化后, 会删除所有的字段初始化值 +## 侧滑方向 -这里我们可以重写访问函数来解决问题, 让该值固定返回 +该类包含侧滑可配置的方向 -```kotlin hl_lines="3" -class SwipeModel() : ItemDrag { - override var itemOrientationSwipe: Int = 0 - get() = ItemOrientation.ALL // 只会返回该值 -} -``` - -## ItemOrientation - -该类包含拖拽可配置的方向 - -| 字段 | 描述 | +| ItemOrientation | 描述 | | ---- | ---- | | `ALL` | 全部方向 | | `VERTICAL` | 垂直方向 | @@ -38,7 +27,7 @@ class SwipeModel() : ItemDrag { ## 自定义侧滑 -如果想要扩展ItemTouchHelper可以给BindingAdapter的变量`itemTouchHelper`赋值 +通过赋值`itemTouchHelper`实现自己的手势 ```kotlin rv.linear().setup { @@ -70,10 +59,14 @@ rv.linear().setup { ## 侧滑按钮 -很多人会问如何实现类似QQ那样的侧滑展示按钮. 这种推荐使用自定义Item的视图对象, 而不是让列表去实现.0 +侧滑按钮推荐使用自定义View实现, 而不是让列表实现 + +!!! quote "推荐的三方库" + + 这种交互交互效果属于iOS的官方实现, Android存在和全屏手势冲突, 并不推荐实现 -这里推荐第三方库: [SwipeToActionLayout](https://github.com/st235/SwipeToActionLayout) - -> 这种交互效果属于iOS的官方效果, 不推荐Android抄袭 diff --git a/docs/toggle.md b/docs/toggle.md index 6bc3e7e68..1cae324af 100644 --- a/docs/toggle.md +++ b/docs/toggle.md @@ -1,4 +1,4 @@ -BRV提供一个切换事件的触发和监听, 相当于会提供一个回调函数遍历所有的列表条目, 你可以在这个回调里面依次更新数据或者视图. +BRV提供一个在回调函数遍历所有的列表条目的监听事件, 你可以在回调内依次更新数据或者视图
这个`切换`可以理解为`遍历`列表条目 @@ -36,20 +36,9 @@ fun onClick(v:View){ ## 函数 -```kotlin -fun toggle() -// 触发切换模式(根据当前状态取反) - -fun getToggleMode(): Boolean -// 范围当前出何种切换模式 - -fun setToggleMode(toggleMode: Boolean) -// 设置切换模式, 如果设置的是当前正处于的模式不会触发回调 - -fun onToggle(block: (position: Int, toggleMode: Boolean, end: Boolean) -> Unit) -// 监听切换事件, 在事件中你可以处理任何视图的数据或者视图修改 -// position: 遍历过程中的列表条目索引 -// toggleMode: 切换模式(布尔值) -// end: 是否全部遍历完成 -``` +| 函数 | 描述 | +|-|-| +| toggle | 触发切换 | +| toggleMode | 当前切换模式 | +| onToggle | 切换事件回调 | diff --git a/docs/view-binding.md b/docs/view-binding.md index 1b8e0881a..29769102a 100644 --- a/docs/view-binding.md +++ b/docs/view-binding.md @@ -48,35 +48,5 @@ class SimpleModel(var name: String = "BRV") : ItemBind { 都是调用方法`getBinding()` -## 多类型 - -如果是多类型注意先判断类型, 避免为绑定错误的ViewBinding - -```kotlin -class SimpleModel(var name: String = "BRV") : ItemBind { - - override fun onBind(holder: BindingAdapter.BindingViewHolder) { - - // 方式1 判断itemViewType - when(holder.itemViewType) { - R.layout.item_simple -> { - getBinding().tvName.text = "文本内容" - } - R.layout.item_simple_2 -> { - getBinding ().tvName.text = "类型2-文本内容" - } - } - - - // 方式2 判断ViewBinding - when (val viewBinding = getBinding ()) { - is ItemSimpleBinding -> { - viewBinding.tvName.text = "文本内容" - } - is ItemComplexBinding -> { - viewBinding.tvName.text = "类型2-文本内容" - } - } - } -} -``` \ No newline at end of file +!!! Failure "多类型需要判断类型" + 如果是多类型请先判断类型再`getBinding()`, 避免获取到错的ViewBinding导致崩溃, 请阅读[区分类型](multi-type.md#_4) diff --git a/mkdocs.yml b/mkdocs.yml index b886cbfa9..3f503652d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,38 +1,74 @@ site_name: BRV -site_author: drake site_description: BRV框架文档 -copyright: Copyright © 2018 - 2020 劉強東 - -repo_name: GitHub repo_url: https://github.com/liangjingkanji/BRV +site_author: 劉強東 +copyright: Copyright © 2018 - 2020 劉強東 +repo_name: GitHub +docs_dir: 'docs' +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/ + - icon: fontawesome/brands/qq + link: https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/img/group-qrcode.png + - icon: fontawesome/brands/twitch + link: https://github.com/liangjingkanji/BRV/discussions extra_css: - css/extra.css - -docs_dir: 'docs' - theme: name: material custom_dir: docs/material - + favicon: img/book-open.svg + logo: img/book-open.svg palette: - scheme: drake - primary: white - + - media: "(prefers-color-scheme: light)" + scheme: default + primary: white font: false - language: zh - + features: + - navigation.top + - navigation.prune + - navigation.footer + - navigation.instant + - search.highlight + - search.suggest + - search.share + - content.code.copy + - content.code.annotate +plugins: + - offline + - search: + separator: '[\s\-,:!=\[\]()"/]+|(?!\b)(?=[A-Z][a-z])|\.(?!\d)|&[lg]t;' + lang: + - en + - zh markdown_extensions: - toc: permalink: true - pymdownx.tasklist: custom_checkbox: true + - pymdownx.tabbed: + alternate_style: true + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - attr_list + - def_list + - md_in_html - admonition + - pymdownx.highlight - pymdownx.details - - pymdownx.superfences - - pymdownx.inlinehilite - - pymdownx.tabbed + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + nav: - 使用: index.md @@ -66,7 +102,7 @@ nav: - 网络请求: net.md - ViewBinding: view-binding.md - 社区讨论: https://github.com/liangjingkanji/BRV/discussions - - 常见问题: https://github.com/liangjingkanji/BRV/blob/master/docs/issues.md + - 常见问题: issues.md - 第三方组件: component.md - 更新日志: updates.md - 1.x文档: api/index.html \ No newline at end of file diff --git a/sample/src/main/java/com/drake/brv/sample/ui/fragment/InterfaceFragment.kt b/sample/src/main/java/com/drake/brv/sample/ui/fragment/InterfaceTypeFragment.kt similarity index 75% rename from sample/src/main/java/com/drake/brv/sample/ui/fragment/InterfaceFragment.kt rename to sample/src/main/java/com/drake/brv/sample/ui/fragment/InterfaceTypeFragment.kt index a000c5a22..34a886aef 100644 --- a/sample/src/main/java/com/drake/brv/sample/ui/fragment/InterfaceFragment.kt +++ b/sample/src/main/java/com/drake/brv/sample/ui/fragment/InterfaceTypeFragment.kt @@ -1,7 +1,7 @@ package com.drake.brv.sample.ui.fragment import com.drake.brv.sample.R -import com.drake.brv.sample.databinding.FragmentInterfaceBinding +import com.drake.brv.sample.databinding.FragmentInterfaceTypeBinding import com.drake.brv.sample.model.BaseInterfaceModel import com.drake.brv.sample.model.InterfaceModel1 import com.drake.brv.sample.model.InterfaceModel2 @@ -9,16 +9,12 @@ import com.drake.brv.sample.model.InterfaceModel3 import com.drake.brv.utils.linear import com.drake.brv.utils.setup import com.drake.engine.base.EngineFragment -import com.drake.tooltip.toast -class InterfaceFragment : EngineFragment (R.layout.fragment_interface) { +class InterfaceTypeFragment : EngineFragment (R.layout.fragment_interface_type) { override fun initView() { binding.rv.linear().setup { addType (R.layout.item_interface_type) - R.id.item.onClick { - toast("点击文本") - } }.models = getData() } diff --git a/sample/src/main/res/layout/fragment_interface.xml b/sample/src/main/res/layout/fragment_interface_type.xml similarity index 93% rename from sample/src/main/res/layout/fragment_interface.xml rename to sample/src/main/res/layout/fragment_interface_type.xml index 7448b8ecb..8d93efd3f 100644 --- a/sample/src/main/res/layout/fragment_interface.xml +++ b/sample/src/main/res/layout/fragment_interface_type.xml @@ -7,7 +7,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".ui.fragment.InterfaceFragment"> + tools:context=".ui.fragment.InterfaceTypeFragment"> + tools:layout="@layout/fragment_interface_type" />