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