Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/widget/selection/SelectableListLayout.java

Issue 2703463002: [List UI Unification] Basic list unification for phones (Closed)
Patch Set: [List UI Unification] Basic list unification for phones Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chrome.browser.widget.selection; 5 package org.chromium.chrome.browser.widget.selection;
6 6
7 import android.content.Context; 7 import android.content.Context;
8 import android.content.res.Configuration; 8 import android.content.res.Configuration;
9 import android.content.res.Resources; 9 import android.content.res.Resources;
10 import android.graphics.Rect;
10 import android.graphics.drawable.Drawable; 11 import android.graphics.drawable.Drawable;
12 import android.support.annotation.VisibleForTesting;
11 import android.support.v4.widget.DrawerLayout; 13 import android.support.v4.widget.DrawerLayout;
12 import android.support.v7.widget.LinearLayoutManager; 14 import android.support.v7.widget.LinearLayoutManager;
13 import android.support.v7.widget.RecyclerView; 15 import android.support.v7.widget.RecyclerView;
14 import android.support.v7.widget.RecyclerView.Adapter; 16 import android.support.v7.widget.RecyclerView.Adapter;
15 import android.support.v7.widget.RecyclerView.AdapterDataObserver; 17 import android.support.v7.widget.RecyclerView.AdapterDataObserver;
18 import android.support.v7.widget.RecyclerView.ItemAnimator;
19 import android.support.v7.widget.RecyclerView.OnScrollListener;
16 import android.support.v7.widget.Toolbar.OnMenuItemClickListener; 20 import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
17 import android.util.AttributeSet; 21 import android.util.AttributeSet;
18 import android.view.LayoutInflater; 22 import android.view.LayoutInflater;
19 import android.view.View; 23 import android.view.View;
20 import android.view.ViewStub; 24 import android.view.ViewStub;
21 import android.widget.RelativeLayout; 25 import android.widget.RelativeLayout;
22 import android.widget.TextView; 26 import android.widget.TextView;
23 27
24 import org.chromium.base.ApiCompatibilityUtils; 28 import org.chromium.base.ApiCompatibilityUtils;
25 import org.chromium.chrome.R; 29 import org.chromium.chrome.R;
26 import org.chromium.chrome.browser.widget.FadingShadow; 30 import org.chromium.chrome.browser.widget.FadingShadow;
27 import org.chromium.chrome.browser.widget.FadingShadowView; 31 import org.chromium.chrome.browser.widget.FadingShadowView;
28 import org.chromium.chrome.browser.widget.LoadingView; 32 import org.chromium.chrome.browser.widget.LoadingView;
29 import org.chromium.chrome.browser.widget.displaystyle.DisplayStyleObserver; 33 import org.chromium.chrome.browser.widget.displaystyle.DisplayStyleObserver;
30 import org.chromium.chrome.browser.widget.displaystyle.HorizontalDisplayStyle; 34 import org.chromium.chrome.browser.widget.displaystyle.HorizontalDisplayStyle;
31 import org.chromium.chrome.browser.widget.displaystyle.UiConfig; 35 import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
32 import org.chromium.chrome.browser.widget.displaystyle.UiConfig.DisplayStyle; 36 import org.chromium.chrome.browser.widget.displaystyle.UiConfig.DisplayStyle;
37 import org.chromium.chrome.browser.widget.selection.SelectionDelegate.SelectionO bserver;
33 import org.chromium.ui.base.DeviceFormFactor; 38 import org.chromium.ui.base.DeviceFormFactor;
34 39
40 import java.util.List;
41
35 import javax.annotation.Nullable; 42 import javax.annotation.Nullable;
36 43
37 /** 44 /**
38 * Contains UI elements common to selectable list views: a loading view, empty v iew, selection 45 * Contains UI elements common to selectable list views: a loading view, empty v iew, selection
39 * toolbar, shadow, and RecyclerView. 46 * toolbar, shadow, and RecyclerView.
40 * 47 *
41 * After the SelectableListLayout is inflated, it should be initialized through calls to 48 * After the SelectableListLayout is inflated, it should be initialized through calls to
42 * #initializeRecyclerView(), #initializeToolbar(), and #initializeEmptyView(). 49 * #initializeRecyclerView(), #initializeToolbar(), and #initializeEmptyView().
43 * 50 *
44 * @param <E> The type of the selectable items this layout holds. 51 * @param <E> The type of the selectable items this layout holds.
45 */ 52 */
46 public class SelectableListLayout<E> extends RelativeLayout implements DisplaySt yleObserver { 53 public class SelectableListLayout<E>
54 extends RelativeLayout implements DisplayStyleObserver, SelectionObserve r<E> {
55 /**
56 * @param res Resources used to retrieve drawables and dimensions.
57 * @return The default list item lateral margin size in pixels. This value s hould be used in
58 * {@link HorizontalDisplayStyle#REGULAR} to hide the lateral shadow and rounded edges
59 * on items that use the list_item* 9-patches as a background.
60 */
61 public static int getDefaultListItemLateralMarginPx(Resources res) {
62 if (sDefaultListItemLateralMarginPx == null) {
63 Rect listItemShadow = new Rect();
64 ApiCompatibilityUtils.getDrawable(res, R.drawable.card_middle)
65 .getPadding(listItemShadow);
66 int cardCornerRadius = res.getDimensionPixelSize(R.dimen.list_item_c orner_radius);
67
68 assert listItemShadow.left == listItemShadow.right;
69 // A negative margin is used in HorizontalDisplayStyle#REGULAR to hi de the lateral
70 // shadow.
gone 2017/02/17 01:45:02 Swap assert and comment to avoid comment sandwich,
Theresa 2017/02/17 17:34:28 I added an extra blank line between the assert and
gone 2017/02/17 17:57:51 My sensibilities thank you.
71 sDefaultListItemLateralMarginPx = -(listItemShadow.left + cardCorner Radius);
72 }
73
74 return sDefaultListItemLateralMarginPx;
75 }
76
47 private static final int WIDE_DISPLAY_MIN_PADDING_DP = 16; 77 private static final int WIDE_DISPLAY_MIN_PADDING_DP = 16;
48 78
79 private static Integer sDefaultListItemLateralMarginPx;
gone 2017/02/17 01:45:02 maybe use a regular int and -1?
Theresa 2017/02/17 17:34:28 Done.
80
49 private Adapter<RecyclerView.ViewHolder> mAdapter; 81 private Adapter<RecyclerView.ViewHolder> mAdapter;
50 private ViewStub mToolbarStub; 82 private ViewStub mToolbarStub;
51 private TextView mEmptyView; 83 private TextView mEmptyView;
52 private LoadingView mLoadingView; 84 private LoadingView mLoadingView;
53 private RecyclerView mRecyclerView; 85 private RecyclerView mRecyclerView;
86 private ItemAnimator mItemAnimator;
54 SelectableListToolbar<E> mToolbar; 87 SelectableListToolbar<E> mToolbar;
88 private FadingShadowView mToolbarShadow;
89
90 private boolean mToolbarPermanentlyHidden;
91 private int mEmptyStringResId;
92 private int mSearchEmptyStringResId;
55 93
56 private UiConfig mUiConfig; 94 private UiConfig mUiConfig;
57 95
58 private final AdapterDataObserver mAdapterObserver = new AdapterDataObserver () { 96 private final AdapterDataObserver mAdapterObserver = new AdapterDataObserver () {
59 @Override 97 @Override
60 public void onChanged() { 98 public void onChanged() {
61 super.onChanged(); 99 super.onChanged();
62 if (mAdapter.getItemCount() == 0) { 100 if (mAdapter.getItemCount() == 0) {
63 mEmptyView.setVisibility(View.VISIBLE); 101 mEmptyView.setVisibility(View.VISIBLE);
64 mRecyclerView.setVisibility(View.GONE); 102 mRecyclerView.setVisibility(View.GONE);
(...skipping 14 matching lines...) Expand all
79 updateEmptyViewVisibility(); 117 updateEmptyViewVisibility();
80 } 118 }
81 119
82 @Override 120 @Override
83 public void onItemRangeRemoved(int positionStart, int itemCount) { 121 public void onItemRangeRemoved(int positionStart, int itemCount) {
84 super.onItemRangeRemoved(positionStart, itemCount); 122 super.onItemRangeRemoved(positionStart, itemCount);
85 updateEmptyViewVisibility(); 123 updateEmptyViewVisibility();
86 } 124 }
87 }; 125 };
88 126
89 /**
90 * Unlike ListView or GridView, RecyclerView does not provide default empty
91 * view implementation. We need to check it ourselves.
92 */
93 private void updateEmptyViewVisibility() {
94 mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : V iew.GONE);
95 }
96
97 public SelectableListLayout(Context context, AttributeSet attrs) { 127 public SelectableListLayout(Context context, AttributeSet attrs) {
98 super(context, attrs); 128 super(context, attrs);
99 } 129 }
100 130
101 @Override 131 @Override
102 protected void onFinishInflate() { 132 protected void onFinishInflate() {
103 super.onFinishInflate(); 133 super.onFinishInflate();
104 134
105 LayoutInflater.from(getContext()).inflate(R.layout.selectable_list_layou t, this); 135 LayoutInflater.from(getContext()).inflate(R.layout.selectable_list_layou t, this);
106 136
(...skipping 21 matching lines...) Expand all
128 * that are displayed within the RecyclerView. 158 * that are displayed within the RecyclerView.
129 * @return The RecyclerView itself. 159 * @return The RecyclerView itself.
130 */ 160 */
131 public RecyclerView initializeRecyclerView(Adapter<RecyclerView.ViewHolder> adapter) { 161 public RecyclerView initializeRecyclerView(Adapter<RecyclerView.ViewHolder> adapter) {
132 mAdapter = adapter; 162 mAdapter = adapter;
133 mAdapter.registerAdapterDataObserver(mAdapterObserver); 163 mAdapter.registerAdapterDataObserver(mAdapterObserver);
134 164
135 mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 165 mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
136 mRecyclerView.setAdapter(mAdapter); 166 mRecyclerView.setAdapter(mAdapter);
137 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 167 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
168
138 mRecyclerView.setHasFixedSize(true); 169 mRecyclerView.setHasFixedSize(true);
170 mRecyclerView.addOnScrollListener(new OnScrollListener() {
171 @Override
172 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
173 setToolbarShadowVisibility();
174 }
175 });
176
177 mItemAnimator = mRecyclerView.getItemAnimator();
139 178
140 return mRecyclerView; 179 return mRecyclerView;
141 } 180 }
142 181
143 /** 182 /**
144 * Initializes the SelectionToolbar. 183 * Initializes the SelectionToolbar.
145 * 184 *
146 * @param toolbarLayoutId The resource id of the toolbar layout. This will b e inflated into 185 * @param toolbarLayoutId The resource id of the toolbar layout. This will b e inflated into
147 * a ViewStub. 186 * a ViewStub.
148 * @param delegate The SelectionDelegate that will inform the toolbar of sel ection changes. 187 * @param delegate The SelectionDelegate that will inform the toolbar of sel ection changes.
149 * @param titleResId The resource id of the title string. May be 0 if this c lass shouldn't set 188 * @param titleResId The resource id of the title string. May be 0 if this c lass shouldn't set
150 * set a title when the selection is cleared. 189 * set a title when the selection is cleared.
151 * @param drawerLayout The DrawerLayout whose navigation icon is displayed i n this toolbar. 190 * @param drawerLayout The DrawerLayout whose navigation icon is displayed i n this toolbar.
152 * @param normalGroupResId The resource id of the menu group to show when a selection isn't 191 * @param normalGroupResId The resource id of the menu group to show when a selection isn't
153 * established. 192 * established.
154 * @param selectedGroupResId The resource id of the menu item to show when a selection is 193 * @param selectedGroupResId The resource id of the menu item to show when a selection is
155 * established. 194 * established.
156 * @param normalBackgroundColorResId The resource id of the color to use as the background color 195 * @param normalBackgroundColorResId The resource id of the color to use as the background color
157 * when selection is not enabled. If null the default appbar 196 * when selection is not enabled. If null the default appbar
158 * background color will be used. 197 * background color will be used.
159 * @param hideShadowOnLargeTablets Whether the toolbar shadow should be hidd en on large tablets. 198 * @param hideShadowOnLargeTablets Whether the toolbar shadow should be hidd en on large tablets.
160 * @param listener The OnMenuItemClickListener to set on the toolbar. 199 * @param listener The OnMenuItemClickListener to set on the toolbar.
161 * @return The initialized SelectionToolbar. 200 * @return The initialized SelectionToolbar.
162 */ 201 */
163 public SelectableListToolbar<E> initializeToolbar(int toolbarLayoutId, 202 public SelectableListToolbar<E> initializeToolbar(int toolbarLayoutId,
164 SelectionDelegate<E> delegate, int titleResId, @Nullable DrawerLayou t drawerLayout, 203 SelectionDelegate<E> delegate, int titleResId, @Nullable DrawerLayou t drawerLayout,
165 int normalGroupResId, int selectedGroupResId, 204 int normalGroupResId, int selectedGroupResId,
166 @Nullable Integer normalBackgroundColorResId, boolean hideShadowOnLa rgeTablets, 205 @Nullable Integer normalBackgroundColorResId, boolean hideShadowOnLa rgeTablets,
167 @Nullable OnMenuItemClickListener listener) { 206 @Nullable OnMenuItemClickListener listener) {
168 FadingShadowView shadow = (FadingShadowView) findViewById(R.id.shadow);
169 if (hideShadowOnLargeTablets && DeviceFormFactor.isLargeTablet(getContex t())) {
170 shadow.setVisibility(View.GONE);
171 } else {
172 shadow.init(ApiCompatibilityUtils.getColor(getResources(),
173 R.color.toolbar_shadow_color), FadingShadow.POSITION_TOP);
174 }
175
176 mToolbarStub.setLayoutResource(toolbarLayoutId); 207 mToolbarStub.setLayoutResource(toolbarLayoutId);
177 @SuppressWarnings("unchecked") 208 @SuppressWarnings("unchecked")
178 SelectableListToolbar<E> toolbar = (SelectableListToolbar<E>) mToolbarSt ub.inflate(); 209 SelectableListToolbar<E> toolbar = (SelectableListToolbar<E>) mToolbarSt ub.inflate();
179 mToolbar = toolbar; 210 mToolbar = toolbar;
180 mToolbar.initialize(delegate, titleResId, drawerLayout, normalGroupResId , 211 mToolbar.initialize(delegate, titleResId, drawerLayout, normalGroupResId ,
181 selectedGroupResId, normalBackgroundColorResId); 212 selectedGroupResId, normalBackgroundColorResId);
182 213
183 if (listener != null) { 214 if (listener != null) {
184 mToolbar.setOnMenuItemClickListener(listener); 215 mToolbar.setOnMenuItemClickListener(listener);
185 } 216 }
186 217
218 mToolbarShadow = (FadingShadowView) findViewById(R.id.shadow);
219 if (hideShadowOnLargeTablets && DeviceFormFactor.isLargeTablet(getContex t())) {
220 mToolbarPermanentlyHidden = true;
221 mToolbarShadow.setVisibility(View.GONE);
222 } else {
223 mToolbarShadow.init(
224 ApiCompatibilityUtils.getColor(getResources(), R.color.toolb ar_shadow_color),
225 FadingShadow.POSITION_TOP);
226 delegate.addObserver(this);
227 setToolbarShadowVisibility();
228 }
229
187 return mToolbar; 230 return mToolbar;
188 } 231 }
189 232
190 /** 233 /**
191 * Initializes the view shown when the selectable list is empty. 234 * Initializes the view shown when the selectable list is empty.
192 * 235 *
193 * @param emptyDrawable The Drawable to show when the selectable list is emp ty. 236 * @param emptyDrawable The Drawable to show when the selectable list is emp ty.
194 * @param emptyStringResId The string to show when the selectable list is em pty. 237 * @param emptyStringResId The string to show when the selectable list is em pty.
238 * @param searchEmptyStringResId The string to show when the selectable list is empty during
239 * a search.
195 * @return The {@link TextView} displayed when the list is empty. 240 * @return The {@link TextView} displayed when the list is empty.
196 */ 241 */
197 public TextView initializeEmptyView(Drawable emptyDrawable, int emptyStringR esId) { 242 public TextView initializeEmptyView(
243 Drawable emptyDrawable, int emptyStringResId, int searchEmptyStringR esId) {
244 mEmptyStringResId = emptyStringResId;
245 mSearchEmptyStringResId = searchEmptyStringResId;
246
198 mEmptyView.setCompoundDrawablesWithIntrinsicBounds(null, emptyDrawable, null, null); 247 mEmptyView.setCompoundDrawablesWithIntrinsicBounds(null, emptyDrawable, null, null);
199 mEmptyView.setText(emptyStringResId); 248 mEmptyView.setText(mEmptyStringResId);
200 return mEmptyView; 249 return mEmptyView;
201 } 250 }
202 251
203 /** 252 /**
204 * @param emptyStringResId The string to show when the selectable list is em pty.
205 */
206 public void setEmptyViewText(int emptyStringResId) {
207 mEmptyView.setText(emptyStringResId);
208 }
209
210 /**
211 * Called when the view that owns the SelectableListLayout is destroyed. 253 * Called when the view that owns the SelectableListLayout is destroyed.
212 */ 254 */
213 public void onDestroyed() { 255 public void onDestroyed() {
214 mAdapter.unregisterAdapterDataObserver(mAdapterObserver); 256 mAdapter.unregisterAdapterDataObserver(mAdapterObserver);
257 mToolbar.getSelectionDelegate().removeObserver(this);
215 } 258 }
216 259
217 /** 260 /**
218 * When this layout has a wide display style, it will be width constrained t o 261 * When this layout has a wide display style, it will be width constrained t o
219 * {@link UiConfig#WIDE_DISPLAY_STYLE_MIN_WIDTH_DP}. If the current screen w idth is greater than 262 * {@link UiConfig#WIDE_DISPLAY_STYLE_MIN_WIDTH_DP}. If the current screen w idth is greater than
220 * UiConfig#WIDE_DISPLAY_STYLE_MIN_WIDTH_DP, the SelectableListLayout will b e visually centered 263 * UiConfig#WIDE_DISPLAY_STYLE_MIN_WIDTH_DP, the SelectableListLayout will b e visually centered
221 * by adding padding to both sides. 264 * by adding padding to both sides.
222 * 265 *
223 * This method should be called after the toolbar and RecyclerView are initi alized. 266 * This method should be called after the toolbar and RecyclerView are initi alized.
224 * 267 *
(...skipping 16 matching lines...) Expand all
241 284
242 @Override 285 @Override
243 public void onDisplayStyleChanged(DisplayStyle newDisplayStyle) { 286 public void onDisplayStyleChanged(DisplayStyle newDisplayStyle) {
244 int padding = getPaddingForDisplayStyle(newDisplayStyle, getResources()) ; 287 int padding = getPaddingForDisplayStyle(newDisplayStyle, getResources()) ;
245 288
246 ApiCompatibilityUtils.setPaddingRelative(mRecyclerView, 289 ApiCompatibilityUtils.setPaddingRelative(mRecyclerView,
247 padding, mRecyclerView.getPaddingTop(), 290 padding, mRecyclerView.getPaddingTop(),
248 padding, mRecyclerView.getPaddingBottom()); 291 padding, mRecyclerView.getPaddingBottom());
249 } 292 }
250 293
294 @Override
295 public void onSelectionStateChange(List<E> selectedItems) {
296 setToolbarShadowVisibility();
297 }
298
299 /**
300 * Called when a search is starting.
301 */
302 public void onStartSearch() {
303 mRecyclerView.setItemAnimator(null);
304 mToolbarShadow.setVisibility(View.VISIBLE);
305 mEmptyView.setText(mSearchEmptyStringResId);
306 }
307
308 /**
309 * Called when a search has ended.
310 */
311 public void onEndSearch() {
312 mRecyclerView.setItemAnimator(mItemAnimator);
313 setToolbarShadowVisibility();
314 mEmptyView.setText(mEmptyStringResId);
315 }
316
251 /** 317 /**
252 * @param displayStyle The current display style.. 318 * @param displayStyle The current display style..
253 * @param resources The {@link Resources} used to retrieve configuration and display metrics. 319 * @param resources The {@link Resources} used to retrieve configuration and display metrics.
254 * @return The lateral padding to use for the current display style. 320 * @return The lateral padding to use for the current display style.
255 */ 321 */
256 public static int getPaddingForDisplayStyle(DisplayStyle displayStyle, Resou rces resources) { 322 public static int getPaddingForDisplayStyle(DisplayStyle displayStyle, Resou rces resources) {
257 int padding = 0; 323 int padding = 0;
258 if (displayStyle.horizontal == HorizontalDisplayStyle.WIDE) { 324 if (displayStyle.horizontal == HorizontalDisplayStyle.WIDE) {
259 int screenWidthDp = resources.getConfiguration().screenWidthDp; 325 int screenWidthDp = resources.getConfiguration().screenWidthDp;
260 float dpToPx = resources.getDisplayMetrics().density; 326 float dpToPx = resources.getDisplayMetrics().density;
261 padding = (int) (((screenWidthDp - UiConfig.WIDE_DISPLAY_STYLE_MIN_W IDTH_DP) / 2.f) 327 padding = (int) (((screenWidthDp - UiConfig.WIDE_DISPLAY_STYLE_MIN_W IDTH_DP) / 2.f)
262 * dpToPx); 328 * dpToPx);
263 padding = (int) Math.max(WIDE_DISPLAY_MIN_PADDING_DP * dpToPx, paddi ng); 329 padding = (int) Math.max(WIDE_DISPLAY_MIN_PADDING_DP * dpToPx, paddi ng);
264 } 330 }
265 return padding; 331 return padding;
266 } 332 }
333
334 private void setToolbarShadowVisibility() {
335 if (mToolbarPermanentlyHidden || mToolbar == null || mRecyclerView == nu ll) return;
336
337 boolean showShadow = mRecyclerView.computeVerticalScrollOffset() != 0
338 || mToolbar.isSearching() || mToolbar.getSelectionDelegate().isS electionEnabled();
339 mToolbarShadow.setVisibility(showShadow ? View.VISIBLE : View.GONE);
340 }
341
342 /**
343 * Unlike ListView or GridView, RecyclerView does not provide default empty
344 * view implementation. We need to check it ourselves.
345 */
346 private void updateEmptyViewVisibility() {
347 mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : V iew.GONE);
348 }
349
350 @VisibleForTesting
351 public View getToolbarShadowForTests() {
352 return mToolbarShadow;
353 }
267 } 354 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698