OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.chrome.browser.photo_picker; | |
6 | |
7 import android.content.Context; | |
8 import android.content.res.Resources; | |
9 import android.graphics.Bitmap; | |
10 import android.graphics.BitmapFactory; | |
11 import android.graphics.Color; | |
12 import android.graphics.PorterDuff; | |
13 import android.graphics.drawable.BitmapDrawable; | |
14 import android.graphics.drawable.Drawable; | |
15 import android.support.annotation.Nullable; | |
16 import android.support.v4.content.ContextCompat; | |
17 import android.util.AttributeSet; | |
18 import android.util.DisplayMetrics; | |
19 import android.view.View; | |
20 import android.view.ViewGroup; | |
21 import android.view.animation.Animation; | |
22 import android.view.animation.Transformation; | |
23 import android.widget.ImageView; | |
24 import android.widget.TextView; | |
25 | |
26 import org.chromium.base.ApiCompatibilityUtils; | |
27 import org.chromium.chrome.R; | |
28 import org.chromium.chrome.browser.widget.selection.SelectableItemView; | |
29 import org.chromium.chrome.browser.widget.selection.SelectionDelegate; | |
30 | |
31 import java.util.List; | |
32 | |
33 /** | |
34 * A container class for a view showing a photo in the photo picker. | |
35 */ | |
36 public class PickerBitmapView extends SelectableItemView<PickerBitmap> { | |
37 // Our context. | |
38 private Context mContext; | |
39 | |
40 // Our parent category. | |
41 private PickerCategoryView mCategoryView; | |
42 | |
43 // Our selection delegate. | |
44 private SelectionDelegate<PickerBitmap> mSelectionDelegate; | |
45 | |
46 // The request details (meta-data) for the bitmap shown. | |
47 private PickerBitmap mRequest; | |
48 | |
49 // The image view containing the bitmap. | |
50 private ImageView mIconView; | |
51 | |
52 // The little shader in the top left corner (provides backdrop for selection ring for | |
53 // unfavorable image backgrounds). | |
54 private View mScrim; | |
55 | |
56 // The view behind the image, representing the selection border. | |
57 private View mBorderView; | |
58 | |
59 // The control that signifies the image has been selected. | |
60 private ImageView mSelectedView; | |
61 | |
62 // The control that signifies the image has not been selected. | |
63 private ImageView mUnselectedView; | |
64 | |
65 // The camera/gallery special tile (with icon as drawable). | |
66 private TextView mSpecialTile; | |
67 | |
68 // Whether the image has been loaded already. | |
69 private boolean mImageLoaded; | |
70 | |
71 // The amount to use for the border. | |
72 private int mBorder; | |
73 | |
74 /** | |
75 * A resize animation class for the images (shrinks the image on selection). | |
76 */ | |
77 private static class ResizeWidthAnimation extends Animation { | |
78 // The view to animate size changes for. | |
79 private View mView; | |
80 | |
81 // The starting size of the view. | |
82 private int mStartingSize; | |
83 | |
84 // The target size we want to achieve. | |
85 private int mTargetSize; | |
86 | |
87 /** | |
88 * The ResizeWidthAnimation constructor. | |
89 * @param view The view to animate size changes for. | |
90 * @param size The target size we want to achieve. | |
91 */ | |
92 public ResizeWidthAnimation(View view, int size) { | |
93 mView = view; | |
94 mStartingSize = view.getWidth(); | |
95 mTargetSize = size; | |
96 } | |
97 | |
98 @Override | |
99 protected void applyTransformation(float interpolatedTime, Transformatio n transformation) { | |
100 int newSize = mStartingSize + (int) ((mTargetSize - mStartingSize) * interpolatedTime); | |
101 int padding = (Math.max(mStartingSize, mTargetSize) - newSize) / 2; | |
102 | |
103 mView.getLayoutParams().height = newSize; | |
104 mView.getLayoutParams().width = newSize; | |
105 // Create a border around the image. | |
106 if (mView instanceof ImageView) { | |
107 addPaddingToParent(mView, padding); | |
108 } | |
109 } | |
110 | |
111 @Override | |
112 public boolean willChangeBounds() { | |
113 return true; | |
114 } | |
115 } | |
116 | |
117 /** | |
118 * Constructor for inflating from XML. | |
119 */ | |
120 public PickerBitmapView(Context context, AttributeSet attrs) { | |
121 super(context, attrs); | |
122 mContext = context; | |
123 } | |
124 | |
125 @Override | |
126 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
127 super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
128 | |
129 if (mCategoryView == null) return; // Android Studio calls onMeasure to draw the widget. | |
Theresa
2017/03/28 20:40:28
Why does it matter if Android Studio calls onMeasu
Finnur
2017/03/31 14:26:50
Excellent. I worried it would not make sense witho
| |
130 int width = mCategoryView.getImageSize(); | |
131 int height = mCategoryView.getImageSize(); | |
132 setMeasuredDimension(width, height); | |
133 } | |
134 | |
135 @Override | |
136 protected void onFinishInflate() { | |
137 super.onFinishInflate(); | |
138 mIconView = (ImageView) findViewById(R.id.bitmap_view); | |
139 mScrim = findViewById(R.id.scrim); | |
140 mBorderView = findViewById(R.id.border); | |
141 mSelectedView = (ImageView) findViewById(R.id.selected); | |
142 mUnselectedView = (ImageView) findViewById(R.id.unselected); | |
143 mSpecialTile = (TextView) findViewById(R.id.special_tile); | |
144 } | |
145 | |
146 @Override | |
147 public void onClick() { | |
Theresa
2017/03/28 20:40:28
We had to introduce some special handling for book
Finnur
2017/03/31 14:26:50
Acknowledged.
Theresa
2017/03/31 18:05:55
I was thinking about this incorrectly -- this actu
Finnur
2017/04/03 17:30:29
Acknowledged.
| |
148 if (mRequest.type() == PickerBitmap.TileTypes.GALLERY) { | |
149 mCategoryView.showGallery(); | |
150 return; | |
151 } else if (mRequest.type() == PickerBitmap.TileTypes.CAMERA) { | |
152 mCategoryView.showCamera(); | |
153 return; | |
154 } | |
155 | |
156 mSelectionDelegate.toggleSelectionForItem(mRequest); | |
157 setChecked(!super.isChecked()); | |
158 } | |
159 | |
160 @Override | |
161 public void setChecked(boolean checked) { | |
162 if (mRequest.type() != PickerBitmap.TileTypes.PICTURE) { | |
163 return; | |
164 } | |
165 | |
166 super.setChecked(checked); | |
167 updateSelectionState(); | |
168 } | |
169 | |
170 @Override | |
171 public void onSelectionStateChange(List<PickerBitmap> selectedItems) { | |
172 boolean selected = selectedItems.contains(mRequest); | |
173 | |
174 if (mRequest.type() != PickerBitmap.TileTypes.PICTURE) { | |
175 if (selected) mSelectionDelegate.toggleSelectionForItem(mRequest); | |
176 updateSelectionState(); | |
177 return; | |
178 } | |
179 | |
180 boolean checked = super.isChecked(); | |
181 | |
182 if (!mCategoryView.isMultiSelect() && !selected && checked) { | |
183 super.toggle(); | |
184 } | |
185 | |
186 updateSelectionState(); | |
187 | |
188 if (!mImageLoaded || selected == checked) { | |
189 return; | |
190 } | |
191 | |
192 int size = selected && !checked ? mCategoryView.getImageSize() - 2 * mBo rder | |
193 : mCategoryView.getImageSize(); | |
194 if (size != mIconView.getWidth()) { | |
195 ResizeWidthAnimation animation = new ResizeWidthAnimation(mIconView, size); | |
196 animation.setDuration(100); | |
Theresa
2017/03/28 20:40:28
nit: define this as a constant somewhere
In the
Finnur
2017/03/31 14:26:50
Done.
| |
197 // TODO: Add MD interpolator | |
198 // animation.setInterpolator((mContext, R.interpolator.fast_out_line ar_in); | |
199 mIconView.startAnimation(animation); | |
200 } | |
201 } | |
202 | |
203 /** | |
204 * Pre-initializes the PickerBitmapView. | |
205 * @param categoryView The category view showing the images. | |
206 */ | |
207 public void preInitialize(PickerCategoryView categoryView) { | |
208 mCategoryView = categoryView; | |
209 mSelectionDelegate = mCategoryView.getSelectionDelegate(); | |
210 super.setSelectionDelegate(mSelectionDelegate); | |
211 | |
212 mSelectedView.setImageBitmap(mCategoryView.getSelectionBitmap(true)); | |
213 mUnselectedView.setImageBitmap(mCategoryView.getSelectionBitmap(false)); | |
214 | |
215 mBorder = (int) getResources().getDimension(R.dimen.file_picker_selected _padding); | |
216 } | |
217 | |
218 /** | |
219 * Completes the initialization of the PickerBitmapView. Must be called befo re the image can | |
220 * respond to click events. | |
221 * @param request The request represented by this PickerBitmapView. | |
222 * @param thumbnail The Bitmap to use for the thumbnail (or null). | |
223 * @param placeholder Whether the image given is a placeholder or the actual image. | |
224 */ | |
225 public void initialize(PickerBitmap request, @Nullable Bitmap thumbnail, boo lean placeholder) { | |
226 resetTile(); | |
227 | |
228 mRequest = request; | |
229 setItem(request); | |
230 setThumbnailBitmap(thumbnail); | |
231 mImageLoaded = !placeholder; | |
232 | |
233 updateSelectionState(); | |
234 | |
235 setOnClickListener(this); | |
236 } | |
237 | |
238 /** | |
239 * Initialization for the special tiles (camera/gallery icon). | |
240 */ | |
241 public void initializeSpecialTile() { | |
242 int size = mCategoryView.getImageSize(); | |
243 Bitmap tile = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); | |
244 tile.eraseColor(Color.argb(0, 0, 0, 0)); | |
245 | |
246 int iconBitmapId, labelStringId; | |
247 if (mRequest.type() == PickerBitmap.TileTypes.CAMERA) { | |
248 iconBitmapId = R.drawable.ic_photo_camera; | |
249 labelStringId = R.string.file_picker_camera; | |
250 } else { | |
251 iconBitmapId = R.drawable.ic_collections_black_24dp; | |
252 labelStringId = R.string.file_picker_browse; | |
253 } | |
254 | |
255 Resources resources = mContext.getResources(); | |
256 mSpecialTile.setText(labelStringId); | |
257 Bitmap icon = BitmapFactory.decodeResource(resources, iconBitmapId); | |
258 DisplayMetrics metrics = resources.getDisplayMetrics(); | |
259 float pixels = 48 * ((float) metrics.densityDpi / DisplayMetrics.DENSITY _DEFAULT); | |
Theresa
2017/03/28 20:40:28
Define a dimension in dimens.xml rather than progr
Finnur
2017/03/31 14:26:50
Done.
| |
260 BitmapDrawable drawable = new BitmapDrawable( | |
261 resources, Bitmap.createScaledBitmap(icon, (int) pixels, (int) p ixels, true)); | |
Theresa
2017/03/28 20:40:28
Android will automatically scale for you if the Im
Finnur
2017/03/31 14:26:50
It is not obvious to me how to interpret this comm
| |
262 ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( | |
263 mSpecialTile, null, drawable, null, null); | |
264 | |
265 initialize(mRequest, tile, false); | |
266 | |
267 mSpecialTile.setVisibility(View.VISIBLE); | |
268 } | |
269 | |
270 /** | |
271 * Sets a thumbnail bitmap for the current view and ensures the selection bo rder and scrim is | |
272 * showing, if the image has already been selected. | |
273 * @param thumbnail The Bitmap to use for the icon ImageView. | |
274 * @return true if no image was loaded before (e.g. not even a low-res image ). | |
Theresa
2017/03/28 20:40:28
nit:s/true/True
Finnur
2017/03/31 14:26:50
Done.
| |
275 */ | |
276 public boolean setThumbnailBitmap(Bitmap thumbnail) { | |
277 mIconView.setImageBitmap(thumbnail); | |
278 | |
279 // If the tile has been selected before the bitmap has loaded, make sure it shows up with | |
280 // a selection border and scrim on load. | |
281 if (super.isChecked()) { | |
282 mIconView.getLayoutParams().height = mCategoryView.getImageSize() - 2 * mBorder; | |
283 mIconView.getLayoutParams().width = mCategoryView.getImageSize() - 2 * mBorder; | |
Theresa
2017/03/28 20:40:28
nit: extract this calculation into a helper method
Finnur
2017/03/31 14:26:50
Done.
| |
284 addPaddingToParent(mIconView, mBorder); | |
285 mScrim.setVisibility(View.VISIBLE); | |
286 } | |
287 | |
288 boolean noImageWasLoaded = !mImageLoaded; | |
289 mImageLoaded = true; | |
290 updateSelectionState(); | |
291 | |
292 return noImageWasLoaded; | |
293 } | |
294 | |
295 /** | |
296 * Initiates fading in of the thumbnail. Note, this should not be called if a grainy version of | |
297 * the thumbnail was loaded from cache. Otherwise a flash will appear. | |
298 */ | |
299 public void fadeInThumbnail() { | |
300 mIconView.setAlpha(0.0f); | |
301 mIconView.animate().alpha(1.0f).setDuration(200).start(); | |
Theresa
2017/03/28 20:40:28
nit: define 200 as a constant somewhere.
Finnur
2017/03/31 14:26:50
Done.
| |
302 } | |
303 | |
304 /** | |
305 * Resets the view to its starting state, which is necessary when the view i s about to be | |
306 * re-used. | |
307 */ | |
308 private void resetTile() { | |
309 mUnselectedView.setVisibility(View.GONE); | |
310 mSelectedView.setVisibility(View.GONE); | |
311 mScrim.setVisibility(View.GONE); | |
312 mSpecialTile.setVisibility(View.GONE); | |
313 } | |
314 | |
315 /** | |
316 * Adds padding to the parent of the |view|. | |
317 * @param view The child view of the view to receive the padding. | |
318 * @param padding The amount of padding to use (in pixels). | |
319 */ | |
320 private static void addPaddingToParent(View view, int padding) { | |
321 ViewGroup layout = (ViewGroup) view.getParent(); | |
322 layout.setPadding(padding, padding, padding, padding); | |
323 layout.requestLayout(); | |
324 } | |
325 | |
326 /** | |
327 * Updates the selection controls for this view. | |
328 */ | |
329 private void updateSelectionState() { | |
330 boolean special = mRequest.type() != PickerBitmap.TileTypes.PICTURE; | |
331 boolean checked = super.isChecked(); | |
332 boolean anySelection = | |
333 mSelectionDelegate != null && mSelectionDelegate.isSelectionEnab led(); | |
334 boolean multiSelect = mCategoryView.isMultiSelect(); | |
335 int bgColorId, fgColorId; | |
336 if (!special) { | |
337 bgColorId = R.color.file_picker_tile_bg_color; | |
338 fgColorId = R.color.file_picker_special_tile_color; | |
339 } else if (!anySelection || !multiSelect) { | |
340 bgColorId = R.color.file_picker_special_tile_bg_color; | |
341 fgColorId = R.color.file_picker_special_tile_color; | |
342 } else { | |
343 bgColorId = R.color.file_picker_special_tile_disabled_bg_color; | |
344 fgColorId = R.color.file_picker_special_tile_disabled_color; | |
345 } | |
346 | |
347 mBorderView.setBackgroundColor(ContextCompat.getColor(mContext, bgColorI d)); | |
348 mSpecialTile.setTextColor(ContextCompat.getColor(mContext, fgColorId)); | |
349 Drawable[] drawables = mSpecialTile.getCompoundDrawables(); | |
350 // The textview only has a top compound drawable (2nd element). | |
351 if (drawables[1] != null) { | |
352 int color = ContextCompat.getColor(mContext, fgColorId); | |
353 drawables[1].setColorFilter(color, PorterDuff.Mode.SRC_IN); | |
354 } | |
355 | |
356 // The visibility of the unselected image is a little more complex becau se we don't want | |
357 // to show it when nothing is selected and also not on a blank canvas. | |
358 mSelectedView.setVisibility(!special && checked ? View.VISIBLE : View.GO NE); | |
359 mUnselectedView.setVisibility( | |
360 !special && !checked && anySelection ? View.VISIBLE : View.GONE) ; | |
361 mScrim.setVisibility(!special && !checked && anySelection ? View.VISIBLE : View.GONE); | |
362 } | |
363 } | |
OLD | NEW |