CeeEOxt~F#aWn8wy-N_ilDDe_o+SwJD>4y?j5Lpj z2&!EX)RNxnadPBAa?fOj5D1C{l1E0X?&G3+ckcVfk`?%2FTsoUf4@~eaS#th=zq7v zMEJR@1T?Pi4;$xiPv`3)9rsrbVUH&b0e2{YTEG%;$GGzKUKEim;R6r>F@Q-}9JR-< zOPpQI>W0Vt6&7d?~$d&}chKTr_rELu} zWY;KTvtpJFr?P~ReHL4~2=ABn1`GN4Li%OI_1{mMRQi1Bf?+^Va?xdn4>h)Bq#ZRK zYo%R_h5etrv|!$1QF8fu80fN?1oXe(Jx#e6H^$+>C}N{*i$bNbELsXDA>cxlh|iFq zh~$yJ?1lTdcFd1Yv+Hr^PP!yupP!0H@Y6(wFcaVE+0?qjDJ1;*-Q8qL{NNPc{GAoi z_kBH`kw^(^7ShmzArk^A-!3_$W%!M-pGaZC=K`p-ch&iT%CV0>ofS74aPd7oT&cRr zXI30fVV6#PR*Z?c*orR0!$K6SUl9!H>hG+%`LdifNk`!Sw7Hon{Wn=|qV{a%v9nEq zAdBW*5kq6il=yA}x8cZQt^c+RBS|TRn;!?$ue?@jIV~0w1dt1FJRYI-K5>z-^01)R z)r}A&QXp^?-?}Uj`}ZPqB#}xO-?{0wrmi|eJOEjzdXbey4$rtKNHz)M*o?Ov+;S=K z-l~`)xV`%7Gvzy5wfvwqc0|80K29k0G~1nuBO+y-6)w11Kz2{>yD{HTt-uybe2pe? zUZK*Eij7TT4NwF1Jr@6R7gMuu^@qn#zPIgRtF?-SJL8 3LBDrh7k#{F^222EXPg}S0d4Lf0!|1 z|2k$^b~)^8$Z-yH{B-vo%7sVU@ZCvXN+Am)-fy$afZ_4HAUpK}j4p`UyXRel-+(VS z#K>-=-oA1pH+Lo$&|!lYB|M7Y&&bF##Oi@y_G3p1X$0I{jS1!NEdTz#x0`H`d*l%X z*8Y3>L*>j@ZQGOdPqwY(GzbA4nxqT(UAP<-tBf{_cb&Hn8hO5gEAot oV;tF6K4~wr2-M0v|2acQ!E@G*g$J z)~&_lvwN%WW>@U_taX5YX@a~pnG7A~jGwQwd4)QKk|^d_x9j+3JYmI5H`a)XMKwDt zk(nmso_I$Kc5m+8iVbIhY<4$34Oz!sg3oZF%UtS(sc6iq3?e8Z;P<{OFU9MACE6y( zeVprnhr!P;oc8pbE%A~S<+NGI2ZT@4A|o9bByQ0er$rYB3(c)7;=)^?$%a${0@70N zuiBVnAMd|qX7BE)8})+FAI&HM|BIb3e=e`b{Do8`J0jc$H>gl$zF26=haG31FDaep zd~i}CHSn$#8|WtE06vcA%1yxiy_TH|RmZ5> pI5*8pJZk0X5 4JDQQZgIf1Pp3*6hepV_cXe)L2iW$Ov=RZ4T)SP^a_8V} z+Nl?NJL7fAi<)Gt98U+LhE>x4W=bfo4F>5)qBx@^8&5-b>y*Wq19MyS(72ka8XFr2 zf*j(ExtQkjwN|4 B?D z7+WzS*h6e_Po+Iqc-2n)gTz|de%FcTd_i9n+Y5*Vb=E{8xj&|h`CcUC*(yeCf~#Mf zzb-_ji&PNcctK6Xhe#gB0skjFFK5C4=k%tQQ}F|ZvEnPcH=#yH4n%z78?McMh!vek zVzwC0*OpmW2*-A6xz0=pE#WdXHMNxSJ*qGY(RoV9)|eu)HSSi_+ |)IgT|!7HRx~ zjM$zp%LEBY)1AKKNI?~*>9DE3 Y2 t5p#jeqeq`1 zsjA-8eQKC* !$%k#=&jm+JG?UD(}M!tI{wD*3FQFt8jgv2xrRUJ}t}rWx2>XWz9ndH*cxl()ZC zoq?di!h6HY$fsglgay7|b6$cUG-f!U4blbj(rpP^ 1ZhHv@Oi~;BBvrv<+uC;%6QK!nyQ!bb3i3D~cvnpDAo3*3 zXRfZ@$J{FP?jf(NY7~-%Kem>jzZ2+LtbG!9I_fdJdD*;^T9gaiY>d+S$EdQrW9W62 z6w8M&v*8VWD_j)fmt?+bdavPn>oW8djd zRnQ}{XsIlwYWPp;GWLXvbSZ8#w25z1T}!<{_~(dcR_i1U?hyAe+lL*(Y6c;j2q7l! zMeN(nuA8Z9$#w2%ETSLjF{A#kE#WKus+%pal;-wx&tTsmFPOcbJtT?j&i(#-rB}l@ zXz|&%MXjD2YcYCZ3h4)?KnC*X$G%5N)1s!0!Ok!F9KLgV@wxMiFJIVH?E5JcwAnZF zU8ZPDJ_U_l81@&npI5WS7Y@ _gf3vTXa;511h_(@{y1q-O{&bzJ z*8g>?c5=lUH6UfPj3=iuuHf4j?KJPq`x@en2Bp>#zIQjX5(C<9-X 4X{ a^S znWF1zJ=7rEUwQ&cZgyV4L12f&2^eIc^dGIJP@ToOgrU_Qe=T)utR;W$_2Vb7NiZ+d z$I0I>GFIutqOWiLmT~-Q<(?n5QaatHWj**>L8sxh1*pAkwG>siFMGEZYuZ)E!^Hfs zYBj`sbMQ5MR;6=1^0W*qO*Zthx-svsYqrUbJW)!vTGhWKGEu8c+=Yc%xi}Rncu3ph zTT1j_>={i3l#~$!rW!%ZtD9e6l6k-k8l{2w53!mmROAD^2yB^e)3f9_Qyf&C#zk`( z|5RL%r&}#t(;vF4nO&n}`iZpIL=p9tYtYv3%r@GzLWJ6%y_D(icSF^sw YM`e8-n43iwo$C~>G<)dd0ze@5}n(!^YD zHf#OVbQ$Li@J}-qcOYn_iWF=_%)EXhrVuaYiai |B<1tXwNsow(m;XfL6^x~|Tr%L3~cs0@c) zDvOFU-AYn1!A;RBM0S}*EhYK49H$mBAxus)CB*KW(87#!#_C0wDr<0*dZ+GN&(3wR z6)cFLiDvOfs*-7Q75ekTAx)k!dtENUKHbP|2y4=tf*d_BeZ(9kR*m;dVzm&0fkKuD zVw5y9N>pz9C_wR+&Ql&&y{4@2M2?fWx~+>f|F%8E@fIfvSM$Dsk26(UL32oNvTR;M zE?F<7<;;jR 4)ChzQaN((foV z)XqautTdMYtv<=oo-3W-t|gN7Q43N~%fnClny|NNcW9bIPPP5KK7_N8g!LB8{mK#! zH$74|$b4TAy@hAZ!;irT2?^B0kZ)7Dc?(7xawRUpO~AmA#}eX9A>+BA7{oDi)LA?F ze&CT`Cu_2=;8CWI)e~I_65cUmMPw5fqY1^6v))pc_TBArvAw_ 5Y8v0+fFFT`T zHP3&PYi2>CDO=a|@`asXnwe>W80%%<>JPo(DS}IQiBEBaNN0EF6HQ1L2i6GOPMOdN zjf3EMN!E(ceXhpd8~<6;6k<57OFRs;mpFM6VviPN>p3?NxrpNs0>K&nH_s ze)2#HhR9JHPAXf#viTkbc{-5C7U`N!`>J-$T!T6%=xo-)1_WO=+BG{J `iIk%tvxF39rJtK49Kj#ne;WG1JF1h7;~wauZ)nMvmBa2PPfrqREMKWX z@v}$0&+|nJrAAfRY-%?hS4+$B%DNMzBb_=Hl*i%euVLI5Ts~UsBVi(QHyKQ2LMXf` z0W+~Kz7$t#MuN|X2BJ(M=xZDRAyTLhPvC8i& 9b=rS-T{k34X}|t+FMqf5gwQirD~N1!kK&^#+#8WvcfENOLA`Mcy@u~ zH10E=t+W=Q;gn}&;`R1D$n(8@Nd6f)9=F%l?A>?2w)H}O4avWOP@7IMVRjQ&aQDb) zzj{)MTY~Nk78>B!^Eb pT{&h zy{wTABQlVVQG<4;UHY?;#Je#-E;cF3gVTx520^#XjvTlEX>+s{?KP#Rh@hM6R;~DE zaQY16$Axm5ycukte}4FtY-VZHc>=Ps8mJDLx3mwVvcF<^`Y6)v5tF`RMXhW1kE-;! z7~tpIQvz5a6~q-8@hTfF9`J;$QGQN%+VF#`>F4K3>h!tFU^L2jEagQ5Pk1U_I5&B> z+i<8EMFGFO$f7Z?pzI(jT0QkKnV)gw=j74h4*jfkk3UsUT5PemxD`pO^Y#~;P2Cte zzZ^pr>SQHC-576SI{p&FRy36<`&{Iej&&A&%>3-L{h(fUbGnb)*b&eaXj>i>gzllk zLXjw`pp#|yQIQ@;?mS=O-1Tj+ZLzy+aqr7%QwWl?j=*6dw5&4}>!wXqh&j%NuF{1q zzx$OXeWiAue+g#nkqQ#Uej@Zu;D+@z^VU*&HuNqqEm?V~(Z%7D`W5KSy^e|yF6kM7 z8Z9fEpcs^ElF9Vnolfs7^4b0fsNt+i?LwUX8Cv|iJeR|GOiFV!JyHdq+XQ&dER(KSqMxW{=M)lA?Exe&ZEB~6SmHg`zkcD7x#myq0h61+zhLr_NzEIjX zr~NGX_Uh~gdcrvjGI(&5K_zaEf}1t*)v3uT>~Gi$r^}R;H+0FEE5El{y;&DniH2@A z@!71_8mFHt1#V8MVsIYn={v&*0;3SWf4M$yLB^BdewOxz;Q=+gakk`S{_R_t!z2b| z+0d ^C?G&7U6$_-W9@eR6SH%+qLx_Tf&Gu5%pn*mOGU0~kv~^K zhPeqYZMWWoA(Y+4GgQo9nNe6S#MZnyce_na@78ZnpwFenVafZC3N2lc5Jk-@V`{|l zhaF`zAL)+($xq8mFm{7fXtHru+DANoGz-A^1*@lTnE;1?03lz8kAnD{zQU=Pb^3f` zT5-g`z5|%qOa!WTBed-8`#AQ~wb9TrUZKU)H*O7!LtNnEd!r8!Oda)u!Gb5P`9(`b z`lMP6CLh4OzvXC# CR|@uo$EcHAyGr=)LB7)>=s3 zvU;aR#cN3<5&CLMFU@keW^R-Tqyf4fdkOnwI(H$x#@I1D6#dkUo@YW#7MU0@=NV-4 zEh2K?O@+2e{qW^7r?B~QTO)j}>hR$q9*n$8M(4+DOZ00WXFonLlk^;os8*zI>YG#? z9oq$CD~byz>;`--_NMy|iJRALZ#+qV8OXn=AmL^GL&|q1Qw-^*#~;WNNNbk(96Tnw zGjjscNyIyM2CYwiJ2l-}u_7mUGcvM+puPF^F89eIBx27&$|p_N G)fOaafGv|_b9G$;1LzZ-1aIE?*R6kHg}dy%~K(Q5S2O6086 z{lN&8;0>!pq^f*Jlh=J%Rmaoed<=uf@$iKl+bieC83IT!09J&IF)9H)C?d!eW 1UQ}BQwxaqQY47DpOk@`zZ zo>#SM@oI^|nrWm~Ol7=r`!Bp 9lQNbBCeHcfN&X$kjj0R(@?f$OHHt|fWe6jDrYg3(mdEd$8P2Yzjt9*EM zLE|cp-Tzsdyt(dvLhU8}_IX&I?B=|yoZ!&<`9&H5PtApt=VUIB4l0a1NH v0SQqt3DM`an1p};^>=lX|A*k@Y-MNT^ZzF}9G-1G696?OEyXH%^Pv9$0dR%J literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..aee44e138434630332d88b1680f33c4b24c70ab3 GIT binary patch literal 10486 zcmai4byOU|lb&5 k+^GN3bv-?^>(QkVinb zlU9`mfQEQnq$S4VGrg6fmMQ=QFarQQ0ss(?uiys&;LQU7M-~7engIZmZaH5x#UC3m z-zvYBd&I} <`b3rPHj1tDgVv1x| zQss$ELI?W?E(!7PKk$lm@;7PwPX3 o43{Ccd9@_BUsL4kQzSMa&= g{>4wj9#)9wgYw;=H@gH9KK{s?Be8N1_8W< z1Rh%Lm&PAfyYb*rGB%E#3q+}riOBB~+@@X<`9mgIiAex!QP8vg-XT>=+N&y*jC-f< zGihyr7XAly+G)|_e)qA?rnKZGG(x?=lLM7nrPk&93@5eX#7I_$g8kMX`0h=}l`HH) z=bpOkBCx=z*-fyr{yp7A9F=%o*qm93t_#tB2lAM@O{fX9ju%X#0~)nRUMvrXClh9w ze8|a0|0}JJg(_@$2wItI?LUY{zF78o(P2BR7;aC^@(jOp{8RE%U3m>MV5%Lu*46b@ zw*c?Nweu!TULS~}*9mi!ejNfNa=`po1*!jiYK)osxi% b59(thEyUZ>#lX@uEXSb_x?3)0kvB?8*TAh)7}IbzSm}5Ia;_?10{}M; z7vq-OS;Ayk8%_c-gg1Ee0FsrRU5phNs#H9Lp!1t+hwyK~9W0bWCxuG$LM~wQuumEw z=fbBD@sQE%1^j z`T@`PZLRVyWjX@*tjc7r;w$H~aW&7vu?|war?84^sg!{J*RH|mhq?KTsCVQBC1~fR z>99jeR=g-Q2b=d;pKwzXwYjrG>?pd3tFSsHN4in{usYLdK;01X2BdRLFI`cuB9yI) zI_ZX?7_(bz`MX2@^mCk nx7 z*f}KV@}TBBc}CXMR8T_5yInD3p`KrNROSA;HoJJtlNG3weri%utO $eeY0 z+w-NEn;(;UCBk=OM$f%=%ma24wV7$idelqyNWI>sz1>BlGwr_3UugqVjY+UYyi9P) zxCB?&rPUetoZN?|*D%=hOOJ_${JU3GRjppY%&8Ws^G6>iokr^Bmv1&*@#2#5mXu05 zhPVXaQ`qe5i0lP-1^XL45x`ertKU5d-8b_?*1+tSU!qCeqD9gZP_>ZLq9p)RKtV(B zOh&^x>gV^eqb&c~Oi0|HgGG|gjpbR`9aRdZhOimvS2Y3e?eCFiw+L#_ mi9j z;nU}gih+zTn{nv_|L}IllD1Dr3~@yitI}+4C&+;SR+cEfelqJ?eUjZ%&Qz)W8S750 z+vG8Lvo}xXz2C}S-m|9*uE?NWQWT#W+p@$DkH8wVn#=gLKa13M!Yv a9qsfE(5Z#0V`A0pN)Ok zP*Eq0(~e$~m@iej0#Av_z703y-7|W6`UuGDS8fpy2rUgINZs#`33@@0(S%~%XUO5G zscEp&x^dU`8syC67USOswNLq>Z_}q#gLh2x`zR) 0wvor72-IW@oDpnT0x zWn%LZ_yvR*7geY6<}MC~SViD+4`S9XC|L}N0ANpsUU;50sAj L zb5h>&s<-wcdf2>}P91QgeAu~ZnB7;;FkfKJp^8ne8!-`jK0+O(^`s~#RE0@)=IWiQ z@(vh6D^4jN5ih;*c4J48FMC9MwoN(cXk1Wiq55Vi-^X#p8R_(!y81}YDdMefwdl2F zNA0n}-!P4!FaCe-jnf{^I#?5W=%9T1C |$ z`+tq*x!rEx)Bkv-eO9$mWML9_yId)A_OltKIH-X=0eJ`Opqqj&s^T;PLIZXJ!p Ei!=3ZLHPGi*~?<(L&m6;{M(636VC<08tan>&c6fW z%KEuUN9x|i7Wc^-0l&Vf20kI~_XfD4hEac=&}5n&MoYL`Xsx=1po#V*6wUpwB@pu* z*@2n|zglL~zr$9&uOd9_%)GWk&0UN`<&GAm8=Ba-@MT&TH*`NHlt+CMi2Ag;LgGpm zm+ybGL-!1Z$kBYk66=39zAsErw1}|-l1npj-?3g1LE#PXU%%_{8kO=5!W!6pQ?z&i zc_MuV(xKMXSA0ga@IsiwYspm&d4|n@L_zji`zUWxsM}|=@R}BFfT2P!uJcrQf81WG z;7~y_$uMK=ih(2hrfqIGOzb(81e}^7h$dQ*w9&zG_k*kV{ml>Dkn2!p9tb_+Sa82P zf!TC+{4a(i^7UC$53;w?sleb~lFWqeCjv5msi}#JQ!wJtA>=k~`WL0M{^a9PG3%vT z6x=jB0{7wX7$gs%H}xJ&s+hHnzrl#L*=KB8OZd%sPoxKs(`;%|I$(^;nFYa4Cg|3D zmbQ)m6I_Y@t)A~{YBRo!2sYI^n!q)$tPp|m&n1BkYVmX22Z+nY#4N{Bb0!Ko=DOhh z8)8*=>e(W&-%LSWUN;u45Wex{{R747!a~45S>12$wNc{9N95&r%gU+b#-B7PcF%`_ zbDPAsmvpVBsQpf}s{igh23+1)`QSj71!|zjij@kvxgob&J{E97Lwu==Z)RY-lujF1 zts{7+jfS(K5+clZ(CY~%ks(F!=cb)YtqEu(dp_7=A?O!zz8KONrrma{eU-54%}Dm| zMb0!-=YUH?S7JzBX|TVr;=fB(8}a+Mcip|v&=pAeFMCaHj_Nkl!sWeZSb#k<%oczm z#`lGsgJHo7RywsRYYQs4O`J_C=fARQ$)B1peZk)|&ULCaa#RJ45lrml54sxO!CCv< zACe-^PSoZc!)x$#iZa*NuMlS%Jd!_x9|UdgLzlGyF0cI$EUFG4O;L+8*+s;KNL-ld z?R+O)guOt(>{+*e-+_A{1MBbRn&>53j=33ngVZ*A9^^??x8!ww@-m%DVVPmliJh;B zA?gVg!0|Rs7)?hBD^!lSxbI8;-8Q65B4DKw29-K9_w0glvBA&vz=a(hBCWqSnbKS0 zUg%$!iEY%1jOqivHBW;uSX*e&(J!Yr7cborEc&_4TQAAt(Hs@99pynWwVQc-PD)!b zEAfVEq-cX>10nj+=mUt(v;j?>9`bLJayfOcTYEOojVJwg!qg=XHGM AonnJPa; zUJ!+pYTulTHW%^S;&|h~V3suNSc{q3^zg~L0z(5QQ;Fz}<5*7QiE`G{EY!_Bq6Tf3 z#Y6<%5EL^6+vT44<%^2!TOb&Drb?#eUqR@vqcv Ad=l_6n*oWcLU38eLio z&XA9a$>+}Po Z&n7&1;j$MfqAp&SK~ziPsl|%{|CWXWM9wxyVKXe0%lk}rDC8g z8X@%6X|;SG;muLTK4d!cPgVxqjvaX=-$(Q65p5S*rI%=0cH7U(J{e1RPLJ7=nOmA) zMlRB`!r37ZXhzV+&X?quSyu}sbAn^a+S992*Te=%QW1izNzH-(Fc!u`0^%jIwx-q{ zjJ$P>vDS90xVX3yM??JQE(8|%*Ent^LOWJSOM1DpOGR5rG_7xH(O_S iI zQPhe?AtaSr$aWQDFB=s4vG} 6A7sKS9#`*O?Gvb$VpNFveZ{M$e6gN?k z BAf6x 8lMv8irB7O2F*?SxjQ+G9(Zzcf(-v6B#Che%7km*jk@ z)2}#vcILe$u75B8OqP#aD^OyEpX+8%bA;T*9+xPtBOA56r>VBH?W|l@4D*s*oHF7b zKiEI(=9Q&zzKDNu(c_-(iYp|O=RX90e|T*1D)Vi}F|XXxwzlFY%vI5oyr@gp+zfor zE{L0=4=<&pTg$Vb2&yaL(=zg-A=-V)<6G@}QKeym;mw^FzryGI(YX6E{x5!pKKNFb zX2wUTC}&?H`qv0{Ouyp!O!9>BD+&bp+x5*hFxlEJ|Jlx!dC36CiNWcOOOUw5NPT2n zckQz+nHS7$v`1`e33@@emu_-PmpnE%>A~wldBhO+8|uKd(C XF1LguU>p-iuo+6+#A(zwt<~}iz8;e zi$`F >cJ*M;o0PM7dMP=uB26set3i}BC!lE@>Gk`4oZQIG&&(O{wh_khwAz^jz zLMdgg*JfCk1{LlNW)C?WLX_!#5OsEIb3ZPWV7*KBWoBhmt&{(fw|eI)9LZTDrF;Cm zrRI0DXcArT*)L<`{Gy!R-`j)ca2)6Ks~48Jcl^Qg{XgWYyo6RpJj`Aq>-T>){#|lR zRPY`?<2vJ#s7v8mNz1zwnz@<9ofov5TnYTqj(PJN^Hv0N1N6rZY2Q2ixJ9IY`5B)j z?o!|2DLA8bc-{QD-^}@UP_JB`BjVr};f3o#5P`$++U2>eVvNM%RKxPV7J0hzme%(z zR7M~;#x= }vL&%^k)1dkFp)ApEinI%CXma_IcfN1= zghNTqbv$mD$mXwAWysU;hUAFR0^jhAYjE}TV=j$O0>v_@{)|7er^HCFN$j4D(Rxa+ zr>@Me?gS|z Vlda*cn+sM7^g8|~YJlBlxK`p<| zo$B!mr$%Z4An3pBbh@BK4Hi-E7l^3GMOiG?^~~z1Oxn$0PAR&}&*9D$O)(_>aB04e z*{ihG%K2UZE9c%O@J$1R+qtuhVW+Li7>Bw~LBLxQ_2GJ6dWmr`sMzGzRfiKQrm?9I zR~`S8uz0=lw5lTY3!?lQ|2LJNx(Ly%0Hkj_Q0C+f8>^@`ot4vM)#Bo9*u)9;#4lPQ zkD$dnQJ;T3;cR_9pRiRuc^MkgYiS>6*;09uV{z*IYw3#i;TH$m(R{* 3w>BS-cM7T<{u?6<8}o91iDU^B)<6wJwL{eG{=U+MNz z>#f)F`15Bnp|A(04!41E4ixt89MvouKW88SEk-A`6{3;V9M)Ips3VNFol3u5WiBmL ze0Uor5Z+x~NDGz=5gd!i#D5L)gN!7;`5bPc*8~;4hQOzIJ_RM07TD_cA!r1XISg_x z%9r&%6tsJq$>~|UQ1|7AZe{Oeu!2V&rjYX=>T-qb@S?3(7FC=Z^XOYf24G=+FJR;^ z&+s!YCtoncOWkA~zS!&wfYTiV$WJeR&@pINr7!v$Vw3}H92S?Mj>$ckH9eSo qhxli^L9 zl6?;LH$mT|@_S}#35}P!_7@h%=& u7n2PH0zl8K6L4SX!;*Nkxnnt~qhgVoG_|@w$t9uwee?p`9lo MG zr|Qqo!ws?ZaVp;+zT!zH^@xtf^zzvEF*EJK-3h dBe&e4hTya+V7cwy9k?-&u+1W$J9MsjiXQu0{sN!(0)p=yn;5R~ zm8G1M$wClU4oHZeWuEucT>8fj9@#M0kY>Zjx}{F%fX>qa5#{2}lM>g}Xnjo}l|ew8 zkXA5h=I9hvEufUW_wOT8b^(DlBKCuM+=VI>J`Ua;1OioQTVInOmu*pv>=0&M>MOS| z%x%82SVXH|##aK|&I9 wXCi2Kuz8@~`}P*VwE0=zPr%s5aHvFP` FsjEx2cBo)6ex*A zWp5GPoq0Vy74R>2aPlQP>~oZKw3$U(jAdy#E}=(clqiqe%$7=zb#t -GOC`@<-LJz{!m%n21KVT2lg4>F^Qyl9E2SvvZNE^Kq<8~8z*~izg_2G$e)DWZ z&r)^t$fjc4=0*E2GgW8V@;;-uQTLpkoe4G&6_Gi{=*bj1demc_{W*z@M)N3w-y!I2 zxt>0g2bLTSCr87lvU@@ ?w=y0(8-&vH2iDYp1oVatM3hj{k zTI09~y|)(A+XuR&rxolH&~6OyHuw;ul gO_ zPuTLyiVw)P|B03nB7klGZ1SdadQT)(_wcJpUd5Dw*Tl^3%=>G;G`B&%wwFm(MjZi# zMzuQuU>R1Zq8as9MkmM~4%8aV4m60Cl4X`?$zw27Nx(x@)C3hiNs$loyeJV|;3R`m z=2BoxiLeZq ;~pUpKfO}+8=>;xkRT&Wh?xRT*$vA=e1-1-a(LQ&8&RQ!R;p| z0{dFY6Iuv97U8}VgGV$6PB!6w5}-jehsz>M8R?2d0-?1=c9Ek)8Yhh)!3TZPk1>d^py>9{d~my1NBGJ)ypHC;!FbEqzyVi zu?k`sqbi!2$c8~?{{=5xCd5}QNx$~UD2(hV0{VWx-}##X2uo*=a!4(~o_<3lOh;=1 zGWy!R&!cXBeOPdKzslPq+FOzt2P)Y6SL*2}8s1q7(#-PEp*Wm`{7r`W-T4WD{gKfb zL=!WtyH86@TGc=5%hW+QVgF5lmp6`bUz|y3kvDq8cEX#Zcon0xK`W6icDQ>?Gb=4k zx9`mayKC`XvhQ;fwwljzxg#~7>oUV^PafLCvQ3GNmYh3%udW9gpP}zdP01_?V#F|} zu+6A+v$!2@w>!LQS}H tz#xrDTMCHF(viHn9B@`r*AN^Uh^K1dYX%OU(L;QO-NS7sm zB}n&5G=+cvZdostK MXC?^Pljs93+p|U_TbCD$_YFH_al)C6D--qOJJg^-4S{e(_Bh(hqonQpIAR3 zLn22yQovcP8^(~lYa;Iw1iN45bC1LAyPgyMn!Us#kC~Od)l{8iBF=vyb{%q5Uo|At z`GioU@7{~W>87(`5`y7oUan|z+y9y6kLnnMdpTsu WXtd+^OE@Rc1&DlS#6q{VJQ~^2R25csGlWAI6%1)G(k1hy(%a6 zP8;j(?t{iGcAAzn*N4^9x1BG`9YQD?lsKuJE}E(!LRb-C04hKL&@?*uDt+rmq#F+E zy;MAG%p~MH`3$_n9%+YIg%-3+vV)5OcqKaeQuCmrhtqvaxZ!JAr|$dSF%)+`Yvoou zOSNuZL?Y9b&gUmyj|pfc5HOzcO#wTn_4)qhXWH?-2h*_V$bXFzOAO}R;U0Utm6jK1 zARXYF88&Au<4 |bU zjIqU6CietjeFXz>A`VLxAln~?Tc3Z$!7ZUwvHhxe6;yAIYyV5DChijA_*mxgWa1Hf zpMe^m_ zi=Br9$|jmRXy`ALU7%BL%h!;kp0u2jEG>Y(3_SumS4~Ap=R2K`FOb*E9xFaK2xw@q5)FC9ki5__UGG^ChH* zg8T@CWK(2ZAhn)tl(@xrQ|@?sJZYbg? wPRykjvXSzBgO!5l;~}n=Vx=*>!3~hpG!QO_vZ7nOf(H%X8Zyf5zQI9<;& VgO`J^g!d%ci*Gayzi9E zzV{ggWXFU OwfXv^Cu9g;LXloZZQq$>osapDJ&dlE+FA zOAq0EeuKAV6~J_=V4ai?3X&T(A2S-Y-bb`Ai`xZ-D`VrnQ>pAdiPR0)l-S!eWp};M zhdf*YpjTWa+F;wAvaF(x6TW7LroZ>f%xX1B>ku{kH y23f4Gr*{SyBzch&H417J0V$b=yDLEIl7<2;YbKQ&{=ZOVvMR0}AxP zsmR+tme$kQHP;7Yn9&3eFJljv567buHH|D~F|nOk <45BcE*rk)#MT#RvWplVxMlzpi*dmU?7Pzz{?ICX{O>V+&4<<0nM?$Lv!< z{{&h7Y~PWt<4vpbwbt~V%}B#ex!UuMNkFpu+|fcYCeeV 7@q6?=qp|+- z^F2j+>w(o9IZ#i9MKt?we*u>AF^=)GwlEo-<8)ZN sl`DO9Ts^3mN?;` zpu-&&=Gn~8C2og^of_Emg!Z)!`}l6?zCnvZ2)$RRO7E_te3B9iY#R5%#LUxR2a$64 zRNuv={A!3W0>=Vd9-Gygqi!GqnO4Wu*hSIx$FOH*78(*CzB@93|C9L^)cR86oytQX zz(VBa;uz&eA4;0&+0T7h>1okMFU4QmpaK8N1A2wlN0S5ncCO%AcYgA${c!kFQ+TiA zSE{2T+HSjei*$%Ai4A}4W1S3}-mXNa1B^jTL+B i w<*SD;pmpz7SdmFu%Z231W zkED`=rBr|FkuV%mCW~b>XQTCw%K0Clxj&QGIm4o%6lpuc4OgwWW ^N>I z$CiUaixkCEQf)R*DBF6P&%z|)%AGchvGhBH3v_5YPKL6o6gDG~@`ZoTScT$`HQPz7 zQiqt q$|yTKXN%7 zSaCG2Ucn>5 0Z`>XxJnz6%(tP lqY9dGm@zHtV2!nWMmS!~Ac!e66 nI-(6fh>Qh>8n)+v%wQv>T#tc54h zB%~5--xs;qRhX+bIms&XJP;?K$K2_5H1EpFn-*GyZaD5sGDZ&n5P~FndmWj1xxfxb zSocm{R9OVmD?CfFE;Oebf@%V^7{ZETZUhZ?GM(@uT|gImuIH#AeMtxlE^*teXWH`b z$LnM8?Q_|v jv^u(kO-Y$cB1?ICmH@j5PY(q zaPxf3LgA{hO>D7{M2?XnUpAsX?0!P#eL3cHStcyY4^PB2N&Y`}U05UvjiREStj@u{ z|B)ET + + 64dp + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..37da8ab --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,24 @@ + ++ \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..2cad2c2 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + ++ +- http://ww4.sinaimg.cn/large/006uZZy8jw1faic1xjab4j30ci08cjrv.jpg
+- http://ww4.sinaimg.cn/large/006uZZy8jw1faic21363tj30ci08ct96.jpg
+- http://ww4.sinaimg.cn/large/006uZZy8jw1faic259ohaj30ci08c74r.jpg
+- http://ww4.sinaimg.cn/large/006uZZy8jw1faic2b16zuj30ci08cwf4.jpg
+- http://ww4.sinaimg.cn/large/006uZZy8jw1faic2e7vsaj30ci08cglz.jpg
++ +- http://img.zcool.cn/community/01b72057a7e0790000018c1bf4fce0.png
+- http://img.zcool.cn/community/01fca557a7f5f90000012e7e9feea8.jpg
+- http://img.zcool.cn/community/01996b57a7f6020000018c1bedef97.jpg
+- http://img.zcool.cn/community/01700557a7f42f0000018c1bd6eb23.jpg
++ +- XLog日志打印用法预览
+- XPermission Android M 权限动态申请用法
+- XRecyclerViewAdapter 用法
+- XLoadingDialog 加载等待对话框用法
+- XToast 自定义Toast用法
+- XLoadingView 页面多状态布局
++ diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..f4dcfb7 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ +@color/main_color +@color/main_color +@color/main_color +#F5F5F5 +#3CB264 +#ffffff +#000000 +#EDEDED ++ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..72e9339 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ +0.5dp + +16dp +16dp ++ diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ +XFrame框架 + ++ + + + + diff --git a/app/src/test/java/com/youth/xframe/ExampleUnitTest.java b/app/src/test/java/com/youth/xframe/ExampleUnitTest.java new file mode 100644 index 0000000..598a866 --- /dev/null +++ b/app/src/test/java/com/youth/xframe/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.youth.xframe; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d9b961b --- /dev/null +++ b/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.2.3' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + maven { url "https://jitpack.io" } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9714ac6 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app', ':xframe' diff --git a/xframe/.gitignore b/xframe/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/xframe/.gitignore @@ -0,0 +1 @@ +/build diff --git a/xframe/build.gradle b/xframe/build.gradle new file mode 100644 index 0000000..8a09547 --- /dev/null +++ b/xframe/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:25+' + provided 'com.android.support:recyclerview-v7:25+' +} diff --git a/xframe/proguard-rules.pro b/xframe/proguard-rules.pro new file mode 100644 index 0000000..f90d4d0 --- /dev/null +++ b/xframe/proguard-rules.pro @@ -0,0 +1,39 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\android\android-sdk-windows/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-ignorewarnings + +-keep public class **.R$* { public static final int *; } +-keep public class * extends android.app.Activity +-keep public class * extends android.support.v4.app.Fragment +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.view.View +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator *; } + +# 继承实现此接口的不予混淆 +-keep public interface com.yuzhi.fine.common.NotObfuscateInterface{public *;} +-keep class * implements com.yuzhi.fine.common.NotObfuscateInterface{ +; + ; +} \ No newline at end of file diff --git a/xframe/src/androidTest/java/com/youth/xframe/ExampleInstrumentedTest.java b/xframe/src/androidTest/java/com/youth/xframe/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f46f2cc --- /dev/null +++ b/xframe/src/androidTest/java/com/youth/xframe/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.youth.xframe; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.youth.baseframe.test", appContext.getPackageName()); + } +} diff --git a/xframe/src/main/AndroidManifest.xml b/xframe/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b70cdb4 --- /dev/null +++ b/xframe/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + diff --git a/xframe/src/main/java/com/youth/xframe/XFrame.java b/xframe/src/main/java/com/youth/xframe/XFrame.java new file mode 100644 index 0000000..b70dc39 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/XFrame.java @@ -0,0 +1,93 @@ +package com.youth.xframe; + + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.DrawableRes; +import android.util.DisplayMetrics; +import android.widget.ImageView; + +import com.youth.xframe.base.XImageLoader; +import com.youth.xframe.utils.XDensityUtils; +import com.youth.xframe.utils.log.XLogConfig; +import com.youth.xframe.utils.log.XLog; + +public class XFrame { + private static Context context; + public static int screenHeight; + public static int screenWidth; + + // #log + public static String tag = "XFrame"; + public static boolean isDebug = true; + + private static XImageLoader imageLoader; + + public XFrame() { + } + + public static void init(Context context) { + XFrame.context = context; + screenHeight = XDensityUtils.getScreenHeight(); + screenWidth = XDensityUtils.getScreenWidth(); + } + + public static XLogConfig initXLog() { + return XLog.init(); + } + + public static void setImageLoader(XImageLoader imageLoader) { + XFrame.imageLoader=imageLoader; + } + public static void load(ImageView imageView, Object imageUrl) { + if (XFrame.imageLoader==null) + throw new NullPointerException("Call XFrame.setImageLoader(XImageLoader imageLoader) within your Application onCreate() method." + + "Or extends XApplication"); + else + XFrame.imageLoader.load(XFrame.context,imageView,imageUrl); + } + public static Context getContext() { + synchronized (XFrame.class) { + if (XFrame.context == null) + throw new NullPointerException("Call XFrame.init(context) within your Application onCreate() method." + + "Or extends XApplication"); + return XFrame.context.getApplicationContext(); + } + } + + public static Resources getResources() { + return XFrame.getContext().getResources(); + } + + public static Resources.Theme getTheme() { + return XFrame.getContext().getTheme(); + } + + public static AssetManager getAssets() { + return XFrame.getContext().getAssets(); + } + + public static Drawable getDrawable( @DrawableRes int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + return XFrame.getContext().getDrawable(id); + else + return XFrame.getResources().getDrawable(id); + } + + public static Object getSystemService(String name){ + return XFrame.getContext().getSystemService(name); + } + + public static Configuration getConfiguration() { + return XFrame.getResources().getConfiguration(); + } + + public static DisplayMetrics getDisplayMetrics() { + return XFrame.getResources().getDisplayMetrics(); + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/adapter/BaseRecyclerViewAdapter.java b/xframe/src/main/java/com/youth/xframe/adapter/BaseRecyclerViewAdapter.java new file mode 100644 index 0000000..8b3e2cf --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/adapter/BaseRecyclerViewAdapter.java @@ -0,0 +1,150 @@ +package com.youth.xframe.adapter; + +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public abstract class BaseRecyclerViewAdapter+ + + +extends RecyclerView.Adapter { + + protected List dataLists; + + public BaseRecyclerViewAdapter(){ + this(new ArrayList ()); + } + + public BaseRecyclerViewAdapter(@NonNull List dataLists){ + this.dataLists = dataLists; + } + + /** + * 获取指定位置的数据 + * @param position + * @return + */ + public T getItem(int position){ + return dataLists.get(position); + } + + /** + * 获取数据集合 + * @return + */ + public List getDataLists(){ + return dataLists; + } + + /** + * 设置全新的数据集合,如果传入null,则清空数据列表(会清空以前的集合数据) + * @param datas + */ + public void setDataLists(List datas) { + dataLists.clear(); + if (datas != null && !datas.isEmpty()) { + dataLists.addAll(datas); + } + notifyDataSetChanged(); + } + /** + * 添加数据条目 + * + * @param data + */ + public void add(T data) { + dataLists.add(data); + notifyDataSetChanged(); + } + /** + * 在指定位置添加数据条目 + * + * @param position + * @param data + */ + public void add(int position, T data) { + dataLists.add(position,data); + notifyDataSetChanged(); + } + /** + * 添加数据条目集合 + * + * @param datas + */ + public void addAll(List datas) { + dataLists.addAll(datas); + notifyDataSetChanged(); + } + /** + * 在指定位置添加数据条目集合 + * + * @param position + * @param datas + */ + public void addAll(int position,List datas) { + dataLists.addAll(position,datas); + notifyDataSetChanged(); + } + /** + * 删除指定索引数据条目 + * + * @param position + */ + public void remove(int position) { + dataLists.remove(position); + notifyDataSetChanged(); + } + /** + * 删除指定数据条目 + * + * @param data + */ + public void remove(T data) { + dataLists.remove(data); + notifyDataSetChanged(); + } + + + /** + * 替换指定索引的数据条目 + * + * @param location + * @param newData + */ + public void replace(int location, T newData) { + dataLists.set(location, newData); + notifyDataSetChanged(); + } + + /** + * 替换指定数据条目 + * + * @param oldData + * @param newData + */ + public void replace(T oldData, T newData) { + replace(dataLists.indexOf(oldData), newData); + } + + /** + * 交换两个数据条目的位置 + * + * @param fromPosition + * @param toPosition + */ + public void move(int fromPosition, int toPosition) { + Collections.swap(dataLists, fromPosition, toPosition); + notifyDataSetChanged(); + } + + /** + * 清空 + */ + public void clear() { + dataLists.clear(); + notifyDataSetChanged(); + } + + +} diff --git a/xframe/src/main/java/com/youth/xframe/adapter/XRecyclerViewAdapter.java b/xframe/src/main/java/com/youth/xframe/adapter/XRecyclerViewAdapter.java new file mode 100644 index 0000000..4fb509b --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/adapter/XRecyclerViewAdapter.java @@ -0,0 +1,293 @@ +package com.youth.xframe.adapter; + +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.youth.xframe.R; + +import java.util.ArrayList; +import java.util.List; + + +public abstract class XRecyclerViewAdapter extends BaseRecyclerViewAdapter { + // 用来存放底部和头部View的集合 比Map要高效一些 + private SparseArray mHeaderViews= new SparseArray<>(); + private SparseArray mFooterViews= new SparseArray<>(); + private static int TYPE_HEADER = 0x100; + private static int TYPE_FOOTER = 0x200; + private static final int TYPE_LOAD_FAILED = 0x1; + private static final int TYPE_NO_MORE = 0x2; + private static final int TYPE_LOAD_MORE = 0x3; + private static final int TYPE_NO_VIEW = 0x4; + private RecyclerView mRecyclerView; + private View mLoadMoreView; + private View mLoadMoreFailedView; + private View mNoMoreView; + private int mLoadItemType = TYPE_NO_VIEW; + private OnLoadMoreListener onLoadMoreListener; + private LayoutInflater inflater; + private int layoutId; + private boolean isLoadError = false;//标记是否加载出错 + private boolean isHaveStatesView = false;//是否有加载更多 + private OnItemClickListener onItemClickListener; + private OnItemLongClickListener onItemLongClickListener; + + public XRecyclerViewAdapter(@NonNull RecyclerView mRecyclerView,List dataLists){ + this(mRecyclerView,dataLists, -1); + } + public XRecyclerViewAdapter(@NonNull RecyclerView mRecyclerView, List dataLists, @LayoutRes int layoutId) { + this.mRecyclerView = mRecyclerView; + this.dataLists = dataLists; + this.layoutId = layoutId; + this.inflater = LayoutInflater.from(mRecyclerView.getContext()); + } + + @Override + public int getItemViewType(int position) { + if (isHeaderPosition(position)) { + return mHeaderViews.keyAt(position); + } + if (isLoadPosition(position)) { + return mLoadItemType; + } + if (isFooterPosition(position)) { + position = position - getHeaderCount() - getDataCount(); + return mFooterViews.keyAt(position); + } + position= position - getHeaderCount(); + return getItemLayoutResId(getItem(position), position); + } + + @Override + public XViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (isHeaderViewType(viewType)) { + return new XViewHolder(mHeaderViews.get(viewType)); + } + + if (isFooterViewType(viewType)) { + return new XViewHolder(mFooterViews.get(viewType)); + } + if (viewType == TYPE_NO_MORE) { + mNoMoreView=inflater.inflate(R.layout.adapter_no_more, mRecyclerView, false); + return new XViewHolder(mNoMoreView); + } + if (viewType == TYPE_LOAD_MORE) { + mLoadMoreView=inflater.inflate(R.layout.adapter_loading, mRecyclerView, false); + return new XViewHolder(mLoadMoreView); + } + if (viewType == TYPE_LOAD_FAILED) { + mLoadMoreFailedView=inflater.inflate(R.layout.adapter_loading_failed, mRecyclerView, false); + return new XViewHolder(mLoadMoreFailedView); + } + return new XViewHolder(inflater.inflate(viewType, parent, false)); + } + + @Override + public void onBindViewHolder(final XViewHolder holder, int position) { + if (holder.getItemViewType() == TYPE_LOAD_FAILED) { + mLoadMoreFailedView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onLoadMoreListener != null) { + onLoadMoreListener.onRetry(); + isLoadMore(true); + } + } + }); + return; + } + if (holder.getItemViewType()==TYPE_LOAD_MORE) { + if (onLoadMoreListener != null && isHaveStatesView) { + if (!isLoadError) { + onLoadMoreListener.onLoadMore(); + } + } + return; + } + if (isFooterPosition(position)||isHeaderPosition(position)) return; + + final int finalPosition = position - getHeaderCount(); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(holder.itemView, finalPosition); + } + } + }); + + holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (onItemLongClickListener != null) { + return onItemLongClickListener.onItemLongClick(holder.itemView, finalPosition); + } + return false; + } + }); + + bindData(holder, getItem(finalPosition), finalPosition); + } + protected abstract void bindData(XViewHolder holder, T data, int position); + public int getItemLayoutResId(T data, int position){ + return layoutId; + } + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); + if(manager instanceof GridLayoutManager){ // 布局是GridLayoutManager所管理 + final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager; + gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + // 如果是Header、Footer的对象则占据spanCount的位置,否则就只占用1个位置 + return (isHeaderPosition(position)||isFooterPosition(position)) ? gridLayoutManager.getSpanCount() : 1; + } + }); + } + } + + + @Override + public void onViewAttachedToWindow(XViewHolder holder) { + super.onViewAttachedToWindow(holder); + int position = holder.getLayoutPosition(); + if (isFooterPosition(position)||isHeaderPosition(position)) { + ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); + if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { + StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; + p.setFullSpan(true); + } + } + } + + @Override + public int getItemCount() { + return getDataCount()+ getHeaderCount() + getFooterCount()+ (isHaveStatesView ? 1 : 0); + } + + public int getDataCount() { + return dataLists.size(); + } + + public int getHeaderCount() { + return mHeaderViews.size(); + } + + public int getFooterCount() { + return mFooterViews.size(); + } + + public void isLoadMore(boolean isHaveStatesView) { + mLoadItemType = TYPE_LOAD_MORE; + isLoadError = false; + this.isHaveStatesView = isHaveStatesView; + notifyItemChanged(getItemCount()); + } + + public void showLoadError() { + mLoadItemType = TYPE_LOAD_FAILED; + isLoadError = true; + isHaveStatesView = true; + notifyItemChanged(getItemCount()); + } + + public void showLoadComplete() { + mLoadItemType = TYPE_NO_MORE; + isLoadError = false; + isHaveStatesView = true; + notifyItemChanged(getItemCount()); + } + + @Override + public void setDataLists(List datas) { + mLoadItemType = TYPE_LOAD_MORE; + super.setDataLists(datas); + } + + private boolean isFooterViewType(int viewType) { + int position = mFooterViews.indexOfKey(viewType); + return position >= 0; + } + + private boolean isHeaderViewType(int viewType) { + int position = mHeaderViews.indexOfKey(viewType); + return position >= 0; + } + + private boolean isFooterPosition(int position) { + return position >= (getHeaderCount() + getDataCount()); + } + private boolean isLoadPosition(int position) { + return position == getItemCount() -1 && isHaveStatesView; + } + private boolean isHeaderPosition(int position) { + return position < getHeaderCount(); + } + + public void addHeaderView(View view) { + int position = mHeaderViews.indexOfValue(view); + if (position < 0) { + mHeaderViews.put(TYPE_HEADER++, view); + } + notifyDataSetChanged(); + } + + public void addFooterView(View view) { + int position = mFooterViews.indexOfValue(view); + if (position < 0) { + mFooterViews.put(TYPE_FOOTER++, view); + } + notifyDataSetChanged(); + } + + public void removeHeaderView(View view) { + int index = mHeaderViews.indexOfValue(view); + if (index < 0) return; + mHeaderViews.removeAt(index); + notifyDataSetChanged(); + } + + public void removeFooterView(View view) { + int index = mFooterViews.indexOfValue(view); + if (index < 0) return; + mFooterViews.removeAt(index); + notifyDataSetChanged(); + } + + public XRecyclerViewAdapter setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) { + this.onLoadMoreListener = onLoadMoreListener; + return this; + } + public XRecyclerViewAdapter setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + return this; + } + + public XRecyclerViewAdapter setOnItemLongClickListener(OnItemLongClickListener onItemLongClickListener) { + this.onItemLongClickListener = onItemLongClickListener; + return this; + } + public interface OnLoadMoreListener { + void onRetry(); + void onLoadMore(); + } + public interface OnItemClickListener { + void onItemClick(View v, int position); + } + + public interface OnItemLongClickListener { + boolean onItemLongClick(View v, int position); + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/adapter/XViewHolder.java b/xframe/src/main/java/com/youth/xframe/adapter/XViewHolder.java new file mode 100644 index 0000000..ef28ca9 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/adapter/XViewHolder.java @@ -0,0 +1,238 @@ +package com.youth.xframe.adapter; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.IdRes; +import android.support.annotation.LayoutRes; +import android.support.v7.widget.RecyclerView; +import android.text.util.Linkify; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.widget.Checkable; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RatingBar; +import android.widget.TextView; + +import com.youth.xframe.XFrame; + + +public class XViewHolder extends RecyclerView.ViewHolder { + + private SparseArray viewArray = new SparseArray<>(); + + /** + * 构造ViewHolder + * + * @param parent 父类容器 + * @param resId 布局资源文件id + */ + public XViewHolder(ViewGroup parent, @LayoutRes int resId) { + super(LayoutInflater.from(parent.getContext()).inflate(resId, parent, false)); + } + + /** + * 构建ViewHolder + * + * @param view 布局View + */ + public XViewHolder(View view) { + super(view); + } + + /** + * 获取布局中的View + * + * @param viewId view的Id + * @param View的类型 + * @return view + */ + public T getView(@IdRes int viewId) { + View view = viewArray.get(viewId); + if (view == null) { + view = itemView.findViewById(viewId); + viewArray.put(viewId, view); + } + return (T) view; + } + + + public View getConvertView() { + return itemView; + } + + /****以下为辅助方法*****/ + + /** + * 设置TextView的值 + * + * @param viewId + * @param text + * @return + */ + public XViewHolder setText(@IdRes int viewId, String text) { + TextView tv = getView(viewId); + tv.setText(text); + return this; + } + + public XViewHolder setImageResource(@IdRes int viewId, int resId) { + ImageView view = getView(viewId); + view.setImageResource(resId); + return this; + } + + public XViewHolder setImageBitmap(@IdRes int viewId, Bitmap bitmap) { + ImageView view = getView(viewId); + view.setImageBitmap(bitmap); + return this; + } + + public XViewHolder setImageDrawable(@IdRes int viewId, Drawable drawable) { + ImageView view = getView(viewId); + view.setImageDrawable(drawable); + return this; + } + + public XViewHolder setBackgroundColor(@IdRes int viewId, int color) { + View view = getView(viewId); + view.setBackgroundColor(color); + return this; + } + + public XViewHolder setBackgroundRes(@IdRes int viewId, int backgroundRes) { + View view = getView(viewId); + view.setBackgroundResource(backgroundRes); + return this; + } + + public XViewHolder setTextColor(@IdRes int viewId, int textColor) { + TextView view = getView(viewId); + view.setTextColor(textColor); + return this; + } + + public XViewHolder setTextColorRes(@IdRes int viewId, int textColorRes) { + TextView view = getView(viewId); + view.setTextColor(XFrame.getResources().getColor(textColorRes)); + return this; + } + + @SuppressLint("NewApi") + public XViewHolder setAlpha(@IdRes int viewId, float value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + getView(viewId).setAlpha(value); + } else { + // Pre-honeycomb hack to set Alpha value + AlphaAnimation alpha = new AlphaAnimation(value, value); + alpha.setDuration(0); + alpha.setFillAfter(true); + getView(viewId).startAnimation(alpha); + } + return this; + } + + public XViewHolder setVisible(@IdRes int viewId, boolean visible) { + View view = getView(viewId); + view.setVisibility(visible ? View.VISIBLE : View.GONE); + return this; + } + + public XViewHolder linkify(@IdRes int viewId) { + TextView view = getView(viewId); + Linkify.addLinks(view, Linkify.ALL); + return this; + } + + public XViewHolder setTypeface(Typeface typeface, @IdRes int... viewIds) { + for (@IdRes int viewId : viewIds) { + TextView view = getView(viewId); + view.setTypeface(typeface); + view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG); + } + return this; + } + + public XViewHolder setProgress(@IdRes int viewId, int progress) { + ProgressBar view = getView(viewId); + view.setProgress(progress); + return this; + } + + public XViewHolder setProgress(@IdRes int viewId, int progress, int max) { + ProgressBar view = getView(viewId); + view.setMax(max); + view.setProgress(progress); + return this; + } + + public XViewHolder setMax(@IdRes int viewId, int max) { + ProgressBar view = getView(viewId); + view.setMax(max); + return this; + } + + public XViewHolder setRating(@IdRes int viewId, float rating) { + RatingBar view = getView(viewId); + view.setRating(rating); + return this; + } + + public XViewHolder setRating(@IdRes int viewId, float rating, int max) { + RatingBar view = getView(viewId); + view.setMax(max); + view.setRating(rating); + return this; + } + + public XViewHolder setTag(@IdRes int viewId, Object tag) { + View view = getView(viewId); + view.setTag(tag); + return this; + } + + public XViewHolder setTag(@IdRes int viewId, int key, Object tag) { + View view = getView(viewId); + view.setTag(key, tag); + return this; + } + + public XViewHolder setChecked(@IdRes int viewId, boolean checked) { + Checkable view = (Checkable) getView(viewId); + view.setChecked(checked); + return this; + } + + /** + * 关于事件的 + */ + public XViewHolder setOnClickListener(@IdRes int viewId, + View.OnClickListener listener) { + View view = getView(viewId); + view.setOnClickListener(listener); + return this; + } + + public XViewHolder setOnTouchListener(@IdRes int viewId, + View.OnTouchListener listener) { + View view = getView(viewId); + view.setOnTouchListener(listener); + return this; + } + + public XViewHolder setOnLongClickListener(@IdRes int viewId, + View.OnLongClickListener listener) { + View view = getView(viewId); + view.setOnLongClickListener(listener); + return this; + } + + +} diff --git a/xframe/src/main/java/com/youth/xframe/adapter/decoration/DividerDecoration.java b/xframe/src/main/java/com/youth/xframe/adapter/decoration/DividerDecoration.java new file mode 100644 index 0000000..8a6cdcb --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/adapter/decoration/DividerDecoration.java @@ -0,0 +1,133 @@ +package com.youth.xframe.adapter.decoration; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.OrientationHelper; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.View; + +import com.youth.xframe.adapter.XRecyclerViewAdapter; + +public class DividerDecoration extends RecyclerView.ItemDecoration{ + private ColorDrawable mColorDrawable; + private int mHeight; + private int mPaddingLeft; + private int mPaddingRight; + private boolean mDrawLastItem = true; + private boolean mDrawHeaderFooter = false; + + public DividerDecoration(int color, int height) { + this.mColorDrawable = new ColorDrawable(color); + this.mHeight = height; + } + public DividerDecoration(int color, int height, int paddingLeft, int paddingRight) { + this.mColorDrawable = new ColorDrawable(color); + this.mHeight = height; + this.mPaddingLeft = paddingLeft; + this.mPaddingRight = paddingRight; + } + + public void setDrawLastItem(boolean mDrawLastItem) { + this.mDrawLastItem = mDrawLastItem; + } + + public void setDrawHeaderFooter(boolean mDrawHeaderFooter) { + this.mDrawHeaderFooter = mDrawHeaderFooter; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); + int orientation = 0; + int headerCount = 0,footerCount = 0; + if (parent.getAdapter() instanceof XRecyclerViewAdapter){ + headerCount = ((XRecyclerViewAdapter) parent.getAdapter()).getHeaderCount(); + footerCount = ((XRecyclerViewAdapter) parent.getAdapter()).getFooterCount(); + } + + RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); + if (layoutManager instanceof StaggeredGridLayoutManager){ + orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation(); + }else if (layoutManager instanceof GridLayoutManager){ + orientation = ((GridLayoutManager) layoutManager).getOrientation(); + }else if (layoutManager instanceof LinearLayoutManager){ + orientation = ((LinearLayoutManager) layoutManager).getOrientation(); + } + + if (position>=headerCount&&position =dataStartPosition&&position =dataStartPosition&&position =headerCount&&position the header view holder + */ + public interface IStickyHeaderAdapter { + + /** + * Returns the header id for the item at the given position. + * The item in one group should return the same HeaderId. + * + * @param position the item position + * @return the header id + */ + long getHeaderId(int position); + + /** + * Creates a new header ViewHolder. + * + * @param parent the header's view parent + * @return a view holder for the created view + */ + T onCreateHeaderViewHolder(ViewGroup parent); + + /** + * Updates the header view to reflect the header data for the given position + * @param viewholder the header view holder + * @param position the header's item position + */ + void onBindHeaderViewHolder(T viewholder, int position); + } + + + public static final long NO_HEADER_ID = -1L; + + private Map mHeaderCache; + + private IStickyHeaderAdapter mAdapter; + + private boolean mRenderInline; + + private boolean mIncludeHeader = false; + + /** + * @param adapter + * the sticky header adapter to use + */ + public StickyHeaderDecoration(IStickyHeaderAdapter adapter) { + this(adapter, false); + } + + /** + * @param adapter + * the sticky header adapter to use + */ + public StickyHeaderDecoration(IStickyHeaderAdapter adapter, boolean renderInline) { + mAdapter = adapter; + mHeaderCache = new HashMap<>(); + mRenderInline = renderInline; + } + + public void setIncludeHeader(boolean mIncludeHeader) { + this.mIncludeHeader = mIncludeHeader; + } + + /** + * {@inheritDoc} + */ + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); + int headerHeight = 0; + + if (!mIncludeHeader){ + if (parent.getAdapter() instanceof XRecyclerViewAdapter){ + int headerCount = ((XRecyclerViewAdapter) parent.getAdapter()).getHeaderCount(); + int footerCount = ((XRecyclerViewAdapter) parent.getAdapter()).getFooterCount(); + int dataCount = ((XRecyclerViewAdapter) parent.getAdapter()).getDataCount(); + if (position =headerCount+dataCount){ + return ; + } + if (position>=headerCount){ + position-=headerCount; + } + + } + } + + if (position != RecyclerView.NO_POSITION + && hasHeader(position) + && showHeaderAboveItem(position)) { + + View header = getHeader(parent, position).itemView; + headerHeight = getHeaderHeightForLayout(header); + } + + outRect.set(0, headerHeight, 0, 0); + } + + private boolean showHeaderAboveItem(int itemAdapterPosition) { + if (itemAdapterPosition == 0) { + return true; + } + return mAdapter.getHeaderId(itemAdapterPosition - 1) != mAdapter.getHeaderId(itemAdapterPosition); + } + + /** + * Clears the header view cache. Headers will be recreated and + * rebound on list scroll after this method has been called. + */ + public void clearHeaderCache() { + mHeaderCache.clear(); + } + + public View findHeaderViewUnder(float x, float y) { + for (RecyclerView.ViewHolder holder : mHeaderCache.values()) { + final View child = holder.itemView; + final float translationX = ViewCompat.getTranslationX(child); + final float translationY = ViewCompat.getTranslationY(child); + + if (x >= child.getLeft() + translationX && + x <= child.getRight() + translationX && + y >= child.getTop() + translationY && + y <= child.getBottom() + translationY) { + return child; + } + } + + return null; + } + + private boolean hasHeader(int position) { + return mAdapter.getHeaderId(position) != NO_HEADER_ID; + } + + private RecyclerView.ViewHolder getHeader(RecyclerView parent, int position) { + final long key = mAdapter.getHeaderId(position); + + if (mHeaderCache.containsKey(key)) { + return mHeaderCache.get(key); + } else { + final RecyclerView.ViewHolder holder = mAdapter.onCreateHeaderViewHolder(parent); + final View header = holder.itemView; + + //noinspection unchecked + mAdapter.onBindHeaderViewHolder(holder, position); + + int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.UNSPECIFIED); + + int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, + parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width); + int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, + parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height); + + header.measure(childWidth, childHeight); + header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); + + mHeaderCache.put(key, holder); + + return holder; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + if (parent.getAdapter() == null){ + return; + } + + final int count = parent.getChildCount(); + long previousHeaderId = -1; + + for (int layoutPos = 0; layoutPos < count; layoutPos++) { + final View child = parent.getChildAt(layoutPos); + int adapterPos = parent.getChildAdapterPosition(child); + + if (!mIncludeHeader){ + if (parent.getAdapter() instanceof XRecyclerViewAdapter){ + int headerCount = ((XRecyclerViewAdapter) parent.getAdapter()).getHeaderCount(); + int footerCount = ((XRecyclerViewAdapter) parent.getAdapter()).getFooterCount(); + int dataCount = ((XRecyclerViewAdapter) parent.getAdapter()).getDataCount(); + if (adapterPos =headerCount+dataCount){ + continue ; + } + if (adapterPos>=headerCount){ + adapterPos-=headerCount; + } + + } + } + + if (adapterPos != RecyclerView.NO_POSITION && hasHeader(adapterPos)) { + long headerId = mAdapter.getHeaderId(adapterPos); + + if (headerId != previousHeaderId) { + previousHeaderId = headerId; + View header = getHeader(parent, adapterPos).itemView; + canvas.save(); + + final int left = child.getLeft(); + final int top = getHeaderTop(parent, child, header, adapterPos, layoutPos); + canvas.translate(left, top); + + header.setTranslationX(left); + header.setTranslationY(top); + header.draw(canvas); + canvas.restore(); + } + } + } + } + + private int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) { + int headerHeight = getHeaderHeightForLayout(header); + int top = ((int) child.getY()) - headerHeight; + if (layoutPos == 0) { + final int count = parent.getChildCount(); + final long currentId = mAdapter.getHeaderId(adapterPos); + // find next view with header and compute the offscreen push if needed + for (int i = 1; i < count; i++) { + int adapterPosHere = parent.getChildAdapterPosition(parent.getChildAt(i)); + if (adapterPosHere != RecyclerView.NO_POSITION) { + long nextId = mAdapter.getHeaderId(adapterPosHere); + if (nextId != currentId) { + final View next = parent.getChildAt(i); + final int offset = ((int) next.getY()) - (headerHeight + getHeader(parent, adapterPosHere).itemView.getHeight()); + if (offset < 0) { + return offset; + } else { + break; + } + } + } + } + + top = Math.max(0, top); + } + + return top; + } + + private int getHeaderHeightForLayout(View header) { + return mRenderInline ? 0 : header.getHeight(); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/base/ICallback.java b/xframe/src/main/java/com/youth/xframe/base/ICallback.java new file mode 100644 index 0000000..c4e3f39 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/base/ICallback.java @@ -0,0 +1,13 @@ +package com.youth.xframe.base; + +import android.os.Bundle; + + +public interface ICallback { + //返回布局文件id + int getLayoutId(); + //初始化数据 + void initData(Bundle savedInstanceState); + //初始化布局文件 + void initView(); +} diff --git a/xframe/src/main/java/com/youth/xframe/base/XActivity.java b/xframe/src/main/java/com/youth/xframe/base/XActivity.java new file mode 100644 index 0000000..8c2f13f --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/base/XActivity.java @@ -0,0 +1,44 @@ +package com.youth.xframe.base; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; + +import com.youth.xframe.common.XActivityStack; +import com.youth.xframe.utils.permission.XPermission; + + +public abstract class XActivity extends AppCompatActivity implements ICallback { + + @Override + protected void onCreate(Bundle savedInstanceState) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + XActivityStack.getInstance().addActivity(this); + if (getLayoutId()>0) { + setContentView(getLayoutId()); + } + initData(savedInstanceState); + initView(); + super.onCreate(savedInstanceState); + } + + /** + * Android M 全局权限申请回调 + * @param requestCode + * @param permissions + * @param grantResults + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] + grantResults) { + XPermission.onRequestPermissionsResult(requestCode, permissions, grantResults); + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + XActivityStack.getInstance().finishActivity(); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/base/XApplication.java b/xframe/src/main/java/com/youth/xframe/base/XApplication.java new file mode 100644 index 0000000..2da6eee --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/base/XApplication.java @@ -0,0 +1,24 @@ +package com.youth.xframe.base; + +import android.app.Application; + +import com.youth.xframe.XFrame; + + +public class XApplication extends Application { + private static XApplication instance; + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + XFrame.init(this); + } + + + public static XApplication getInstance() { + return instance; + } + + +} diff --git a/xframe/src/main/java/com/youth/xframe/base/XFragment.java b/xframe/src/main/java/com/youth/xframe/base/XFragment.java new file mode 100644 index 0000000..be1b0a7 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/base/XFragment.java @@ -0,0 +1,28 @@ +package com.youth.xframe.base; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public abstract class XFragment extends Fragment implements ICallback{ + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (getLayoutId()>0){ + return inflater.inflate(getLayoutId(),container,false); + }else { + return super.onCreateView(inflater, container, savedInstanceState); + } + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + initData(savedInstanceState); + initView(); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/base/XImageLoader.java b/xframe/src/main/java/com/youth/xframe/base/XImageLoader.java new file mode 100644 index 0000000..4781622 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/base/XImageLoader.java @@ -0,0 +1,16 @@ +package com.youth.xframe.base; + +import android.content.Context; +import android.widget.ImageView; + + +/** + * 框架全局图片加载配置 + * + * @author 王兴春 + * @email 1028729086@qq.com + * @time 2017/1/10 10:16 + */ +public interface XImageLoader { + void load(Context context, ImageView imageView, T imageUrl); +} diff --git a/xframe/src/main/java/com/youth/xframe/common/NotObfuscateInterface.java b/xframe/src/main/java/com/youth/xframe/common/NotObfuscateInterface.java new file mode 100644 index 0000000..1294f23 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/common/NotObfuscateInterface.java @@ -0,0 +1,6 @@ +package com.youth.xframe.common; +/** + * 实现或继承此接口的类,其共有属性和方法将不参与混淆 + */ +public interface NotObfuscateInterface { +} diff --git a/xframe/src/main/java/com/youth/xframe/common/XActivityStack.java b/xframe/src/main/java/com/youth/xframe/common/XActivityStack.java new file mode 100644 index 0000000..58fdbed --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/common/XActivityStack.java @@ -0,0 +1,139 @@ +package com.youth.xframe.common; + +import android.app.Activity; +import android.content.Context; + +import java.util.Stack; + + +/** + * 应用程序Activity管理类:用于Activity管理和应用程序退出 + */ +final public class XActivityStack { + private static Stack activityStack; + private static final XActivityStack instance = new XActivityStack(); + + private XActivityStack() { + } + + public static XActivityStack getInstance() { + return instance; + } + + /** + * 获取当前Activity栈中元素个数 + */ + public int getCount() { + return activityStack.size(); + } + + /** + * 添加Activity到栈 + */ + public void addActivity(Activity activity) { + if (activityStack == null) { + activityStack = new Stack<>(); + } + activityStack.add(activity); + } + + /** + * 获取当前Activity(栈顶Activity) + */ + public Activity topActivity() { + if (activityStack == null) { + throw new NullPointerException( + "Activity stack is Null,your Activity must extend XActivity"); + } + if (activityStack.isEmpty()) { + return null; + } + Activity activity = activityStack.lastElement(); + return activity; + } + + /** + * 获取当前Activity(栈顶Activity) 没有找到则返回null + */ + public Activity findActivity(Class> cls) { + Activity activity = null; + for (Activity aty : activityStack) { + if (aty.getClass().equals(cls)) { + activity = aty; + break; + } + } + return activity; + } + + /** + * 结束当前Activity(栈顶Activity) + */ + public void finishActivity() { + Activity activity = activityStack.lastElement(); + finishActivity(activity); + } + + /** + * 结束指定的Activity(重载) + */ + public void finishActivity(Activity activity) { + if (activity != null) { + activityStack.remove(activity); + // activity.finish();//此处不用finish + activity = null; + } + } + + /** + * 结束指定的Activity(重载) + */ + public void finishActivity(Class> cls) { + for (Activity activity : activityStack) { + if (activity.getClass().equals(cls)) { + finishActivity(activity); + } + } + } + + /** + * 关闭除了指定activity以外的全部activity 如果cls不存在于栈中,则栈全部清空 + * + * @param cls + */ + public void finishOthersActivity(Class> cls) { + for (Activity activity : activityStack) { + if (!(activity.getClass().equals(cls))) { + finishActivity(activity); + } + } + } + + /** + * 结束所有Activity + */ + public void finishAllActivity() { + for (int i = 0, size = activityStack.size(); i < size; i++) { + if (null != activityStack.get(i)) { + (activityStack.get(i)).finish(); + } + } + activityStack.clear(); + } + + + /** + * 应用程序退出 + */ + public void appExit() { + try { + finishAllActivity(); + //退出JVM(java虚拟机),释放所占内存资源,0表示正常退出(非0的都为异常退出) + System.exit(0); + //从操作系统中结束掉当前程序的进程 + android.os.Process.killProcess(android.os.Process.myPid()); + } catch (Exception e) { + System.exit(-1); + } + } +} \ No newline at end of file diff --git a/xframe/src/main/java/com/youth/xframe/entity/DateDifference.java b/xframe/src/main/java/com/youth/xframe/entity/DateDifference.java new file mode 100644 index 0000000..df22010 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/entity/DateDifference.java @@ -0,0 +1,54 @@ +package com.youth.xframe.entity; + + +/** + * 时间差计算结果封装类 + */ +public class DateDifference { + private long millisecond; + private long second; + private long minute; + private long hour; + private long day; + + public long getMillisecond() { + return millisecond; + } + + public void setMillisecond(long millisecond) { + this.millisecond = millisecond; + } + + public long getSecond() { + return second; + } + + public void setSecond(long second) { + this.second = second; + } + + public long getMinute() { + return minute; + } + + public void setMinute(long minute) { + this.minute = minute; + } + + public long getHour() { + return hour; + } + + public void setHour(long hour) { + this.hour = hour; + } + + public long getDay() { + return day; + } + + public void setDay(long day) { + this.day = day; + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XAppUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XAppUtils.java new file mode 100644 index 0000000..73b26f0 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XAppUtils.java @@ -0,0 +1,220 @@ +package com.youth.xframe.utils; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; + +import com.youth.xframe.XFrame; + +import java.io.File; +import java.security.MessageDigest; +import java.util.Iterator; +import java.util.List; + +/** + * 应用工具类. + */ +public class XAppUtils { + private static Context context=XFrame.getContext(); + /** + * 读取application 节点 meta-data 信息 + */ + public static String readMetaDataFromApplication(String key) { + try { + ApplicationInfo appInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA); + return appInfo.metaData.getString(key); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 描述:打开并安装文件. + * + * @param file apk文件路径 + */ + public static void installApk(File file) { + Intent intent = new Intent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(file), + "application/vnd.android.package-archive"); + context.startActivity(intent); + } + + /** + * 描述:卸载程序. + * + * @param packageName 包名 + */ + public static void uninstallApk(String packageName) { + Intent intent = new Intent(Intent.ACTION_DELETE); + Uri packageURI = Uri.parse("package:" + packageName); + intent.setData(packageURI); + context.startActivity(intent); + } + /** + * need < uses-permission android:name ="android.permission.GET_TASKS"/> + * 判断是否前台运行 + */ + public static boolean isRunningForeground() { + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List taskList = am.getRunningTasks(1); + if (taskList != null && !taskList.isEmpty()) { + ComponentName componentName = taskList.get(0).topActivity; + if (componentName != null && componentName.getPackageName().equals(context.getPackageName())) { + return true; + } + } + return false; + } + + /** + * 用来判断服务是否运行. + * + * @param className 判断的服务名字 "com.xxx.xx..XXXService" + * @return true 在运行 false 不在运行 + */ + public static boolean isServiceRunning(String className) { + boolean isRunning = false; + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + List servicesList = activityManager.getRunningServices(Integer.MAX_VALUE); + Iterator l = servicesList.iterator(); + while (l.hasNext()) { + ActivityManager.RunningServiceInfo si = (ActivityManager.RunningServiceInfo) l.next(); + if (className.equals(si.service.getClassName())) { + isRunning = true; + } + } + return isRunning; + } + + /** + * 停止服务. + * + * @param className the class name + * @return true, if successful + */ + public static boolean stopRunningService(String className) { + Intent intent = null; + boolean ret = false; + try { + intent = new Intent(context, Class.forName(className)); + } catch (Exception e) { + e.printStackTrace(); + } + if (intent != null) { + ret = context.stopService(intent); + } + return ret; + } + /** + * 获取PackageInfo + * @return PackageInfo + */ + public static PackageInfo getPackageInfo() { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = null; + try { + packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return packageInfo; + } + /** + * 获取版本号 + * String + * @return 当前应用的版本名称 + */ + public static String getVersionName(Context context) { + return getPackageInfo().versionName; + } + + /** + * 获取版本号 + * int + * @return 当前应用的版本号 + */ + public static int getVersionCode(Context context) { + return getPackageInfo().versionCode; + } + /** + * 获取应用签名 + * + * @param pkgName 包名 + * @return 返回应用的签名 + */ + public static String getSign(String pkgName) { + try { + PackageInfo pis = context.getPackageManager() + .getPackageInfo(pkgName, + PackageManager.GET_SIGNATURES); + return hexDigest(pis.signatures[0].toByteArray()); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return null; + } + + } + /** + * 将签名字符串转换成需要的32位签名 + * + * @param paramArrayOfByte 签名byte数组 + * @return 32位签名字符串 + */ + private static String hexDigest(byte[] paramArrayOfByte) { + final char[] hexDigits = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, + 98, 99, 100, 101, 102 }; + try { + MessageDigest localMessageDigest = MessageDigest.getInstance("MD5"); + localMessageDigest.update(paramArrayOfByte); + byte[] arrayOfByte = localMessageDigest.digest(); + char[] arrayOfChar = new char[32]; + for (int i = 0, j = 0; ; i++, j++) { + if (i >= 16) { + return new String(arrayOfChar); + } + int k = arrayOfByte[i]; + arrayOfChar[j] = hexDigits[(0xF & k >>> 4)]; + arrayOfChar[++j] = hexDigits[(k & 0xF)]; + } + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + /** + * 比较版本号的大小,前者大则返回一个正数,后者大返回一个负数,相等则返回0 支持4.1.2,4.1.23.4.1.rc111这种形式 + * @param version1 + * @param version2 + * @return + */ + public static int compareVersion(String version1, String version2) throws Exception { + if (version1 == null || version2 == null) { + throw new Exception("compareVersion xloading_error:illegal params."); + } + String[] versionArray1 = version1.split("\\.");//注意此处为正则匹配,不能用"."; + String[] versionArray2 = version2.split("\\."); + int idx = 0; + int minLength = Math.min(versionArray1.length, versionArray2.length);//取最小长度值 + int diff = 0; + while (idx < minLength + && (diff = versionArray1[idx].length() - versionArray2[idx].length()) == 0//先比较长度 + && (diff = versionArray1[idx].compareTo(versionArray2[idx])) == 0) {//再比较字符 + ++idx; + } + //如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大; + diff = (diff != 0) ? diff : versionArray1.length - versionArray2.length; + return diff; + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XBitmapUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XBitmapUtils.java new file mode 100644 index 0000000..d678adb --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XBitmapUtils.java @@ -0,0 +1,1482 @@ +package com.youth.xframe.utils; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader.TileMode; +import android.graphics.drawable.Drawable; +import android.media.ExifInterface; +import android.os.Build; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.view.View; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + + +/** + * Bitmap工具类主要包括获取Bitmap和对Bitmap的操作 + */ +public class XBitmapUtils { + + /** + * 图片压缩处理(使用Options的方法) + * + *
+ * 说明使用方法: + * 首先你要将Options的inJustDecodeBounds属性设置为true,BitmapFactory.decode一次图片 。 + * 然后将Options连同期望的宽度和高度一起传递到到本方法中。 + * 之后再使用本方法的返回值做参数调用BitmapFactory.decode创建图片。 + * + *
+ * 说明BitmapFactory创建bitmap会尝试为已经构建的bitmap分配内存 + * ,这时就会很容易导致OOM出现。为此每一种创建方法都提供了一个可选的Options参数 + * ,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存 + * ,返回值也不再是一个Bitmap对象, 而是null。虽然Bitmap是null了,但是Options的outWidth、 + * outHeight和outMimeType属性都会被赋值。 + * + * @param reqWidth 目标宽度,这里的宽高只是阀值,实际显示的图片将小于等于这个值 + * @param reqHeight 目标高度,这里的宽高只是阀值,实际显示的图片将小于等于这个值 + */ + public static BitmapFactory.Options calculateInSampleSize( + final BitmapFactory.Options options, final int reqWidth, + final int reqHeight) { + // 源图片的高度和宽度 + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + if (height > 400 || width > 450) { + if (height > reqHeight || width > reqWidth) { + // 计算出实际宽高和目标宽高的比率 + final int heightRatio = Math.round((float) height + / (float) reqHeight); + final int widthRatio = Math.round((float) width + / (float) reqWidth); + // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 + // 一定都会大于等于目标的宽和高。 + inSampleSize = heightRatio < widthRatio ? heightRatio + : widthRatio; + } + } + // 设置压缩比例 + options.inSampleSize = inSampleSize; + options.inJustDecodeBounds = false; + return options; + } + + /** + * 获取一个指定大小的bitmap + * + * @param res Resources + * @param resId 图片ID + * @param reqWidth 目标宽度 + * @param reqHeight 目标高度 + */ + public static Bitmap getBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight) { + // BitmapFactory.Options options = new BitmapFactory.Options(); + // options.inJustDecodeBounds = true; + // BitmapFactory.decodeResource(res, resId, options); + // options = BitmapHelper.calculateInSampleSize(options, reqWidth, + // reqHeight); + // return BitmapFactory.decodeResource(res, resId, options); + + // 通过JNI的形式读取本地图片达到节省内存的目的 + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Config.RGB_565; + options.inPurgeable = true; + options.inInputShareable = true; + InputStream is = res.openRawResource(resId); + return getBitmapFromStream(is, null, reqWidth, reqHeight); + } + + /** + * 获取一个指定大小的bitmap + * + * @param reqWidth 目标宽度 + * @param reqHeight 目标高度 + */ + public static Bitmap getBitmapFromFile(String pathName, int reqWidth, + int reqHeight) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(pathName, options); + options = calculateInSampleSize(options, reqWidth, reqHeight); + return BitmapFactory.decodeFile(pathName, options); + } + + /** + * 获取一个指定大小的bitmap + * + * @param data Bitmap的byte数组 + * @param offset image从byte数组创建的起始位置 + * @param length the number of bytes, 从offset处开始的长度 + * @param reqWidth 目标宽度 + * @param reqHeight 目标高度 + */ + public static Bitmap getBitmapFromByteArray(byte[] data, int offset, + int length, int reqWidth, int reqHeight) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(data, offset, length, options); + options = calculateInSampleSize(options, reqWidth, reqHeight); + return BitmapFactory.decodeByteArray(data, offset, length, options); + } + + /** + * 把bitmap转化为bytes + * + * @param bitmap 源Bitmap + * @return Byte数组 + */ + public static byte[] getBytesFromBitmap(Bitmap bitmap) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos); + return baos.toByteArray(); + } + + /** + * Stream转换成Byte + * + * @param inputStream InputStream + * @return Byte数组 + */ + public static byte[] getBytesFromStream(InputStream inputStream) { + ByteArrayOutputStream os = new ByteArrayOutputStream(1024); + byte[] buffer = new byte[1024]; + int len; + try { + while ((len = inputStream.read(buffer)) >= 0) { + os.write(buffer, 0, len); + } + } catch (IOException e) { + e.printStackTrace(); + } + return os.toByteArray(); + } + + /** + * 获取一个指定大小的bitmap + * + * @param b Byte数组 + * @return 需要的Bitmap + */ + public static Bitmap getBitmapFromBytes(byte[] b) { + if (b.length != 0) { + return BitmapFactory.decodeByteArray(b, 0, b.length); + } else { + return null; + } + } + + + /** + * 获取一个指定大小的bitmap + * + * @param is 从输入流中读取Bitmap + * @param outPadding If not null, return the padding rect for the bitmap if it + * exists, otherwise set padding to [-1,-1,-1,-1]. If no bitmap + * is returned (null) then padding is unchanged. + * @param reqWidth 目标宽度 + * @param reqHeight 目标高度 + */ + public static Bitmap getBitmapFromStream(InputStream is, Rect outPadding, + int reqWidth, int reqHeight) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, outPadding, options); + options = calculateInSampleSize(options, reqWidth, reqHeight); + return BitmapFactory.decodeStream(is, outPadding, options); + } + + /** + * 从View获取Bitmap + * + * @param view View + * @return Bitmap + */ + public static Bitmap getBitmapFromView(View view) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), + Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + view.layout(view.getLeft(), view.getTop(), view.getRight(), + view.getBottom()); + view.draw(canvas); + + return bitmap; + } + + /** + * 把一个View的对象转换成bitmap + * + * @param view View + * @return Bitmap + */ + public static Bitmap getBitmapFromView2(View view) { + + view.clearFocus(); + view.setPressed(false); + + // 能画缓存就返回false + boolean willNotCache = view.willNotCacheDrawing(); + view.setWillNotCacheDrawing(false); + int color = view.getDrawingCacheBackgroundColor(); + view.setDrawingCacheBackgroundColor(0); + if (color != 0) { + view.destroyDrawingCache(); + } + view.buildDrawingCache(); + Bitmap cacheBitmap = view.getDrawingCache(); + if (cacheBitmap == null) { + XPrintUtils.e( "failed getViewBitmap(" + view + ") -->"+ + new RuntimeException()); + return null; + } + Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); + // Restore the view + view.destroyDrawingCache(); + view.setWillNotCacheDrawing(willNotCache); + view.setDrawingCacheBackgroundColor(color); + return bitmap; + } + + /** + * 将Drawable转化为Bitmap + * + * @param drawable Drawable + * @return Bitmap + */ + public static Bitmap getBitmapFromDrawable(Drawable drawable) { + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, drawable + .getOpacity() != PixelFormat.OPAQUE ? Config.ARGB_8888 + : Config.RGB_565); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + drawable.draw(canvas); + return bitmap; + + } + + /** + * 合并Bitmap + * + * @param bgd 背景Bitmap + * @param fg 前景Bitmap + * @return 合成后的Bitmap + */ + public static Bitmap combineImages(Bitmap bgd, Bitmap fg) { + Bitmap bmp; + + int width = bgd.getWidth() > fg.getWidth() ? bgd.getWidth() : fg + .getWidth(); + int height = bgd.getHeight() > fg.getHeight() ? bgd.getHeight() : fg + .getHeight(); + + bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888); + Paint paint = new Paint(); + paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); + + Canvas canvas = new Canvas(bmp); + canvas.drawBitmap(bgd, 0, 0, null); + canvas.drawBitmap(fg, 0, 0, paint); + + return bmp; + } + + /** + * 合并 + * + * @param bgd 后景Bitmap + * @param fg 前景Bitmap + * @return 合成后Bitmap + */ + public static Bitmap combineImagesToSameSize(Bitmap bgd, Bitmap fg) { + Bitmap bmp; + + int width = bgd.getWidth() < fg.getWidth() ? bgd.getWidth() : fg + .getWidth(); + int height = bgd.getHeight() < fg.getHeight() ? bgd.getHeight() : fg + .getHeight(); + + if (fg.getWidth() != width && fg.getHeight() != height) { + fg = zoom(fg, width, height); + } + if (bgd.getWidth() != width && bgd.getHeight() != height) { + bgd = zoom(bgd, width, height); + } + + bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888); + Paint paint = new Paint(); + paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); + + Canvas canvas = new Canvas(bmp); + canvas.drawBitmap(bgd, 0, 0, null); + canvas.drawBitmap(fg, 0, 0, paint); + + return bmp; + } + + /** + * 放大缩小图片 + * + * @param bitmap 源Bitmap + * @param w 宽 + * @param h 高 + * @return 目标Bitmap + */ + public static Bitmap zoom(Bitmap bitmap, int w, int h) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Matrix matrix = new Matrix(); + float scaleWidht = ((float) w / width); + float scaleHeight = ((float) h / height); + matrix.postScale(scaleWidht, scaleHeight); + Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, + matrix, true); + return newbmp; + } + + /** + * 获得圆角图片的方法 + * + * @param bitmap 源Bitmap + * @param roundPx 圆角大小 + * @return 期望Bitmap + */ + public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx) { + + Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + final RectF rectF = new RectF(rect); + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + + paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + + return output; + } + + /** + * 获得带倒影的图片方法 + * + * @param bitmap 源Bitmap + * @return 带倒影的Bitmap + */ + public static Bitmap createReflectionBitmap(Bitmap bitmap) { + final int reflectionGap = 4; + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + Matrix matrix = new Matrix(); + matrix.preScale(1, -1); + + Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, height / 2, + width, height / 2, matrix, false); + + Bitmap bitmapWithReflection = Bitmap.createBitmap(width, + (height + height / 2), Config.ARGB_8888); + + Canvas canvas = new Canvas(bitmapWithReflection); + canvas.drawBitmap(bitmap, 0, 0, null); + Paint deafalutPaint = new Paint(); + canvas.drawRect(0, height, width, height + reflectionGap, deafalutPaint); + + canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null); + + Paint paint = new Paint(); + LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0, + bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, + 0x00ffffff, TileMode.CLAMP); + paint.setShader(shader); + // Set the Transfer mode to be porter duff and destination in + paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); + // Draw a rectangle using the paint with our linear gradient + canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + + reflectionGap, paint); + + return bitmapWithReflection; + } + + /** + * 压缩图片大小 + * + * @param image 源Bitmap + * @return 压缩后的Bitmap + */ + public static Bitmap compressImage(Bitmap image) { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 + int options = 100; + while (baos.toByteArray().length / 1024 > 100) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩 + baos.reset();// 重置baos即清空baos + image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中 + options -= 10;// 每次都减少10 + } + ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中 + Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片 + return bitmap; + } + + /** + * 将彩色图转换为灰度图 + * + * @param img 源Bitmap + * @return 返回转换好的位图 + */ + public static Bitmap convertGreyImg(Bitmap img) { + int width = img.getWidth(); // 获取位图的宽 + int height = img.getHeight(); // 获取位图的高 + + int[] pixels = new int[width * height]; // 通过位图的大小创建像素点数组 + + img.getPixels(pixels, 0, width, 0, 0, width, height); + int alpha = 0xFF << 24; + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int grey = pixels[width * i + j]; + + int red = ((grey & 0x00FF0000) >> 16); + int green = ((grey & 0x0000FF00) >> 8); + int blue = (grey & 0x000000FF); + + grey = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11); + grey = alpha | (grey << 16) | (grey << 8) | grey; + pixels[width * i + j] = grey; + } + } + Bitmap result = Bitmap.createBitmap(width, height, Config.RGB_565); + result.setPixels(pixels, 0, width, 0, 0, width, height); + return result; + } + + /** + * 转换图片成圆形 + * + * @param bitmap 传入Bitmap对象 + * @return 圆形Bitmap + */ + public static Bitmap getRoundBitmap(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + float roundPx; + float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom; + if (width <= height) { + roundPx = width / 2; + top = 0; + bottom = width; + left = 0; + right = width; + height = width; + dst_left = 0; + dst_top = 0; + dst_right = width; + dst_bottom = width; + } else { + roundPx = height / 2; + float clip = (width - height) / 2; + left = clip; + right = width - clip; + top = 0; + bottom = height; + width = height; + dst_left = 0; + dst_top = 0; + dst_right = height; + dst_bottom = height; + } + + Bitmap output = Bitmap.createBitmap(width, height, Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect src = new Rect((int) left, (int) top, (int) right, + (int) bottom); + final Rect dst = new Rect((int) dst_left, (int) dst_top, + (int) dst_right, (int) dst_bottom); + final RectF rectF = new RectF(dst); + + paint.setAntiAlias(true); + + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + + paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); + canvas.drawBitmap(bitmap, src, dst, paint); + return output; + } + + /** + * Returns a Bitmap representing the thumbnail of the specified Bitmap. The + * size of the thumbnail is defined by the dimension + * android.R.dimen.launcher_application_icon_size. + * + * This method is not thread-safe and should be invoked on the UI thread + * only. + * + * @param bitmap The bitmap to get a thumbnail of. + * @param context The application's context. + * @return A thumbnail for the specified bitmap or the bitmap itself if the + * thumbnail could not be created. + */ + public static Bitmap createThumbnailBitmap(Bitmap bitmap, Context context) { + int sIconWidth = -1; + int sIconHeight = -1; + final Resources resources = context.getResources(); + sIconWidth = sIconHeight = (int) resources + .getDimension(android.R.dimen.app_icon_size); + + final Paint sPaint = new Paint(); + final Rect sBounds = new Rect(); + final Rect sOldBounds = new Rect(); + Canvas sCanvas = new Canvas(); + + int width = sIconWidth; + int height = sIconHeight; + + sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + + if (width > 0 && height > 0) { + if (width < bitmapWidth || height < bitmapHeight) { + final float ratio = (float) bitmapWidth / bitmapHeight; + + if (bitmapWidth > bitmapHeight) { + height = (int) (width / ratio); + } else if (bitmapHeight > bitmapWidth) { + width = (int) (height * ratio); + } + + final Config c = (width == sIconWidth && height == sIconHeight) ? bitmap + .getConfig() : Config.ARGB_8888; + final Bitmap thumb = Bitmap.createBitmap(sIconWidth, + sIconHeight, c); + final Canvas canvas = sCanvas; + final Paint paint = sPaint; + canvas.setBitmap(thumb); + paint.setDither(false); + paint.setFilterBitmap(true); + sBounds.set((sIconWidth - width) / 2, + (sIconHeight - height) / 2, width, height); + sOldBounds.set(0, 0, bitmapWidth, bitmapHeight); + canvas.drawBitmap(bitmap, sOldBounds, sBounds, paint); + return thumb; + } else if (bitmapWidth < width || bitmapHeight < height) { + final Config c = Config.ARGB_8888; + final Bitmap thumb = Bitmap.createBitmap(sIconWidth, + sIconHeight, c); + final Canvas canvas = sCanvas; + final Paint paint = sPaint; + canvas.setBitmap(thumb); + paint.setDither(false); + paint.setFilterBitmap(true); + canvas.drawBitmap(bitmap, (sIconWidth - bitmapWidth) / 2, + (sIconHeight - bitmapHeight) / 2, paint); + return thumb; + } + } + + return bitmap; + } + + /** + * 生成水印图片 水印在右下角 + * + * @param src the bitmap object you want proecss + * @param watermark the water mark above the src + * @return return a bitmap object ,if paramter's length is 0,return null + */ + public static Bitmap createWatermarkBitmap(Bitmap src, Bitmap watermark) { + if (src == null) { + return null; + } + + int w = src.getWidth(); + int h = src.getHeight(); + int ww = watermark.getWidth(); + int wh = watermark.getHeight(); + // create the new blank bitmap + Bitmap newb = Bitmap.createBitmap(w, h, Config.ARGB_8888);// 创建一个新的和SRC长度宽度一样的位图 + Canvas cv = new Canvas(newb); + // draw src into + cv.drawBitmap(src, 0, 0, null);// 在 0,0坐标开始画入src + // draw watermark into + cv.drawBitmap(watermark, w - ww + 5, h - wh + 5, null);// 在src的右下角画入水印 + // save all clip + cv.save(Canvas.ALL_SAVE_FLAG);// 保存 + // store + cv.restore();// 存储 + return newb; + } + + /** + * 重新编码Bitmap + * + * @param src 需要重新编码的Bitmap + * @param format 编码后的格式(目前只支持png和jpeg这两种格式) + * @param quality 重新生成后的bitmap的质量 + * @return 返回重新生成后的bitmap + */ + public static Bitmap codec(Bitmap src, Bitmap.CompressFormat format, + int quality) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + src.compress(format, quality, os); + + byte[] array = os.toByteArray(); + return BitmapFactory.decodeByteArray(array, 0, array.length); + } + + /** + * 图片压缩方法:(使用compress的方法) + * + *
+ * 说明 如果bitmap本身的大小小于maxSize,则不作处理 + * + * @param bitmap 要压缩的图片 + * @param maxSize 压缩后的大小,单位kb + */ + public static void compress(Bitmap bitmap, double maxSize) { + // 将bitmap放至数组中,意在获得bitmap的大小(与实际读取的原文件要大) + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // 格式、质量、输出流 + bitmap.compress(Bitmap.CompressFormat.PNG, 70, baos); + byte[] b = baos.toByteArray(); + // 将字节换成KB + double mid = b.length / 1024; + // 获取bitmap大小 是允许最大大小的多少倍 + double i = mid / maxSize; + // 判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩 + if (i > 1) { + // 缩放图片 此处用到平方根 将宽带和高度压缩掉对应的平方根倍 + // (保持宽高不变,缩放后也达到了最大占用空间的大小) + bitmap = scale(bitmap, bitmap.getWidth() / Math.sqrt(i), + bitmap.getHeight() / Math.sqrt(i)); + } + } + + /** + * 图片的缩放方法 + * + * @param src :源图片资源 + * @param newWidth :缩放后宽度 + * @param newHeight :缩放后高度 + */ + public static Bitmap scale(Bitmap src, double newWidth, double newHeight) { + // 记录src的宽高 + float width = src.getWidth(); + float height = src.getHeight(); + // 创建一个matrix容器 + Matrix matrix = new Matrix(); + // 计算缩放比例 + float scaleWidth = ((float) newWidth) / width; + float scaleHeight = ((float) newHeight) / height; + // 开始缩放 + matrix.postScale(scaleWidth, scaleHeight); + // 创建缩放后的图片 + return Bitmap.createBitmap(src, 0, 0, (int) width, (int) height, + matrix, true); + } + + /** + * 图片的缩放方法 + * + * @param src :源图片资源 + * @param scaleMatrix :缩放规则 + */ + public static Bitmap scale(Bitmap src, Matrix scaleMatrix) { + return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), + scaleMatrix, true); + } + + /** + * 图片的缩放方法 + * + * @param src :源图片资源 + * @param scaleX :横向缩放比例 + * @param scaleY :纵向缩放比例 + */ + public static Bitmap scale(Bitmap src, float scaleX, float scaleY) { + Matrix matrix = new Matrix(); + matrix.postScale(scaleX, scaleY); + return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), + matrix, true); + } + + /** + * 图片的缩放方法 + * + * @param src :源图片资源 + * @param scale :缩放比例 + */ + public static Bitmap scale(Bitmap src, float scale) { + return scale(src, scale, scale); + } + + /** + * 旋转图片 + * + * @param angle 旋转角度 + * @param bitmap 要旋转的图片 + * @return 旋转后的图片 + */ + public static Bitmap rotate(Bitmap bitmap, int angle) { + Matrix matrix = new Matrix(); + matrix.postRotate(angle); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, true); + } + + /** + * 水平翻转处理 + * + * @param bitmap 原图 + * @return 水平翻转后的图片 + */ + public static Bitmap reverseByHorizontal(Bitmap bitmap) { + Matrix matrix = new Matrix(); + matrix.preScale(-1, 1); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, false); + } + + /** + * 垂直翻转处理 + * + * @param bitmap 原图 + * @return 垂直翻转后的图片 + */ + public static Bitmap reverseByVertical(Bitmap bitmap) { + Matrix matrix = new Matrix(); + matrix.preScale(1, -1); + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, false); + } + + /** + * 更改图片色系,变亮或变暗 + * + * @param delta 图片的亮暗程度值,越小图片会越亮,取值范围(0,24) + * @return + */ + public static Bitmap adjustTone(Bitmap src, int delta) { + if (delta >= 24 || delta <= 0) { + return null; + } + // 设置高斯矩阵 + int[] gauss = new int[]{1, 2, 1, 2, 4, 2, 1, 2, 1}; + int width = src.getWidth(); + int height = src.getHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + + int pixR = 0; + int pixG = 0; + int pixB = 0; + int pixColor = 0; + int newR = 0; + int newG = 0; + int newB = 0; + int idx = 0; + int[] pixels = new int[width * height]; + + src.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + idx = 0; + for (int m = -1; m <= 1; m++) { + for (int n = -1; n <= 1; n++) { + pixColor = pixels[(i + m) * width + k + n]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR += (pixR * gauss[idx]); + newG += (pixG * gauss[idx]); + newB += (pixB * gauss[idx]); + idx++; + } + } + newR /= delta; + newG /= delta; + newB /= delta; + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + pixels[i * width + k] = Color.argb(255, newR, newG, newB); + newR = 0; + newG = 0; + newB = 0; + } + } + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } + + /** + * 将彩色图转换为黑白图 + * + * @param bmp 位图 + * @return 返回转换好的位图 + */ + public static Bitmap convertToBlackWhite(Bitmap bmp) { + int width = bmp.getWidth(); + int height = bmp.getHeight(); + int[] pixels = new int[width * height]; + bmp.getPixels(pixels, 0, width, 0, 0, width, height); + + int alpha = 0xFF << 24; // 默认将bitmap当成24色图片 + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int grey = pixels[width * i + j]; + + int red = ((grey & 0x00FF0000) >> 16); + int green = ((grey & 0x0000FF00) >> 8); + int blue = (grey & 0x000000FF); + + grey = (int) (red * 0.3 + green * 0.59 + blue * 0.11); + grey = alpha | (grey << 16) | (grey << 8) | grey; + pixels[width * i + j] = grey; + } + } + Bitmap newBmp = Bitmap.createBitmap(width, height, Config.RGB_565); + newBmp.setPixels(pixels, 0, width, 0, 0, width, height); + return newBmp; + } + + /** + * 读取图片属性:图片被旋转的角度 + * + * @param path 图片绝对路径 + * @return 旋转的角度 + */ + public static int getImageDegree(String path) { + int degree = 0; + try { + ExifInterface exifInterface = new ExifInterface(path); + int orientation = exifInterface.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return degree; + } + + + /** + * 饱和度处理 + * + * @param bitmap 原图 + * @param saturationValue 新的饱和度值 + * @return 改变了饱和度值之后的图片 + */ + public static Bitmap saturation(Bitmap bitmap, int saturationValue) { + // 计算出符合要求的饱和度值 + float newSaturationValue = saturationValue * 1.0F / 127; + // 创建一个颜色矩阵 + ColorMatrix saturationColorMatrix = new ColorMatrix(); + // 设置饱和度值 + saturationColorMatrix.setSaturation(newSaturationValue); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(saturationColorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + // 将原图使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } + + /** + * 亮度处理 + * + * @param bitmap 原图 + * @param lumValue 新的亮度值 + * @return 改变了亮度值之后的图片 + */ + public static Bitmap lum(Bitmap bitmap, int lumValue) { + // 计算出符合要求的亮度值 + float newlumValue = lumValue * 1.0F / 127; + // 创建一个颜色矩阵 + ColorMatrix lumColorMatrix = new ColorMatrix(); + // 设置亮度值 + lumColorMatrix.setScale(newlumValue, newlumValue, newlumValue, 1); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(lumColorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + // 将原图使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } + + /** + * 色相处理 + * + * @param bitmap 原图 + * @param hueValue 新的色相值 + * @return 改变了色相值之后的图片 + */ + public static Bitmap hue(Bitmap bitmap, int hueValue) { + // 计算出符合要求的色相值 + float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; + // 创建一个颜色矩阵 + ColorMatrix hueColorMatrix = new ColorMatrix(); + // 控制让红色区在色轮上旋转的角度 + hueColorMatrix.setRotate(0, newHueValue); + // 控制让绿红色区在色轮上旋转的角度 + hueColorMatrix.setRotate(1, newHueValue); + // 控制让蓝色区在色轮上旋转的角度 + hueColorMatrix.setRotate(2, newHueValue); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(hueColorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + // 将原图使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } + + /** + * 亮度、色相、饱和度处理 + * + * @param bitmap 原图 + * @param lumValue 亮度值 + * @param hueValue 色相值 + * @param saturationValue 饱和度值 + * @return 亮度、色相、饱和度处理后的图片 + */ + public static Bitmap lumAndHueAndSaturation(Bitmap bitmap, int lumValue, + int hueValue, int saturationValue) { + // 计算出符合要求的饱和度值 + float newSaturationValue = saturationValue * 1.0F / 127; + // 计算出符合要求的亮度值 + float newlumValue = lumValue * 1.0F / 127; + // 计算出符合要求的色相值 + float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; + + // 创建一个颜色矩阵并设置其饱和度 + ColorMatrix colorMatrix = new ColorMatrix(); + + // 设置饱和度值 + colorMatrix.setSaturation(newSaturationValue); + // 设置亮度值 + colorMatrix.setScale(newlumValue, newlumValue, newlumValue, 1); + // 控制让红色区在色轮上旋转的角度 + colorMatrix.setRotate(0, newHueValue); + // 控制让绿红色区在色轮上旋转的角度 + colorMatrix.setRotate(1, newHueValue); + // 控制让蓝色区在色轮上旋转的角度 + colorMatrix.setRotate(2, newHueValue); + + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + // 将原图使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } + + /** + * 怀旧效果处理 + * + * @param bitmap 原图 + * @return 怀旧效果处理后的图片 + */ + public static Bitmap nostalgic(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + int pixColor = 0; + int pixR = 0; + int pixG = 0; + int pixB = 0; + int newR = 0; + int newG = 0; + int newB = 0; + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 0; i < height; i++) { + for (int k = 0; k < width; k++) { + pixColor = pixels[width * i + k]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + newR = (int) (0.393 * pixR + 0.769 * pixG + 0.189 * pixB); + newG = (int) (0.349 * pixR + 0.686 * pixG + 0.168 * pixB); + newB = (int) (0.272 * pixR + 0.534 * pixG + 0.131 * pixB); + int newColor = Color.argb(255, newR > 255 ? 255 : newR, + newG > 255 ? 255 : newG, newB > 255 ? 255 : newB); + pixels[width * i + k] = newColor; + } + } + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } + /** + * 模糊效果处理 + * @return 模糊效果处理后的图片 + */ + public Bitmap blur(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + + int pixColor = 0; + + int newR = 0; + int newG = 0; + int newB = 0; + + int newColor = 0; + + int[][] colors = new int[9][3]; + for (int i = 1, length = width - 1; i < length; i++) { + for (int k = 1, len = height - 1; k < len; k++) { + for (int m = 0; m < 9; m++) { + int s = 0; + int p = 0; + switch (m) { + case 0: + s = i - 1; + p = k - 1; + break; + case 1: + s = i; + p = k - 1; + break; + case 2: + s = i + 1; + p = k - 1; + break; + case 3: + s = i + 1; + p = k; + break; + case 4: + s = i + 1; + p = k + 1; + break; + case 5: + s = i; + p = k + 1; + break; + case 6: + s = i - 1; + p = k + 1; + break; + case 7: + s = i - 1; + p = k; + break; + case 8: + s = i; + p = k; + } + pixColor = bitmap.getPixel(s, p); + colors[m][0] = Color.red(pixColor); + colors[m][1] = Color.green(pixColor); + colors[m][2] = Color.blue(pixColor); + } + + for (int m = 0; m < 9; m++) { + newR += colors[m][0]; + newG += colors[m][1]; + newB += colors[m][2]; + } + + newR = (int) (newR / 9F); + newG = (int) (newG / 9F); + newB = (int) (newB / 9F); + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + newColor = Color.argb(255, newR, newG, newB); + newBitmap.setPixel(i, k, newColor); + + newR = 0; + newG = 0; + newB = 0; + } + } + return newBitmap; + } + /** + * 柔化效果处理 + * + * @param bitmap 原图 + * @return 柔化效果处理后的图片 + */ + public static Bitmap soften(Bitmap bitmap) { + // 高斯矩阵 + int[] gauss = new int[]{1, 2, 1, 2, 4, 2, 1, 2, 1}; + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + + int pixR = 0; + int pixG = 0; + int pixB = 0; + + int pixColor = 0; + + int newR = 0; + int newG = 0; + int newB = 0; + + int delta = 16; // 值越小图片会越亮,越大则越暗 + + int idx = 0; + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + idx = 0; + for (int m = -1; m <= 1; m++) { + for (int n = -1; n <= 1; n++) { + pixColor = pixels[(i + m) * width + k + n]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = newR + (int) (pixR * gauss[idx]); + newG = newG + (int) (pixG * gauss[idx]); + newB = newB + (int) (pixB * gauss[idx]); + idx++; + } + } + + newR /= delta; + newG /= delta; + newB /= delta; + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[i * width + k] = Color.argb(255, newR, newG, newB); + + newR = 0; + newG = 0; + newB = 0; + } + } + + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } + + /** + * 光照效果处理 + * + * @param bitmap 原图 + * @param centerX 光源在X轴的位置 + * @param centerY 光源在Y轴的位置 + * @return 光照效果处理后的图片 + */ + public static Bitmap sunshine(Bitmap bitmap, int centerX, int centerY) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + + int pixR = 0; + int pixG = 0; + int pixB = 0; + + int pixColor = 0; + + int newR = 0; + int newG = 0; + int newB = 0; + int radius = Math.min(centerX, centerY); + + final float strength = 150F; // 光照强度 100~150 + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + int pos = 0; + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + pos = i * width + k; + pixColor = pixels[pos]; + + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = pixR; + newG = pixG; + newB = pixB; + + // 计算当前点到光照中心的距离,平面座标系中求两点之间的距离 + int distance = (int) (Math.pow((centerY - i), 2) + Math.pow( + centerX - k, 2)); + if (distance < radius * radius) { + // 按照距离大小计算增加的光照值 + int result = (int) (strength * (1.0 - Math.sqrt(distance) + / radius)); + newR = pixR + result; + newG = pixG + result; + newB = pixB + result; + } + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[pos] = Color.argb(255, newR, newG, newB); + } + } + + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } + + /** + * 底片效果处理 + * + * @param bitmap 原图 + * @return 底片效果处理后的图片 + */ + public static Bitmap film(Bitmap bitmap) { + // RGBA的最大值 + final int MAX_VALUE = 255; + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + + int pixR = 0; + int pixG = 0; + int pixB = 0; + + int pixColor = 0; + + int newR = 0; + int newG = 0; + int newB = 0; + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + int pos = 0; + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + pos = i * width + k; + pixColor = pixels[pos]; + + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = MAX_VALUE - pixR; + newG = MAX_VALUE - pixG; + newB = MAX_VALUE - pixB; + + newR = Math.min(MAX_VALUE, Math.max(0, newR)); + newG = Math.min(MAX_VALUE, Math.max(0, newG)); + newB = Math.min(MAX_VALUE, Math.max(0, newB)); + + pixels[pos] = Color.argb(MAX_VALUE, newR, newG, newB); + } + } + + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } + + /** + * 锐化效果处理 + * + * @param bitmap 原图 + * @return 锐化效果处理后的图片 + */ + public static Bitmap sharpen(Bitmap bitmap) { + // 拉普拉斯矩阵 + int[] laplacian = new int[]{-1, -1, -1, -1, 9, -1, -1, -1, -1}; + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + + int pixR = 0; + int pixG = 0; + int pixB = 0; + + int pixColor = 0; + + int newR = 0; + int newG = 0; + int newB = 0; + + int idx = 0; + float alpha = 0.3F; + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + idx = 0; + for (int m = -1; m <= 1; m++) { + for (int n = -1; n <= 1; n++) { + pixColor = pixels[(i + n) * width + k + m]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = newR + (int) (pixR * laplacian[idx] * alpha); + newG = newG + (int) (pixG * laplacian[idx] * alpha); + newB = newB + (int) (pixB * laplacian[idx] * alpha); + idx++; + } + } + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[i * width + k] = Color.argb(255, newR, newG, newB); + newR = 0; + newG = 0; + newB = 0; + } + } + + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } + + /** + * 浮雕效果处理 + * + * @param bitmap 原图 + * @return 浮雕效果处理后的图片 + */ + public static Bitmap emboss(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + Bitmap newBitmap = Bitmap.createBitmap(width, height, + Config.RGB_565); + + int pixR = 0; + int pixG = 0; + int pixB = 0; + + int pixColor = 0; + + int newR = 0; + int newG = 0; + int newB = 0; + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + int pos = 0; + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + pos = i * width + k; + pixColor = pixels[pos]; + + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + pixColor = pixels[pos + 1]; + newR = Color.red(pixColor) - pixR + 127; + newG = Color.green(pixColor) - pixG + 127; + newB = Color.blue(pixColor) - pixB + 127; + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[pos] = Color.argb(255, newR, newG, newB); + } + } + + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } + + /** + * 将YUV格式的图片的源数据从横屏模式转为竖屏模式,注意:将源图片的宽高互换一下就是新图片的宽高 + * + * @param sourceData YUV格式的图片的源数据 + * @param width 源图片的宽 + * @param height 源图片的高 + * @return + */ + public static final byte[] yuvLandscapeToPortrait(byte[] sourceData, + int width, int height) { + byte[] rotatedData = new byte[sourceData.length]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) + rotatedData[x * height + height - y - 1] = sourceData[x + y + * width]; + } + return rotatedData; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XCrashHandlerUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XCrashHandlerUtils.java new file mode 100644 index 0000000..536f943 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XCrashHandlerUtils.java @@ -0,0 +1,202 @@ +package com.youth.xframe.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Environment; +import android.os.Looper; +import android.widget.Toast; + +import com.youth.xframe.common.XActivityStack; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告. + * 在Application 中调用 + * CrashHandlerUtil.getInstance().init(this); + */ +public class XCrashHandlerUtils implements Thread.UncaughtExceptionHandler { + + //系统默认的UncaughtException处理类 + private Thread.UncaughtExceptionHandler mDefaultHandler; + //CrashHandler实例 + private static XCrashHandlerUtils INSTANCE = new XCrashHandlerUtils(); + //程序的Context对象 + private Context mContext; + //用来存储设备信息和异常信息 + private Mapinfos = new HashMap<>(); + private String crashTip = "很抱歉,程序出现异常,即将退出!"; + + public String getCrashTip() { + return crashTip; + } + + public void setCrashTip(String crashTip) { + this.crashTip = crashTip; + } + + /** + * 保证只有一个CrashHandler实例 + */ + private XCrashHandlerUtils() { + } + + /** + * 获取CrashHandler实例 ,单例模式 + * + * @return 单例 + */ + public static XCrashHandlerUtils getInstance() { + return INSTANCE; + } + + /** + * 初始化 + * + * @param context 上下文 + */ + public void init(Context context) { + mContext = context; + //获取系统默认的UncaughtException处理器 + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + //设置该CrashHandler为程序的默认处理器 + Thread.setDefaultUncaughtExceptionHandler(this); + } + + /** + * 当UncaughtException发生时会转入该函数来处理 + * + * @param thread 线程 + * @param ex 异常 + */ + @Override + public void uncaughtException(Thread thread, Throwable ex) { + if (!handleException(ex) && mDefaultHandler != null) { + //如果用户没有处理则让系统默认的异常处理器来处理 + mDefaultHandler.uncaughtException(thread, ex); + } else { + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //退出程序 + XActivityStack.getInstance().appExit(); + } + } + + /** + * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. + * + * @param throwable 异常 + * @return true:如果处理了该异常信息;否则返回false. + */ + private boolean handleException(final Throwable throwable) { + if (throwable == null) { + return false; + } + //使用Toast来显示异常信息 + new Thread() { + @Override + public void run() { + Looper.prepare(); + throwable.printStackTrace(); + Toast.makeText(mContext, getCrashTip(), Toast.LENGTH_LONG).show(); + Looper.loop(); + } + }.start(); + //收集设备参数信息 + collectDeviceInfo(mContext); + //保存日志文件 + saveCrashInfo2File(throwable); + return true; + } + + /** + * 收集设备参数信息 + * + * @param ctx 上下文 + */ + public void collectDeviceInfo(Context ctx) { + try { + PackageManager pm = ctx.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); + if (pi != null) { + String versionName = pi.versionName == null ? "null" : pi.versionName; + String versionCode = pi.versionCode + ""; + infos.put("versionName", versionName); + infos.put("versionCode", versionCode); + } + } catch (PackageManager.NameNotFoundException e) { + XPrintUtils.e("an adapter_loading_error occured when collect package info --> "+e); + } + Field[] fields = Build.class.getDeclaredFields(); + for (Field field : fields) { + try { + field.setAccessible(true); + infos.put(field.getName(), field.get(null).toString()); + XPrintUtils.d(field.getName() + " : " + field.get(null)); + } catch (Exception e) { + XPrintUtils.e("an adapter_loading_error occured when collect crash info --> "+e); + } + } + } + + /** + * 保存错误信息到文件中 + * + * @param ex 异常 + * @return 返回文件名称, 便于将文件传送到服务器 + */ + private String saveCrashInfo2File(Throwable ex) { + + StringBuffer sb = new StringBuffer(); + for (Map.Entry entry : infos.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + sb.append(key + "=" + value + "\n"); + } + + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + ex.printStackTrace(printWriter); + Throwable cause = ex.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + printWriter.close(); + String result = writer.toString(); + sb.append(result); + try { + long timestamp = System.currentTimeMillis(); + String fileName = "crash-" + XDateUtils.getCurrentDate() + "-" + timestamp + ".XPrintUtils"; + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + String path = Environment.getExternalStorageDirectory().getPath() + "/crash/"; + XPrintUtils.d("path=" + path); + File dir = new File(path); + if (!dir.exists()) { + dir.mkdirs(); + } + FileOutputStream fos = new FileOutputStream(path + fileName); + fos.write(sb.toString().getBytes()); + fos.close(); + } + return fileName; + } catch (Exception e) { + XPrintUtils.e("an adapter_loading_error occured while writing file... --> "+e); + } + return null; + } + + +} \ No newline at end of file diff --git a/xframe/src/main/java/com/youth/xframe/utils/XDateUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XDateUtils.java new file mode 100644 index 0000000..cf19f0b --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XDateUtils.java @@ -0,0 +1,719 @@ +package com.youth.xframe.utils; + +import android.annotation.SuppressLint; + +import com.youth.xframe.entity.DateDifference; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + 日期操作工具类. + SimpleDateFormat函数语法: + G 年代标志符 + y 年 + M 月 + d 日 + h 时 在上午或下午 (1~12) + H 时 在一天中 (0~23) + m 分 + s 秒 + S 毫秒 + E 星期 + D 一年中的第几天 + F 一月中第几个星期几 + w 一年中第几个星期 + W 一月中第几个星期 + a 上午 / 下午 标记符 + k 时 在一天中 (1~24) + K 时 在上午或下午 (0~11) + z 时区 + */ +public class XDateUtils { + + /** + * 秒与毫秒的倍数 + */ + public static final int SEC = 1000; + /** + * 分与毫秒的倍数 + */ + public static final int MIN = SEC*60; + /** + * 时与毫秒的倍数 + */ + public static final int HOUR = MIN*60; + /** + * 天与毫秒的倍数 + */ + public static final int DAY = HOUR*24; + + /** + * 周与毫秒的倍数 + */ + public static final int WEEK = DAY*7; + + /** + * 月与毫秒的倍数 + */ + public static final int MONTH = DAY*30; + + /** + * 年与毫秒的倍数 + */ + public static final int YEAR = DAY*365; + + /** + * 默认格式 + */ + public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + /** + * SimpleDateFormat不是线程安全的,以下是线程安全实例化操作 + */ + private static final ThreadLocal local = new ThreadLocal () { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(); + } + }; + /** + * 获取SimpleDateFormat实例 + * + * @param pattern + * 模式串 + * @return + */ + public static SimpleDateFormat getSimpleDateFormat(String pattern) { + SimpleDateFormat format = local.get(); + format.applyPattern(pattern); + return format; + } + + /** + * 获取表示当前时间的字符串 + * + * @return + */ + public static String getCurrentDate() { + return format(new Date(), DEFAULT_PATTERN); + } + + /** + * 获取表示当前时间的字符串 + * + * @param pattern + * 模式串 + * @return + */ + public static String getCurrentDate(String pattern) { + return format(new Date(), pattern); + } + + /** + * 日期时间格式化 + * + * @param date + * Date + * @return + */ + public static String format(Date date) { + SimpleDateFormat format = getSimpleDateFormat(DEFAULT_PATTERN); + return format.format(date); + } + + /** + * 日期时间格式化 + * + * @param date + * Date + * @param pattern + * 模式串 + * @return + */ + public static String format(Date date, String pattern) { + SimpleDateFormat format = getSimpleDateFormat(pattern); + return format.format(date); + } + /** + * 将时间戳转为时间字符串 + * 格式为yyyy-MM-dd HH:mm:ss
+ * + * @param millis 毫秒时间戳 + * @return 时间字符串 + */ + public static String millis2String(long millis) { + SimpleDateFormat format = getSimpleDateFormat(DEFAULT_PATTERN); + return format.format(new Date(millis)); + } + + /** + * 将时间戳转为时间字符串 + *格式为pattern
+ * + * @param millis 毫秒时间戳 + * @param pattern 时间格式 + * @return 时间字符串 + */ + public static String millis2String(long millis, String pattern) { + SimpleDateFormat format = getSimpleDateFormat(pattern); + return format.format(new Date(millis)); + } + + /** + * 将时间字符串转为时间戳 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 毫秒时间戳 + */ + public static long string2Millis(String time) { + return string2Millis(time, DEFAULT_PATTERN); + } + + /** + * 将时间字符串转为时间戳 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 毫秒时间戳 + */ + public static long string2Millis(String time, String pattern) { + SimpleDateFormat format = getSimpleDateFormat(pattern); + try { + return format.parse(time).getTime(); + } catch (ParseException e) { + e.printStackTrace(); + } + return -1; + } + + /** + * 将时间字符串转为Date类型 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return Date类型 + */ + public static Date string2Date(String time) { + return string2Date(time, DEFAULT_PATTERN); + } + + /** + * 将时间字符串转为Date类型 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return Date类型 + */ + public static Date string2Date(String time, String pattern) { + return new Date(string2Millis(time, pattern)); + } + + /** + * 将Date类型转为时间字符串 + *格式为yyyy-MM-dd HH:mm:ss
+ * + * @param date Date类型时间 + * @return 时间字符串 + */ + public static String date2String(Date date) { + return date2String(date, DEFAULT_PATTERN); + } + + /** + * 将Date类型转为时间字符串 + *格式为pattern
+ * + * @param date Date类型时间 + * @param pattern 时间格式 + * @return 时间字符串 + */ + public static String date2String(Date date, String pattern) { + SimpleDateFormat format = getSimpleDateFormat(pattern); + return format.format(date); + } + + /** + * 将Date类型转为时间戳 + * + * @param date Date类型时间 + * @return 毫秒时间戳 + */ + public static long date2Millis(Date date) { + return date.getTime(); + } + + /** + * 将时间戳转为Date类型 + * + * @param millis 毫秒时间戳 + * @return Date类型时间 + */ + public static Date millis2Date(long millis) { + return new Date(millis); + } + + /** + * 获取与当前时间的时间差 + * @param date 需要计算的时间,应小于当前时间 + * @return DateDifference实体类,内封装有获取相差的毫秒、秒、分钟、小时、天的方法 + */ + public static DateDifference getTwoDataDifference(Date date) { + return getTwoDataDifference(new Date() ,date); + } + + /** + * 获取与当前时间的时间差 + * @param str 需要计算的时间,应小于当前时间 + * @return DateDifference实体类,内封装有获取相差的毫秒、秒、分钟、小时、天的方法 + */ + public static DateDifference getTwoDataDifference(String str) { + return getTwoDataDifference(new Date() ,string2Date(str)); + } + + + /** + * 得到二个日期间的时间差 + * @param str1 两个时间中较大的那个 + * @param str2 两个时间中较小的那个 + * @return DateDifference实体类,内封装有获取相差的毫秒、秒、分钟、小时、天的方法 + */ + public static DateDifference getTwoDataDifference(String str1, String str2) { + return getTwoDataDifference(string2Date(str1),string2Date(str2)); + } + + /** + * 得到二个日期间的时间差 + * @param date1 两个时间中较大的那个 + * @param date2 两个时间中较小的那个 + * @return DateDifference实体类,内封装有获取相差的毫秒、秒、分钟、小时、天的方法 + */ + public static DateDifference getTwoDataDifference(Date date1, Date date2) { + DateDifference difference=new DateDifference(); + long millis=date1.getTime() - date2.getTime(); + difference.setMillisecond(millis); + difference.setSecond(millis/SEC); + difference.setMinute(millis/MIN); + difference.setHour(millis/HOUR); + difference.setHour(millis/DAY); + return difference; + } + + + /** + * 判断是否同一天 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return {@code true}: 是
{@code false}: 否 + */ + public static boolean isSameDay(String time) { + return isSameDay(string2Millis(time, DEFAULT_PATTERN)); + } + + /** + * 判断是否同一天 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return {@code true}: 是
{@code false}: 否 + */ + public static boolean isSameDay(String time, String pattern) { + return isSameDay(string2Millis(time, pattern)); + } + + /** + * 判断是否同一天 + * + * @param date Date类型时间 + * @return {@code true}: 是
{@code false}: 否 + */ + public static boolean isSameDay(Date date) { + return isSameDay(date.getTime()); + } + + /** + * 判断是否同一天 + * + * @param millis 毫秒时间戳 + * @return {@code true}: 是
{@code false}: 否 + */ + public static boolean isSameDay(long millis) { + long wee = (System.currentTimeMillis() / DAY) * DAY; + return millis >= wee && millis < wee + DAY; + } + + /** + * 判断是否闰年 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return {@code true}: 闰年
{@code false}: 平年 + */ + public static boolean isLeapYear(String time) { + return isLeapYear(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 判断是否闰年 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return {@code true}: 闰年
{@code false}: 平年 + */ + public static boolean isLeapYear(String time, String pattern) { + return isLeapYear(string2Date(time, pattern)); + } + + /** + * 判断是否闰年 + * + * @param date Date类型时间 + * @return {@code true}: 闰年
{@code false}: 平年 + */ + public static boolean isLeapYear(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + int year = cal.get(Calendar.YEAR); + return isLeapYear(year); + } + + /** + * 判断是否闰年 + * + * @param millis 毫秒时间戳 + * @return {@code true}: 闰年
{@code false}: 平年 + */ + public static boolean isLeapYear(long millis) { + return isLeapYear(millis2Date(millis)); + } + + /** + * 判断是否闰年 + * + * @param year 年份 + * @return {@code true}: 闰年
{@code false}: 平年 + */ + public static boolean isLeapYear(int year) { + return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; + } + + /** + * 获取星期 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 星期 + */ + public static String getWeek(String time) { + return getWeek(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 获取星期 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 星期 + */ + public static String getWeek(String time, String pattern) { + return getWeek(string2Date(time, pattern)); + } + + /** + * 获取星期 + * + * @param date Date类型时间 + * @return 星期 + */ + public static String getWeek(Date date) { + return new SimpleDateFormat("EEEE", Locale.getDefault()).format(date); + } + + /** + * 获取星期 + * + * @param millis 毫秒时间戳 + * @return 星期 + */ + public static String getWeek(long millis) { + return getWeek(new Date(millis)); + } + + /** + * 获取星期 + *注意:周日的Index才是1,周六为7
+ *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 1...5 + */ + public static int getWeekIndex(String time) { + return getWeekIndex(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 获取星期 + *注意:周日的Index才是1,周六为7
+ *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 1...7 + */ + public static int getWeekIndex(String time, String pattern) { + return getWeekIndex(string2Date(time, pattern)); + } + + /** + * 获取星期 + *注意:周日的Index才是1,周六为7
+ * + * @param date Date类型时间 + * @return 1...7 + */ + public static int getWeekIndex(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.DAY_OF_WEEK); + } + + /** + * 获取星期 + *注意:周日的Index才是1,周六为7
+ * + * @param millis 毫秒时间戳 + * @return 1...7 + */ + public static int getWeekIndex(long millis) { + return getWeekIndex(millis2Date(millis)); + } + + /** + * 获取月份中的第几周 + *注意:国外周日才是新的一周的开始
+ *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 1...5 + */ + public static int getWeekOfMonth(String time) { + return getWeekOfMonth(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 获取月份中的第几周 + *注意:国外周日才是新的一周的开始
+ *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 1...5 + */ + public static int getWeekOfMonth(String time, String pattern) { + return getWeekOfMonth(string2Date(time, pattern)); + } + + /** + * 获取月份中的第几周 + *注意:国外周日才是新的一周的开始
+ * + * @param date Date类型时间 + * @return 1...5 + */ + public static int getWeekOfMonth(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.WEEK_OF_MONTH); + } + + /** + * 获取月份中的第几周 + *注意:国外周日才是新的一周的开始
+ * + * @param millis 毫秒时间戳 + * @return 1...5 + */ + public static int getWeekOfMonth(long millis) { + return getWeekOfMonth(millis2Date(millis)); + } + + /** + * 获取年份中的第几周 + *注意:国外周日才是新的一周的开始
+ *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 1...54 + */ + public static int getWeekOfYear(String time) { + return getWeekOfYear(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 获取年份中的第几周 + *注意:国外周日才是新的一周的开始
+ *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 1...54 + */ + public static int getWeekOfYear(String time, String pattern) { + return getWeekOfYear(string2Date(time, pattern)); + } + + /** + * 获取年份中的第几周 + *注意:国外周日才是新的一周的开始
+ * + * @param date Date类型时间 + * @return 1...54 + */ + public static int getWeekOfYear(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return cal.get(Calendar.WEEK_OF_YEAR); + } + + /** + * 获取年份中的第几周 + *注意:国外周日才是新的一周的开始
+ * + * @param millis 毫秒时间戳 + * @return 1...54 + */ + public static int getWeekOfYear(long millis) { + return getWeekOfYear(millis2Date(millis)); + } + + private static final String[] CHINESE_ZODIAC = {"猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"}; + + /** + * 获取生肖 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 生肖 + */ + public static String getChineseZodiac(String time) { + return getChineseZodiac(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 获取生肖 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 生肖 + */ + public static String getChineseZodiac(String time, String pattern) { + return getChineseZodiac(string2Date(time, pattern)); + } + + /** + * 获取生肖 + * + * @param date Date类型时间 + * @return 生肖 + */ + public static String getChineseZodiac(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + return CHINESE_ZODIAC[cal.get(Calendar.YEAR) % 12]; + } + + /** + * 获取生肖 + * + * @param millis 毫秒时间戳 + * @return 生肖 + */ + public static String getChineseZodiac(long millis) { + return getChineseZodiac(millis2Date(millis)); + } + + /** + * 获取生肖 + * + * @param year 年 + * @return 生肖 + */ + public static String getChineseZodiac(int year) { + return CHINESE_ZODIAC[year % 12]; + } + + private static final String[] ZODIAC = {"水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "魔羯座"}; + private static final int[] ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22}; + + /** + * 获取星座 + *time格式为yyyy-MM-dd HH:mm:ss
+ * + * @param time 时间字符串 + * @return 生肖 + */ + public static String getZodiac(String time) { + return getZodiac(string2Date(time, DEFAULT_PATTERN)); + } + + /** + * 获取星座 + *time格式为pattern
+ * + * @param time 时间字符串 + * @param pattern 时间格式 + * @return 生肖 + */ + public static String getZodiac(String time, String pattern) { + return getZodiac(string2Date(time, pattern)); + } + + /** + * 获取星座 + * + * @param date Date类型时间 + * @return 星座 + */ + public static String getZodiac(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + int month = cal.get(Calendar.MONTH) + 1; + int day = cal.get(Calendar.DAY_OF_MONTH); + return getZodiac(month, day); + } + + /** + * 获取星座 + * + * @param millis 毫秒时间戳 + * @return 星座 + */ + public static String getZodiac(long millis) { + return getZodiac(millis2Date(millis)); + } + + /** + * 获取星座 + * + * @param month 月 + * @param day 日 + * @return 星座 + */ + public static String getZodiac(int month, int day) { + return ZODIAC[day >= ZODIAC_FLAGS[month - 1] + ? month - 1 + : (month + 10) % 12]; + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XDensityUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XDensityUtils.java new file mode 100644 index 0000000..abd0d4b --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XDensityUtils.java @@ -0,0 +1,284 @@ +package com.youth.xframe.utils; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import com.youth.xframe.XFrame; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * 屏幕、尺寸、View相关工具类 + */ +public class XDensityUtils { + /** + * dp转px + * + * @param dpValue dp值 + * @return px值 + */ + public static int dp2px(float dpValue) { + final float scale = XFrame.getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + /** + * px转dp + * + * @param pxValue px值 + * @return dp值 + */ + public static int px2dp( float pxValue) { + final float scale = XFrame.getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } + + /** + * sp转px + * + * @param spValue sp值 + * @return px值 + */ + public static int sp2px(float spValue) { + final float fontScale = XFrame.getDisplayMetrics().scaledDensity; + return (int) (spValue * fontScale + 0.5f); + } + + /** + * px转sp + * + * @param pxValue px值 + * @return sp值 + */ + public static int px2sp( float pxValue) { + final float fontScale = XFrame.getDisplayMetrics().scaledDensity; + return (int) (pxValue / fontScale + 0.5f); + } + + /** + * 各种单位转换 + *该方法存在于TypedValue
+ * + * @param unit 单位 + * @param value 值 + * @param metrics DisplayMetrics + * @return 转换结果 + */ + public static float applyDimension(int unit, float value, DisplayMetrics metrics) { + switch (unit) { + case TypedValue.COMPLEX_UNIT_PX: + return value; + case TypedValue.COMPLEX_UNIT_DIP: + return value * metrics.density; + case TypedValue.COMPLEX_UNIT_SP: + return value * metrics.scaledDensity; + case TypedValue.COMPLEX_UNIT_PT: + return value * metrics.xdpi * (1.0f / 72); + case TypedValue.COMPLEX_UNIT_IN: + return value * metrics.xdpi; + case TypedValue.COMPLEX_UNIT_MM: + return value * metrics.xdpi * (1.0f / 25.4f); + } + return 0; + } + + /** + * 获取屏幕的宽度 + * @return + */ + public static int getScreenWidth() { + return XFrame.getDisplayMetrics().widthPixels; + } + + /** + * 获取屏幕的高度 + * @return + */ + public static int getScreenHeight() { + return XFrame.getDisplayMetrics().heightPixels; + } + + /** + * 获取屏幕真正的高度 + * @return + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public static int getScreenRealHeight() { + int h; + WindowManager winMgr = (WindowManager) XFrame.getSystemService(Context.WINDOW_SERVICE); + Display display = winMgr.getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + if (Build.VERSION.SDK_INT >= 17) { + display.getRealMetrics(dm); + h = dm.heightPixels; + } else { + try { + Method method = Class.forName("android.view.Display").getMethod("getRealMetrics", DisplayMetrics.class); + method.invoke(display, dm); + h = dm.heightPixels; + } catch (Exception e) { + display.getMetrics(dm); + h = dm.heightPixels; + } + } + return h; + } + + /** + * 获取顶部状态栏高度 + * @return + */ + public static int getStatusBarHeight() { + Class> c; + Object obj; + Field field; + int statusBarHeight = 0; + try { + c = Class.forName("com.android.internal.R$dimen"); + obj = c.newInstance(); + field = c.getField("status_bar_height"); + int x = Integer.parseInt(field.get(obj).toString()); + statusBarHeight = XFrame.getResources().getDimensionPixelSize(x); + } catch (Exception e1) { + e1.printStackTrace(); + } + return statusBarHeight; + } + + /** + * 获取底部导航栏高度 + * @return + */ + public static int getNavigationBarHeight() { + int navigationBarHeight = 0; + Resources resources = XFrame.getResources(); + int resourceId = resources.getIdentifier(resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android"); + if (resourceId > 0 && checkDeviceHasNavigationBar()) { + navigationBarHeight = resources.getDimensionPixelSize(resourceId); + } + return navigationBarHeight; + } + + /** + * 检测是否具有底部导航栏。(一加手机上判断不准确,希望有猿友能修复这个 bug) + * + * @return + */ + public static boolean checkDeviceHasNavigationBar() { + boolean hasNavigationBar = false; + Resources resources = XFrame.getResources(); + int id = resources.getIdentifier("config_showNavigationBar", "bool", "android"); + if (id > 0) { + hasNavigationBar = resources.getBoolean(id); + } + try { + Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method m = systemPropertiesClass.getMethod("get", String.class); + String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); + if ("1".equals(navBarOverride)) { + hasNavigationBar = false; + } else if ("0".equals(navBarOverride)) { + hasNavigationBar = true; + } + } catch (Exception e) { + + } + return hasNavigationBar; + } + + /** + * 在onCreate中获取视图的尺寸 + *需回调onGetSizeListener接口,在onGetSize中获取view宽高
+ *用法示例如下所示
+ *+ * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() { + * Override + * public void onGetSize(View view) { + * view.getWidth(); + * } + * }); + *+ * + * @param view 视图 + * @param listener 监听器 + */ + public static void forceGetViewSize(final View view, final onGetSizeListener listener) { + view.post(new Runnable() { + @Override + public void run() { + if (listener != null) { + listener.onGetSize(view); + } + } + }); + } + + /** + * 获取到View尺寸的监听 + */ + public interface onGetSizeListener { + void onGetSize(View view); + } + + public static void setListener(onGetSizeListener listener) { + mListener = listener; + } + + private static onGetSizeListener mListener; + + /** + * 测量视图尺寸 + * + * @param view 视图 + * @return arr[0]: 视图宽度, arr[1]: 视图高度 + */ + public static int[] measureView(View view) { + ViewGroup.LayoutParams lp = view.getLayoutParams(); + if (lp == null) { + lp = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + } + int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width); + int lpHeight = lp.height; + int heightSpec; + if (lpHeight > 0) { + heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY); + } else { + heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + } + view.measure(widthSpec, heightSpec); + return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()}; + } + + /** + * 获取测量视图宽度 + * + * @param view 视图 + * @return 视图宽度 + */ + public static int getMeasuredWidth(View view) { + return measureView(view)[0]; + } + + /** + * 获取测量视图高度 + * + * @param view 视图 + * @return 视图高度 + */ + public static int getMeasuredHeight(View view) { + return measureView(view)[1]; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XEmptyUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XEmptyUtils.java new file mode 100644 index 0000000..306f868 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XEmptyUtils.java @@ -0,0 +1,67 @@ +package com.youth.xframe.utils; + +import android.os.Build; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.SparseLongArray; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; + +/** + * 判断是否为空工具类 + */ +public class XEmptyUtils { + + /** + * 判断对象是否为null或长度数量为0 + * + * @param obj 对象 + * @return {@code true}: 为空
{@code false}: 不为空 + */ + public static boolean isEmpty(Object obj) { + if (obj == null) { + return true; + } + if (obj instanceof String && obj.toString().length() == 0) { + return true; + } + if (obj.getClass().isArray() && Array.getLength(obj) == 0) { + return true; + } + if (obj instanceof Collection && ((Collection) obj).isEmpty()) { + return true; + } + if (obj instanceof Map && ((Map) obj).isEmpty()) { + return true; + } + if (obj instanceof SparseArray && ((SparseArray) obj).size() == 0) { + return true; + } + if (obj instanceof SparseBooleanArray && ((SparseBooleanArray) obj).size() == 0) { + return true; + } + if (obj instanceof SparseIntArray && ((SparseIntArray) obj).size() == 0) { + return true; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + if (obj instanceof SparseLongArray && ((SparseLongArray) obj).size() == 0) { + return true; + } + } + return false; + } + + /** + * 判断字符串是否为null或全为空格 + * + * @param s 待校验字符串 + * @return {@code true}: null或全空格
{@code false}: 不为null且不全空格 + */ + public static boolean isSpace(String s) { + return (s == null || s.trim().length() == 0); + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XEncryptUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XEncryptUtils.java new file mode 100644 index 0000000..1ed5925 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XEncryptUtils.java @@ -0,0 +1,42 @@ +package com.youth.xframe.utils; + +import java.security.MessageDigest; + +/** + * 加密工具类 + */ + +public class XEncryptUtils { + + /** + * 描述:MD5加密. + * + * @param str + * 要加密的字符串 + * @return String 加密的字符串 + */ + public final static String MD5(String str) { + char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符 + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + try { + byte[] strTemp = str.getBytes(); + MessageDigest mdTemp = MessageDigest.getInstance("MD5"); + mdTemp.update(strTemp); + byte tmp[] = mdTemp.digest(); // MD5 的计算结果是一个 128 位的长整数, + // 用字节表示就是 16 个字节 + char strs[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符, + // 所以表示成 16 进制需要 32 个字符 + int k = 0; // 表示转换结果中对应的字符位置 + for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节 + // 转换成 16 进制字符的转换 + byte byte0 = tmp[i]; // 取第 i 个字节 + strs[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, + // >>> 为逻辑右移,将符号位一起右移 + strs[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换 + } + return new String(strs).toLowerCase(); // 换后的结果转换为字符串 + } catch (Exception e) { + return null; + } + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XFastBlurUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XFastBlurUtils.java new file mode 100644 index 0000000..b6b985e --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XFastBlurUtils.java @@ -0,0 +1,257 @@ +package com.youth.xframe.utils; + +import android.graphics.Bitmap; +import android.media.ThumbnailUtils; + +/** + * 毛玻璃效果 + */ +public class XFastBlurUtils { + /** + * 对图片进行毛玻璃化 + * @param sentBitmap 位图 + * @param radius 虚化程度 + * @param canReuseInBitmap 是否重用 + * @return 位图 + */ + public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) { + Bitmap bitmap; + if (canReuseInBitmap) { + bitmap = sentBitmap; + } else { + bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); + } + + if (radius < 1) { + return (null); + } + + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + int[] pix = new int[w * h]; + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int r[] = new int[wh]; + int g[] = new int[wh]; + int b[] = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int vmin[] = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int dv[] = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } + + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < h; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < w; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += w; + } + for (x = 0; x < w; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += w; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += w; + } + } + + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + + // print("虚化后 ",bitmap); + return (bitmap); + } + + /** + * 对图片进行毛玻璃化 数值越大效果越明显 + * @param originBitmap 位图 + * @param scaleRatio 缩放比率 + * @param blurRadius 毛玻璃化比率,虚化程度 + * @return 位图 + */ + public static Bitmap doBlur(Bitmap originBitmap, int scaleRatio, int blurRadius){ + // print("原图::",originBitmap); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(originBitmap, + originBitmap.getWidth() / scaleRatio, + originBitmap.getHeight() / scaleRatio, + false); + Bitmap blurBitmap = doBlur(scaledBitmap, blurRadius, false); + scaledBitmap.recycle(); + return blurBitmap; + } + + + /** + * 对图片进行 毛玻璃化,虚化 数值越大效果越明显 + * @param originBitmap 位图 + * @param width 缩放后的期望宽度 + * @param height 缩放后的期望高度 + * @param blurRadius 虚化程度 + * @return 位图 + */ + public static Bitmap doBlur(Bitmap originBitmap,int width,int height,int blurRadius){ + Bitmap thumbnail = ThumbnailUtils.extractThumbnail(originBitmap, width, height); + Bitmap blurBitmap = doBlur(thumbnail, blurRadius, true); + thumbnail.recycle(); + return blurBitmap; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XFormatTimeUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XFormatTimeUtils.java new file mode 100644 index 0000000..3a413fe --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XFormatTimeUtils.java @@ -0,0 +1,77 @@ +package com.youth.xframe.utils; + +/** + * 用来计算显示的时间是多久之前的! + */ + +public class XFormatTimeUtils { + + /** + * 格式化友好的时间差显示方式 + * + * @param millis 开始时间戳 + * @return + */ + public String getTimeSpanByNow1(long millis) { + long now = System.currentTimeMillis(); + long span = now - millis; + if (span < 1000) { + return "刚刚"; + }else if (span < XDateUtils.MIN) { + return String.format("%d秒前", span / XDateUtils.SEC); + }else if (span < XDateUtils.HOUR) { + return String.format("%d分钟前", span / XDateUtils.MIN); + }else if (span < XDateUtils.DAY) { + return String.format("%d小时前", span / XDateUtils.HOUR); + }else if (span < XDateUtils.WEEK) { + return String.format("%d天前", span / XDateUtils.DAY); + }else if (span < XDateUtils.MONTH) { + return String.format("%d周前", span / XDateUtils.WEEK); + }else if (span < XDateUtils.YEAR) { + return String.format("%d月前", span / XDateUtils.MONTH); + }else { + return String.format("%d年前", span / XDateUtils.YEAR); + } + } + + + /** + * 格式化友好的时间差显示方式 + * + * @param millis 开始时间戳 + * @return + */ + public static String getTimeSpanByNow2(long millis) { + long now = System.currentTimeMillis(); + long span = now - millis; + long day = span /XDateUtils.DAY; + if (day == 0) {// 今天 + long hour=span/XDateUtils.HOUR; + if(hour <=4){ + return String.format("凌晨%tR", millis); + }else if(hour >4 && hour <=6){ + return String.format("早上%tR", millis); + }else if(hour >6 && hour <=11){ + return String.format("上午%tR", millis); + }else if(hour >11 && hour <=13){ + return String.format("中午%tR", millis); + }else if(hour >13 && hour <=18){ + return String.format("下午%tR", millis); + }else if(hour >18 && hour <=19){ + return String.format("傍晚%tR", millis); + }else if(hour >19 && hour <=24){ + return String.format("晚上%tR", millis); + }else{ + return String.format("今天%tR", millis); + } + } else if (day == 1) {// 昨天 + return String.format("昨天%tR", millis); + } else if (day == 2) {// 前天 + return String.format("前天%tR", millis); + } else { + return String.format("%tF", millis); + } + } + + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XKeyboardUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XKeyboardUtils.java new file mode 100644 index 0000000..0cfed9a --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XKeyboardUtils.java @@ -0,0 +1,117 @@ +package com.youth.xframe.utils; + +import android.app.Activity; +import android.app.Dialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +/** + * 键盘相关工具类 + */ +public class XKeyboardUtils { + + + /** + * 关闭activity中打开的键盘 + * + * @param activity + */ + public static void closeKeyboard(Activity activity) { + View view = activity.getWindow().peekDecorView(); + if (view != null) { + InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + /** + * 关闭dialog中打开的键盘 + * + * @param dialog + */ + public static void closeKeyboard(Dialog dialog) { + View view = dialog.getWindow().peekDecorView(); + if (view != null) { + InputMethodManager inputMethodManager = (InputMethodManager) dialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + /** + * 打开键盘 + * + * @param context + * @param editText + */ + public static void openKeyboard(final Context context, final EditText editText) { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + editText.requestFocus(); + editText.setSelection(editText.getText().toString().length()); + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED); + } + }, 300); + } + + /** + * 拷贝文档到黏贴板 + * + * @param text + */ + public static void clip(Context context, String text) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + android.text.ClipboardManager clipboardManager = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + clipboardManager.setText(text); + } else { + ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + clipboardManager.setPrimaryClip(ClipData.newPlainText("content", text)); + } + } + + /** + * 切换键盘的显示与隐藏 + * + * @param activity + */ + public static void toggleKeyboard(Activity activity) { + InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputMethodManager.isActive()) { + inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } + } + + /** + * 处理点击非 EditText 区域时,自动关闭键盘 + * + * @param isAutoCloseKeyboard 是否自动关闭键盘 + * @param currentFocusView 当前获取焦点的控件 + * @param motionEvent 触摸事件 + * @param dialogOrActivity Dialog 或 Activity + */ + public static void handleAutoCloseKeyboard(boolean isAutoCloseKeyboard, View currentFocusView, MotionEvent motionEvent, Object dialogOrActivity) { + if (isAutoCloseKeyboard && motionEvent.getAction() == MotionEvent.ACTION_DOWN && currentFocusView != null && (currentFocusView instanceof EditText) && dialogOrActivity != null) { + int[] leftTop = {0, 0}; + currentFocusView.getLocationInWindow(leftTop); + int left = leftTop[0]; + int top = leftTop[1]; + int bottom = top + currentFocusView.getHeight(); + int right = left + currentFocusView.getWidth(); + if (!(motionEvent.getX() > left && motionEvent.getX() < right && motionEvent.getY() > top && motionEvent.getY() < bottom)) { + if (dialogOrActivity instanceof Dialog) { + XKeyboardUtils.closeKeyboard((Dialog) dialogOrActivity); + } else if (dialogOrActivity instanceof Activity) { + XKeyboardUtils.closeKeyboard((Activity) dialogOrActivity); + } + } + } + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XNetworkUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XNetworkUtils.java new file mode 100644 index 0000000..0ff8478 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XNetworkUtils.java @@ -0,0 +1,357 @@ +package com.youth.xframe.utils; + +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.telephony.TelephonyManager; + +import com.youth.xframe.XFrame; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +/** + * 网络相关工具类 + */ + +public class XNetworkUtils { + private static Context context=XFrame.getContext(); + public enum NetworkType { + NETWORK_WIFI, + NETWORK_4G, + NETWORK_3G, + NETWORK_2G, + NETWORK_UNKNOWN, + NETWORK_NO + } + + /** + * 打开网络设置界面 + *3.0以下打开设置界面
+ */ + public static void openWirelessSettings() { + if (android.os.Build.VERSION.SDK_INT > 10) { + context.startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } else { + context.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + } + + /** + * 获取活动网络信息 + *需添加权限 {@code
+ * + * @return NetworkInfo + */ + private static NetworkInfo getActiveNetworkInfo() { + return ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); + } + + /** + * 判断网络是否连接 + *} 需添加权限 {@code
+ * + * @return {@code true}: 是}
{@code false}: 否 + */ + public static boolean isConnected() { + NetworkInfo info = getActiveNetworkInfo(); + return info != null && info.isConnected(); + } + + /** + * 判断网络是否可用 + *需添加权限 {@code
+ * + * @return {@code true}: 可用}
{@code false}: 不可用 + */ + public static boolean isAvailable() { + NetworkInfo info = getActiveNetworkInfo(); + return info != null && info.isAvailable(); + } + + /** + * Android 判断是否能真正上网(避免连入wifi无网的状态) + * @return + */ + public static final boolean ping() { + try { + String ip = "www.baidu.com";// ping 的地址,可以换成任何一种可靠的外网 + Process p = Runtime.getRuntime().exec("ping -c 3 -w 100 " + ip);// ping网址3次 + // ping的状态 + int status = p.waitFor(); + if (status == 0) { + return true; + } + } catch (Exception e) { + } + return false; + } + /** + * 判断移动数据是否打开 + * + * @return {@code true}: 是
{@code false}: 否 + */ + public static boolean getDataEnabled() { + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + Method getMobileDataEnabledMethod = tm.getClass().getDeclaredMethod("getDataEnabled"); + if (null != getMobileDataEnabledMethod) { + return (boolean) getMobileDataEnabledMethod.invoke(tm); + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * 打开或关闭移动数据 + *需系统应用 需添加权限{@code
+ * + * @param enabled {@code true}: 打开}
{@code false}: 关闭 + */ + public static void setDataEnabled(boolean enabled) { + try { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + Method setMobileDataEnabledMethod = tm.getClass().getDeclaredMethod("setDataEnabled", boolean.class); + if (null != setMobileDataEnabledMethod) { + setMobileDataEnabledMethod.invoke(tm, enabled); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 判断网络是否是4G + *需添加权限 {@code
+ * + * @return {@code true}: 是}
{@code false}: 否 + */ + public static boolean is4G() { + NetworkInfo info = getActiveNetworkInfo(); + return info != null && info.isAvailable() && info.getSubtype() == TelephonyManager.NETWORK_TYPE_LTE; + } + + /** + * 判断wifi是否打开 + *需添加权限 {@code
+ * + * @return {@code true}: 是}
{@code false}: 否 + */ + public static boolean getWifiEnabled() { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + return wifiManager.isWifiEnabled(); + } + + /** + * 打开或关闭wifi + *需添加权限 {@code
+ * + * @param enabled {@code true}: 打开}
{@code false}: 关闭 + */ + public static void setWifiEnabled(boolean enabled) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (enabled) { + if (!wifiManager.isWifiEnabled()) { + wifiManager.setWifiEnabled(true); + } + } else { + if (wifiManager.isWifiEnabled()) { + wifiManager.setWifiEnabled(false); + } + } + } + + /** + * 判断wifi是否连接状态 + *需添加权限 {@code
+ * + * @return {@code true}: 连接}
{@code false}: 未连接 + */ + public static boolean isWifiConnected() { + ConnectivityManager cm = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI; + } + + /** + * 判断wifi数据是否可用 + *需添加权限 {@code
+ *} 需添加权限 {@code
+ * + * @return {@code true}: 是}
{@code false}: 否 + */ + public static boolean isWifiAvailable() { + return getWifiEnabled() && isAvailable(); + } + + /** + * 获取网络运营商名称 + *中国移动、如中国联通、中国电信
+ * + * @return 运营商名称 + */ + public static String getNetworkOperatorName() { + TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + return tm != null ? tm.getNetworkOperatorName() : null; + } + + private static final int NETWORK_TYPE_GSM = 16; + private static final int NETWORK_TYPE_TD_SCDMA = 17; + private static final int NETWORK_TYPE_IWLAN = 18; + + /** + * 获取当前网络类型 + *需添加权限 {@code
+ * + * @return 网络类型 + *} + *
+ */ + public static NetworkType getNetworkType() { + NetworkType netType = NetworkType.NETWORK_NO; + NetworkInfo info = getActiveNetworkInfo(); + if (info != null && info.isAvailable()) { + + if (info.getType() == ConnectivityManager.TYPE_WIFI) { + netType = NetworkType.NETWORK_WIFI; + } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) { + switch (info.getSubtype()) { + + case NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + netType = NetworkType.NETWORK_2G; + break; + + case NETWORK_TYPE_TD_SCDMA: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + netType = NetworkType.NETWORK_3G; + break; + + case NETWORK_TYPE_IWLAN: + case TelephonyManager.NETWORK_TYPE_LTE: + netType = NetworkType.NETWORK_4G; + break; + default: + + String subtypeName = info.getSubtypeName(); + if (subtypeName.equalsIgnoreCase("TD-SCDMA") + || subtypeName.equalsIgnoreCase("WCDMA") + || subtypeName.equalsIgnoreCase("CDMA2000")) { + netType = NetworkType.NETWORK_3G; + } else { + netType = NetworkType.NETWORK_UNKNOWN; + } + break; + } + } else { + netType = NetworkType.NETWORK_UNKNOWN; + } + } + return netType; + } + + /** + * 获取IP地址 + *- {@link XNetworkUtils.NetworkType#NETWORK_WIFI }
+ *- {@link XNetworkUtils.NetworkType#NETWORK_4G }
+ *- {@link XNetworkUtils.NetworkType#NETWORK_3G }
+ *- {@link XNetworkUtils.NetworkType#NETWORK_2G }
+ *- {@link XNetworkUtils.NetworkType#NETWORK_UNKNOWN}
+ *- {@link XNetworkUtils.NetworkType#NETWORK_NO }
+ *需添加权限 {@code
+ * + * @param useIPv4 是否用IPv4 + * @return IP地址 + */ + public static String getIPAddress(boolean useIPv4) { + try { + for (Enumeration} nis = NetworkInterface.getNetworkInterfaces(); nis.hasMoreElements(); ) { + NetworkInterface ni = nis.nextElement(); + // 防止小米手机返回10.0.2.15 + if (!ni.isUp()) continue; + for (Enumeration addresses = ni.getInetAddresses(); addresses.hasMoreElements(); ) { + InetAddress inetAddress = addresses.nextElement(); + if (!inetAddress.isLoopbackAddress()) { + String hostAddress = inetAddress.getHostAddress(); + boolean isIPv4 = hostAddress.indexOf(':') < 0; + if (useIPv4) { + if (isIPv4) return hostAddress; + } else { + if (!isIPv4) { + int index = hostAddress.indexOf('%'); + return index < 0 ? hostAddress.toUpperCase() : hostAddress.substring(0, index).toUpperCase(); + } + } + } + } + } + } catch (SocketException e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取域名ip地址 + * 需添加权限 {@code
+ * + * @param domain 域名 + * @return ip地址 + */ + public static String getDomainAddress(final String domain) { + try { + ExecutorService exec = Executors.newCachedThreadPool(); + Future} fs = exec.submit(new Callable () { + @Override + public String call() throws Exception { + InetAddress inetAddress; + try { + inetAddress = InetAddress.getByName(domain); + return inetAddress.getHostAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + return null; + } + }); + return fs.get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XOutdatedUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XOutdatedUtils.java new file mode 100644 index 0000000..13ff894 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XOutdatedUtils.java @@ -0,0 +1,40 @@ +package com.youth.xframe.utils; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.view.View; + +import com.youth.xframe.XFrame; + +/** + * 此类主要是用来放一些系统过时方法的处理 + */ +public class XOutdatedUtils { + /** + * setBackgroundDrawable过时方法处理 + * @param view + * @param drawable + */ + public static void setBackground(@NonNull View view, Drawable drawable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + view.setBackground(drawable); + else + view.setBackgroundDrawable(drawable); + } + + /** + * getDrawable过时方法处理 + * @param id + * @return + */ + public static Drawable getDrawable(@DrawableRes int id) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + return XFrame.getContext().getDrawable(id); + else + return XFrame.getResources().getDrawable(id); + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XPreferencesUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XPreferencesUtils.java new file mode 100644 index 0000000..fec96e4 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XPreferencesUtils.java @@ -0,0 +1,162 @@ +package com.youth.xframe.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.youth.xframe.XFrame; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; + + +public class XPreferencesUtils { + /** + * 保存在手机里面的文件名 + */ + public static final String FILE_NAME = "x_frame_config"; + + /** + * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法 + * + * @param key + * @param object + */ + public static void put(String key, Object object) { + + SharedPreferences sp = XFrame.getContext().getSharedPreferences(FILE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + + if (object instanceof String) { + editor.putString(key, (String) object); + } else if (object instanceof Integer) { + editor.putInt(key, (Integer) object); + } else if (object instanceof Boolean) { + editor.putBoolean(key, (Boolean) object); + } else if (object instanceof Float) { + editor.putFloat(key, (Float) object); + } else if (object instanceof Long) { + editor.putLong(key, (Long) object); + } else { + editor.putString(key, object.toString()); + } + + SharedPreferencesCompat.apply(editor); + } + + /** + * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值 + * @param key + * @param defaultObject + * @return + */ + public static Object get(String key, Object defaultObject) { + SharedPreferences sp = XFrame.getContext().getSharedPreferences(FILE_NAME, + Context.MODE_PRIVATE); + if (defaultObject instanceof String) { + return sp.getString(key, (String) defaultObject); + } else if (defaultObject instanceof Integer) { + return sp.getInt(key, (Integer) defaultObject); + } else if (defaultObject instanceof Boolean) { + return sp.getBoolean(key, (Boolean) defaultObject); + } else if (defaultObject instanceof Float) { + return sp.getFloat(key, (Float) defaultObject); + } else if (defaultObject instanceof Long) { + return sp.getLong(key, (Long) defaultObject); + } + return null; + } + + /** + * 移除某个key值已经对应的值 + * + * @param key + */ + public static void remove(String key) { + SharedPreferences sp = XFrame.getContext().getSharedPreferences(FILE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.remove(key); + SharedPreferencesCompat.apply(editor); + } + + /** + * 清除所有数据 + * + */ + public static void clearAll() { + SharedPreferences sp = XFrame.getContext().getSharedPreferences(FILE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sp.edit(); + editor.clear(); + SharedPreferencesCompat.apply(editor); + } + + /** + * 查询某个key是否已经存在 + * + * @param key + * @return + */ + public static boolean contains(String key) { + SharedPreferences sp = XFrame.getContext().getSharedPreferences(FILE_NAME, + Context.MODE_PRIVATE); + return sp.contains(key); + } + + /** + * 返回所有的键值对 + * + * @return + */ + public static Map getAll() { + SharedPreferences sp = XFrame.getContext().getSharedPreferences(FILE_NAME, + Context.MODE_PRIVATE); + return sp.getAll(); + } + + /** + * 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类 + * + * @author zhy + */ + private static class SharedPreferencesCompat { + private static final Method sApplyMethod = findApplyMethod(); + + /** + * 反射查找apply的方法 + * + * @return + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Method findApplyMethod() { + try { + Class clz = SharedPreferences.Editor.class; + return clz.getMethod("apply"); + } catch (NoSuchMethodException e) { + } + + return null; + } + + /** + * 如果找到则使用apply执行,否则使用commit + * + * @param editor + */ + public static void apply(SharedPreferences.Editor editor) { + try { + if (sApplyMethod != null) { + sApplyMethod.invoke(editor); + return; + } + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + editor.commit(); + } + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XPrintUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XPrintUtils.java new file mode 100644 index 0000000..b14734b --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XPrintUtils.java @@ -0,0 +1,64 @@ +package com.youth.xframe.utils; + +import com.youth.xframe.XFrame; + +/** + * 此内用于框架系统打印输出控制,使用者用XLog格式化体验更好。 + * + */ +public class XPrintUtils { + private static String tag = XFrame.tag; + private static boolean log = XFrame.isDebug; + + public static void setLog(boolean log) { + XPrintUtils.log = log; + } + + public static void i(String msg) { + if (log) + android.util.Log.i(tag, msg); + } + + public static void i(String tag, String msg) { + if (log) + android.util.Log.i(tag, msg); + } + + public static void d(String msg) { + if (log) + android.util.Log.d(tag, msg); + } + + public static void d(String tag, String msg) { + if (log) + android.util.Log.d(tag, msg); + } + + public static void w(String msg) { + if (log) + android.util.Log.w(tag, msg); + } + + public static void w(String tag, String msg) { + if (log) + android.util.Log.w(tag, msg); + } + + public static void v(String msg) { + if (log) + android.util.Log.v(tag, msg); + } + + public static void v(String tag, String msg) { + if (log) + android.util.Log.v(tag, msg); + } + + public static void e(String msg) { + android.util.Log.e(tag, msg); + } + + public static void e(String tag, String msg) { + android.util.Log.e(tag, msg); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XRegexUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XRegexUtils.java new file mode 100644 index 0000000..a77047d --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XRegexUtils.java @@ -0,0 +1,235 @@ +package com.youth.xframe.utils; + +import java.util.regex.Pattern; + +/** + * 正则工具类 + * 验证邮箱、手机号、电话号码、身份证号码、银行卡卡号、IP、邮政编码、车牌号等 + */ +public class XRegexUtils { + /** + * 手机号码,中间4位星号替换 + * + * @param phone 手机号 + * @return 星号替换的手机号 + */ + public static String phoneNoHide(String phone) { + // 括号表示组,被替换的部分$n表示第n组的内容 + // 正则表达式中,替换字符串,括号的意思是分组,在replace()方法中, + // 参数二中可以使用$n(n为数字)来依次引用模式串中用括号定义的字串。 + // "(\d{3})\d{4}(\d{4})", "$1****$2"的这个意思就是用括号, + // 分为(前3个数字)中间4个数字(最后4个数字)替换为(第一组数值,保持不变$1)(中间为*)(第二组数值,保持不变$2) + return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); + } + + /** + * 银行卡号,保留最后4位,其他星号替换 + * + * @param cardId 卡号 + * @return 星号替换的银行卡号 + */ + public static String cardIdHide(String cardId) { + return cardId.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1"); + } + + /** + * 身份证号,中间10位星号替换 + * + * @param id 身份证号 + * @return 星号替换的身份证号 + */ + public static String idHide(String id) { + return id.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1** **** ****$2"); + } + + /** + * 是否为车牌号(沪A88888) + * + * @param vehicleNo 车牌号 + * @return 是否为车牌号 + */ + + public static boolean checkVehicleNo(String vehicleNo) { + Pattern pattern = Pattern.compile("^[\u4e00-\u9fa5]{1}[a-zA-Z]{1}[a-zA-Z_0-9]{5}$"); + return pattern.matcher(vehicleNo).find(); + + } + + /** + * 验证身份证号码 + * + * @param idCard 居民身份证号码15位或18位,最后一位可能是数字或字母 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkIdCard(String idCard) { + String regex = "[1-9]\\d{13,16}[a-zA-Z0-9]{1}"; + return Pattern.matches(regex, idCard); + } + + + /** + * 验证手机号码(支持国际格式,+86135xxxx...(中国内地),+00852137xxxx...(中国香港)) + * + * @param mobile 移动、联通、电信运营商的号码段 + * 移动的号段:134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、187、188
+ *联通的号段:130、131、132、155、156、185、186
+ *电信的号段:133、153、180、189
+ * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkMobile(String mobile) { + String regex = "(\\+\\d+)?1[3458]\\d{9}$"; + return Pattern.matches(regex, mobile); + } + + /** + * 验证固定电话号码 + * + * @param phone 电话号码,格式:国家(地区)电话代码 + 区号(城市代码) + 电话号码,如:+8602085588447 + *国家(地区) 代码 :标识电话号码的国家(地区)的标准国家(地区)代码。它包含从 0 到 9 的一位或多位数字, + * 数字之后是空格分隔的国家(地区)代码。
+ *区号(城市代码):这可能包含一个或多个从 0 到 9 的数字,地区或城市代码放在圆括号—— + * 对不使用地区或城市代码的国家(地区),则省略该组件。
+ *电话号码:这包含从 0 到 9 的一个或多个数字
+ * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkPhone(String phone) { + String regex = "(\\+\\d+)?(\\d{3,4}\\-?)?\\d{7,8}$"; + return Pattern.matches(regex, phone); + } + + /** + * 验证Email + * + * @param email email地址,格式:zhangsan@sina.com,zhangsan@xxx.com.cn,xxx代表邮件服务商 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkEmail(String email) { + String regex = "\\w+@\\w+\\.[a-z]+(\\.[a-z]+)?"; + return Pattern.matches(regex, email); + } + + /** + * 验证整数(正整数和负整数) + * + * @param digit 一位或多位0-9之间的整数 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkDigit(String digit) { + String regex = "\\-?[1-9]\\d+"; + return Pattern.matches(regex, digit); + } + + /** + * 验证整数和浮点数(正负整数和正负浮点数) + * + * @param decimals 一位或多位0-9之间的浮点数,如:1.23,233.30 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkDecimals(String decimals) { + String regex = "\\-?[1-9]\\d+(\\.\\d+)?"; + return Pattern.matches(regex, decimals); + } + + /** + * 验证空白字符 + * + * @param blankSpace 空白字符,包括:空格、\t、\n、\r、\f、\x0B + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkBlankSpace(String blankSpace) { + String regex = "\\s+"; + return Pattern.matches(regex, blankSpace); + } + + /** + * 验证中文 + * + * @param chinese 中文字符 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkChinese(String chinese) { + String regex = "^[\u4E00-\u9FA5]+$"; + return Pattern.matches(regex, chinese); + } + + /** + * 验证日期(年月日) + * + * @param birthday 日期,格式:1992-09-03,或1992.09.03 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkBirthday(String birthday) { + String regex = "[1-9]{4}([-./])\\d{1,2}\\1\\d{1,2}"; + return Pattern.matches(regex, birthday); + } + + /** + * 验证URL地址 + * + * @param url 格式:http://blog.csdn.net:80/xyang81/article/details/7705960? 或 http://www.csdn.net:80 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkURL(String url) { + String regex = "(https?://(w{3}\\.)?)?\\w+\\.\\w+(\\.[a-zA-Z]+)*(:\\d{1,5})?(/\\w*)*(\\??(.+=.*)?(&.+=.*)?)?"; + return Pattern.matches(regex, url); + } + + /** + * 匹配中国邮政编码 + * + * @param postcode 邮政编码 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkPostcode(String postcode) { + String regex = "[1-9]\\d{5}"; + return Pattern.matches(regex, postcode); + } + + /** + * 匹配IP地址(简单匹配,格式,如:192.168.1.1,127.0.0.1,没有匹配IP段的大小) + * + * @param ipAddress IPv4标准地址 + * @return 验证成功返回true,验证失败返回false + */ + public static boolean checkIpAddress(String ipAddress) { + String regex = "[1-9](\\d{1,2})?\\.(0|([1-9](\\d{1,2})?))\\.(0|([1-9](\\d{1,2})?))\\.(0|([1-9](\\d{1,2})?))"; + return Pattern.matches(regex, ipAddress); + } + /** + * 校验银行卡卡号 + * + * @param cardId 卡号 + * @return 是否有效卡号 + */ + public static boolean checkBankCard(String cardId) { + char bit = getBankCardCheckCode(cardId + .substring(0, cardId.length() - 1)); + return bit != 'N' && cardId.charAt(cardId.length() - 1) == bit; + } + + /** + * 从不含校验位的银行卡卡号采用 Luhm 校验算法获得校验位 + * + * @param nonCheckCodeCardId 不含校验位的银行卡 + * @return 校验结果 + */ + public static char getBankCardCheckCode(String nonCheckCodeCardId) { + if (nonCheckCodeCardId == null + || nonCheckCodeCardId.trim().length() == 0 + || !nonCheckCodeCardId.matches("\\d+")) { + // 如果传的不是数据返回N + return 'N'; + } + char[] chs = nonCheckCodeCardId.trim().toCharArray(); + int luhmSum = 0; + for (int i = chs.length - 1, j = 0; i >= 0; i--, j++) { + int k = chs[i] - '0'; + if (j % 2 == 0) { + k *= 2; + k = k / 10 + k % 10; + } + luhmSum += k; + } + return (luhmSum % 10 == 0) ? '0' : (char) ((10 - luhmSum % 10) + '0'); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/XViewHolderUtils.java b/xframe/src/main/java/com/youth/xframe/utils/XViewHolderUtils.java new file mode 100644 index 0000000..c300516 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/XViewHolderUtils.java @@ -0,0 +1,27 @@ +package com.youth.xframe.utils; + +import android.util.SparseArray; +import android.view.View; + +/** + * ViewHolder 简化创建工具类 + */ + +public class XViewHolderUtils { + @SuppressWarnings("unchecked") + public staticT get(View convertView, int id) { + + SparseArray viewHolder = (SparseArray ) convertView.getTag(); + if (viewHolder == null) { + viewHolder = new SparseArray (); + convertView.setTag(viewHolder); + } + + View childView = viewHolder.get(id); + if (childView == null) { + childView = convertView.findViewById(id); + viewHolder.put(id, childView); + } + return (T) childView; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/handler/XHandler.java b/xframe/src/main/java/com/youth/xframe/utils/handler/XHandler.java new file mode 100644 index 0000000..455ed5f --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/handler/XHandler.java @@ -0,0 +1,477 @@ +package com.youth.xframe.utils.handler; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + +import java.lang.ref.WeakReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@SuppressWarnings("unused") +public class XHandler { + private final Handler.Callback mCallback; // hard reference to Callback. We need to keep callback in memory + private final ExecHandler mExec; + private Lock mLock = new ReentrantLock(); + @SuppressWarnings("ConstantConditions") + @VisibleForTesting + final ChainedRef mRunnables = new ChainedRef(mLock, null); + + /** + * Default constructor associates this handler with the {@link Looper} for the + * current thread. + * + * If this thread does not have a looper, this handler won't be able to receive messages + * so an exception is thrown. + */ + public XHandler() { + mCallback = null; + mExec = new ExecHandler(); + } + + /** + * Constructor associates this handler with the {@link Looper} for the + * current thread and takes a callback interface in which you can handle + * messages. + * + * If this thread does not have a looper, this handler won't be able to receive messages + * so an exception is thrown. + * + * @param callback The callback interface in which to handle messages, or null. + */ + public XHandler(@Nullable Handler.Callback callback) { + mCallback = callback; // Hard referencing body + mExec = new ExecHandler(new WeakReference<>(callback)); // Weak referencing inside ExecHandler + } + + /** + * Use the provided {@link Looper} instead of the default one. + * + * @param looper The looper, must not be null. + */ + public XHandler(@NonNull Looper looper) { + mCallback = null; + mExec = new ExecHandler(looper); + } + + /** + * Use the provided {@link Looper} instead of the default one and take a callback + * interface in which to handle messages. + * + * @param looper The looper, must not be null. + * @param callback The callback interface in which to handle messages, or null. + */ + public XHandler(@NonNull Looper looper, @NonNull Handler.Callback callback) { + mCallback = callback; + mExec = new ExecHandler(looper, new WeakReference<>(callback)); + } + + /** + * Causes the Runnable r to be added to the message queue. + * The runnable will be run on the thread to which this handler is + * attached. + * + * @param r The Runnable that will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean post(@NonNull Runnable r) { + return mExec.post(wrapRunnable(r)); + } + + /** + * Causes the Runnable r to be added to the message queue, to be run + * at a specific time given by uptimeMillis. + * The time-base is {@link android.os.SystemClock#uptimeMillis}. + * The runnable will be run on the thread to which this handler is attached. + * + * @param r The Runnable that will be executed. + * @param uptimeMillis The absolute time at which the callback should run, + * using the {@link android.os.SystemClock#uptimeMillis} time-base. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) { + return mExec.postAtTime(wrapRunnable(r), uptimeMillis); + } + + /** + * Causes the Runnable r to be added to the message queue, to be run + * at a specific time given by uptimeMillis. + * The time-base is {@link android.os.SystemClock#uptimeMillis}. + * The runnable will be run on the thread to which this handler is attached. + * + * @param r The Runnable that will be executed. + * @param uptimeMillis The absolute time at which the callback should run, + * using the {@link android.os.SystemClock#uptimeMillis} time-base. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + * + * @see android.os.SystemClock#uptimeMillis + */ + public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { + return mExec.postAtTime(wrapRunnable(r), token, uptimeMillis); + } + + /** + * Causes the Runnable r to be added to the message queue, to be run + * after the specified amount of time elapses. + * The runnable will be run on the thread to which this handler + * is attached. + * + * @param r The Runnable that will be executed. + * @param delayMillis The delay (in milliseconds) until the Runnable + * will be executed. + * + * @return Returns true if the Runnable was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the Runnable will be processed -- + * if the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean postDelayed(Runnable r, long delayMillis) { + return mExec.postDelayed(wrapRunnable(r), delayMillis); + } + + /** + * Posts a message to an object that implements Runnable. + * Causes the Runnable r to executed on the next iteration through the + * message queue. The runnable will be run on the thread to which this + * handler is attached. + * This method is only for use in very special circumstances -- it + * can easily starve the message queue, cause ordering problems, or have + * other unexpected side-effects. + * + * @param r The Runnable that will be executed. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean postAtFrontOfQueue(Runnable r) { + return mExec.postAtFrontOfQueue(wrapRunnable(r)); + } + + /** + * Remove any pending posts of Runnable r that are in the message queue. + */ + public final void removeCallbacks(Runnable r) { + final WeakRunnable runnable = mRunnables.remove(r); + if (runnable != null) { + mExec.removeCallbacks(runnable); + } + } + + /** + * Remove any pending posts of Runnable r with Object + * token that are in the message queue. If token is null, + * all callbacks will be removed. + */ + public final void removeCallbacks(Runnable r, Object token) { + final WeakRunnable runnable = mRunnables.remove(r); + if (runnable != null) { + mExec.removeCallbacks(runnable, token); + } + } + + /** + * Pushes a message onto the end of the message queue after all pending messages + * before the current time. It will be received in callback, + * in the thread attached to this handler. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendMessage(Message msg) { + return mExec.sendMessage(msg); + } + + /** + * Sends a Message containing only the what value. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendEmptyMessage(int what) { + return mExec.sendEmptyMessage(what); + } + + /** + * Sends a Message containing only the what value, to be delivered + * after the specified amount of time elapses. + * @see #sendMessageDelayed(android.os.Message, long) + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { + return mExec.sendEmptyMessageDelayed(what, delayMillis); + } + + /** + * Sends a Message containing only the what value, to be delivered + * at a specific time. + * @see #sendMessageAtTime(android.os.Message, long) + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { + return mExec.sendEmptyMessageAtTime(what, uptimeMillis); + } + + /** + * Enqueue a message into the message queue after all pending messages + * before (current time + delayMillis). You will receive it in + * callback, in the thread attached to this handler. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the message will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public final boolean sendMessageDelayed(Message msg, long delayMillis) { + return mExec.sendMessageDelayed(msg, delayMillis); + } + + /** + * Enqueue a message into the message queue after all pending messages + * before the absolute time (in milliseconds) uptimeMillis. + * The time-base is {@link android.os.SystemClock#uptimeMillis}. + * You will receive it in callback, in the thread attached + * to this handler. + * + * @param uptimeMillis The absolute time at which the message should be + * delivered, using the + * {@link android.os.SystemClock#uptimeMillis} time-base. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. Note that a + * result of true does not mean the message will be processed -- if + * the looper is quit before the delivery time of the message + * occurs then the message will be dropped. + */ + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + return mExec.sendMessageAtTime(msg, uptimeMillis); + } + + /** + * Enqueue a message at the front of the message queue, to be processed on + * the next iteration of the message loop. You will receive it in + * callback, in the thread attached to this handler. + * This method is only for use in very special circumstances -- it + * can easily starve the message queue, cause ordering problems, or have + * other unexpected side-effects. + * + * @return Returns true if the message was successfully placed in to the + * message queue. Returns false on failure, usually because the + * looper processing the message queue is exiting. + */ + public final boolean sendMessageAtFrontOfQueue(Message msg) { + return mExec.sendMessageAtFrontOfQueue(msg); + } + + /** + * Remove any pending posts of messages with code 'what' that are in the + * message queue. + */ + public final void removeMessages(int what) { + mExec.removeMessages(what); + } + + /** + * Remove any pending posts of messages with code 'what' and whose obj is + * 'object' that are in the message queue. If object is null, + * all messages will be removed. + */ + public final void removeMessages(int what, Object object) { + mExec.removeMessages(what, object); + } + + /** + * Remove any pending posts of callbacks and sent messages whose + * obj is token. If token is null, + * all callbacks and messages will be removed. + */ + public final void removeCallbacksAndMessages(Object token) { + mExec.removeCallbacksAndMessages(token); + } + + /** + * Check if there are any pending posts of messages with code 'what' in + * the message queue. + */ + public final boolean hasMessages(int what) { + return mExec.hasMessages(what); + } + + /** + * Check if there are any pending posts of messages with code 'what' and + * whose obj is 'object' in the message queue. + */ + public final boolean hasMessages(int what, Object object) { + return mExec.hasMessages(what, object); + } + + public final Looper getLooper() { + return mExec.getLooper(); + } + + private WeakRunnable wrapRunnable(@NonNull Runnable r) { + //noinspection ConstantConditions + if (r == null) { + throw new NullPointerException("Runnable can't be null"); + } + final ChainedRef hardRef = new ChainedRef(mLock, r); + mRunnables.insertAfter(hardRef); + return hardRef.wrapper; + } + + private static class ExecHandler extends Handler { + private final WeakReference mCallback; + + ExecHandler() { + mCallback = null; + } + + ExecHandler(WeakReference callback) { + mCallback = callback; + } + + ExecHandler(Looper looper) { + super(looper); + mCallback = null; + } + + ExecHandler(Looper looper, WeakReference callback) { + super(looper); + mCallback = callback; + } + + @Override + public void handleMessage(@NonNull Message msg) { + if (mCallback == null) { + return; + } + final Handler.Callback callback = mCallback.get(); + if (callback == null) { // Already disposed + return; + } + callback.handleMessage(msg); + } + } + + static class WeakRunnable implements Runnable { + private final WeakReference mDelegate; + private final WeakReference mReference; + + WeakRunnable(WeakReference delegate, WeakReference reference) { + mDelegate = delegate; + mReference = reference; + } + + @Override + public void run() { + final Runnable delegate = mDelegate.get(); + final ChainedRef reference = mReference.get(); + if (reference != null) { + reference.remove(); + } + if (delegate != null) { + delegate.run(); + } + } + } + + static class ChainedRef { + @Nullable + ChainedRef next; + @Nullable + ChainedRef prev; + @NonNull + final Runnable runnable; + @NonNull + final WeakRunnable wrapper; + + @NonNull + Lock lock; + + public ChainedRef(@NonNull Lock lock, @NonNull Runnable r) { + this.runnable = r; + this.lock = lock; + this.wrapper = new WeakRunnable(new WeakReference<>(r), new WeakReference<>(this)); + } + + public WeakRunnable remove() { + lock.lock(); + try { + if (prev != null) { + prev.next = next; + } + if (next != null) { + next.prev = prev; + } + prev = null; + next = null; + } finally { + lock.unlock(); + } + return wrapper; + } + + public void insertAfter(@NonNull ChainedRef candidate) { + lock.lock(); + try { + if (this.next != null) { + this.next.prev = candidate; + } + + candidate.next = this.next; + this.next = candidate; + candidate.prev = this; + } finally { + lock.unlock(); + } + } + + @Nullable + public WeakRunnable remove(Runnable obj) { + lock.lock(); + try { + ChainedRef curr = this.next; // Skipping head + while (curr != null) { + if (curr.runnable == obj) { // We do comparison exactly how Handler does inside + return curr.remove(); + } + curr = curr.next; + } + } finally { + lock.unlock(); + } + return null; + } + } +} \ No newline at end of file diff --git a/xframe/src/main/java/com/youth/xframe/utils/log/LoggerPrinter.java b/xframe/src/main/java/com/youth/xframe/utils/log/LoggerPrinter.java new file mode 100644 index 0000000..89c413e --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/log/LoggerPrinter.java @@ -0,0 +1,291 @@ +package com.youth.xframe.utils.log; + +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +final class LoggerPrinter implements Printer { + + /** + * Android一个日志条目不能超过4076字节, + * 这里设置以4000字节来计算 + */ + private static final int CHUNK_SIZE = 4000; + /** + * log settings + */ + private static final XLogConfig config = new XLogConfig(); + /** + * 样式 + */ + private static final char TOP_LEFT_CORNER = '┏'; + private static final char BOTTOM_LEFT_CORNER = '┗'; + private static final char MIDDLE_CORNER = '┠'; + private static final char HORIZONTAL_DOUBLE_LINE = '┃'; + private static final String DOUBLE_DIVIDER = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; + private static final String SINGLE_DIVIDER = "──────────────────────────────────────────────"; + private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER; + private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER; + private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER; + + public static String LINE_SEPARATOR = System.getProperty("line.separator"); + + private StringBuilder logStr=new StringBuilder(); + + /** + * 初始化 + */ + @Override + public XLogConfig init() { + return config; + } + + /** + * 返回最后一次格式化的打印结果样式 + * @return + */ + @Override + public String getFormatLog() { + return logStr.toString(); + } + + @Override + public void d(String message, Object... args) { + log(Log.DEBUG, message, args); + } + + @Override + public void e(String message, Object... args) { + e(null, message, args); + } + + @Override + public void e(Throwable throwable, String message, Object... args) { + if (throwable != null && message != null) { + message += " : " + throwable.toString(); + } + if (throwable != null && message == null) { + message = throwable.toString(); + } + if (message == null) { + message = "message/exception 为空!"; + } + log(Log.ERROR, message, args); + } + + @Override + public void w(String message, Object... args) { + log(Log.WARN, message, args); + } + + @Override + public void i(String message, Object... args) { + log(Log.INFO, message, args); + } + + @Override + public void v(String message, Object... args) { + log(Log.VERBOSE, message, args); + } + + @Override + public void wtf(String message, Object... args) { + log(Log.ASSERT, message, args); + } + + /** + * 格式化json + */ + @Override + public void json(String json) { + if (TextUtils.isEmpty(json)) { + d("json 数据为空!"); + return ; + } + try { + String message=""; + if (json.startsWith("{")) { + JSONObject jo = new JSONObject(json); + message = jo.toString(4); + } else if (json.startsWith("[")) { + JSONArray ja = new JSONArray(json); + message = ja.toString(4); + } + d(message); + } catch (Exception e) { + e(e.getCause().getMessage() + LINE_SEPARATOR + json); + } + } + + /** + * 格式化xml + */ + @Override + public void xml(String xml) { + if (TextUtils.isEmpty(xml)) { + d("xml 数据为空!"); + return; + } + try { + Source xmlInput = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.transform(xmlInput, xmlOutput); + String message=xmlOutput.getWriter().toString().replaceFirst(">", ">" + LINE_SEPARATOR); + d(message); + } catch (TransformerException e) { + e(e.getCause().getMessage() + LINE_SEPARATOR + xml); + } + } + + /** + * 格式化Map集合 + */ + @Override + public void map(Map map) { + if (map != null) { + StringBuilder stringBuilder = new StringBuilder(); + for (Object entry : map.entrySet()) { + stringBuilder.append("[key] → "); + stringBuilder.append(((Map.Entry) entry).getKey()); + stringBuilder.append(",[value] → "); + stringBuilder.append(((Map.Entry) entry).getValue()); + stringBuilder.append(LINE_SEPARATOR); + } + d(stringBuilder.toString()); + } + } + + /** + * 格式化List集合 + */ + @Override + public void list(List list) { + if (list != null) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < list.size(); i++) { + stringBuilder.append("[" + i + "] → "); + stringBuilder.append(list.get(i)); + stringBuilder.append(LINE_SEPARATOR); + } + d(stringBuilder.toString()); + } + } + + /** + * 同步日志打印顺序 + */ + private synchronized void log(int priority, String msg, Object... args) { + if (!config.isDebug()) { + return; + } + logStr.delete(0,logStr.length()); + String message = args.length == 0 ? msg : String.format(msg, args); + logChunk(priority, TOP_BORDER); + if (config.isShowThreadInfo()) { + //打印线程 + getStackInfo(priority); + } + //得到系统的默认字符集的信息字节(UTF-8) + byte[] bytes = message.getBytes(); + int length = bytes.length; + if (length <= CHUNK_SIZE) { + logContent(priority, message); + logChunk(priority, BOTTOM_BORDER); + return; + } + for (int i = 0; i < length; i += CHUNK_SIZE) { + int count = Math.min(length - i, CHUNK_SIZE); + //创建系统的默认字符集的一个新的字符串(UTF-8) + logContent(priority, new String(bytes, i, count)); + } + logChunk(priority, BOTTOM_BORDER); + } + + private void logContent(int priority, String chunk) { + String[] lines = chunk.split(LINE_SEPARATOR); + for (String line : lines) { + logChunk(priority, HORIZONTAL_DOUBLE_LINE + " " + line); + } + } + + private void logChunk(int priority, String chunk) { + logStr.append(LINE_SEPARATOR); + logStr.append(chunk); + String TAG = config.getTag(); + switch (priority) { + case Log.ERROR: + Log.e(TAG, chunk); + break; + case Log.INFO: + Log.i(TAG, chunk); + break; + case Log.VERBOSE: + Log.v(TAG, chunk); + break; + case Log.WARN: + Log.w(TAG, chunk); + break; + case Log.ASSERT: + Log.wtf(TAG, chunk); + break; + case Log.DEBUG: + default: + Log.d(TAG, chunk); + break; + } + } + + /** + * 打印堆栈信息. + */ + private void getStackInfo(int priority) { + logChunk(priority, HORIZONTAL_DOUBLE_LINE + "[Thread] → " + Thread.currentThread().getName()); + logChunk(priority, MIDDLE_BORDER); + String str=""; + StackTraceElement[] traces = Thread.currentThread().getStackTrace(); + + for (int i = 0; i < traces.length; i++) { + StackTraceElement element = traces[i]; + StringBuilder perTrace = new StringBuilder(str); + if (element.isNativeMethod()) { + continue; + } + String className=element.getClassName(); + if (className.startsWith("android.") + ||className.contains("com.android") + ||className.contains("java.lang") + ||className.contains("com.youth.xframe")) { + continue; + } + perTrace.append(element.getClassName()) + .append('.') + .append(element.getMethodName()) + .append(" (") + .append(element.getFileName()) + .append(':') + .append(element.getLineNumber()) + .append(")"); + str+=" "; + logContent(priority, perTrace.toString()); + } + logChunk(priority, MIDDLE_BORDER); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/log/Printer.java b/xframe/src/main/java/com/youth/xframe/utils/log/Printer.java new file mode 100644 index 0000000..0d2ce2d --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/log/Printer.java @@ -0,0 +1,33 @@ +package com.youth.xframe.utils.log; + +import java.util.List; +import java.util.Map; + +public interface Printer { + + XLogConfig init(); + + String getFormatLog(); + + void d(String message, Object... args); + + void e(String message, Object... args); + + void e(Throwable throwable, String message, Object... args); + + void w(String message, Object... args); + + void i(String message, Object... args); + + void v(String message, Object... args); + + void wtf(String message, Object... args); + + void json(String json); + + void xml(String xml); + + void map(Map map); + + void list(List list); +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/log/XLog.java b/xframe/src/main/java/com/youth/xframe/utils/log/XLog.java new file mode 100644 index 0000000..2c5747f --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/log/XLog.java @@ -0,0 +1,66 @@ +package com.youth.xframe.utils.log; + +import java.util.List; +import java.util.Map; + +public final class XLog { + + private static final Printer printer = new LoggerPrinter(); + + private XLog() { + + } + + public static XLogConfig init() { + return printer.init(); + } + + public static String getFormatLog() { + return printer.getFormatLog(); + } + + public static void d(String message, Object... args) { + printer.d(message, args); + } + + public static void e(String message, Object... args) { + printer.e(null, message, args); + } + + public static void e(Throwable throwable, String message, Object... args) { + printer.e(throwable, message, args); + } + + public static void i(String message, Object... args) { + printer.i(message, args); + } + + public static void v(String message, Object... args) { + printer.v(message, args); + } + + public static void w(String message, Object... args) { + printer.w(message, args); + } + + public static void wtf(String message, Object... args) { + printer.wtf(message, args); + } + + public static void json(String json) { + printer.json(json); + } + + public static void xml(String xml) { + printer.xml(xml); + } + + public static void map(Map map) { + printer.map(map); + } + + public static void list(List list) { + printer.list(list); + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/log/XLogConfig.java b/xframe/src/main/java/com/youth/xframe/utils/log/XLogConfig.java new file mode 100644 index 0000000..3a413df --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/log/XLogConfig.java @@ -0,0 +1,44 @@ +package com.youth.xframe.utils.log; + + +import android.text.TextUtils; + +import com.youth.xframe.XFrame; + +public class XLogConfig { + + private boolean showThreadInfo = true; + private boolean debug = XFrame.isDebug; + private String tag = XFrame.tag; + + + public XLogConfig setTag(String tag) { + if (!TextUtils.isEmpty(tag)) { + this.tag = tag; + } + return this; + } + + public XLogConfig setShowThreadInfo(boolean showThreadInfo) { + this.showThreadInfo = showThreadInfo; + return this; + } + + + public XLogConfig setDebug(boolean debug) { + this.debug = debug; + return this; + } + + public String getTag() { + return tag; + } + + public boolean isDebug() { + return debug; + } + + public boolean isShowThreadInfo() { + return showThreadInfo; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/permission/XPermission.java b/xframe/src/main/java/com/youth/xframe/utils/permission/XPermission.java new file mode 100644 index 0000000..9488ab0 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/permission/XPermission.java @@ -0,0 +1,119 @@ +package com.youth.xframe.utils.permission; + + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; + +import com.youth.xframe.R; + +import java.util.ArrayList; +import java.util.List; + +public class XPermission { + private static int mRequestCode = -1; + + private static OnPermissionListener mOnPermissionListener; + + public interface OnPermissionListener { + + void onPermissionGranted(); + + void onPermissionDenied(); + } + + @TargetApi(Build.VERSION_CODES.M) + public static void requestPermissions(Context context, int requestCode + , String[] permissions, OnPermissionListener listener) { + if (context instanceof Activity) { + mOnPermissionListener = listener; + List deniedPermissions = getDeniedPermissions(context, permissions); + if (deniedPermissions.size() > 0) { + mRequestCode = requestCode; + ((Activity) context).requestPermissions(deniedPermissions + .toArray(new String[deniedPermissions.size()]), requestCode); + + } else { + if (mOnPermissionListener != null) + mOnPermissionListener.onPermissionGranted(); + } + } else { + throw new RuntimeException("Context must be an Activity"); + } + } + + /** + * 获取请求权限中需要授权的权限 + */ + private static List getDeniedPermissions(Context context, String... permissions) { + List deniedPermissions = new ArrayList<>(); + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) { + deniedPermissions.add(permission); + } + } + return deniedPermissions; + } + + /** + * 请求权限结果,对应Activity中onRequestPermissionsResult()方法。 + */ + public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (mRequestCode != -1 && requestCode == mRequestCode) { + if (mOnPermissionListener != null) { + if (verifyPermissions(grantResults)) { + mOnPermissionListener.onPermissionGranted(); + } else { + mOnPermissionListener.onPermissionDenied(); + } + } + } + } + + /** + * 验证所有权限是否都已经授权 + */ + private static boolean verifyPermissions(int[] grantResults) { + for (int grantResult : grantResults) { + if (grantResult != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + + /** + * 显示提示对话框 + */ + public static void showTipsDialog(final Context context) { + new AlertDialog.Builder(context) + .setTitle("提示信息") + .setMessage("当前应用缺少必要权限,该功能暂时无法使用。如若需要,请单击【确定】按钮前往设置中心进行权限授权。") + .setNegativeButton("取消", null) + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startAppSettings(context); + } + }).show(); + } + /** + * 启动当前应用设置页面 + */ + private static void startAppSettings(Context context) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + context.getPackageName())); + context.startActivity(intent); + } +} diff --git a/xframe/src/main/java/com/youth/xframe/utils/statusbar/XStatusBar.java b/xframe/src/main/java/com/youth/xframe/utils/statusbar/XStatusBar.java new file mode 100644 index 0000000..d95d0ba --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/utils/statusbar/XStatusBar.java @@ -0,0 +1,606 @@ +package com.youth.xframe.utils.statusbar; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.support.annotation.ColorInt; +import android.support.v4.widget.DrawerLayout; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.LinearLayout; + +/** + * 状态栏工具类 + * + * 设置状态栏沉浸式或者状态栏颜色 + */ +public class XStatusBar { + public static final int DEFAULT_STATUS_BAR_ALPHA = 0; + + /** + * 设置状态栏颜色 + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + */ + public static void setColor(Activity activity, @ColorInt int color) { + setColor(activity, color, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 设置状态栏颜色 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @param statusBarAlpha 状态栏透明度 + */ + + public static void setColor(Activity activity, @ColorInt int color, int statusBarAlpha) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + activity.getWindow().setStatusBarColor(calculateStatusColor(color, statusBarAlpha)); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); + int count = decorView.getChildCount(); + if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { + decorView.getChildAt(count - 1).setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); + } else { + StatusBarView statusView = createStatusBarView(activity, color, statusBarAlpha); + decorView.addView(statusView); + } + setRootView(activity); + } + } + + /** + * 为滑动返回界面设置状态栏颜色 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + */ + public static void setColorForSwipeBack(Activity activity, int color) { + setColorForSwipeBack(activity, color, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 为滑动返回界面设置状态栏颜色 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @param statusBarAlpha 状态栏透明度 + */ + public static void setColorForSwipeBack(Activity activity, @ColorInt int color, int statusBarAlpha) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + ViewGroup contentView = ((ViewGroup) activity.findViewById(android.R.id.content)); + contentView.setPadding(0, getStatusBarHeight(activity), 0, 0); + contentView.setBackgroundColor(calculateStatusColor(color, statusBarAlpha)); + setTransparentForWindow(activity); + } + } + + /** + * 设置状态栏纯色 不加半透明效果 + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + */ + public static void setColorNoTranslucent(Activity activity, @ColorInt int color) { + setColor(activity, color, 0); + } + + /** + * 设置状态栏颜色(5.0以下无半透明效果,不建议使用) + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + */ + @Deprecated + public static void setColorDiff(Activity activity, @ColorInt int color) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + transparentStatusBar(activity); + ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + // 移除半透明矩形,以免叠加 + if (contentView.getChildCount() > 1) { + contentView.getChildAt(1).setBackgroundColor(color); + } else { + contentView.addView(createStatusBarView(activity, color)); + } + setRootView(activity); + } + + /** + * 使状态栏半透明 + * + * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + */ + public static void setTranslucent(Activity activity) { + setTranslucent(activity, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 使状态栏半透明 + * + * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + * @param statusBarAlpha 状态栏透明度 + */ + public static void setTranslucent(Activity activity, int statusBarAlpha) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + setTransparent(activity); + addTranslucentView(activity, statusBarAlpha); + } + + /** + * 针对根布局是 CoordinatorLayout, 使状态栏半透明 + * + * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + * @param statusBarAlpha 状态栏透明度 + */ + public static void setTranslucentForCoordinatorLayout(Activity activity, int statusBarAlpha) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + transparentStatusBar(activity); + addTranslucentView(activity, statusBarAlpha); + } + + /** + * 设置状态栏全透明 + * + * @param activity 需要设置的activity + */ + public static void setTransparent(Activity activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + transparentStatusBar(activity); + setRootView(activity); + } + + /** + * 使状态栏透明(5.0以上半透明效果,不建议使用) + * + * 适用于图片作为背景的界面,此时需要图片填充到状态栏 + * + * @param activity 需要设置的activity + */ + @Deprecated + public static void setTranslucentDiff(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // 设置状态栏透明 + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + setRootView(activity); + } + } + + /** + * 为DrawerLayout 布局设置状态栏变色 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + * @param color 状态栏颜色值 + */ + public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { + setColorForDrawerLayout(activity, drawerLayout, color, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 为DrawerLayout 布局设置状态栏颜色,纯色 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + * @param color 状态栏颜色值 + */ + public static void setColorNoTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { + setColorForDrawerLayout(activity, drawerLayout, color, 0); + } + + /** + * 为DrawerLayout 布局设置状态栏变色 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + * @param color 状态栏颜色值 + * @param statusBarAlpha 状态栏透明度 + */ + public static void setColorForDrawerLayout(Activity activity, DrawerLayout drawerLayout, @ColorInt int color, + int statusBarAlpha) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + } else { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + // 生成一个状态栏大小的矩形 + // 添加 statusBarView 到布局中 + ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); + if (contentLayout.getChildCount() > 0 && contentLayout.getChildAt(0) instanceof StatusBarView) { + contentLayout.getChildAt(0).setBackgroundColor(color); + } else { + StatusBarView statusBarView = createStatusBarView(activity, color); + contentLayout.addView(statusBarView, 0); + } + // 内容布局不是 LinearLayout 时,设置padding top + if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { + contentLayout.getChildAt(1) + .setPadding(contentLayout.getPaddingLeft(), getStatusBarHeight(activity) + contentLayout.getPaddingTop(), + contentLayout.getPaddingRight(), contentLayout.getPaddingBottom()); + } + // 设置属性 + setDrawerLayoutProperty(drawerLayout, contentLayout); + addTranslucentView(activity, statusBarAlpha); + } + + /** + * 设置 DrawerLayout 属性 + * + * @param drawerLayout DrawerLayout + * @param drawerLayoutContentLayout DrawerLayout 的内容布局 + */ + private static void setDrawerLayoutProperty(DrawerLayout drawerLayout, ViewGroup drawerLayoutContentLayout) { + ViewGroup drawer = (ViewGroup) drawerLayout.getChildAt(1); + drawerLayout.setFitsSystemWindows(false); + drawerLayoutContentLayout.setFitsSystemWindows(false); + drawerLayoutContentLayout.setClipToPadding(true); + drawer.setFitsSystemWindows(false); + } + + /** + * 为DrawerLayout 布局设置状态栏变色(5.0以下无半透明效果,不建议使用) + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + * @param color 状态栏颜色值 + */ + @Deprecated + public static void setColorForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout, @ColorInt int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + // 生成一个状态栏大小的矩形 + ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); + if (contentLayout.getChildCount() > 0 && contentLayout.getChildAt(0) instanceof StatusBarView) { + contentLayout.getChildAt(0).setBackgroundColor(calculateStatusColor(color, DEFAULT_STATUS_BAR_ALPHA)); + } else { + // 添加 statusBarView 到布局中 + StatusBarView statusBarView = createStatusBarView(activity, color); + contentLayout.addView(statusBarView, 0); + } + // 内容布局不是 LinearLayout 时,设置padding top + if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { + contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); + } + // 设置属性 + setDrawerLayoutProperty(drawerLayout, contentLayout); + } + } + + /** + * 为 DrawerLayout 布局设置状态栏透明 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { + setTranslucentForDrawerLayout(activity, drawerLayout, DEFAULT_STATUS_BAR_ALPHA); + } + + /** + * 为 DrawerLayout 布局设置状态栏透明 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + public static void setTranslucentForDrawerLayout(Activity activity, DrawerLayout drawerLayout, int statusBarAlpha) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + setTransparentForDrawerLayout(activity, drawerLayout); + addTranslucentView(activity, statusBarAlpha); + } + + /** + * 为 DrawerLayout 布局设置状态栏透明 + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + public static void setTransparentForDrawerLayout(Activity activity, DrawerLayout drawerLayout) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + } else { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + + ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); + // 内容布局不是 LinearLayout 时,设置padding top + if (!(contentLayout instanceof LinearLayout) && contentLayout.getChildAt(1) != null) { + contentLayout.getChildAt(1).setPadding(0, getStatusBarHeight(activity), 0, 0); + } + + // 设置属性 + setDrawerLayoutProperty(drawerLayout, contentLayout); + } + + /** + * 为 DrawerLayout 布局设置状态栏透明(5.0以上半透明效果,不建议使用) + * + * @param activity 需要设置的activity + * @param drawerLayout DrawerLayout + */ + @Deprecated + public static void setTranslucentForDrawerLayoutDiff(Activity activity, DrawerLayout drawerLayout) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + // 设置状态栏透明 + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + // 设置内容布局属性 + ViewGroup contentLayout = (ViewGroup) drawerLayout.getChildAt(0); + contentLayout.setFitsSystemWindows(true); + contentLayout.setClipToPadding(true); + // 设置抽屉布局属性 + ViewGroup vg = (ViewGroup) drawerLayout.getChildAt(1); + vg.setFitsSystemWindows(false); + // 设置 DrawerLayout 属性 + drawerLayout.setFitsSystemWindows(false); + } + } + + /** + * 为头部是 ImageView 的界面设置状态栏全透明 + * + * @param activity 需要设置的activity + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTransparentForImageView(Activity activity, View needOffsetView) { + setTranslucentForImageView(activity, 0, needOffsetView); + } + + /** + * 为头部是 ImageView 的界面设置状态栏透明(使用默认透明度) + * + * @param activity 需要设置的activity + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTranslucentForImageView(Activity activity, View needOffsetView) { + setTranslucentForImageView(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); + } + + /** + * 为头部是 ImageView 的界面设置状态栏透明 + * + * @param activity 需要设置的activity + * @param statusBarAlpha 状态栏透明度 + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTranslucentForImageView(Activity activity, int statusBarAlpha, View needOffsetView) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return; + } + setTransparentForWindow(activity); + addTranslucentView(activity, statusBarAlpha); + if (needOffsetView != null) { + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) needOffsetView.getLayoutParams(); + layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin + getStatusBarHeight(activity), + layoutParams.rightMargin, layoutParams.bottomMargin); + } + } + + /** + * 为 fragment 头部是 ImageView 的设置状态栏透明 + * + * @param activity fragment 对应的 activity + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTranslucentForImageViewInFragment(Activity activity, View needOffsetView) { + setTranslucentForImageViewInFragment(activity, DEFAULT_STATUS_BAR_ALPHA, needOffsetView); + } + + /** + * 为 fragment 头部是 ImageView 的设置状态栏透明 + * + * @param activity fragment 对应的 activity + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTransparentForImageViewInFragment(Activity activity, View needOffsetView) { + setTranslucentForImageViewInFragment(activity, 0, needOffsetView); + } + + /** + * 为 fragment 头部是 ImageView 的设置状态栏透明 + * + * @param activity fragment 对应的 activity + * @param statusBarAlpha 状态栏透明度 + * @param needOffsetView 需要向下偏移的 View + */ + public static void setTranslucentForImageViewInFragment(Activity activity, int statusBarAlpha, View needOffsetView) { + setTranslucentForImageView(activity, statusBarAlpha, needOffsetView); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + clearPreviousSetting(activity); + } + } + + /////////////////////////////////////////////////////////////////////////////////// + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static void clearPreviousSetting(Activity activity) { + ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); + int count = decorView.getChildCount(); + if (count > 0 && decorView.getChildAt(count - 1) instanceof StatusBarView) { + decorView.removeViewAt(count - 1); + ViewGroup rootView = (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0); + rootView.setPadding(0, 0, 0, 0); + } + } + + /** + * 添加半透明矩形条 + * + * @param activity 需要设置的 activity + * @param statusBarAlpha 透明值 + */ + private static void addTranslucentView(Activity activity, int statusBarAlpha) { + ViewGroup contentView = (ViewGroup) activity.findViewById(android.R.id.content); + if (contentView.getChildCount() > 1) { + contentView.getChildAt(1).setBackgroundColor(Color.argb(statusBarAlpha, 0, 0, 0)); + } else { + contentView.addView(createTranslucentStatusBarView(activity, statusBarAlpha)); + } + } + + /** + * 生成一个和状态栏大小相同的彩色矩形条 + * + * @param activity 需要设置的 activity + * @param color 状态栏颜色值 + * @return 状态栏矩形条 + */ + private static StatusBarView createStatusBarView(Activity activity, @ColorInt int color) { + // 绘制一个和状态栏一样高的矩形 + StatusBarView statusBarView = new StatusBarView(activity); + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); + statusBarView.setLayoutParams(params); + statusBarView.setBackgroundColor(color); + return statusBarView; + } + + /** + * 生成一个和状态栏大小相同的半透明矩形条 + * + * @param activity 需要设置的activity + * @param color 状态栏颜色值 + * @param alpha 透明值 + * @return 状态栏矩形条 + */ + private static StatusBarView createStatusBarView(Activity activity, @ColorInt int color, int alpha) { + // 绘制一个和状态栏一样高的矩形 + StatusBarView statusBarView = new StatusBarView(activity); + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); + statusBarView.setLayoutParams(params); + statusBarView.setBackgroundColor(calculateStatusColor(color, alpha)); + return statusBarView; + } + + /** + * 设置根布局参数 + */ + private static void setRootView(Activity activity) { + ViewGroup parent = (ViewGroup) activity.findViewById(android.R.id.content); + for (int i = 0, count = parent.getChildCount(); i < count; i++) { + View childView = parent.getChildAt(i); + if (childView instanceof ViewGroup) { + childView.setFitsSystemWindows(true); + ((ViewGroup) childView).setClipToPadding(true); + } + } + } + + /** + * 设置透明 + */ + private static void setTransparentForWindow(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + activity.getWindow() + .getDecorView() + .setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + activity.getWindow() + .setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + + /** + * 使状态栏透明 + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + private static void transparentStatusBar(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + } else { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + + /** + * 创建半透明矩形 View + * + * @param alpha 透明值 + * @return 半透明 View + */ + private static StatusBarView createTranslucentStatusBarView(Activity activity, int alpha) { + // 绘制一个和状态栏一样高的矩形 + StatusBarView statusBarView = new StatusBarView(activity); + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity)); + statusBarView.setLayoutParams(params); + statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); + return statusBarView; + } + + /** + * 获取状态栏高度 + * + * @param context context + * @return 状态栏高度 + */ + private static int getStatusBarHeight(Context context) { + // 获得状态栏高度 + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); + return context.getResources().getDimensionPixelSize(resourceId); + } + + /** + * 计算状态栏颜色 + * + * @param color color值 + * @param alpha alpha值 + * @return 最终的状态栏颜色 + */ + private static int calculateStatusColor(@ColorInt int color, int alpha) { + float a = 1 - alpha / 255f; + int red = color >> 16 & 0xff; + int green = color >> 8 & 0xff; + int blue = color & 0xff; + red = (int) (red * a + 0.5); + green = (int) (green * a + 0.5); + blue = (int) (blue * a + 0.5); + return 0xff << 24 | red << 16 | green << 8 | blue; + } + public static class StatusBarView extends View { + public StatusBarView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public StatusBarView(Context context) { + super(context); + } + } +} diff --git a/xframe/src/main/java/com/youth/xframe/widget/NoScrollGridView.java b/xframe/src/main/java/com/youth/xframe/widget/NoScrollGridView.java new file mode 100644 index 0000000..0c0ce65 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/widget/NoScrollGridView.java @@ -0,0 +1,36 @@ +package com.youth.xframe.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.GridView; + +/** + * GridView嵌套在SrcollView中会出现显示不全的情况 + * + * 这个类通过设置不滚动来避免 + */ + +public class NoScrollGridView extends GridView { + public NoScrollGridView(Context context) { + super(context); + } + + public NoScrollGridView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NoScrollGridView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * 重写该方法,设置不滚动 + */ + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, + MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + + } +} diff --git a/xframe/src/main/java/com/youth/xframe/widget/NoScrollListView.java b/xframe/src/main/java/com/youth/xframe/widget/NoScrollListView.java new file mode 100644 index 0000000..bebd496 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/widget/NoScrollListView.java @@ -0,0 +1,36 @@ +package com.youth.xframe.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ListView; + + +/** + * Listview嵌套在SrcollView中会出现显示不全的情况 + *
+ * 这个类通过设置不滚动来避免 + */ +public class NoScrollListView extends ListView { + + public NoScrollListView(Context context) { + super(context); + } + + public NoScrollListView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NoScrollListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + /** + * 重写该方法,设置不滚动 + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, + MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } + +} diff --git a/xframe/src/main/java/com/youth/xframe/widget/XLoadingDialog.java b/xframe/src/main/java/com/youth/xframe/widget/XLoadingDialog.java new file mode 100644 index 0000000..bbf0cc9 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/widget/XLoadingDialog.java @@ -0,0 +1,61 @@ +package com.youth.xframe.widget; + + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.text.TextUtils; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.youth.xframe.R; +import com.youth.xframe.XFrame; + +public class XLoadingDialog extends Dialog { + private static XLoadingDialog dialog; + private Context context; + private TextView xTextView; + + public XLoadingDialog(Context context) { + super(context, R.style.loading_dialog); + this.context=context; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.xloading_dialog); + xTextView = (TextView) findViewById(R.id.loading_message); + } + + public static XLoadingDialog with(Context context){ + if (dialog==null){ + dialog=new XLoadingDialog(context); + } + return dialog; + } + + @Override + public void dismiss() { + super.dismiss(); + if (dialog!=null) + dialog=null; + } + + public XLoadingDialog setCanceled(boolean cancel) { + setCanceledOnTouchOutside(cancel); + setCancelable(cancel); + return dialog; + } + + public XLoadingDialog setMessage(String message) { + if (xTextView != null && !TextUtils.isEmpty(message)) { + xTextView.setText(message); + } + return this; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/widget/XLoadingView.java b/xframe/src/main/java/com/youth/xframe/widget/XLoadingView.java new file mode 100644 index 0000000..a491fc9 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/widget/XLoadingView.java @@ -0,0 +1,147 @@ +package com.youth.xframe.widget; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v4.app.Fragment; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.youth.xframe.R; + +import java.util.HashMap; +import java.util.Map; + +/** + * 简单实用的页面状态统一管理 ,加载中、无网络、无数据、出错等状态的随意切换 + */ +public class XLoadingView extends FrameLayout { + + private int mEmptyViewResId; + private int mErrorViewResId; + private int mLoadingViewResId; + private int mNoNetworkViewResId; + private int mContentViewResId; + private LayoutInflater mInflater; + private OnClickListener mOnRetryClickListener; + private Map
mResId = new HashMap<>(); + + public static XLoadingView wrap(Activity activity) { + return wrap(((ViewGroup) activity.findViewById(android.R.id.content)).getChildAt(0)); + } + + public static XLoadingView wrap(Fragment fragment) { + return wrap(fragment.getView()); + } + + public static XLoadingView wrap(View view) { + if (view == null) { + throw new RuntimeException("content view can not be null"); + } + ViewGroup parent = (ViewGroup) view.getParent(); + if (view == null) { + throw new RuntimeException("parent view can not be null"); + } + ViewGroup.LayoutParams lp = view.getLayoutParams(); + int index = parent.indexOfChild(view); + parent.removeView(view); + + XLoadingView xLoadingView = new XLoadingView(view.getContext()); + parent.addView(xLoadingView, index, lp); + xLoadingView.addView(view); + xLoadingView.setContentView(view); + return xLoadingView; + } + + public XLoadingView(Context context) { + this(context, null); + } + + public XLoadingView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public XLoadingView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mInflater = LayoutInflater.from(context); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.XLoadingView, defStyleAttr, 0); + mEmptyViewResId = a.getResourceId(R.styleable.XLoadingView_emptyView, R.layout.xloading_empty_view); + mErrorViewResId = a.getResourceId(R.styleable.XLoadingView_errorView, R.layout.xloading_error_view); + mLoadingViewResId = a.getResourceId(R.styleable.XLoadingView_loadingView, R.layout.xloading_loading_view); + mNoNetworkViewResId = a.getResourceId(R.styleable.XLoadingView_noNetworkView, R.layout.xloading_no_network_view); + a.recycle(); + } + + private void setContentView(View view) { + mContentViewResId = view.getId(); + mResId.put(mContentViewResId, view); + } + + public final void showEmpty() { + show(mEmptyViewResId); + } + + public final void showError() { + show(mErrorViewResId); + } + + public final void showLoading() { + show(mLoadingViewResId); + } + + public final void showNoNetwork() { + show(mNoNetworkViewResId); + } + + public final void showContent() { + show(mContentViewResId); + } + + private void show(int resId) { + for (View view : mResId.values()) { + view.setVisibility(GONE); + } + layout(resId).setVisibility(VISIBLE); + } + + private View layout(int resId) { + if (mResId.containsKey(resId)) { + return mResId.get(resId); + } + View view = mInflater.inflate(resId, this, false); + view.setVisibility(GONE); + addView(view); + mResId.put(resId, view); + if (resId == mErrorViewResId||resId == mNoNetworkViewResId) { + View v=view.findViewById(R.id.xloading_retry); + if (mOnRetryClickListener != null && v != null) { + v.setOnClickListener(mOnRetryClickListener); + } + } + return view; + } + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (getChildCount() == 0) { + return; + } + if (getChildCount() > 1) { + removeViews(1, getChildCount() - 1); + } + View view = getChildAt(0); + setContentView(view); + showLoading(); + } + /** + * 设置重试点击事件 + * + * @param onRetryClickListener 重试点击事件 + */ + public void setOnRetryClickListener(OnClickListener onRetryClickListener) { + this.mOnRetryClickListener = onRetryClickListener; + } +} diff --git a/xframe/src/main/java/com/youth/xframe/widget/XSplashView.java b/xframe/src/main/java/com/youth/xframe/widget/XSplashView.java new file mode 100644 index 0000000..46f4505 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/widget/XSplashView.java @@ -0,0 +1,360 @@ +package com.youth.xframe.widget; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.shapes.Shape; +import android.os.Build; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.youth.xframe.XFrame; +import com.youth.xframe.utils.XPreferencesUtils; +import com.youth.xframe.utils.handler.XHandler; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +/** + 闪屏页或者广告页: + 在合适的时机显示 SplashView - 可控性 + 下载、缓存、更新图片 + 回调响应图片点击事件 + 倒计时 Dismiss View,主动跳过 Dissmiss View + 本地没有缓存时,显示默认图片或者不显示 SplashView + */ +public class XSplashView extends FrameLayout { + + ImageView splashImageView; + TextView skipButton; + + private static final String IMG_URL = "X_SPLASH_IMG_URL"; + private static final String ACT_URL = "X_SPLASH_ACT_URL"; + private static String IMG_PATH = null; + private static final int skipButtonSizeInDip = 36; + private static final int skipButtonMarginInDip = 16; + private Integer duration = 6; + private static final int delayTime = 1000; // 每隔1000 毫秒执行一次 + + private String imgUrl = null; + private String actUrl = null; + + private boolean isActionBarShowing = true; + + private Activity mActivity = null; + + private OnSplashViewActionListener mOnSplashViewActionListener = null; + + private XHandler handler = new XHandler(); + private Runnable timerRunnable = new Runnable() { + @Override + public void run() { + if (0 == duration) { + dismissSplashView(false); + return; + } else { + setDuration(--duration); + } + handler.postDelayed(timerRunnable, delayTime); + } + }; + + private void setImage(Bitmap image) { + splashImageView.setImageBitmap(image); + } + + public XSplashView(Activity context) { + super(context); + mActivity = context; + initComponents(); + } + + public XSplashView(Activity context, AttributeSet attrs) { + super(context, attrs); + mActivity = context; + initComponents(); + } + + public XSplashView(Activity context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mActivity = context; + initComponents(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public XSplashView(Activity context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mActivity = context; + initComponents(); + } + + private GradientDrawable splashSkipButtonBg = new GradientDrawable(); + + void initComponents() { + splashSkipButtonBg.setShape(GradientDrawable.OVAL); + splashSkipButtonBg.setColor(Color.parseColor("#66333333")); + + splashImageView = new ImageView(mActivity); + splashImageView.setScaleType(ImageView.ScaleType.FIT_XY); + splashImageView.setBackgroundColor(mActivity.getResources().getColor(android.R.color.white)); + LayoutParams imageViewLayoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + this.addView(splashImageView, imageViewLayoutParams); + splashImageView.setClickable(true); + + skipButton = new TextView(mActivity); + int skipButtonSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, skipButtonSizeInDip, mActivity.getResources().getDisplayMetrics()); + LayoutParams skipButtonLayoutParams = new LayoutParams(skipButtonSize, skipButtonSize); + skipButtonLayoutParams.gravity = Gravity.TOP|Gravity.RIGHT; + int skipButtonMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, skipButtonMarginInDip, mActivity.getResources().getDisplayMetrics()); + skipButtonLayoutParams.setMargins(0, skipButtonMargin, skipButtonMargin, 0); + skipButton.setGravity(Gravity.CENTER); + skipButton.setTextColor(mActivity.getResources().getColor(android.R.color.white)); + skipButton.setBackgroundDrawable(splashSkipButtonBg); + skipButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10); + this.addView(skipButton, skipButtonLayoutParams); + + skipButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + dismissSplashView(true); + } + }); + + setDuration(duration); + handler.postDelayed(timerRunnable, delayTime); + } + + private void setImgUrl(String imgUrl) { + this.imgUrl = imgUrl; + } + + private void setActUrl(String actUrl) { + this.actUrl = actUrl; + } + + private void setDuration(Integer duration) { + this.duration = duration; + skipButton.setText(String.format("跳过\n%d s", duration)); + } + + private void setOnSplashImageClickListener(@Nullable final OnSplashViewActionListener listener) { + if (null == listener) return; + mOnSplashViewActionListener = listener; + splashImageView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + listener.onSplashImageClick(actUrl); + } + }); + } + + /** + * static method, show splashView on above of the activity + * you should called after setContentView() + * @param activity activity instance + * @param durationTime time to countDown + * @param defaultBitmapRes if there's no cached bitmap, show this default bitmap; + * if null == defaultBitmapRes, then will not show the splashView + * @param listener splash view listener contains onImageClick and onDismiss + */ + public static void showSplashView(@NonNull Activity activity, + @Nullable Integer durationTime, + @Nullable Integer defaultBitmapRes, + @Nullable OnSplashViewActionListener listener) { + + ViewGroup contentView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content); + if (null == contentView || 0 == contentView.getChildCount()) { + throw new IllegalStateException("You should call showSplashView() after setContentView() in Activity instance"); + } + IMG_PATH = activity.getFilesDir().getAbsolutePath().toString() + "/splash_img.jpg"; + XSplashView splashView = new XSplashView(activity); + RelativeLayout.LayoutParams param = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + splashView.setOnSplashImageClickListener(listener); + if (null != durationTime) splashView.setDuration(durationTime); + Bitmap bitmapToShow = null; + String imgUrl=(String)XPreferencesUtils.get(IMG_URL, null); + if (!TextUtils.isEmpty(imgUrl) && isFileExist(IMG_PATH)) { + bitmapToShow = BitmapFactory.decodeFile(IMG_PATH); + splashView.setImgUrl(imgUrl); + splashView.setActUrl((String)XPreferencesUtils.get(ACT_URL, null)); + } else if (null != defaultBitmapRes) { + bitmapToShow = BitmapFactory.decodeResource(activity.getResources(), defaultBitmapRes); + } + + if (null == bitmapToShow) return; + splashView.setImage(bitmapToShow); + activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + if (activity instanceof AppCompatActivity) { + ActionBar supportActionBar = ((AppCompatActivity) activity).getSupportActionBar(); + if (null != supportActionBar) { + supportActionBar.setShowHideAnimationEnabled(false); + splashView.isActionBarShowing = supportActionBar.isShowing(); + supportActionBar.hide(); + } + } else if (activity instanceof Activity) { + android.app.ActionBar actionBar = activity.getActionBar(); + if (null != actionBar) { + splashView.isActionBarShowing = actionBar.isShowing(); + actionBar.hide(); + } + } + contentView.addView(splashView, param); + } + + /** + * simple way to show splash view, set all non-able param as non + * @param activity + */ + public static void simpleShowSplashView(@NonNull Activity activity) { + showSplashView(activity, null, null, null); + } + + private void dismissSplashView(boolean initiativeDismiss) { + if (null != mOnSplashViewActionListener) mOnSplashViewActionListener.onSplashViewDismiss(initiativeDismiss); + + + handler.removeCallbacks(timerRunnable); + final ViewGroup parent = (ViewGroup) this.getParent(); + if (null != parent) { + ObjectAnimator animator = ObjectAnimator.ofFloat(XSplashView.this, "scale", 0.0f, 0.5f).setDuration(600); + animator.start(); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float cVal = (Float) animation.getAnimatedValue(); + XSplashView.this.setAlpha(1.0f - 2.0f * cVal); + XSplashView.this.setScaleX(1.0f + cVal); + XSplashView.this.setScaleY(1.0f + cVal); + } + }); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + parent.removeView(XSplashView.this); + showSystemUi(); + } + + @Override + public void onAnimationCancel(Animator animation) { + parent.removeView(XSplashView.this); + showSystemUi(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + } + } + + private void showSystemUi() { + mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + if (mActivity instanceof AppCompatActivity) { + ActionBar supportActionBar = ((AppCompatActivity) mActivity).getSupportActionBar(); + if (null != supportActionBar) { + if (isActionBarShowing) supportActionBar.show(); + } + } else if (mActivity instanceof Activity) { + android.app.ActionBar actionBar = mActivity.getActionBar(); + if (null != actionBar) { + if (isActionBarShowing) actionBar.show(); + } + } + } + + + /** + * static method, update splash view data + * @param imgUrl - url of image which you want to set as splash image + * @param actionUrl - related action url, such as webView etc. + */ + public static void updateSplashData(@NonNull Activity activity, @NonNull String imgUrl, @Nullable String actionUrl) { + IMG_PATH = activity.getFilesDir().getAbsolutePath().toString() + "/splash_img.jpg"; + + XPreferencesUtils.put(IMG_URL, imgUrl); + XPreferencesUtils.put(ACT_URL, actionUrl); + + getAndSaveNetWorkBitmap(imgUrl); + } + + public interface OnSplashViewActionListener { + void onSplashImageClick(String actionUrl); + void onSplashViewDismiss(boolean initiativeDismiss); + } + + private static void getAndSaveNetWorkBitmap(final String urlString) { + Runnable getAndSaveImageRunnable = new Runnable() { + @Override + public void run() { + URL imgUrl = null; + Bitmap bitmap = null; + try { + imgUrl = new URL(urlString); + HttpURLConnection urlConn = (HttpURLConnection) imgUrl.openConnection(); + urlConn.setDoInput(true); + urlConn.connect(); + InputStream is = urlConn.getInputStream(); + bitmap = BitmapFactory.decodeStream(is); + is.close(); + saveBitmapFile(bitmap, IMG_PATH); + } catch (MalformedURLException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }; + new Thread(getAndSaveImageRunnable).start(); + } + + private static void saveBitmapFile(Bitmap bm, String filePath) throws IOException { + File myCaptureFile = new File(filePath); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile)); + bm.compress(Bitmap.CompressFormat.JPEG, 80, bos); + bos.flush(); + bos.close(); + } + + public static boolean isFileExist(String filePath) { + if(TextUtils.isEmpty(filePath)) { + return false; + } else { + File file = new File(filePath); + return file.exists() && file.isFile(); + } + } +} diff --git a/xframe/src/main/java/com/youth/xframe/widget/XToast.java b/xframe/src/main/java/com/youth/xframe/widget/XToast.java new file mode 100644 index 0000000..f12ae25 --- /dev/null +++ b/xframe/src/main/java/com/youth/xframe/widget/XToast.java @@ -0,0 +1,178 @@ +package com.youth.xframe.widget; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.support.annotation.CheckResult; +import android.support.annotation.ColorInt; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.youth.xframe.R; +import com.youth.xframe.XFrame; +import com.youth.xframe.utils.XOutdatedUtils; + +/** + * Toast + */ +public class XToast { + private static final @ColorInt int DEFAULT_TEXT_COLOR = Color.parseColor("#FFFFFF"); + + private static final @ColorInt int ERROR_COLOR = Color.parseColor("#D8524E"); + private static final @ColorInt int INFO_COLOR = Color.parseColor("#3278B5"); + private static final @ColorInt int SUCCESS_COLOR = Color.parseColor("#5BB75B"); + private static final @ColorInt int WARNING_COLOR = Color.parseColor("#FB9B4D"); + private static final @ColorInt int NORMAL_COLOR = Color.parseColor("#444344"); + + private static final String TOAST_TYPEFACE = "sans-serif-condensed"; + + private static Context context= XFrame.getContext(); + + private XToast() { + } + + public static @CheckResult Toast normal( @NonNull String message) { + return normal( message, Toast.LENGTH_SHORT, null); + } + + public static @CheckResult Toast normal( @NonNull String message, Drawable icon) { + return normal( message, Toast.LENGTH_SHORT, icon); + } + + public static @CheckResult Toast normal( @NonNull String message, int duration) { + return normal( message, duration); + } + + public static @CheckResult Toast normal( @NonNull String message, int duration, + Drawable icon) { + return custom( message, icon ,NORMAL_COLOR, duration); + } + + public static @CheckResult Toast warning( @NonNull String message) { + return warning( message, Toast.LENGTH_SHORT, true); + } + + public static @CheckResult Toast warning( @NonNull String message, int duration) { + return warning( message, duration, true); + } + + public static @CheckResult Toast warning( @NonNull String message, int duration, boolean withIcon) { + Drawable icon=null; + if (withIcon){ + icon=XOutdatedUtils.getDrawable( R.drawable.xtoast_warning); + } + return custom( message,icon, WARNING_COLOR, duration); + } + + public static @CheckResult Toast info( @NonNull String message) { + return info( message, Toast.LENGTH_SHORT, true); + } + + public static @CheckResult Toast info( @NonNull String message, int duration) { + return info( message, duration, true); + } + + public static @CheckResult Toast info( @NonNull String message, int duration, boolean withIcon) { + Drawable icon=null; + if (withIcon){ + icon=XOutdatedUtils.getDrawable( R.drawable.xtoast_info); + } + return custom( message,icon, INFO_COLOR, duration); + } + + public static @CheckResult Toast success( @NonNull String message) { + return success( message, Toast.LENGTH_SHORT, true); + } + + public static @CheckResult Toast success( @NonNull String message, int duration) { + return success( message, duration, true); + } + + public static @CheckResult Toast success( @NonNull String message, int duration, boolean withIcon) { + Drawable icon=null; + if (withIcon){ + icon=XOutdatedUtils.getDrawable( R.drawable.xtoast_success); + } + return custom( message,icon, SUCCESS_COLOR, duration); + } + + public static @CheckResult Toast error( @NonNull String message) { + return error( message, Toast.LENGTH_SHORT, true); + } + + public static @CheckResult Toast error( @NonNull String message, int duration) { + return error( message, duration, true); + } + + public static @CheckResult Toast error( @NonNull String message, int duration, boolean withIcon) { + Drawable icon=null; + if (withIcon){ + icon=XOutdatedUtils.getDrawable( R.drawable.xtoast_error); + } + return custom( message,icon, ERROR_COLOR, duration); + } + + public static @CheckResult Toast custom(@NonNull String message, @ColorInt int tintColor) { + return custom( message, null, DEFAULT_TEXT_COLOR, tintColor,Toast.LENGTH_SHORT); + } + + public static @CheckResult Toast custom( @NonNull String message, Drawable icon, @ColorInt int tintColor) { + return custom( message, icon, DEFAULT_TEXT_COLOR, tintColor,Toast.LENGTH_SHORT); + } + + public static @CheckResult Toast custom(@NonNull String message, @ColorInt int tintColor,int duration) { + return custom( message, null, DEFAULT_TEXT_COLOR, tintColor,duration); + } + + public static @CheckResult Toast custom( @NonNull String message, Drawable icon, @ColorInt int tintColor,int duration) { + return custom( message, icon, DEFAULT_TEXT_COLOR, tintColor,duration); + } + public static @CheckResult Toast custom( @NonNull String message, @DrawableRes int iconRes, + @ColorInt int textColor, @ColorInt int tintColor, int duration) { + return custom( message, XOutdatedUtils.getDrawable( iconRes), textColor,tintColor, duration); + } + + /** + * 自定义toast方法 + * @param message 提示消息文本 + * @param icon 提示消息的icon,传入null代表不显示 + * @param textColor 提示消息文本颜色 + * @param tintColor 提示背景颜色 + * @param duration 显示时长 + * @return + */ + public static @CheckResult Toast custom( @NonNull String message, Drawable icon, + @ColorInt int textColor, @ColorInt int tintColor, int duration) { + Toast currentToast = new Toast(context); + View toastLayout = LayoutInflater.from(context).inflate(R.layout.xtoast_view, null); + ImageView toastIcon = (ImageView) toastLayout.findViewById(R.id.xtoast_icon); + TextView toastText = (TextView) toastLayout.findViewById(R.id.xtoast_text); + + Drawable drawableFrame= XOutdatedUtils.getDrawable(R.drawable.xtoast_frame); + drawableFrame.setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)); + XOutdatedUtils.setBackground(toastLayout, drawableFrame); + + if (icon == null){ + toastIcon.setVisibility(View.GONE); + }else{ + XOutdatedUtils.setBackground(toastIcon, icon); + } + + toastText.setTextColor(textColor); + toastText.setText(message); + toastText.setTypeface(Typeface.create(TOAST_TYPEFACE, Typeface.NORMAL)); + + currentToast.setView(toastLayout); + currentToast.setDuration(duration); + currentToast.show(); + return currentToast; + } +} diff --git a/xframe/src/main/res/drawable-xhdpi/adapter_loading_error.png b/xframe/src/main/res/drawable-xhdpi/adapter_loading_error.png new file mode 100644 index 0000000000000000000000000000000000000000..8b0c4a531deb67352fee0237315c7ee561e122af GIT binary patch literal 851 zcmV-Z1FZasP) r4euCG`^O!gG0LhWR;K<`oP5_6_;EQEoGMV)9Jb$kNTdmgpa5$X5 z1ni3dD5X9nN%E~2FcHm!5QEqd(E+5CeE_&K1xiF8gb?3jLPQ0aPN!$9)#?ENE-XPV z(=_c227_fRh^PQk%4-1l;t1{r5wHF`fbn>I(QdaNk|a5E1(#)6XEYi;xyEtckCp)` z -G8%7v4v!EC8jHzNmD40w~^pLl%ImRVM&bn|o}<^HAMIziv@DTCD}xq+S;v z=QYs~h^_