From 83f59abc9c566da5deb98afe7ea35cfb061f2920 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Tue, 5 May 2015 17:21:58 -0700 Subject: [PATCH] Explorations in dense grids for all apps. - Adds sticky section headers - Removing AppsListAdapter - Adding search bar field - Subtitle filtering Bug: 20222023 Change-Id: I1eaef701b5d68f475615f09d86561eacc91c937f --- res/drawable-hdpi/ic_arrow_back_grey.png | Bin 0 -> 190 bytes res/drawable-hdpi/ic_search_grey.png | Bin 0 -> 743 bytes res/drawable-mdpi/ic_arrow_back_grey.png | Bin 0 -> 151 bytes res/drawable-mdpi/ic_search_grey.png | Bin 0 -> 497 bytes res/drawable-xhdpi/ic_arrow_back_grey.png | Bin 0 -> 234 bytes res/drawable-xhdpi/ic_search_grey.png | Bin 0 -> 972 bytes res/drawable-xxhdpi/ic_arrow_back_grey.png | Bin 0 -> 308 bytes res/drawable-xxhdpi/ic_search_grey.png | Bin 0 -> 1473 bytes res/drawable-xxxhdpi/ic_arrow_back_grey.png | Bin 0 -> 359 bytes res/drawable-xxxhdpi/ic_search_grey.png | Bin 0 -> 1996 bytes res/layout/apps_list_view.xml | 63 +++-- res/values/dimens.xml | 1 + .../launcher3/AlphabeticalAppsList.java | 91 ++++++- .../launcher3/AppsContainerRecyclerView.java | 26 +- .../android/launcher3/AppsContainerView.java | 225 +++++++++++++----- .../android/launcher3/AppsGridAdapter.java | 149 +++++++++--- .../android/launcher3/AppsListAdapter.java | 143 ----------- src/com/android/launcher3/DeviceProfile.java | 7 + src/com/android/launcher3/Launcher.java | 51 ++-- .../widget/WidgetsContainerRecyclerView.java | 2 +- 20 files changed, 479 insertions(+), 279 deletions(-) create mode 100755 res/drawable-hdpi/ic_arrow_back_grey.png create mode 100755 res/drawable-hdpi/ic_search_grey.png create mode 100755 res/drawable-mdpi/ic_arrow_back_grey.png create mode 100755 res/drawable-mdpi/ic_search_grey.png create mode 100755 res/drawable-xhdpi/ic_arrow_back_grey.png create mode 100755 res/drawable-xhdpi/ic_search_grey.png create mode 100755 res/drawable-xxhdpi/ic_arrow_back_grey.png create mode 100755 res/drawable-xxhdpi/ic_search_grey.png create mode 100755 res/drawable-xxxhdpi/ic_arrow_back_grey.png create mode 100755 res/drawable-xxxhdpi/ic_search_grey.png delete mode 100644 src/com/android/launcher3/AppsListAdapter.java diff --git a/res/drawable-hdpi/ic_arrow_back_grey.png b/res/drawable-hdpi/ic_arrow_back_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..ccd3900ddf0e69d99a7e318847415e17dd6658e2 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawT0LDHLo)8Yy}X;Z!GOm#(0>AF zf=_wPCUpbu9Sb@%CJOpr+#C16W82w|jt-4auR^*=ajkr)F+pz5Wpn+gIgETV z76lIuG%_#G_IuP(7}v1x=@*7c(K8j*{jSL(y$KKnF2+y85}Sb4q9e0K8L08vp?P)8LQEIh_B)d(GsBl;3iCg^VSZnJ%sJ;fGchJim@r|&_z&%5m?h6C1xge+ zAml=uITteb zIx>biRql*3%6Y}aX|JV< zrTmfj6L%+Aa@?QO-stx3nQ*~mxwDuaO(DDQOt_@77PY0>m95G0K-v@6rzNyzE##eND2N}L#QAMHv8b?g1^=fDv5#-AACgcr64?^gy3&9gieCChU6~G_~3^H zzo8*CBMQ#@;1>lyr6H6S1uuPc>R*B%)DUX3&azRD^tg4FwHS@CDvF+y@IbckL$E7G zC-hryHJ?0?pDpsO(F$Lz!daKpdyCpMdZE|xew=lsBbgGfjb`}Z_=;G1YAw;?n5;6B zMmNORb^OEyW7WR4h%s(0r?1ZUnxe;+C5K=5|cz}vaTJdD;Yx%TP}1q|7>)}^@E!HV?-az z6dzG3vLd@OM|Dv$hE@icW}RcMDRILw8%#0iJVJR{Hzn8V-5ffU_JgR@*=VJMP{~DO zbt>&OUTMEVsUuW!D0Ti1O5Yo=v==BLhf?Epj4_6e2c=->{8b8$&Vy2rbi#xQ6DEx3 Z{037>p9cHt>oouX002ovPDHLkV1nM|U8ev5 literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_arrow_back_grey.png b/res/drawable-mdpi/ic_arrow_back_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..11996efe3f710c98d035a90ab0927641f843ccc7 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DI8PVHkch)?CvN0zaNs$Tr=76D z!=$5!NBBVS#b*=WxhBTvdv~%h%yn_A`WhZx_4Rz%`hOjqwpCwV#Q$xd`L&+ohQG;! ylaGbfJF}A10=;!qv^pou>GZXWN)l%%DPrcRnNmLGu{Y2n1_n=8KbLh*2~7YQWHkZ+ literal 0 HcmV?d00001 diff --git a/res/drawable-mdpi/ic_search_grey.png b/res/drawable-mdpi/ic_search_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..e83891c11cd38aa5f2974fbaa495674414143778 GIT binary patch literal 497 zcmV(4}cZTXIavHlJWd({i?CfszwG!Jejt zY{>{Ew|oMXrnziMHzf~Zg1bKpj$&IBwk4yK-0=w%nwGaEiK|$;MfkDfls1X@~rhG-ips31G)D4P?97WBbD9cxr nLQs}ko)AJ%l3LygC*;L9+X5DqusM8900000NkvXXu0mjfMZ?}n literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_arrow_back_grey.png b/res/drawable-xhdpi/ic_arrow_back_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..79b9b486c9bfee950fd4e9a9c4a8e78491cc137a GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgHhQ`^hE&{od-WvmAqN50i+l`4 zEeg+$8SP{&Y~0za(`UA9cDQ|`R^~JgpkjuI)@#w{*Z!Y#t@w4iuf!K)wKHEftJYko ze?KGo|FyC&FE20mpKn+D3(nwvS)X|Ft2pP4p!&v!+^?)67EY{sg^lT34!Em1Hnr4y z?C_qTu+Z;NToVhYsgRtIipPWx#+>aPXE>LiII8nd=BI>^(JOw==_gK4Dd2@TKwNu< UnT5E9m=lQW>FVdQ&MBb@00SypIsgCw literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_search_grey.png b/res/drawable-xhdpi/ic_search_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..bd5fdf4446d4f0ceede6ae3ec23798e375333057 GIT binary patch literal 972 zcmV;-12g=IP)Q6U5| zpjV|r@F0+4LLvw$Sr5`6Gqbhb-I;c9-rTy~_j_-5K7R9?B}79*LqkJDLqo$N;713h zv4kX2ILA3sNMaGw=zy=-rB`DN-(X~Edx#=XjM7Wcg0-yD|8A>jE-v}C*vMIYrmdr< zIK-DChKs!A8!&Kz36vIEz7HQQiO;kxcnc~20e;w$Z@|D2LWPq52&cB?8!&K!hlP3i z=JLNFhA;vsg9|Q{A%HNZu${%ieffQ#-+9uQMV;wg^_WB2q$9~33%G?(Tp2G>o`dz* z+C^}$x)?^rH z;dGuVOH4JKd8|pi0XAi8x6Bn`xK>2VuVQB&TYL$IuTrdmF=pN`x5ejY)`d~A2I%d| ztZl}5ig8!$W|4m3b+$P}45zp?868xp&yKJrDb|QM1JhKfm?Nw+6zilo14~q>up_Lk z6l+490s3V-QeQxjV%-*JAVq~LbA;7Hu^x&ua88ACIl}6uSkF{9kXF459jiWwlj004 z3YkXRDAr|p3)z$-tZ|ApE-#gEV8%uXzRF7_d`#k1Z=17$;q;5lNB5{;bG8{5D8?PJ z`++F4p}A{|uY##?M8z5iFfaU-qv@f6d!l#6t4zozmbp3^t~cVV63v{_Y|vtX>=(n; zD5Em8&V=31|NIet92VgDUlyd;(&9mWCtj%K$WL1JP4uR1@OxfXiVu`r;%g?>%KBk@HNZ zVx{@aYVHNIny)53`L5lp&TAlkY=VmCBnY~4XOo_G>yghj{DDs#Lp~{JRSHbC>F|G@{nd0Uj_Y$`rHIM~e2Pk?de@a`O_ALvu7?dn3(e`xSw&p zm3^;WNgfTB|ihv1@k;kgi&5>TkN+zluOVgV&Hq%X3<*c=z{|JSQzzF4Xu z@UYXx+^CboUuBoDr%Q#>#by_!L&`93sBHbtbZyUZH`mp%?|{L;;OXk;vd$@?2>|`B BbD#hK literal 0 HcmV?d00001 diff --git a/res/drawable-xxhdpi/ic_search_grey.png b/res/drawable-xxhdpi/ic_search_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..1d5c91361c1cd6ca4c4cd0f8e55653e94858bf44 GIT binary patch literal 1473 zcmV;y1wQ(TP)TOVFn)54Nfv%JriIp)8Z8t87Ht(*q9GE6f~81| zVyRj`aI5g7_1lezpkPc96SYvQXu(}qB1Ei8s}@Y7F^IUA1cl;PG|8N1(M2iq&gAya zJNL{@^7{{-_s*SjW-hVwM>Wf6;WVA}($4_HBVN%*FP)qQ8hHz`OYmhP%Q?n<0yIWI z53MvZaqI+I$Vr}}d3r)S3raoE1lDjHEx;|B$(3fHGFI~dO+YVAr59*EzoQAd&b-nI zRK;P2(1Z3brCx23x^+0QRiF)*$=IjP4;{ck1{n-qZ;|DY)9pthWD91T8 zMQ6xmDNtD+pPqm_9Az_$nMpMjBdVFnVz$sq7Xdm-?O6(RV3cV(S#)FPuaixa#?W39?UpgFuiWBkZ_NS3Q(t&76VP-0qV3JNmvW&)I-i*pf!)Xn7IOu>*Bz zuoLK*bNZ=8Je%#Dh>q9^bRP}XiKO5v8mik$py|#tT#uw+BN}R$X*L2ab6#N8NDAI@ z9?()7fm}oX9V8`pT?>wlK&R1AN0F4Yp`ki#1nNXXZAMbE6%BRSMxb6a)M6wh^{%bn zMxcH))J!BLwP>ivHUbTxp{kLT)S#hW*a$R?hN?hPQi+Bd%0!^?K(EYRVlQn3>Kp%( z9>2|d85Fk^eNen)d(~(y`l``xBM@k)E+i%Q&`{fK1ZqS>1x!!rafdoI*a%cjfQH(D zq+pZtfTr3AB=n%6t{^G+)%`rmN}yKf8O}mH`^fqEJ!~gXBkFJh@$3}pu*A}f!za!c zhcgh5KHv=+s?XAkL!ljY_zCgoJnGPfSPHZNMN?ln8c?TCYy}c-p$-q3jCe4WN2tSf z#9E+c)ag9p!9~>R3wwcb^rB863EP1>-J#50AfX9$3iujH&^Ks=&#nGW`UdJW$T}n; z8yG^JE+bY0&0`RC3TW|uaVZ0QY&Vc_2#s`sDFxoSE~1fkB9;SH(v3!XSilOw$zix=>GUGaEsuLW`3~NJ1qC z8AKEIn*Ed`|Ii4#O$Bn8%QZAXSExh&MT?V2NYYAr&;)d|0{IV(u`3IKgd9!WL|&w>7KpU!R=gn}rB-jdmzt$9)b^L{`I8Ot3U$r6Qo_hBElT?iym zxPNcjeBhYaI2_%p}0tqCL1rtahfdmprAb|ujfdmpr bpdafC?m9)~Vz%P)00000NkvXXu0mjf^4^xC literal 0 HcmV?d00001 diff --git a/res/drawable-xxxhdpi/ic_arrow_back_grey.png b/res/drawable-xxxhdpi/ic_arrow_back_grey.png new file mode 100755 index 0000000000000000000000000000000000000000..854a9bd1a9a74af9350f0bb94fe2a680e1bf2a99 GIT binary patch literal 359 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 e1OR{l0MI{a5w(t - + android:layout_height="52dp" + android:orientation="horizontal" + android:background="@drawable/apps_search_bg"> + + + + + + 8dp 52dp + 8dp 64dp 24sp 6dp diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index c7ee2e99ac..477c00fe80 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -86,6 +86,8 @@ public class AlphabeticalAppsList { public String sectionName; // The number of applications in this section public int numAppsInSection; + // The section AdapterItem for this section + public AdapterItem sectionItem; // The first app AdapterItem for this section public AdapterItem firstAppItem; @@ -137,6 +139,9 @@ public class AlphabeticalAppsList { public boolean retainApp(AppInfo info, String sectionName); } + // The maximum number of rows allowed in a merged section before we stop merging + private static final int MAX_ROWS_IN_MERGED_SECTION = Integer.MAX_VALUE; + private List mApps = new ArrayList<>(); private List mFilteredApps = new ArrayList<>(); private List mSectionedFilteredApps = new ArrayList<>(); @@ -145,10 +150,23 @@ public class AlphabeticalAppsList { private Filter mFilter; private AlphabeticIndexCompat mIndexer; private AppNameComparator mAppNameComparator; + private int mNumAppsPerRow; + // The maximum number of section merges we allow at a given time before we stop merging + private int mMaxAllowableMerges = Integer.MAX_VALUE; - public AlphabeticalAppsList(Context context) { + public AlphabeticalAppsList(Context context, int numAppsPerRow) { mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppNameComparator(context); + setNumAppsPerRow(numAppsPerRow); + } + + /** + * Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED. + */ + public void setNumAppsPerRow(int numAppsPerRow) { + mNumAppsPerRow = numAppsPerRow; + mMaxAllowableMerges = (int) Math.ceil(numAppsPerRow / 2f); + onAppsUpdated(); } /** @@ -179,6 +197,13 @@ public class AlphabeticalAppsList { return mFilteredApps.size(); } + /** + * Returns whether there are is a filter set. + */ + public boolean hasFilter() { + return (mFilter != null); + } + /** * Returns whether there are no filtered results. */ @@ -190,9 +215,11 @@ public class AlphabeticalAppsList { * Sets the current filter for this list of apps. */ public void setFilter(Filter f) { - mFilter = f; - onAppsUpdated(); - mAdapter.notifyDataSetChanged(); + if (mFilter != f) { + mFilter = f; + onAppsUpdated(); + mAdapter.notifyDataSetChanged(); + } } /** @@ -298,9 +325,13 @@ public class AlphabeticalAppsList { lastSectionInfo = new SectionInfo(sectionName); mSections.add(lastSectionInfo); - // Create a new section item + // Create a new section item, this item is used to break the flow of items in the + // list AdapterItem sectionItem = AdapterItem.asSection(position++, sectionName); - mSectionedFilteredApps.add(sectionItem); + if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) { + lastSectionInfo.sectionItem = sectionItem; + mSectionedFilteredApps.add(sectionItem); + } } // Create an app item @@ -312,5 +343,53 @@ public class AlphabeticalAppsList { mSectionedFilteredApps.add(appItem); mFilteredApps.add(info); } + + if (AppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) { + // Go through each section and try and merge some of the sections + int minNumAppsPerRow = (int) Math.ceil(mNumAppsPerRow / 2f); + int sectionAppCount = 0; + for (int i = 0; i < mSections.size(); i++) { + SectionInfo section = mSections.get(i); + String mergedSectionName = section.sectionName; + sectionAppCount = section.numAppsInSection; + int mergeCount = 1; + // Merge rows if the last app in this section is in a column that is greater than + // 0, but less than the min number of apps per row. In addition, apply the + // constraint to stop merging if the number of rows in the section is greater than + // some limit, and also if there are no lessons to merge. + while (0 < (sectionAppCount % mNumAppsPerRow) && + (sectionAppCount % mNumAppsPerRow) < minNumAppsPerRow && + (int) Math.ceil(sectionAppCount / mNumAppsPerRow) < MAX_ROWS_IN_MERGED_SECTION && + (i + 1) < mSections.size()) { + SectionInfo nextSection = mSections.remove(i + 1); + // Merge the section names + if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) { + mergedSectionName += nextSection.sectionName; + } + // Remove the next section break + mSectionedFilteredApps.remove(nextSection.sectionItem); + if (AppsContainerView.GRID_MERGE_SECTION_HEADERS) { + // Update the section names for the two sections + int pos = mSectionedFilteredApps.indexOf(section.firstAppItem); + for (int j = pos; j < (pos + section.numAppsInSection + nextSection.numAppsInSection); j++) { + AdapterItem item = mSectionedFilteredApps.get(j); + item.sectionName = mergedSectionName; + } + } + // Update the following adapter items of the removed section + int pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem); + for (int j = pos; j < mSectionedFilteredApps.size(); j++) { + AdapterItem item = mSectionedFilteredApps.get(j); + item.position--; + } + section.numAppsInSection += nextSection.numAppsInSection; + sectionAppCount += nextSection.numAppsInSection; + mergeCount++; + if (mergeCount >= mMaxAllowableMerges) { + break; + } + } + } + } } } diff --git a/src/com/android/launcher3/AppsContainerRecyclerView.java b/src/com/android/launcher3/AppsContainerRecyclerView.java index bf478edddc..d91bceac98 100644 --- a/src/com/android/launcher3/AppsContainerRecyclerView.java +++ b/src/com/android/launcher3/AppsContainerRecyclerView.java @@ -66,6 +66,7 @@ public class AppsContainerRecyclerView extends RecyclerView private int mScrollbarWidth; private int mScrollbarMinHeight; private int mScrollbarInset; + private RecyclerView.OnScrollListener mScrollListenerProxy; public AppsContainerRecyclerView(Context context) { this(context, null); @@ -102,7 +103,7 @@ public class AppsContainerRecyclerView extends RecyclerView mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD; ScrollListener listener = new ScrollListener(); - addOnScrollListener(listener); + setOnScrollListener(listener); } private class ScrollListener extends RecyclerView.OnScrollListener { @@ -112,6 +113,7 @@ public class AppsContainerRecyclerView extends RecyclerView @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { mDy = dy; + mScrollListenerProxy.onScrolled(recyclerView, dx, dy); } } @@ -129,6 +131,13 @@ public class AppsContainerRecyclerView extends RecyclerView mNumAppsPerRow = rowSize; } + /** + * Sets an additional scroll listener, not necessary in master support lib. + */ + public void setOnScrollListenerProxy(RecyclerView.OnScrollListener listener) { + mScrollListenerProxy = listener; + } + /** * Sets the fast scroller alpha. */ @@ -178,10 +187,6 @@ public class AppsContainerRecyclerView extends RecyclerView handleTouchEvent(ev); } - public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { - // Do nothing - } - /** * Handles the touch event and determines whether to show the fast scroller (or updates it if * it is already showing). @@ -322,6 +327,7 @@ public class AppsContainerRecyclerView extends RecyclerView // Find the position of the first application in the section that contains the row at the // current progress + List items = mApps.getAdapterItems(); int rowAtProgress = (int) (progress * getNumRows()); int rowCount = 0; AlphabeticalAppsList.SectionInfo lastSectionInfo = null; @@ -333,7 +339,7 @@ public class AppsContainerRecyclerView extends RecyclerView } rowCount += numRowsInSection; } - int position = mApps.getAdapterItems().indexOf(lastSectionInfo.firstAppItem); + int position = items.indexOf(lastSectionInfo.firstAppItem); // Scroll the position into view, anchored at the top of the screen if possible. We call the // scroll method on the LayoutManager directly since it is not exposed by RecyclerView. @@ -342,15 +348,17 @@ public class AppsContainerRecyclerView extends RecyclerView layoutManager.scrollToPositionWithOffset(position, 0); // Return the section name of the row - return mApps.getAdapterItems().get(position).sectionName; + return lastSectionInfo.sectionName; } /** * Returns the bounds for the scrollbar. */ private void updateVerticalScrollbarBounds() { + List items = mApps.getAdapterItems(); + // Skip early if there are no items - if (mApps.getAdapterItems().isEmpty()) { + if (items.isEmpty()) { mVerticalScrollbarBounds.setEmpty(); return; } @@ -369,7 +377,7 @@ public class AppsContainerRecyclerView extends RecyclerView View child = getChildAt(i); int position = getChildPosition(child); if (position != NO_POSITION) { - AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); + AlphabeticalAppsList.AdapterItem item = items.get(position); if (!item.isSectionHeader) { rowIndex = findRowForAppIndex(item.appIndex); rowTopOffset = getLayoutManager().getDecoratedTop(child); diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java index c3cf629b88..9122427fdd 100644 --- a/src/com/android/launcher3/AppsContainerView.java +++ b/src/com/android/launcher3/AppsContainerView.java @@ -34,7 +34,6 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; - import com.android.launcher3.util.Thunk; import java.util.List; @@ -45,24 +44,32 @@ import java.util.List; */ public class AppsContainerView extends FrameLayout implements DragSource, Insettable, TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, View.OnTouchListener, - View.OnLongClickListener { + View.OnClickListener, View.OnLongClickListener { + + public static final boolean GRID_MERGE_SECTIONS = true; + public static final boolean GRID_MERGE_SECTION_HEADERS = false; + public static final boolean GRID_HIDE_SECTION_HEADERS = false; private static final boolean ALLOW_SINGLE_APP_LAUNCH = true; - - private static final int GRID_LAYOUT = 0; - private static final int LIST_LAYOUT = 1; - private static final int USE_LAYOUT = GRID_LAYOUT; + private static final boolean DYNAMIC_HEADER_ELEVATION = false; + private static final float HEADER_ELEVATION_DP = 4; + private static final int FADE_IN_DURATION = 175; + private static final int FADE_OUT_DURATION = 125; @Thunk Launcher mLauncher; @Thunk AlphabeticalAppsList mApps; - private RecyclerView.Adapter mAdapter; + private AppsGridAdapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private RecyclerView.ItemDecoration mItemDecoration; private LinearLayout mContentView; @Thunk AppsContainerRecyclerView mAppsRecyclerView; - private EditText mSearchBarView; - + private View mHeaderView; + private View mSearchBarContainerView; + private View mSearchButtonView; + private View mDismissSearchButtonView; + private EditText mSearchBarEditView; + private int mNumAppsPerRow; private Point mLastTouchDownPos = new Point(-1, -1); private Point mLastTouchPos = new Point(); @@ -73,6 +80,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett private int mContainerInset; // Fixed bounds container insets private int mFixedBoundsContainerInset; + // RecyclerView scroll position + @Thunk int mRecyclerViewScrollY; public AppsContainerView(Context context) { this(context, null); @@ -93,23 +102,14 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mFixedBoundsContainerInset = context.getResources().getDimensionPixelSize( R.dimen.apps_container_fixed_bounds_inset); mLauncher = (Launcher) context; - mApps = new AlphabeticalAppsList(context); - if (USE_LAYOUT == GRID_LAYOUT) { - mNumAppsPerRow = grid.appsViewNumCols; - AppsGridAdapter adapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, - mLauncher, this); - adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); - mLayoutManager = adapter.getLayoutManager(context); - mItemDecoration = adapter.getItemDecoration(); - mAdapter = adapter; - mContentMarginStart = adapter.getContentMarginStart(); - } else if (USE_LAYOUT == LIST_LAYOUT) { - mNumAppsPerRow = 1; - AppsListAdapter adapter = new AppsListAdapter(context, mApps, this, mLauncher, this); - adapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); - mLayoutManager = adapter.getLayoutManager(context); - mAdapter = adapter; - } + mNumAppsPerRow = grid.appsViewNumCols; + mApps = new AlphabeticalAppsList(context, mNumAppsPerRow); + mAdapter = new AppsGridAdapter(context, mApps, mNumAppsPerRow, this, mLauncher, this); + mAdapter.setEmptySearchText(res.getString(R.string.loading_apps_message)); + mAdapter.setNumAppsPerRow(mNumAppsPerRow); + mLayoutManager = mAdapter.getLayoutManager(); + mItemDecoration = mAdapter.getItemDecoration(); + mContentMarginStart = mAdapter.getContentMarginStart(); mApps.setAdapter(mAdapter); } @@ -142,10 +142,10 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett } /** - * Hides the search bar + * Hides the header bar */ - public void hideSearchBar() { - mSearchBarView.setVisibility(View.GONE); + public void hideHeaderBar() { + mHeaderView.setVisibility(View.GONE); updateBackgrounds(); updatePaddings(); } @@ -155,6 +155,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett */ public void scrollToTop() { mAppsRecyclerView.scrollToPosition(0); + mRecyclerViewScrollY = 0; } /** @@ -175,9 +176,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett protected void onFinishInflate() { boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL); - if (USE_LAYOUT == GRID_LAYOUT) { - ((AppsGridAdapter) mAdapter).setRtl(isRtl); - } + mAdapter.setRtl(isRtl); // Work around the search box getting first focus and showing the cursor by // proxying the focus from the content view to the recycler view directly @@ -190,10 +189,20 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett } } }); - mSearchBarView = (EditText) findViewById(R.id.app_search_box); - if (mSearchBarView != null) { - mSearchBarView.addTextChangedListener(this); - mSearchBarView.setOnEditorActionListener(this); + mHeaderView = findViewById(R.id.header); + mHeaderView.setOnClickListener(this); + if (Utilities.isLmpOrAbove() && !DYNAMIC_HEADER_ELEVATION) { + mHeaderView.setElevation(DynamicGrid.pxFromDp(HEADER_ELEVATION_DP, + getContext().getResources().getDisplayMetrics())); + } + mSearchButtonView = mHeaderView.findViewById(R.id.search_button); + mSearchBarContainerView = findViewById(R.id.app_search_container); + mDismissSearchButtonView = mSearchBarContainerView.findViewById(R.id.dismiss_search_button); + mDismissSearchButtonView.setOnClickListener(this); + mSearchBarEditView = (EditText) findViewById(R.id.app_search_box); + if (mSearchBarEditView != null) { + mSearchBarEditView.addTextChangedListener(this); + mSearchBarEditView.setOnEditorActionListener(this); } mAppsRecyclerView = (AppsContainerRecyclerView) findViewById(R.id.apps_list_view); mAppsRecyclerView.setApps(mApps); @@ -201,6 +210,18 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mAppsRecyclerView.setLayoutManager(mLayoutManager); mAppsRecyclerView.setAdapter(mAdapter); mAppsRecyclerView.setHasFixedSize(true); + mAppsRecyclerView.setOnScrollListenerProxy(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + // Do nothing + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mRecyclerViewScrollY += dy; + onRecyclerViewScrolled(); + } + }); if (mItemDecoration != null) { mAppsRecyclerView.addItemDecoration(mItemDecoration); } @@ -225,12 +246,15 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett if (grid.updateAppsViewNumCols(context.getResources(), fixedBounds.width())) { mNumAppsPerRow = grid.appsViewNumCols; mAppsRecyclerView.setNumAppsPerRow(mNumAppsPerRow); - if (USE_LAYOUT == GRID_LAYOUT) { - ((AppsGridAdapter) mAdapter).setNumAppsPerRow(mNumAppsPerRow); - } + mAdapter.setNumAppsPerRow(mNumAppsPerRow); + mApps.setNumAppsPerRow(mNumAppsPerRow); } mFixedBounds.set(fixedBounds); + if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + mFixedBounds.top = mInsets.top; + mFixedBounds.bottom = getMeasuredHeight(); + } } // Post the updates since they can trigger a relayout, and this call can be triggered from // a layout pass itself. @@ -264,6 +288,15 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett return false; } + @Override + public void onClick(View v) { + if (v == mHeaderView) { + showSearchField(); + } else if (v == mDismissSearchButtonView) { + hideSearchField(true, true); + } + } + @Override public boolean onLongClick(View v) { // Return early if this is not initiated from a touch @@ -363,24 +396,27 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mApps.setFilter(null); } else { String formatStr = getResources().getString(R.string.apps_view_no_search_results); - if (USE_LAYOUT == GRID_LAYOUT) { - ((AppsGridAdapter) mAdapter).setEmptySearchText(String.format(formatStr, - s.toString())); - } else { - ((AppsListAdapter) mAdapter).setEmptySearchText(String.format(formatStr, - s.toString())); - } + mAdapter.setEmptySearchText(String.format(formatStr, s.toString())); final String filterText = s.toString().toLowerCase().replaceAll("\\s+", ""); mApps.setFilter(new AlphabeticalAppsList.Filter() { @Override public boolean retainApp(AppInfo info, String sectionName) { String title = info.title.toString(); - return sectionName.toLowerCase().contains(filterText) || - title.toLowerCase().replaceAll("\\s+", "").contains(filterText); + if (sectionName.toLowerCase().contains(filterText)) { + return true; + } + String[] words = title.toLowerCase().split("\\s+"); + for (int i = 0; i < words.length; i++) { + if (words[i].startsWith(filterText)) { + return true; + } + } + return false; } }); } + scrollToTop(); } @Override @@ -396,9 +432,7 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett AlphabeticalAppsList.AdapterItem item = items.get(i); if (!item.isSectionHeader) { mAppsRecyclerView.getChildAt(i).performClick(); - InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(getWindowToken(), 0); + getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); return true; } } @@ -428,10 +462,22 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett @Override public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { - if (mSearchBarView != null) { + if (mSearchBarEditView != null) { if (toWorkspace) { - // Clear the search bar - mSearchBarView.setText(""); + hideSearchField(false, false); + } + } + } + + /** + * Updates the container when the recycler view is scrolled. + */ + private void onRecyclerViewScrolled() { + if (DYNAMIC_HEADER_ELEVATION) { + int elevation = Math.min(mRecyclerViewScrollY, DynamicGrid.pxFromDp(HEADER_ELEVATION_DP, + getContext().getResources().getDisplayMetrics())); + if (Float.compare(mHeaderView.getElevation(), elevation) != 0) { + mHeaderView.setElevation(elevation); } } } @@ -494,8 +540,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett private void updatePaddings() { boolean isRtl = (getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL); - boolean hasSearchBar = (mSearchBarView != null) && - (mSearchBarView.getVisibility() == View.VISIBLE); + boolean hasSearchBar = (mSearchBarEditView != null) && + (mSearchBarEditView.getVisibility() == View.VISIBLE); if (mFixedBounds.isEmpty()) { // If there are no fixed bounds, then use the default padding and insets @@ -516,10 +562,10 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett mAppsRecyclerView.setPadding(inset + mContentMarginStart, inset, inset, inset); } - // Update the search bar + // Update the header if (hasSearchBar) { LinearLayout.LayoutParams lp = - (LinearLayout.LayoutParams) mSearchBarView.getLayoutParams(); + (LinearLayout.LayoutParams) mHeaderView.getLayoutParams(); lp.leftMargin = lp.rightMargin = inset; } } @@ -529,8 +575,8 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett */ private void updateBackgrounds() { int inset = mFixedBounds.isEmpty() ? mContainerInset : mFixedBoundsContainerInset; - boolean hasSearchBar = (mSearchBarView != null) && - (mSearchBarView.getVisibility() == View.VISIBLE); + boolean hasSearchBar = (mSearchBarEditView != null) && + (mSearchBarEditView.getVisibility() == View.VISIBLE); // Update the background of the reveal view and list to be inset with the fixed bound // insets instead of the default insets @@ -542,4 +588,63 @@ public class AppsContainerView extends FrameLayout implements DragSource, Insett getContext().getResources().getDrawable(R.drawable.apps_reveal_bg), inset, 0, inset, 0)); } + + /** + * Shows the search field. + */ + private void showSearchField() { + // Show the search bar and focus the search + mSearchBarContainerView.setVisibility(View.VISIBLE); + mSearchBarContainerView.setAlpha(0f); + mSearchBarContainerView.animate().alpha(1f).setDuration(FADE_IN_DURATION).withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + mSearchBarEditView.requestFocus(); + getInputMethodManager().showSoftInput(mSearchBarEditView, + InputMethodManager.SHOW_IMPLICIT); + } + }); + mSearchButtonView.animate().alpha(0f).setDuration(FADE_OUT_DURATION).withLayer(); + } + + /** + * Hides the search field. + */ + private void hideSearchField(boolean animated, final boolean returnFocusToRecyclerView) { + if (animated) { + // Hide the search bar and focus the recycler view + mSearchBarContainerView.animate().alpha(0f).setDuration(FADE_IN_DURATION).withLayer() + .withEndAction(new Runnable() { + @Override + public void run() { + mSearchBarContainerView.setVisibility(View.INVISIBLE); + mSearchBarEditView.setText(""); + mApps.setFilter(null); + if (returnFocusToRecyclerView) { + mAppsRecyclerView.requestFocus(); + } + scrollToTop(); + } + }); + mSearchButtonView.animate().alpha(1f).setDuration(FADE_OUT_DURATION).withLayer(); + } else { + mSearchBarContainerView.setVisibility(View.INVISIBLE); + mSearchBarEditView.setText(""); + mApps.setFilter(null); + mSearchButtonView.setAlpha(1f); + if (returnFocusToRecyclerView) { + mAppsRecyclerView.requestFocus(); + } + scrollToTop(); + } + getInputMethodManager().hideSoftInputFromWindow(getWindowToken(), 0); + } + + /** + * Returns an input method manager. + */ + private InputMethodManager getInputMethodManager() { + return (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + } } diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index 5bc3981dfc..62d9129c9f 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -4,16 +4,18 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.Rect; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import com.android.launcher3.util.Thunk; +import java.util.HashMap; import java.util.List; @@ -23,6 +25,7 @@ import java.util.List; class AppsGridAdapter extends RecyclerView.Adapter { public static final String TAG = "AppsGridAdapter"; + private static final boolean DEBUG = false; private static final int SECTION_BREAK_VIEW_TYPE = 0; private static final int ICON_VIEW_TYPE = 1; @@ -48,6 +51,12 @@ class AppsGridAdapter extends RecyclerView.Adapter { * Helper class to size the grid items. */ public class GridSpanSizer extends GridLayoutManager.SpanSizeLookup { + + public GridSpanSizer() { + super(); + setSpanIndexCacheEnabled(true); + } + @Override public int getSpanSize(int position) { if (mApps.hasNoFilteredResults()) { @@ -57,7 +66,11 @@ class AppsGridAdapter extends RecyclerView.Adapter { if (mApps.getAdapterItems().get(position).isSectionHeader) { // Section break spans full width - return mAppsPerRow; + if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + return 0; + } else { + return mAppsPerRow; + } } else { return 1; } @@ -69,31 +82,88 @@ class AppsGridAdapter extends RecyclerView.Adapter { */ public class GridItemDecoration extends RecyclerView.ItemDecoration { + private static final boolean FADE_OUT_SECTIONS = false; + + private HashMap mCachedSectionBounds = new HashMap<>(); + private Rect mTmpBounds = new Rect(); + @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mApps.hasFilter()) { + return; + } + List items = mApps.getAdapterItems(); + String lastSectionName = null; + int appIndexInSection = 0; + int lastSectionTop = 0; + int lastSectionHeight = 0; for (int i = 0; i < parent.getChildCount(); i++) { View child = parent.getChildAt(i); ViewHolder holder = (ViewHolder) parent.getChildViewHolder(child); - if (shouldDrawItemSection(holder, child, items)) { - // Draw at the parent - AlphabeticalAppsList.AdapterItem item = - items.get(holder.getPosition()); - String section = item.sectionName; - mSectionTextPaint.getTextBounds(section, 0, section.length(), - mTmpBounds); - if (mIsRtl) { - int left = parent.getWidth() - mPaddingStart - mStartMargin; - c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2, - child.getTop() + (2 * child.getPaddingTop()) + - mTmpBounds.height(), mSectionTextPaint); - } else { - int left = mPaddingStart; - c.drawText(section, left + (mStartMargin - mTmpBounds.width()) / 2, - child.getTop() + (2 * child.getPaddingTop()) + - mTmpBounds.height(), mSectionTextPaint); + if (shouldDrawItemSection(holder, child, i, items)) { + int cellTopOffset = (2 * child.getPaddingTop()); + int pos = holder.getPosition(); + AlphabeticalAppsList.AdapterItem item = items.get(pos); + if (!item.sectionName.equals(lastSectionName)) { + lastSectionName = item.sectionName; + + // Find the section code points + String sectionBegin = null; + String sectionEnd = null; + int charOffset = 0; + while (charOffset < item.sectionName.length()) { + int codePoint = item.sectionName.codePointAt(charOffset); + int codePointSize = Character.charCount(codePoint); + if (charOffset == 0) { + // The first code point + sectionBegin = item.sectionName.substring(charOffset, charOffset + codePointSize); + } else if ((charOffset + codePointSize) >= item.sectionName.length()) { + // The last code point + sectionEnd = item.sectionName.substring(charOffset, charOffset + codePointSize); + } + charOffset += codePointSize; + } + + Point sectionBeginBounds = getAndCacheSectionBounds(sectionBegin); + int minTop = cellTopOffset + sectionBeginBounds.y; + int top = child.getTop() + cellTopOffset + sectionBeginBounds.y; + int left = mIsRtl ? parent.getWidth() - mPaddingStart - mStartMargin : + mPaddingStart; + int col = appIndexInSection % mAppsPerRow; + int nextRowPos = Math.min(pos - col + mAppsPerRow, items.size() - 1); + int alpha = 255; + boolean fixedToRow = !items.get(nextRowPos).sectionName.equals(item.sectionName); + if (fixedToRow) { + alpha = Math.min(255, (int) (255 * (Math.max(0, top) / (float) minTop))); + } else { + // If we aren't fixed to the current row, then bound into the viewport + top = Math.max(minTop, top); + } + if (lastSectionHeight > 0 && top <= (lastSectionTop + lastSectionHeight)) { + top += lastSectionTop - top + lastSectionHeight; + } + if (FADE_OUT_SECTIONS) { + mSectionTextPaint.setAlpha(alpha); + } + if (sectionEnd != null) { + Point sectionEndBounds = getAndCacheSectionBounds(sectionEnd); + c.drawText(sectionBegin + "/" + sectionEnd, + left + (mStartMargin - sectionBeginBounds.x - sectionEndBounds.x) / 2, top, + mSectionTextPaint); + } else { + c.drawText(sectionBegin, left + (mStartMargin - sectionBeginBounds.x) / 2, top, + mSectionTextPaint); + } + lastSectionTop = top; + lastSectionHeight = sectionBeginBounds.y + mSectionHeaderOffset; } } + if (holder.mIsSectionHeader) { + appIndexInSection = 0; + } else { + appIndexInSection++; + } } } @@ -103,7 +173,17 @@ class AppsGridAdapter extends RecyclerView.Adapter { // Do nothing } - private boolean shouldDrawItemSection(ViewHolder holder, View child, + private Point getAndCacheSectionBounds(String sectionName) { + Point bounds = mCachedSectionBounds.get(sectionName); + if (bounds == null) { + mSectionTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTmpBounds); + bounds = new Point(mTmpBounds.width(), mTmpBounds.height()); + mCachedSectionBounds.put(sectionName, bounds); + } + return bounds; + } + + private boolean shouldDrawItemSection(ViewHolder holder, View child, int childIndex, List items) { // Ensure item is not already removed GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) @@ -121,11 +201,19 @@ class AppsGridAdapter extends RecyclerView.Adapter { } // Ensure we have a holder position int pos = holder.getPosition(); - if (pos <= 0 || pos >= items.size()) { + if (pos < 0 || pos >= items.size()) { return false; } - // Only draw the first item in the section (the first one after the section header) - return items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader; + // Ensure this is not a section header + if (items.get(pos).isSectionHeader) { + return false; + } + // Only draw the header for the first item in a section, or whenever the sub-sections + // changes (if AppsContainerView.GRID_MERGE_SECTIONS is true, but + // AppsContainerView.GRID_MERGE_SECTION_HEADERS is false) + return (childIndex == 0) || + items.get(pos - 1).isSectionHeader && !items.get(pos).isSectionHeader || + (!items.get(pos - 1).sectionName.equals(items.get(pos).sectionName)); } } @@ -144,8 +232,8 @@ class AppsGridAdapter extends RecyclerView.Adapter { // Section drawing @Thunk int mPaddingStart; @Thunk int mStartMargin; + @Thunk int mSectionHeaderOffset; @Thunk Paint mSectionTextPaint; - @Thunk Rect mTmpBounds = new Rect(); public AppsGridAdapter(Context context, AlphabeticalAppsList apps, int appsPerRow, @@ -163,7 +251,10 @@ class AppsGridAdapter extends RecyclerView.Adapter { mTouchListener = touchListener; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; - mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); + if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); + mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset); + } mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset); mSectionTextPaint = new Paint(); mSectionTextPaint.setTextSize(res.getDimensionPixelSize( @@ -197,7 +288,7 @@ class AppsGridAdapter extends RecyclerView.Adapter { /** * Returns the grid layout manager. */ - public GridLayoutManager getLayoutManager(Context context) { + public GridLayoutManager getLayoutManager() { return mGridLayoutMgr; } @@ -205,7 +296,11 @@ class AppsGridAdapter extends RecyclerView.Adapter { * Returns the item decoration for the recycler view. */ public RecyclerView.ItemDecoration getItemDecoration() { - return mItemDecoration; + // We don't draw any headers when we are uncomfortably dense + if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + return mItemDecoration; + } + return null; } /** diff --git a/src/com/android/launcher3/AppsListAdapter.java b/src/com/android/launcher3/AppsListAdapter.java deleted file mode 100644 index ffd309261a..0000000000 --- a/src/com/android/launcher3/AppsListAdapter.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.android.launcher3; - -import android.content.Context; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -/** - * The linear list view adapter for all the apps. - */ -class AppsListAdapter extends RecyclerView.Adapter { - - /** - * ViewHolder for each row. - */ - public static class ViewHolder extends RecyclerView.ViewHolder { - public View mContent; - - public ViewHolder(View v) { - super(v); - mContent = v; - } - } - - private static final int SECTION_BREAK_VIEW_TYPE = 0; - private static final int ICON_VIEW_TYPE = 1; - private static final int EMPTY_VIEW_TYPE = 2; - - private LayoutInflater mLayoutInflater; - private AlphabeticalAppsList mApps; - private View.OnTouchListener mTouchListener; - private View.OnClickListener mIconClickListener; - private View.OnLongClickListener mIconLongClickListener; - private String mEmptySearchText; - - public AppsListAdapter(Context context, AlphabeticalAppsList apps, - View.OnTouchListener touchListener, View.OnClickListener iconClickListener, - View.OnLongClickListener iconLongClickListener) { - mApps = apps; - mLayoutInflater = LayoutInflater.from(context); - mTouchListener = touchListener; - mIconClickListener = iconClickListener; - mIconLongClickListener = iconLongClickListener; - } - - public RecyclerView.LayoutManager getLayoutManager(Context context) { - return new LinearLayoutManager(context); - } - - /** - * Sets the text to show when there are no apps. - */ - public void setEmptySearchText(String query) { - mEmptySearchText = query; - } - - @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case EMPTY_VIEW_TYPE: - return new ViewHolder(mLayoutInflater.inflate(R.layout.apps_empty_view, parent, - false)); - case SECTION_BREAK_VIEW_TYPE: - return new ViewHolder(new View(parent.getContext())); - case ICON_VIEW_TYPE: - // Inflate the row and all the icon children necessary - ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.apps_list_row_view, - parent, false); - BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( - R.layout.apps_list_row_icon_view, row, false); - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, - ViewGroup.LayoutParams.WRAP_CONTENT, 1); - lp.gravity = Gravity.CENTER_VERTICAL; - icon.setLayoutParams(lp); - icon.setOnTouchListener(mTouchListener); - icon.setOnClickListener(mIconClickListener); - icon.setOnLongClickListener(mIconLongClickListener); - icon.setFocusable(true); - row.addView(icon); - return new ViewHolder(row); - default: - throw new RuntimeException("Unexpected view type"); - } - } - - @Override - public void onBindViewHolder(ViewHolder holder, int position) { - switch (holder.getItemViewType()) { - case ICON_VIEW_TYPE: - AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position); - ViewGroup content = (ViewGroup) holder.mContent; - String sectionDescription = item.sectionName; - - // Bind the section header - boolean showSectionHeader = true; - if (position > 0) { - AlphabeticalAppsList.AdapterItem prevItem = - mApps.getAdapterItems().get(position - 1); - showSectionHeader = prevItem.isSectionHeader; - } - TextView tv = (TextView) content.findViewById(R.id.section); - if (showSectionHeader) { - tv.setText(sectionDescription); - tv.setVisibility(View.VISIBLE); - } else { - tv.setVisibility(View.INVISIBLE); - } - - // Bind the icon - BubbleTextView icon = (BubbleTextView) content.getChildAt(1); - icon.applyFromApplicationInfo(item.appInfo); - break; - case EMPTY_VIEW_TYPE: - TextView emptyViewText = (TextView) holder.mContent.findViewById(R.id.empty_text); - emptyViewText.setText(mEmptySearchText); - break; - } - } - - @Override - public int getItemCount() { - if (mApps.hasNoFilteredResults()) { - // For the empty view - return 1; - } - return mApps.getAdapterItems().size(); - } - - @Override - public int getItemViewType(int position) { - if (mApps.hasNoFilteredResults()) { - return EMPTY_VIEW_TYPE; - } else if (mApps.getAdapterItems().get(position).isSectionHeader) { - return SECTION_BREAK_VIEW_TYPE; - } - return ICON_VIEW_TYPE; - } -} diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index deb807501e..918517ebd3 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -428,6 +428,13 @@ public class DeviceProfile { } public boolean updateAppsViewNumCols(Resources res, int containerWidth) { + if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) { + if (appsViewNumCols != allAppsNumCols) { + appsViewNumCols = allAppsNumCols; + return true; + } + return false; + } int appsViewLeftMarginPx = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 339b4e498d..c0f09f4860 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -135,6 +135,9 @@ public class Launcher extends Activity static final String TAG = "Launcher"; static final boolean LOGD = true; + // Temporary flag + static final boolean DISABLE_ALL_APPS_SEARCH_INTEGRATION = true; + static final boolean PROFILE_STARTUP = false; static final boolean DEBUG_WIDGETS = true; static final boolean DEBUG_STRICT_MODE = false; @@ -530,10 +533,12 @@ public class Launcher extends Activity @Override public void dismissAllApps() { - // Dismiss All Apps if we aren't already paused/invisible - if (!mPaused) { - showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, - null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */); + if (!DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + // Dismiss All Apps if we aren't already paused/invisible + if (!mPaused) { + showWorkspace(WorkspaceStateTransitionAnimation.SCROLL_TO_CURRENT_PAGE, true, + null /* onCompleteRunnable */, false /* notifyLauncherCallbacks */); + } } } }); @@ -1019,7 +1024,7 @@ public class Launcher extends Activity mOnResumeState = State.NONE; // Restore the apps state if we are in all apps - if (mState == State.APPS) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mState == State.APPS) { if (mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsShown(); } @@ -1453,8 +1458,8 @@ public class Launcher extends Activity // Setup Apps mAppsView = (AppsContainerView) findViewById(R.id.apps_view); - if (mLauncherCallbacks != null && mLauncherCallbacks.overrideAllAppsSearch()) { - mAppsView.hideSearchBar(); + if (isAllAppsSearchOverridden()) { + mAppsView.hideHeaderBar(); } // Setup AppsCustomize @@ -2877,15 +2882,22 @@ public class Launcher extends Activity /** Updates the interaction state. */ public void updateInteraction(Workspace.State fromState, Workspace.State toState) { - // Only update the interacting state if we are transitioning to/from a view without an + // Only update the interacting state if we are transitioning to/from a view with an // overlay - boolean fromStateWithoutOverlay = fromState != Workspace.State.NORMAL && - fromState != Workspace.State.NORMAL_HIDDEN; - boolean toStateWithoutOverlay = toState != Workspace.State.NORMAL && - toState != Workspace.State.NORMAL_HIDDEN; - if (toStateWithoutOverlay) { + boolean fromStateWithOverlay; + boolean toStateWithOverlay; + if (Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + fromStateWithOverlay = fromState != Workspace.State.NORMAL; + toStateWithOverlay = toState != Workspace.State.NORMAL; + } else { + fromStateWithOverlay = fromState != Workspace.State.NORMAL && + fromState != Workspace.State.NORMAL_HIDDEN; + toStateWithOverlay = toState != Workspace.State.NORMAL && + toState != Workspace.State.NORMAL_HIDDEN; + } + if (toStateWithOverlay) { onInteractionBegin(); - } else if (fromStateWithoutOverlay) { + } else if (fromStateWithOverlay) { onInteractionEnd(); } } @@ -3367,7 +3379,7 @@ public class Launcher extends Activity .sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); if (notifyLauncherCallbacks) { // Dismiss all apps when the workspace is shown - if (mLauncherCallbacks != null) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsHidden(); } } @@ -3419,7 +3431,7 @@ public class Launcher extends Activity if (toState == State.APPS) { mStateTransitionAnimation.startAnimationToAllApps(animated); - if (mLauncherCallbacks != null) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsShown(); } } else { @@ -3472,7 +3484,7 @@ public class Launcher extends Activity if (successfulDrop) { // We need to trigger all apps hidden to notify search to update itself before the // delayed call to showWorkspace below - if (mLauncherCallbacks != null) { + if (!Launcher.DISABLE_ALL_APPS_SEARCH_INTEGRATION && mLauncherCallbacks != null) { mLauncherCallbacks.onAllAppsHidden(); } } @@ -4454,9 +4466,12 @@ public class Launcher extends Activity /** * Returns whether the launcher callbacks overrides search in all apps. - * @return */ @Thunk boolean isAllAppsSearchOverridden() { + if (DISABLE_ALL_APPS_SEARCH_INTEGRATION) { + return false; + } + if (mLauncherCallbacks != null) { return mLauncherCallbacks.overrideAllAppsSearch(); } diff --git a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java index f70f170edf..80e13bcf25 100644 --- a/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java +++ b/src/com/android/launcher3/widget/WidgetsContainerRecyclerView.java @@ -51,7 +51,7 @@ public class WidgetsContainerRecyclerView extends RecyclerView mDeltaThreshold = getResources().getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD; ScrollListener listener = new ScrollListener(); - addOnScrollListener(listener); + setOnScrollListener(listener); } private class ScrollListener extends RecyclerView.OnScrollListener {