From e2b81c9637a27abd8e190b2619da95cf751f3d47 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 24 Jan 2017 11:04:32 -0800 Subject: [PATCH] Update look and feel of fast-scroll-to-bottom Closes #6086 // FREEBIE --- res/drawable-hdpi/ic_scroll_down.png | Bin 0 -> 629 bytes res/drawable-mdpi/ic_scroll_down.png | Bin 0 -> 416 bytes res/drawable-xhdpi/ic_scroll_down.png | Bin 0 -> 692 bytes res/drawable-xxhdpi/ic_scroll_down.png | Bin 0 -> 1296 bytes res/drawable-xxxhdpi/ic_scroll_down.png | Bin 0 -> 498 bytes res/layout/conversation_fragment.xml | 34 +++++----- res/values/dimens.xml | 3 - .../securesms/ConversationFragment.java | 59 ++++++++++++------ 8 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 res/drawable-hdpi/ic_scroll_down.png create mode 100644 res/drawable-mdpi/ic_scroll_down.png create mode 100644 res/drawable-xhdpi/ic_scroll_down.png create mode 100644 res/drawable-xxhdpi/ic_scroll_down.png create mode 100644 res/drawable-xxxhdpi/ic_scroll_down.png diff --git a/res/drawable-hdpi/ic_scroll_down.png b/res/drawable-hdpi/ic_scroll_down.png new file mode 100644 index 0000000000000000000000000000000000000000..5d23096c4d59bd21befa95db9e7bf607fff2c09d GIT binary patch literal 629 zcmV-*0*d{KP)<*pox)y#N2es?$=zU=q==j-(vh6aF2rQ$&b?`ghHy04^v zi#(mRELxQlhGF=O%)ak-J4;5_)avx?0E@-K^%&FpfZ7do6yGTCsr1PV?DcwW0Jqz% z2fm;x0D6QTV}LLZ9rP!GcpFuT?G&>4>{&$ppR-pQAv)oQs4 zc&l86_GesCd&9k}h!^Da$df*qDnoR;UHAPkFr??${vN0(WRTO#{67l$7ms^_8{n>&lS8lQ!16z zbUGa*fKMa{^Uy(``!~%4i^U><=K^>oKzLAjZcFJB z4EQqKed~?@-KFXG`>*_Ecu@a4WSsCT_bUp8Law}fjeA|;{|oyO00960mJ=NHp}^(g P00000NkvXXu0mjf!g((U literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_scroll_down.png b/res/drawable-mdpi/ic_scroll_down.png new file mode 100644 index 0000000000000000000000000000000000000000..2b570d6823ffb28cb134ee9421901abdaaf35220 GIT binary patch literal 416 zcmV;R0bl-!P)`XXc(9Q|zg}1PzBnv0N@Y4b(W(ooIe!I*wyVDTVL*M$>OJ_NZ}2_k3HE z$wY)X+dEz1&7 z6bYEez!v6Lf}A~SoY6h)jZ{@tbqK>1=m3}_PRX&bJqk~XF4#^$*0A-uahQn7DvI`FegIq2Q#yg3j^}O66z8T~3_&qaXwQG(jcBfD% zX!O$R9M2li%#L9WFK?OJWHO0Sgar~C3gX2z6z2|1!WhPSfzZp^Y&Okev0&j?jv$d^ zVBv=Dkj&@uETJ!B7|R@*uasGRC`18{u@;n(0Cngw^v!t|OHAmk4_Td>$8sTXF4Sk} ziwi?vi800LAC@bcPg0wio8@!WyB zu|O0ic3|d=wR4p=e5K6+D7`m?x!nh&IlNf`)GO$9WiSO$yFSEuWib1o_`M>Cs9KnP zQ0z6L>S6u`h12>$-5ZPx)#-GeQTGa?X7?6veWtAO-^MY29{)>waLYOa71a?fz@a(6Uuz2MAxAKD{b&A!K*bMBovGjEn1F!vPw!hDFL$P5n; z+xGuL2+n(jJI&a@z;(B=u`#p0zHVE6zgIZ#gwfGab98iM(loU#u7SH)Ncv=P1_u(r z2u@5)nEm~Io51DerEL!m4otCFY)Q0JP5^zup*f)eEocT^b6C_ssZ?r7pjxfkc5iRb z6bc3VOeYqSXV3?IHJ5NhOKW$ETNxNaz{SNy^BOTSGGgL5HVX?2W_NejS|~}9wxf){ z;Tb%OKIjV$a0#!g6%-id!NEbZy}fP48jVIXky@?Rku81plfIf08qn(c2C}ua)l3AV zuM^C&rO$rS2OLrqxVw&_p&^^&%*+gu_=*yHAc^S_KR!Ng*)8?%hrj7()xd!3OK?Mj zwYr9AdU_fQ{0SxWH^qk7$elT{r4;6~pY-9+FDNdE`!O`26}%eRb{7{H&Dq)6V@lwR z7JSNhs*;ObTVhi{c-7i!>fVOI|a56vw000+hr;MBFE4*aqHnIQt}wa{N-*XEQyz71aczp) z;J%g%oZyBAw4e#?ppXqngt|M<-}_4nrjo!l>N}R%3Kw2Ypvjr(VLx}jZRNbF$?1~1FytNGU|N2(Wa=9 zuMfDu32ta;&0uhLMI;xoy&l3kPNH#};!3Xl_!=A!nKOb$w{}I_(oSU)q@7DAYi-F1 zy0(1oSyN5>@pXkcTDYO5wL8VFUh8tOJv=vdh2&z6Z=Pf3>jz`x=FlBHNXPvk~`aQIQAF0$K_z^OsEm_u$teHP3KF&z`PmY48_)mCX zWKG7*okB}%2Z4G)KcJT=X2v&@6{x7O-radxYkpPx79=jSEtWFA|-%CH}H#MiF;SS?JJ~I~=`fzbw4Nmd$@<@#o&OUl!hB%LYep z+b;`kY>6XMk_-D~A;Xq+L~iVt1=p6CB}A_5mj!K0I@JD#$esPNAXb~4oV20uRgiwPVQ z#(Dl@GY1ro^LdZWCFPIvxsS~um5h5U+ZA$miGVnhq43r5M#VP1IalDoB^HWqJ^ZMAl2oJkd70ap)zNLbZW_Q zNRiGF=Mr;J{8Ky4Ns)$t)TKGXBx1@7ha*Y?k3?|lfznfxoO+9vCTVh5BwbDxNr&Sc zHrO&qF=q$Gf<|v;^@8?HiaAXR+Jh8xniaGT1Hn-*XkEmyf@TGcM`9JUn&*rK1+Dhc zXJ$duIE~FHXj%uv%z~zQX3Z#Q+Q;ZbL2GfcKT*(H{n;>5&|JGT-pVAYJ?DkA49-7k oIh;FbNgRvCVzF2(7K>c|07`c`>*zu>#{d8T07*qoM6N<$g1M#9qW}N^ literal 0 HcmV?d00001 diff --git a/res/layout/conversation_fragment.xml b/res/layout/conversation_fragment.xml index 9204d31a2c..042b677466 100644 --- a/res/layout/conversation_fragment.xml +++ b/res/layout/conversation_fragment.xml @@ -2,7 +2,8 @@ + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> - - - + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3fa139d1b3..72b4318cdd 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -66,7 +66,4 @@ 20sp 3dp - - 10dp - 10dp diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 69d2f061a6..9193587d67 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -24,6 +24,7 @@ import android.database.Cursor; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; @@ -44,6 +45,8 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; import android.widget.Toast; import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener; @@ -79,7 +82,6 @@ public class ConversationFragment extends Fragment private final ActionModeCallback actionModeCallback = new ActionModeCallback(); private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener(); - private final OnScrollListener scrollListener = new ConversationScrollListener(); private ConversationFragmentListener listener; @@ -117,7 +119,6 @@ public class ConversationFragment extends Fragment final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, true); list.setHasFixedSize(false); list.setLayoutManager(layoutManager); - list.addOnScrollListener(scrollListener); list.setItemAnimator(null); loadMoreView = inflater.inflate(R.layout.load_more_header, container, false); @@ -173,8 +174,11 @@ public class ConversationFragment extends Fragment } private void initializeResources() { - this.recipients = RecipientFactory.getRecipientsForIds(getActivity(), getActivity().getIntent().getLongArrayExtra("recipients"), true); - this.threadId = this.getActivity().getIntent().getLongExtra("thread_id", -1); + this.recipients = RecipientFactory.getRecipientsForIds(getActivity(), getActivity().getIntent().getLongArrayExtra("recipients"), true); + this.threadId = this.getActivity().getIntent().getLongExtra("thread_id", -1); + + OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); + list.addOnScrollListener(scrollListener); } private void initializeListAdapter() { @@ -408,26 +412,39 @@ public class ConversationFragment extends Fragment } private class ConversationScrollListener extends OnScrollListener { - private boolean wasAtBottom = true; + + private final Animation scrollButtonInAnimation; + private final Animation scrollButtonOutAnimation; + + private boolean wasAtBottom = true; + private boolean wasAtZoomScrollHeight = false; + + ConversationScrollListener(@NonNull Context context) { + this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in); + this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out); + + this.scrollButtonInAnimation.setDuration(100); + this.scrollButtonOutAnimation.setDuration(50); + } @Override public void onScrolled(final RecyclerView rv, final int dx, final int dy) { - boolean currentlyAtBottom = isAtBottom(); - - if (wasAtBottom != currentlyAtBottom) { - composeDivider.setVisibility(currentlyAtBottom ? View.INVISIBLE : View.VISIBLE); - scrollToBottomButton.setVisibility(currentlyAtBottom ? View.INVISIBLE : View.VISIBLE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { - composeDivider.animate().alpha(currentlyAtBottom ? 0 : 1); - scrollToBottomButton.animate().alpha(currentlyAtBottom ? 0 : 1); - } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { - composeDivider.setAlpha(currentlyAtBottom ? 0 : 1); - scrollToBottomButton.setAlpha(currentlyAtBottom ? 0 : 1); - } + boolean currentlyAtBottom = isAtBottom(); + boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight(); + + if (currentlyAtBottom && !wasAtBottom) { + ViewUtil.fadeOut(composeDivider, 50, View.INVISIBLE); + ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE); + } else if (!currentlyAtBottom && wasAtBottom) { + ViewUtil.fadeIn(composeDivider, 500); + } - wasAtBottom = currentlyAtBottom; + if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) { + ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation); } + + wasAtBottom = currentlyAtBottom; + wasAtZoomScrollHeight = currentlyAtZoomScrollHeight; } private boolean isAtBottom() { @@ -439,6 +456,10 @@ public class ConversationFragment extends Fragment return isAtBottom && bottomView.getBottom() <= list.getHeight(); } + + private boolean isAtZoomScrollHeight() { + return ((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition() > 4; + } } private class ConversationFragmentItemClickListener implements ItemClickListener {