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

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 == -1) {
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
70 // A negative margin is used in HorizontalDisplayStyle#REGULAR to hi de the lateral
71 // shadow.
72 sDefaultListItemLateralMarginPx = -(listItemShadow.left + cardCorner Radius);
73 }
74
75 return sDefaultListItemLateralMarginPx;
76 }
77
47 private static final int WIDE_DISPLAY_MIN_PADDING_DP = 16; 78 private static final int WIDE_DISPLAY_MIN_PADDING_DP = 16;
48 79
80 private static int sDefaultListItemLateralMarginPx = -1;
81
49 private Adapter<RecyclerView.ViewHolder> mAdapter; 82 private Adapter<RecyclerView.ViewHolder> mAdapter;
50 private ViewStub mToolbarStub; 83 private ViewStub mToolbarStub;
51 private TextView mEmptyView; 84 private TextView mEmptyView;
52 private LoadingView mLoadingView; 85 private LoadingView mLoadingView;
53 private RecyclerView mRecyclerView; 86 private RecyclerView mRecyclerView;
87 private ItemAnimator mItemAnimator;
54 SelectableListToolbar<E> mToolbar; 88 SelectableListToolbar<E> mToolbar;
89 private FadingShadowView mToolbarShadow;
90
91 private boolean mToolbarPermanentlyHidden;
92 private int mEmptyStringResId;
93 private int mSearchEmptyStringResId;
55 94
56 private UiConfig mUiConfig; 95 private UiConfig mUiConfig;
57 96
58 private final AdapterDataObserver mAdapterObserver = new AdapterDataObserver () { 97 private final AdapterDataObserver mAdapterObserver = new AdapterDataObserver () {
59 @Override 98 @Override
60 public void onChanged() { 99 public void onChanged() {
61 super.onChanged(); 100 super.onChanged();
62 if (mAdapter.getItemCount() == 0) { 101 if (mAdapter.getItemCount() == 0) {
63 mEmptyView.setVisibility(View.VISIBLE); 102 mEmptyView.setVisibility(View.VISIBLE);
64 mRecyclerView.setVisibility(View.GONE); 103 mRecyclerView.setVisibility(View.GONE);
(...skipping 14 matching lines...) Expand all
79 updateEmptyViewVisibility(); 118 updateEmptyViewVisibility();
80 } 119 }
81 120
82 @Override 121 @Override
83 public void onItemRangeRemoved(int positionStart, int itemCount) { 122 public void onItemRangeRemoved(int positionStart, int itemCount) {
84 super.onItemRangeRemoved(positionStart, itemCount); 123 super.onItemRangeRemoved(positionStart, itemCount);
85 updateEmptyViewVisibility(); 124 updateEmptyViewVisibility();
86 } 125 }
87 }; 126 };
88 127
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) { 128 public SelectableListLayout(Context context, AttributeSet attrs) {
98 super(context, attrs); 129 super(context, attrs);
99 } 130 }
100 131
101 @Override 132 @Override
102 protected void onFinishInflate() { 133 protected void onFinishInflate() {
103 super.onFinishInflate(); 134 super.onFinishInflate();
104 135
105 LayoutInflater.from(getContext()).inflate(R.layout.selectable_list_layou t, this); 136 LayoutInflater.from(getContext()).inflate(R.layout.selectable_list_layou t, this);
106 137
(...skipping 21 matching lines...) Expand all
128 * that are displayed within the RecyclerView. 159 * that are displayed within the RecyclerView.
129 * @return The RecyclerView itself. 160 * @return The RecyclerView itself.
130 */ 161 */
131 public RecyclerView initializeRecyclerView(Adapter<RecyclerView.ViewHolder> adapter) { 162 public RecyclerView initializeRecyclerView(Adapter<RecyclerView.ViewHolder> adapter) {
132 mAdapter = adapter; 163 mAdapter = adapter;
133 mAdapter.registerAdapterDataObserver(mAdapterObserver); 164 mAdapter.registerAdapterDataObserver(mAdapterObserver);
134 165
135 mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); 166 mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
136 mRecyclerView.setAdapter(mAdapter); 167 mRecyclerView.setAdapter(mAdapter);
137 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 168 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
169
138 mRecyclerView.setHasFixedSize(true); 170 mRecyclerView.setHasFixedSize(true);
171 mRecyclerView.addOnScrollListener(new OnScrollListener() {
172 @Override
173 public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
174 setToolbarShadowVisibility();
175 }
176 });
177
178 mItemAnimator = mRecyclerView.getItemAnimator();
139 179
140 return mRecyclerView; 180 return mRecyclerView;
141 } 181 }
142 182
143 /** 183 /**
144 * Initializes the SelectionToolbar. 184 * Initializes the SelectionToolbar.
145 * 185 *
146 * @param toolbarLayoutId The resource id of the toolbar layout. This will b e inflated into 186 * @param toolbarLayoutId The resource id of the toolbar layout. This will b e inflated into
147 * a ViewStub. 187 * a ViewStub.
148 * @param delegate The SelectionDelegate that will inform the toolbar of sel ection changes. 188 * @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 189 * @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. 190 * set a title when the selection is cleared.
151 * @param drawerLayout The DrawerLayout whose navigation icon is displayed i n this toolbar. 191 * @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 192 * @param normalGroupResId The resource id of the menu group to show when a selection isn't
153 * established. 193 * established.
154 * @param selectedGroupResId The resource id of the menu item to show when a selection is 194 * @param selectedGroupResId The resource id of the menu item to show when a selection is
155 * established. 195 * established.
156 * @param normalBackgroundColorResId The resource id of the color to use as the background color 196 * @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 197 * when selection is not enabled. If null the default appbar
158 * background color will be used. 198 * background color will be used.
159 * @param hideShadowOnLargeTablets Whether the toolbar shadow should be hidd en on large tablets. 199 * @param hideShadowOnLargeTablets Whether the toolbar shadow should be hidd en on large tablets.
160 * @param listener The OnMenuItemClickListener to set on the toolbar. 200 * @param listener The OnMenuItemClickListener to set on the toolbar.
161 * @return The initialized SelectionToolbar. 201 * @return The initialized SelectionToolbar.
162 */ 202 */
163 public SelectableListToolbar<E> initializeToolbar(int toolbarLayoutId, 203 public SelectableListToolbar<E> initializeToolbar(int toolbarLayoutId,
164 SelectionDelegate<E> delegate, int titleResId, @Nullable DrawerLayou t drawerLayout, 204 SelectionDelegate<E> delegate, int titleResId, @Nullable DrawerLayou t drawerLayout,
165 int normalGroupResId, int selectedGroupResId, 205 int normalGroupResId, int selectedGroupResId,
166 @Nullable Integer normalBackgroundColorResId, boolean hideShadowOnLa rgeTablets, 206 @Nullable Integer normalBackgroundColorResId, boolean hideShadowOnLa rgeTablets,
167 @Nullable OnMenuItemClickListener listener) { 207 @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); 208 mToolbarStub.setLayoutResource(toolbarLayoutId);
177 @SuppressWarnings("unchecked") 209 @SuppressWarnings("unchecked")
178 SelectableListToolbar<E> toolbar = (SelectableListToolbar<E>) mToolbarSt ub.inflate(); 210 SelectableListToolbar<E> toolbar = (SelectableListToolbar<E>) mToolbarSt ub.inflate();
179 mToolbar = toolbar; 211 mToolbar = toolbar;
180 mToolbar.initialize(delegate, titleResId, drawerLayout, normalGroupResId , 212 mToolbar.initialize(delegate, titleResId, drawerLayout, normalGroupResId ,
181 selectedGroupResId, normalBackgroundColorResId); 213 selectedGroupResId, normalBackgroundColorResId);
182 214
183 if (listener != null) { 215 if (listener != null) {
184 mToolbar.setOnMenuItemClickListener(listener); 216 mToolbar.setOnMenuItemClickListener(listener);
185 } 217 }
186 218
219 mToolbarShadow = (FadingShadowView) findViewById(R.id.shadow);
220 if (hideShadowOnLargeTablets && DeviceFormFactor.isLargeTablet(getContex t())) {
221 mToolbarPermanentlyHidden = true;
222 mToolbarShadow.setVisibility(View.GONE);
223 } else {
224 mToolbarShadow.init(
225 ApiCompatibilityUtils.getColor(getResources(), R.color.toolb ar_shadow_color),
226 FadingShadow.POSITION_TOP);
227 delegate.addObserver(this);
228 setToolbarShadowVisibility();
229 }
230
187 return mToolbar; 231 return mToolbar;
188 } 232 }
189 233
190 /** 234 /**
191 * Initializes the view shown when the selectable list is empty. 235 * Initializes the view shown when the selectable list is empty.
192 * 236 *
193 * @param emptyDrawable The Drawable to show when the selectable list is emp ty. 237 * @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. 238 * @param emptyStringResId The string to show when the selectable list is em pty.
239 * @param searchEmptyStringResId The string to show when the selectable list is empty during
240 * a search.
195 * @return The {@link TextView} displayed when the list is empty. 241 * @return The {@link TextView} displayed when the list is empty.
196 */ 242 */
197 public TextView initializeEmptyView(Drawable emptyDrawable, int emptyStringR esId) { 243 public TextView initializeEmptyView(
244 Drawable emptyDrawable, int emptyStringResId, int searchEmptyStringR esId) {
245 mEmptyStringResId = emptyStringResId;
246 mSearchEmptyStringResId = searchEmptyStringResId;
247
198 mEmptyView.setCompoundDrawablesWithIntrinsicBounds(null, emptyDrawable, null, null); 248 mEmptyView.setCompoundDrawablesWithIntrinsicBounds(null, emptyDrawable, null, null);
199 mEmptyView.setText(emptyStringResId); 249 mEmptyView.setText(mEmptyStringResId);
200 return mEmptyView; 250 return mEmptyView;
201 } 251 }
202 252
203 /** 253 /**
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. 254 * Called when the view that owns the SelectableListLayout is destroyed.
212 */ 255 */
213 public void onDestroyed() { 256 public void onDestroyed() {
214 mAdapter.unregisterAdapterDataObserver(mAdapterObserver); 257 mAdapter.unregisterAdapterDataObserver(mAdapterObserver);
258 mToolbar.getSelectionDelegate().removeObserver(this);
215 } 259 }
216 260
217 /** 261 /**
218 * When this layout has a wide display style, it will be width constrained t o 262 * 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 263 * {@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 264 * UiConfig#WIDE_DISPLAY_STYLE_MIN_WIDTH_DP, the SelectableListLayout will b e visually centered
221 * by adding padding to both sides. 265 * by adding padding to both sides.
222 * 266 *
223 * This method should be called after the toolbar and RecyclerView are initi alized. 267 * This method should be called after the toolbar and RecyclerView are initi alized.
224 * 268 *
(...skipping 16 matching lines...) Expand all
241 285
242 @Override 286 @Override
243 public void onDisplayStyleChanged(DisplayStyle newDisplayStyle) { 287 public void onDisplayStyleChanged(DisplayStyle newDisplayStyle) {
244 int padding = getPaddingForDisplayStyle(newDisplayStyle, getResources()) ; 288 int padding = getPaddingForDisplayStyle(newDisplayStyle, getResources()) ;
245 289
246 ApiCompatibilityUtils.setPaddingRelative(mRecyclerView, 290 ApiCompatibilityUtils.setPaddingRelative(mRecyclerView,
247 padding, mRecyclerView.getPaddingTop(), 291 padding, mRecyclerView.getPaddingTop(),
248 padding, mRecyclerView.getPaddingBottom()); 292 padding, mRecyclerView.getPaddingBottom());
249 } 293 }
250 294
295 @Override
296 public void onSelectionStateChange(List<E> selectedItems) {
297 setToolbarShadowVisibility();
298 }
299
300 /**
301 * Called when a search is starting.
302 */
303 public void onStartSearch() {
304 mRecyclerView.setItemAnimator(null);
305 mToolbarShadow.setVisibility(View.VISIBLE);
306 mEmptyView.setText(mSearchEmptyStringResId);
307 }
308
309 /**
310 * Called when a search has ended.
311 */
312 public void onEndSearch() {
313 mRecyclerView.setItemAnimator(mItemAnimator);
314 setToolbarShadowVisibility();
315 mEmptyView.setText(mEmptyStringResId);
316 }
317
251 /** 318 /**
252 * @param displayStyle The current display style.. 319 * @param displayStyle The current display style..
253 * @param resources The {@link Resources} used to retrieve configuration and display metrics. 320 * @param resources The {@link Resources} used to retrieve configuration and display metrics.
254 * @return The lateral padding to use for the current display style. 321 * @return The lateral padding to use for the current display style.
255 */ 322 */
256 public static int getPaddingForDisplayStyle(DisplayStyle displayStyle, Resou rces resources) { 323 public static int getPaddingForDisplayStyle(DisplayStyle displayStyle, Resou rces resources) {
257 int padding = 0; 324 int padding = 0;
258 if (displayStyle.horizontal == HorizontalDisplayStyle.WIDE) { 325 if (displayStyle.horizontal == HorizontalDisplayStyle.WIDE) {
259 int screenWidthDp = resources.getConfiguration().screenWidthDp; 326 int screenWidthDp = resources.getConfiguration().screenWidthDp;
260 float dpToPx = resources.getDisplayMetrics().density; 327 float dpToPx = resources.getDisplayMetrics().density;
261 padding = (int) (((screenWidthDp - UiConfig.WIDE_DISPLAY_STYLE_MIN_W IDTH_DP) / 2.f) 328 padding = (int) (((screenWidthDp - UiConfig.WIDE_DISPLAY_STYLE_MIN_W IDTH_DP) / 2.f)
262 * dpToPx); 329 * dpToPx);
263 padding = (int) Math.max(WIDE_DISPLAY_MIN_PADDING_DP * dpToPx, paddi ng); 330 padding = (int) Math.max(WIDE_DISPLAY_MIN_PADDING_DP * dpToPx, paddi ng);
264 } 331 }
265 return padding; 332 return padding;
266 } 333 }
334
335 private void setToolbarShadowVisibility() {
336 if (mToolbarPermanentlyHidden || mToolbar == null || mRecyclerView == nu ll) return;
337
338 boolean showShadow = mRecyclerView.computeVerticalScrollOffset() != 0
339 || mToolbar.isSearching() || mToolbar.getSelectionDelegate().isS electionEnabled();
340 mToolbarShadow.setVisibility(showShadow ? View.VISIBLE : View.GONE);
341 }
342
343 /**
344 * Unlike ListView or GridView, RecyclerView does not provide default empty
345 * view implementation. We need to check it ourselves.
346 */
347 private void updateEmptyViewVisibility() {
348 mEmptyView.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : V iew.GONE);
349 }
350
351 @VisibleForTesting
352 public View getToolbarShadowForTests() {
353 return mToolbarShadow;
354 }
267 } 355 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698