• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.bitmapfun.ui;
18 
19 import android.annotation.TargetApi;
20 import android.app.ActivityOptions;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Bundle;
24 import android.support.v4.app.Fragment;
25 import android.util.Log;
26 import android.util.TypedValue;
27 import android.view.LayoutInflater;
28 import android.view.Menu;
29 import android.view.MenuInflater;
30 import android.view.MenuItem;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.ViewGroup.LayoutParams;
34 import android.view.ViewTreeObserver;
35 import android.widget.AbsListView;
36 import android.widget.AdapterView;
37 import android.widget.BaseAdapter;
38 import android.widget.GridView;
39 import android.widget.ImageView;
40 import android.widget.Toast;
41 
42 import com.example.android.bitmapfun.BuildConfig;
43 import com.example.android.bitmapfun.R;
44 import com.example.android.bitmapfun.provider.Images;
45 import com.example.android.bitmapfun.util.ImageCache.ImageCacheParams;
46 import com.example.android.bitmapfun.util.ImageFetcher;
47 import com.example.android.bitmapfun.util.Utils;
48 
49 /**
50  * The main fragment that powers the ImageGridActivity screen. Fairly straight forward GridView
51  * implementation with the key addition being the ImageWorker class w/ImageCache to load children
52  * asynchronously, keeping the UI nice and smooth and caching thumbnails for quick retrieval. The
53  * cache is retained over configuration changes like orientation change so the images are populated
54  * quickly if, for example, the user rotates the device.
55  */
56 public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
57     private static final String TAG = "ImageGridFragment";
58     private static final String IMAGE_CACHE_DIR = "thumbs";
59 
60     private int mImageThumbSize;
61     private int mImageThumbSpacing;
62     private ImageAdapter mAdapter;
63     private ImageFetcher mImageFetcher;
64 
65     /**
66      * Empty constructor as per the Fragment documentation
67      */
ImageGridFragment()68     public ImageGridFragment() {}
69 
70     @Override
onCreate(Bundle savedInstanceState)71     public void onCreate(Bundle savedInstanceState) {
72         super.onCreate(savedInstanceState);
73         setHasOptionsMenu(true);
74 
75         mImageThumbSize = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_size);
76         mImageThumbSpacing = getResources().getDimensionPixelSize(R.dimen.image_thumbnail_spacing);
77 
78         mAdapter = new ImageAdapter(getActivity());
79 
80         ImageCacheParams cacheParams = new ImageCacheParams(getActivity(), IMAGE_CACHE_DIR);
81 
82         cacheParams.setMemCacheSizePercent(0.25f); // Set memory cache to 25% of app memory
83 
84         // The ImageFetcher takes care of loading images into our ImageView children asynchronously
85         mImageFetcher = new ImageFetcher(getActivity(), mImageThumbSize);
86         mImageFetcher.setLoadingImage(R.drawable.empty_photo);
87         mImageFetcher.addImageCache(getActivity().getSupportFragmentManager(), cacheParams);
88     }
89 
90     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)91     public View onCreateView(
92             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
93 
94         final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);
95         final GridView mGridView = (GridView) v.findViewById(R.id.gridView);
96         mGridView.setAdapter(mAdapter);
97         mGridView.setOnItemClickListener(this);
98         mGridView.setOnScrollListener(new AbsListView.OnScrollListener() {
99             @Override
100             public void onScrollStateChanged(AbsListView absListView, int scrollState) {
101                 // Pause fetcher to ensure smoother scrolling when flinging
102                 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
103                     // Before Honeycomb pause image loading on scroll to help with performance
104                     if (!Utils.hasHoneycomb()) {
105                         mImageFetcher.setPauseWork(true);
106                     }
107                 } else {
108                     mImageFetcher.setPauseWork(false);
109                 }
110             }
111 
112             @Override
113             public void onScroll(AbsListView absListView, int firstVisibleItem,
114                     int visibleItemCount, int totalItemCount) {
115             }
116         });
117 
118         // This listener is used to get the final width of the GridView and then calculate the
119         // number of columns and the width of each column. The width of each column is variable
120         // as the GridView has stretchMode=columnWidth. The column width is used to set the height
121         // of each view so we get nice square thumbnails.
122         mGridView.getViewTreeObserver().addOnGlobalLayoutListener(
123                 new ViewTreeObserver.OnGlobalLayoutListener() {
124                     @Override
125                     public void onGlobalLayout() {
126                         if (mAdapter.getNumColumns() == 0) {
127                             final int numColumns = (int) Math.floor(
128                                     mGridView.getWidth() / (mImageThumbSize + mImageThumbSpacing));
129                             if (numColumns > 0) {
130                                 final int columnWidth =
131                                         (mGridView.getWidth() / numColumns) - mImageThumbSpacing;
132                                 mAdapter.setNumColumns(numColumns);
133                                 mAdapter.setItemHeight(columnWidth);
134                                 if (BuildConfig.DEBUG) {
135                                     Log.d(TAG, "onCreateView - numColumns set to " + numColumns);
136                                 }
137                             }
138                         }
139                     }
140                 });
141 
142         return v;
143     }
144 
145     @Override
onResume()146     public void onResume() {
147         super.onResume();
148         mImageFetcher.setExitTasksEarly(false);
149         mAdapter.notifyDataSetChanged();
150     }
151 
152     @Override
onPause()153     public void onPause() {
154         super.onPause();
155         mImageFetcher.setPauseWork(false);
156         mImageFetcher.setExitTasksEarly(true);
157         mImageFetcher.flushCache();
158     }
159 
160     @Override
onDestroy()161     public void onDestroy() {
162         super.onDestroy();
163         mImageFetcher.closeCache();
164     }
165 
166     @TargetApi(16)
167     @Override
onItemClick(AdapterView<?> parent, View v, int position, long id)168     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
169         final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
170         i.putExtra(ImageDetailActivity.EXTRA_IMAGE, (int) id);
171         if (Utils.hasJellyBean()) {
172             // makeThumbnailScaleUpAnimation() looks kind of ugly here as the loading spinner may
173             // show plus the thumbnail image in GridView is cropped. so using
174             // makeScaleUpAnimation() instead.
175             ActivityOptions options =
176                     ActivityOptions.makeScaleUpAnimation(v, 0, 0, v.getWidth(), v.getHeight());
177             getActivity().startActivity(i, options.toBundle());
178         } else {
179             startActivity(i);
180         }
181     }
182 
183     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)184     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
185         inflater.inflate(R.menu.main_menu, menu);
186     }
187 
188     @Override
onOptionsItemSelected(MenuItem item)189     public boolean onOptionsItemSelected(MenuItem item) {
190         switch (item.getItemId()) {
191             case R.id.clear_cache:
192                 mImageFetcher.clearCache();
193                 Toast.makeText(getActivity(), R.string.clear_cache_complete_toast,
194                         Toast.LENGTH_SHORT).show();
195                 return true;
196         }
197         return super.onOptionsItemSelected(item);
198     }
199 
200     /**
201      * The main adapter that backs the GridView. This is fairly standard except the number of
202      * columns in the GridView is used to create a fake top row of empty views as we use a
203      * transparent ActionBar and don't want the real top row of images to start off covered by it.
204      */
205     private class ImageAdapter extends BaseAdapter {
206 
207         private final Context mContext;
208         private int mItemHeight = 0;
209         private int mNumColumns = 0;
210         private int mActionBarHeight = 0;
211         private GridView.LayoutParams mImageViewLayoutParams;
212 
ImageAdapter(Context context)213         public ImageAdapter(Context context) {
214             super();
215             mContext = context;
216             mImageViewLayoutParams = new GridView.LayoutParams(
217                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
218             // Calculate ActionBar height
219             TypedValue tv = new TypedValue();
220             if (context.getTheme().resolveAttribute(
221                     android.R.attr.actionBarSize, tv, true)) {
222                 mActionBarHeight = TypedValue.complexToDimensionPixelSize(
223                         tv.data, context.getResources().getDisplayMetrics());
224             }
225         }
226 
227         @Override
getCount()228         public int getCount() {
229             // Size + number of columns for top empty row
230             return Images.imageThumbUrls.length + mNumColumns;
231         }
232 
233         @Override
getItem(int position)234         public Object getItem(int position) {
235             return position < mNumColumns ?
236                     null : Images.imageThumbUrls[position - mNumColumns];
237         }
238 
239         @Override
getItemId(int position)240         public long getItemId(int position) {
241             return position < mNumColumns ? 0 : position - mNumColumns;
242         }
243 
244         @Override
getViewTypeCount()245         public int getViewTypeCount() {
246             // Two types of views, the normal ImageView and the top row of empty views
247             return 2;
248         }
249 
250         @Override
getItemViewType(int position)251         public int getItemViewType(int position) {
252             return (position < mNumColumns) ? 1 : 0;
253         }
254 
255         @Override
hasStableIds()256         public boolean hasStableIds() {
257             return true;
258         }
259 
260         @Override
getView(int position, View convertView, ViewGroup container)261         public View getView(int position, View convertView, ViewGroup container) {
262             // First check if this is the top row
263             if (position < mNumColumns) {
264                 if (convertView == null) {
265                     convertView = new View(mContext);
266                 }
267                 // Set empty view with height of ActionBar
268                 convertView.setLayoutParams(new AbsListView.LayoutParams(
269                         ViewGroup.LayoutParams.MATCH_PARENT, mActionBarHeight));
270                 return convertView;
271             }
272 
273             // Now handle the main ImageView thumbnails
274             ImageView imageView;
275             if (convertView == null) { // if it's not recycled, instantiate and initialize
276                 imageView = new RecyclingImageView(mContext);
277                 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
278                 imageView.setLayoutParams(mImageViewLayoutParams);
279             } else { // Otherwise re-use the converted view
280                 imageView = (ImageView) convertView;
281             }
282 
283             // Check the height matches our calculated column width
284             if (imageView.getLayoutParams().height != mItemHeight) {
285                 imageView.setLayoutParams(mImageViewLayoutParams);
286             }
287 
288             // Finally load the image asynchronously into the ImageView, this also takes care of
289             // setting a placeholder image while the background thread runs
290             mImageFetcher.loadImage(Images.imageThumbUrls[position - mNumColumns], imageView);
291             return imageView;
292         }
293 
294         /**
295          * Sets the item height. Useful for when we know the column width so the height can be set
296          * to match.
297          *
298          * @param height
299          */
setItemHeight(int height)300         public void setItemHeight(int height) {
301             if (height == mItemHeight) {
302                 return;
303             }
304             mItemHeight = height;
305             mImageViewLayoutParams =
306                     new GridView.LayoutParams(LayoutParams.MATCH_PARENT, mItemHeight);
307             mImageFetcher.setImageSize(height);
308             notifyDataSetChanged();
309         }
310 
setNumColumns(int numColumns)311         public void setNumColumns(int numColumns) {
312             mNumColumns = numColumns;
313         }
314 
getNumColumns()315         public int getNumColumns() {
316             return mNumColumns;
317         }
318     }
319 }
320