OSDN Git Service

Fix filmstrip jank on N4.
authorPaul Rohde <codelogic@google.com>
Tue, 3 Feb 2015 22:43:06 +0000 (14:43 -0800)
committerPaul Rohde <codelogic@google.com>
Wed, 4 Feb 2015 20:53:08 +0000 (12:53 -0800)
Bug: 19164291
Bug: 19220382
Bug: 19020507

Change-Id: Id1c2011b29b1cee206593fb395d9b4a4c89e71ab

15 files changed:
src/com/android/camera/CameraActivity.java
src/com/android/camera/data/CameraFilmstripDataAdapter.java
src/com/android/camera/data/FilmstripItem.java
src/com/android/camera/data/FilmstripItemBase.java
src/com/android/camera/data/FixedFirstProxyAdapter.java
src/com/android/camera/data/FixedLastProxyAdapter.java
src/com/android/camera/data/GlideFilmstripManager.java [new file with mode: 0644]
src/com/android/camera/data/PhotoItem.java
src/com/android/camera/data/PhotoItemFactory.java
src/com/android/camera/data/PlaceholderItem.java
src/com/android/camera/data/SessionItem.java
src/com/android/camera/data/VideoItem.java
src/com/android/camera/data/VideoItemFactory.java
src/com/android/camera/filmstrip/FilmstripDataAdapter.java
src/com/android/camera/widget/FilmstripView.java

index 35bef0a..c00a373 100644 (file)
@@ -87,6 +87,7 @@ import com.android.camera.data.FilmstripItemData;
 import com.android.camera.data.FilmstripItemType;
 import com.android.camera.data.FilmstripItemUtils;
 import com.android.camera.data.FixedLastProxyAdapter;
+import com.android.camera.data.GlideFilmstripManager;
 import com.android.camera.data.LocalFilmstripDataAdapter;
 import com.android.camera.data.LocalFilmstripDataAdapter.FilmstripItemListener;
 import com.android.camera.data.MediaDetails;
@@ -1389,15 +1390,14 @@ public class CameraActivity extends QuickActivity
             // Prefill glides bitmap pool to prevent excessive jank
             // when loading large images.
             glide.preFillBitmapPool(
-                new PreFillType.Builder(
-                      FilmstripItem.MAXIMUM_TEXTURE_SIZE)
+                new PreFillType.Builder(GlideFilmstripManager.MAXIMUM_TEXTURE_SIZE)
                   .setWeight(5),
                   // It's more important for jank and GC to have
                   // A larger weight of max texture size images than
                   // media store sized images.
                 new PreFillType.Builder(
-                      FilmstripItem.MEDIASTORE_THUMB_WIDTH,
-                      FilmstripItem.MEDIASTORE_THUMB_HEIGHT));
+                      GlideFilmstripManager.MEDIASTORE_THUMB_WIDTH,
+                      GlideFilmstripManager.MEDIASTORE_THUMB_HEIGHT));
         }
 
         mOnCreateTime = System.currentTimeMillis();
@@ -1516,9 +1516,10 @@ public class CameraActivity extends QuickActivity
         mPanoramaViewHelper.onCreate();
 
         ContentResolver appContentResolver = mAppContext.getContentResolver();
-        mPhotoItemFactory = new PhotoItemFactory(mAppContext, appContentResolver,
+        GlideFilmstripManager glideManager = new GlideFilmstripManager(mAppContext);
+        mPhotoItemFactory = new PhotoItemFactory(mAppContext, glideManager, appContentResolver,
               new PhotoDataFactory());
-        mVideoItemFactory = new VideoItemFactory(mAppContext, appContentResolver,
+        mVideoItemFactory = new VideoItemFactory(mAppContext, glideManager, appContentResolver,
               new VideoDataFactory());
         mDataAdapter = new CameraFilmstripDataAdapter(mAppContext,
               mPhotoItemFactory, mVideoItemFactory);
index 509d24e..b837b7b 100644 (file)
@@ -136,16 +136,11 @@ public class CameraFilmstripDataAdapter implements LocalFilmstripDataAdapter {
             return null;
         }
 
-        return mFilmstripItems.get(index).getView(Optional.fromNullable(recycled), mSuggestedWidth,
-              mSuggestedHeight, this, /* inProgress */ false, videoClickedCallback);
-    }
+        FilmstripItem item = mFilmstripItems.get(index);
+        item.setSuggestedSize(mSuggestedWidth, mSuggestedHeight);
 
-    @Override
-    public void resizeView(int index, View view, int w, int h) {
-        if (index >= mFilmstripItems.size() || index < 0) {
-            return;
-        }
-        mFilmstripItems.get(index).loadFullImage(mSuggestedWidth, mSuggestedHeight, view);
+        return item.getView(Optional.fromNullable(recycled), this, /* inProgress */ false,
+              videoClickedCallback);
     }
 
     @Override
index d08c68e..f24727e 100644 (file)
@@ -22,26 +22,16 @@ import android.view.View;
 
 import com.android.camera.debug.Log;
 import com.android.camera.util.Size;
-import com.android.camera2.R;
 import com.google.common.base.Optional;
 
+import javax.annotation.Nonnull;
+
 /**
  * An abstract interface that represents the Local filmstrip items.
  */
 public interface FilmstripItem {
     static final Log.Tag TAG = new Log.Tag("FilmstripItem");
 
-    public static final int MEDIASTORE_THUMB_WIDTH = 512;
-    public static final int MEDIASTORE_THUMB_HEIGHT = 384;
-
-    // GL max texture size: keep bitmaps below this value.
-    public static final int MAXIMUM_TEXTURE_SIZE = 2048;
-    public static final int MAXIMUM_SMOOTH_TEXTURE_SIZE = 1024;
-
-    /** Default placeholder to display while images load */
-    static final int DEFAULT_PLACEHOLDER_RESOURCE = R.color.photo_placeholder;
-
-
     /**
      * An action callback to be used for actions on the filmstrip items.
      */
@@ -84,27 +74,45 @@ public interface FilmstripItem {
      * hierarchy. {@code FilmStripView} should always call this function after its
      * corresponding view is removed from the view hierarchy.
      */
-    public void recycle(View view);
+    public void recycle(@Nonnull View view);
 
     /**
      * Create or recycle an existing view (if provided) to render this item.
      *
-     * @param viewWidthPx Width in pixels of the suggested zoomed out view/image size.
-     * @param viewHeightPx Height in pixels of the suggested zoomed out view/image size.
      * @param adapter Data adapter for this data item.
      */
-    public View getView(Optional<View> view, int viewWidthPx, int viewHeightPx,
+    public View getView(Optional<View> view,
           LocalFilmstripDataAdapter adapter, boolean isInProgress,
           VideoClickedCallback videoClickedCallback);
 
     /**
-     * Request resize of View created by getView().
+     * Configure the suggested width and height in pixels for this view to render at.
+     *
+     * @param widthPx Suggested width in pixels.
+     * @param heightPx Suggested height in pixels.
+     */
+    public void setSuggestedSize(int widthPx, int heightPx);
+
+    /**
+     * Request to load a tiny preview image into the view as fast as possible.
+     *
+     * @param view View created by getView();
+     */
+    public void renderTiny(@Nonnull View view);
+
+    /**
+     * Request to load screen sized version of the image into the view.
+     *
+     * @param view View created by getView();
+     */
+    public void renderThumbnail(@Nonnull View view);
+
+    /**
+     * Request to load the highest possible resolution image supported.
      *
-     * @param thumbWidth Width in pixels of the suggested zoomed out view/image size.
-     * @param thumbHeight Height in pixels of the suggested zoomed out view/image size.
      * @param view View created by getView();
      */
-    public void loadFullImage(int thumbWidth, int thumbHeight, View view);
+    public void renderFullRes(@Nonnull View view);
 
     /**
      * Removes the data from the storage if possible.
index 99c6473..81ab9ca 100644 (file)
 package com.android.camera.data;
 
 import android.content.Context;
-import android.graphics.Bitmap;
-import android.net.Uri;
 import android.view.View;
 
 import com.android.camera.Storage;
 import com.android.camera.debug.Log;
 import com.android.camera.util.Size;
-import com.bumptech.glide.BitmapRequestBuilder;
 import com.bumptech.glide.Glide;
 import com.bumptech.glide.load.Key;
 import com.bumptech.glide.signature.MediaStoreSignature;
@@ -33,6 +30,8 @@ import com.google.common.base.Optional;
 import java.io.File;
 import java.text.DateFormat;
 
+import javax.annotation.Nonnull;
+
 /**
  * A base class for all the local media files. The bitmap is loaded in
  * background thread. Subclasses should implement their own background loading
@@ -44,16 +43,26 @@ public abstract class FilmstripItemBase<T extends FilmstripItemData> implements
     public static final int QUERY_ALL_MEDIA_ID = -1;
 
     protected final Context mContext;
+    protected final GlideFilmstripManager mGlideManager;
     protected final T mData;
     protected final Metadata mMetaData;
     protected final FilmstripItemAttributes mAttributes;
     protected final DateFormat mDateFormatter = DateFormat.getDateTimeInstance();
 
-    public FilmstripItemBase(Context context, T data, FilmstripItemAttributes attributes) {
+    protected int mSuggestedWidthPx;
+    protected int mSuggestedHeightPx;
+
+    public FilmstripItemBase(Context context, GlideFilmstripManager glideManager, T data,
+          FilmstripItemAttributes attributes) {
         mContext = context;
+        mGlideManager = glideManager;
         mData = data;
         mAttributes = attributes;
+
         mMetaData = new Metadata();
+
+        mSuggestedWidthPx = GlideFilmstripManager.TINY_THUMBNAIL_SIZE;
+        mSuggestedHeightPx = GlideFilmstripManager.TINY_THUMBNAIL_SIZE;
     }
 
     @Override
@@ -70,13 +79,19 @@ public abstract class FilmstripItemBase<T extends FilmstripItemData> implements
     }
 
     @Override
-    public void loadFullImage(int thumbWidth, int thumbHeight, View view) {
-        // Default is do nothing.
-        // Can be implemented by sub-classes.
+    public void setSuggestedSize(int widthPx, int heightPx) {
+        if (widthPx > 0 && heightPx > 0) {
+            mSuggestedWidthPx = widthPx;
+            mSuggestedHeightPx = heightPx;
+        } else {
+            Log.w(TAG, "Suggested size was set to a zero area value!");
+        }
     }
 
     @Override
-    public void recycle(View view) { }
+    public void recycle(@Nonnull View view) {
+        Glide.clear(view);
+    }
 
     @Override
     public Optional<MediaDetails> getMediaDetails() {
@@ -119,81 +134,12 @@ public abstract class FilmstripItemBase<T extends FilmstripItemData> implements
         return mData.getOrientation();
     }
 
-    // TODO: Move the glide classes to a specific rendering class.
-    protected BitmapRequestBuilder<Uri, Bitmap> glideFullResBitmap(Uri uri,
-          int width, int height) {
-        // compute a ratio such that viewWidth and viewHeight are less than
-        // MAXIMUM_SMOOTH_TEXTURE_SIZE but maintain their aspect ratio.
-        float downscaleRatio = downscaleRatioToFit(width, height,
-              MAXIMUM_TEXTURE_SIZE);
-
-        return Glide.with(mContext)
-              .loadFromMediaStore(uri)
-              .asBitmap()
-                  .atMost()
-                  .fitCenter()
-              .signature(getGlideKey())
-              .override(
-                    Math.round(width * downscaleRatio),
-                    Math.round(height * downscaleRatio));
-    }
-
-    protected BitmapRequestBuilder<Uri, Bitmap> glideFilmstripThumb(Uri uri,
-          int viewWidth, int viewHeight) {
-        // compute a ratio such that viewWidth and viewHeight are less than
-        // MAXIMUM_SMOOTH_TEXTURE_SIZE but maintain their aspect ratio.
-        float downscaleRatio = downscaleRatioToFit(viewWidth, viewHeight,
-              MAXIMUM_SMOOTH_TEXTURE_SIZE);
-
-        return Glide.with(mContext)
-              .loadFromMediaStore(uri)
-              .asBitmap()
-                  .atMost()
-                  .fitCenter()
-              .signature(getGlideKey())
-              .override(
-                    Math.round(viewWidth * downscaleRatio),
-                    Math.round(viewHeight * downscaleRatio));
-    }
-
-    protected BitmapRequestBuilder<Uri, Bitmap>glideMediaStoreThumb(Uri uri) {
-        return Glide.with(mContext)
-              .loadFromMediaStore(uri)
-              .asBitmap()
-                  .atMost()
-                  .fitCenter()
-              .signature(getGlideKey())
-              // This attempts to ensure we load the cached media store version.
-              .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT);
-    }
-
-    protected BitmapRequestBuilder<Uri, Bitmap> glideTinyThumb(Uri uri) {
-        return Glide.with(mContext)
-              .loadFromMediaStore(uri)
-              .asBitmap()
-                  .atMost()
-                  .fitCenter()
-              .signature(getGlideKey())
-              .override(256, 265);
-    }
-
-    protected Key getGlideKey() {
+    protected final Key generateSignature(FilmstripItemData data) {
         // Per Glide docs, make default mime type be the empty String
-        String mimeType = (mData.getMimeType() == null) ? "" : mData.getMimeType();
-        long modTimeSeconds = (mData.getLastModifiedDate() == null) ? 0 :
-            mData.getLastModifiedDate().getTime() / 1000;
-        return new MediaStoreSignature(mimeType, modTimeSeconds, mData.getOrientation());
-    }
-
-    private float downscaleRatioToFit(int width, int height, int fitWithinSize) {
-        // Find the longest dimension
-        int longest = Math.max(width, height);
-
-        if (longest > fitWithinSize) {
-            return (float)fitWithinSize / (float)longest;
-        }
-
-        return 1.0f;
+        String mimeType = (data.getMimeType() == null) ? "" : data.getMimeType();
+        long modTimeSeconds = (data.getLastModifiedDate() == null) ? 0 :
+              data.getLastModifiedDate().getTime() / 1000;
+        return new MediaStoreSignature(mimeType, modTimeSeconds, data.getOrientation());
     }
 
     private void deleteIfEmptyCameraSubDir(File directory) {
index 49951c7..6ac2165 100644 (file)
@@ -112,8 +112,9 @@ public class FixedFirstProxyAdapter extends FilmstripDataAdapterProxy
     @Override
     public View getView(View recycled, int index, VideoClickedCallback videoClickedCallback) {
         if (index == 0) {
-            return mFirstData.getView(Optional.fromNullable(recycled), mSuggestedWidth,
-                  mSuggestedHeight, null, false, videoClickedCallback);
+            mFirstData.setSuggestedSize(mSuggestedWidth, mSuggestedHeight);
+            return mFirstData.getView(Optional.fromNullable(recycled), null, false,
+                  videoClickedCallback);
         }
         return mAdapter.getView(recycled, index - 1, videoClickedCallback);
     }
@@ -127,11 +128,6 @@ public class FixedFirstProxyAdapter extends FilmstripDataAdapterProxy
     }
 
     @Override
-    public void resizeView(int index, View view, int w, int h) {
-        // Do nothing.
-    }
-
-    @Override
     public FilmstripItem getFilmstripItemAt(int index) {
         if (index == 0) {
             return mFirstData;
index 157c93a..935b0c0 100644 (file)
@@ -117,8 +117,9 @@ public class FixedLastProxyAdapter extends FilmstripDataAdapterProxy {
         if (index < totalNumber) {
             return mAdapter.getView(recycled, index, videoClickedCallback);
         } else if (index == totalNumber) {
-            return mLastData.getView(Optional.fromNullable(recycled), mSuggestedWidth,
-                  mSuggestedHeight, null, false, videoClickedCallback);
+            mLastData.setSuggestedSize(mSuggestedWidth, mSuggestedHeight);
+            return mLastData.getView(Optional.fromNullable(recycled), null, false,
+                  videoClickedCallback);
         }
         return null;
     }
@@ -136,11 +137,6 @@ public class FixedLastProxyAdapter extends FilmstripDataAdapterProxy {
    }
 
     @Override
-    public void resizeView(int index, View view, int w, int h) {
-        // Do nothing.
-    }
-
-    @Override
     public FilmstripItem getFilmstripItemAt(int index) {
         int totalNumber = mAdapter.getTotalNumber();
 
diff --git a/src/com/android/camera/data/GlideFilmstripManager.java b/src/com/android/camera/data/GlideFilmstripManager.java
new file mode 100644 (file)
index 0000000..8238332
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.data;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import com.android.camera2.R;
+import com.bumptech.glide.DrawableRequestBuilder;
+import com.bumptech.glide.GenericRequestBuilder;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.load.Key;
+import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
+import com.bumptech.glide.load.resource.gif.GifResourceEncoder;
+import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceEncoder;
+import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder;
+
+/**
+ * Manage common glide image requests for the camera filmstrip.
+ */
+public final class GlideFilmstripManager {
+    /** Default placeholder to display while images load */
+    public static final int DEFAULT_PLACEHOLDER_RESOURCE = R.color.photo_placeholder;
+
+    // GL max texture size: keep bitmaps below this value.
+    public static final int MAXIMUM_TEXTURE_SIZE = 2048;
+    public static final int MAXIMUM_SMOOTH_PIXELS = 1024 * 1024;
+
+    public static final int MEDIASTORE_THUMB_WIDTH = 512;
+    public static final int MEDIASTORE_THUMB_HEIGHT = 384;
+
+    public static final int TINY_THUMBNAIL_SIZE = 256;
+
+    private static final int JPEG_COMPRESS_QUALITY = 90;
+
+    private final GenericRequestBuilder<Uri, ?, ?, GlideDrawable> mTinyImageBuilder;
+    private final DrawableRequestBuilder<Uri> mLargeImageBuilder;
+
+    public GlideFilmstripManager(Context context) {
+        Glide glide = Glide.get(context);
+        BitmapEncoder bitmapEncoder = new BitmapEncoder(Bitmap.CompressFormat.JPEG,
+              JPEG_COMPRESS_QUALITY);
+        GifBitmapWrapperResourceEncoder drawableEncoder = new GifBitmapWrapperResourceEncoder(
+              bitmapEncoder,
+              new GifResourceEncoder(glide.getBitmapPool()));
+        RequestManager request = Glide.with(context);
+
+        mTinyImageBuilder = request
+              .fromMediaStore()
+              .asBitmap() // This prevents gifs from animating at tiny sizes.
+              .transcode(new BitmapToGlideDrawableTranscoder(context), GlideDrawable.class)
+              .fitCenter()
+              .dontAnimate();
+
+        mLargeImageBuilder = request
+              .fromMediaStore()
+              .encoder(drawableEncoder)
+              .fitCenter()
+              .dontAnimate();
+    }
+
+    /**
+     * Create a full size drawable request for a given width and height that is
+     * as large as we can reasonably load into a view without causing massive
+     * jank problems.
+     */
+    public final DrawableRequestBuilder<Uri> loadFull(Uri uri, Key key, int width,
+          int height) {
+        // compute a ratio such that viewWidth and viewHeight are less than
+        // MAXIMUM_SMOOTH_TEXTURE_SIZE but maintain their aspect ratio.
+        float downscaleRatio = downscaleRatioToFit(width, height,
+              (double) MAXIMUM_TEXTURE_SIZE * MAXIMUM_TEXTURE_SIZE);
+
+        return mLargeImageBuilder
+              .clone()
+              .load(uri)
+              .signature(key)
+              .override(
+                    Math.round(width * downscaleRatio),
+                    Math.round(height * downscaleRatio));
+    }
+
+    /**
+     * Create a full size drawable request for a given width and height that is
+     * smaller than loadFull, but is intended be large enough to fill the screen
+     * pixels.
+     */
+    public DrawableRequestBuilder<Uri> loadScreen(Uri uri, Key key, int width,
+          int height) {
+        // compute a ratio such that viewWidth and viewHeight are less than
+        // MAXIMUM_SMOOTH_TEXTURE_SIZE but maintain their aspect ratio.
+        float downscaleRatio = downscaleRatioToFit(width, height, (double) MAXIMUM_SMOOTH_PIXELS);
+
+        return mLargeImageBuilder
+              .clone()
+              .load(uri)
+              .signature(key)
+              .override(
+                    Math.round(width * downscaleRatio),
+                    Math.round(height * downscaleRatio));
+    }
+
+    /**
+     * Create a small thumbnail sized image that has the same bounds as the
+     * media store thumbnail images.
+     *
+     * If the Uri points at an animated gif, the gif will not play.
+     */
+    public GenericRequestBuilder<Uri, ?, ?, GlideDrawable> loadMediaStoreThumb(Uri uri, Key key) {
+        return mTinyImageBuilder
+              .clone()
+              .load(uri)
+              .signature(key)
+              .placeholder(DEFAULT_PLACEHOLDER_RESOURCE)
+              // This attempts to ensure we load the cached media store version.
+              .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT);
+    }
+
+    /**
+     * Create very tiny thumbnail request that should complete as fast
+     * as possible.
+     *
+     * If the Uri points at an animated gif, the gif will not play.
+     */
+    public GenericRequestBuilder<Uri, ?, ?, GlideDrawable> loadTinyThumb(Uri uri, Key key) {
+        return mTinyImageBuilder
+              .clone()
+              .load(uri)
+              .signature(key)
+              .placeholder(DEFAULT_PLACEHOLDER_RESOURCE)
+              .override(TINY_THUMBNAIL_SIZE, TINY_THUMBNAIL_SIZE);
+    }
+
+    private float downscaleRatioToFit(int width, int height, double area) {
+        // Compute a ratio that will keep the area of the image within the fit size parameter.
+
+        float ratio = (float) Math.sqrt(area / (height * width));
+        return Math.min(ratio, 1.0f);
+    }
+}
index 926feab..1c31440 100644 (file)
@@ -33,13 +33,17 @@ import com.android.camera.debug.Log;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.Size;
 import com.android.camera2.R;
-import com.bumptech.glide.BitmapRequestBuilder;
+import com.bumptech.glide.DrawableRequestBuilder;
+import com.bumptech.glide.GenericRequestBuilder;
 import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.drawable.GlideDrawable;
 import com.google.common.base.Optional;
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 
+import javax.annotation.Nonnull;
+
 /**
  * Backing data for a single photo displayed in the filmstrip.
  */
@@ -63,8 +67,9 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
 
     private Bitmap mSessionPlaceholderBitmap;
 
-    public PhotoItem(Context context, FilmstripItemData data, PhotoItemFactory photoItemFactory) {
-        super(context, data, PHOTO_ITEM_ATTRIBUTES);
+    public PhotoItem(Context context, GlideFilmstripManager manager, FilmstripItemData data,
+          PhotoItemFactory photoItemFactory) {
+        super(context, manager, data, PHOTO_ITEM_ATTRIBUTES);
         mPhotoItemFactory = photoItemFactory;
     }
 
@@ -110,9 +115,8 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
     }
 
     @Override
-    public View getView(Optional<View> optionalView, int viewWidthPx, int viewHeightPx,
-          LocalFilmstripDataAdapter adapter, boolean isInProgress,
-          VideoClickedCallback videoClickedCallback) {
+    public View getView(Optional<View> optionalView, LocalFilmstripDataAdapter adapter,
+          boolean isInProgress, VideoClickedCallback videoClickedCallback) {
         ImageView imageView;
 
         if (optionalView.isPresent()) {
@@ -129,12 +133,7 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
     }
 
     protected void fillImageView(final ImageView imageView) {
-        Uri uri = mData.getUri();
-
-        glideTinyThumb(uri)
-            .placeholder(DEFAULT_PLACEHOLDER_RESOURCE)
-            .dontAnimate()
-            .into(imageView);
+        renderTinySize(mData.getUri()).into(imageView);
 
         // TODO consider having metadata have a "get description" string
         // or some other way of selecting rendering details based on metadata.
@@ -155,7 +154,7 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
     }
 
     @Override
-    public void recycle(View view) {
+    public void recycle(@Nonnull View view) {
         Glide.clear(view);
         mSessionPlaceholderBitmap = null;
     }
@@ -166,25 +165,59 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
     }
 
     @Override
-    public void loadFullImage(int thumbWidth, int thumbHeight, View v) {
-        Uri uri = mData.getUri();
-        Size size = mData.getDimensions();
+    public void renderTiny(@Nonnull View view) {
+        if (view instanceof ImageView) {
+            renderTinySize(mData.getUri()).into((ImageView) view);
+        } else {
+            Log.w(TAG, "renderTiny was called with an object that is not an ImageView!");
+        }
+    }
 
-        BitmapRequestBuilder<Uri, Bitmap> builder =
-                glideFullResBitmap(uri, size.getWidth(), size.getHeight());
+    @Override
+    public void renderThumbnail(@Nonnull View view) {
+        if (view instanceof ImageView) {
+            renderScreenSize(mData.getUri()).into((ImageView) view);
+        } else {
+            Log.w(TAG, "renderThumbnail was called with an object that is not an ImageView!");
+        }
+    }
 
-        if (mSessionPlaceholderBitmap != null) {
-            builder.placeholder(new BitmapDrawable(mContext.getResources(),
-                    mSessionPlaceholderBitmap));
+    @Override
+    public void renderFullRes(@Nonnull View view) {
+        if (view instanceof ImageView) {
+            renderFullSize(mData.getUri()).into((ImageView) view);
         } else {
-            builder
-                    .thumbnail(glideTinyThumb(uri))
-                    .placeholder(DEFAULT_PLACEHOLDER_RESOURCE);
+            Log.w(TAG, "renderFullRes was called with an object that is not an ImageView!");
         }
+    }
+
+    private GenericRequestBuilder<Uri, ?, ?, GlideDrawable> renderTinySize(Uri uri) {
+        return mGlideManager.loadTinyThumb(uri, generateSignature(mData));
+    }
+
+    private DrawableRequestBuilder<Uri> renderScreenSize(Uri uri) {
+        DrawableRequestBuilder<Uri> request =
+              mGlideManager.loadScreen(uri, generateSignature(mData),
+                    mSuggestedWidthPx, mSuggestedHeightPx);
 
-        builder
-              .dontAnimate()
-              .into((ImageView) v);
+        // If we have a non-null placeholder, use that and do NOT ever render a
+        // tiny thumbnail to prevent un-intended "flash of low resolution image"
+        if (mSessionPlaceholderBitmap != null) {
+            return request.placeholder(new BitmapDrawable(mContext.getResources(),
+                  mSessionPlaceholderBitmap));
+        }
+
+        // If we do not have a placeholder bitmap, render a thumbnail with
+        // the default placeholder resource like normal.
+        return request
+              .thumbnail(renderTinySize(uri));
+    }
+
+    private DrawableRequestBuilder<Uri> renderFullSize(Uri uri) {
+        Size size = mData.getDimensions();
+        return mGlideManager.loadFull(uri, generateSignature(mData), size.getWidth(),
+              size.getHeight())
+              .thumbnail(renderScreenSize(uri));
     }
 
     @Override
index a955f84..0007607 100644 (file)
@@ -30,12 +30,14 @@ public class PhotoItemFactory implements CursorToFilmstripItemFactory<PhotoItem>
     private static final Log.Tag TAG = new Log.Tag("PhotoItemFact");
 
     private final Context mContext;
+    private final GlideFilmstripManager mGlideManager;
     private final ContentResolver mContentResolver;
     private final PhotoDataFactory mPhotoDataFactory;
 
-    public PhotoItemFactory(Context context, ContentResolver contentResolver,
-          PhotoDataFactory photoDataFactory) {
+    public PhotoItemFactory(Context context, GlideFilmstripManager glideManager,
+          ContentResolver contentResolver, PhotoDataFactory photoDataFactory) {
         mContext = context;
+        mGlideManager = glideManager;
         mContentResolver = contentResolver;
         mPhotoDataFactory = photoDataFactory;
     }
@@ -44,7 +46,7 @@ public class PhotoItemFactory implements CursorToFilmstripItemFactory<PhotoItem>
     public PhotoItem get(Cursor c) {
         FilmstripItemData data = mPhotoDataFactory.fromCursor(c);
         if (data != null) {
-            return new PhotoItem(mContext, data, this);
+            return new PhotoItem(mContext, mGlideManager, data, this);
         } else {
             Log.w(TAG, "skipping item with null data, returning null for item");
             return null;
index 8aff527..5565255 100644 (file)
@@ -28,6 +28,8 @@ import com.google.common.base.Optional;
 import java.util.Date;
 import java.util.UUID;
 
+import javax.annotation.Nonnull;
+
 /**
  * A LocalData that does nothing but only shows a view.
  */
@@ -102,19 +104,26 @@ public class PlaceholderItem implements FilmstripItem {
     }
 
     @Override
-    public View getView(Optional<View> optionalView, int viewWidthPx, int viewHeightPx,
+    public View getView(Optional<View> optionalView,
           LocalFilmstripDataAdapter adapter, boolean isInProgress,
           VideoClickedCallback videoClickedCallback) {
         return mView;
     }
 
     @Override
-    public void loadFullImage(int w, int h, View view) {
-        // do nothing.
-    }
+    public void setSuggestedSize(int widthPx, int heightPx) { }
+
+    @Override
+    public void renderTiny(@Nonnull View view) { }
+
+    @Override
+    public void renderThumbnail(@Nonnull View view) { }
+
+    @Override
+    public void renderFullRes(@Nonnull View view) { }
 
     @Override
-    public void recycle(View view) {
+    public void recycle(@Nonnull View view) {
         // Do nothing.
     }
 
index 4401dc7..95fe1e6 100644 (file)
@@ -31,6 +31,8 @@ import com.google.common.base.Optional;
 
 import java.util.Date;
 
+import javax.annotation.Nonnull;
+
 /**
  * This is used to represent a local data item that is in progress and not
  * yet in the media store.
@@ -64,9 +66,8 @@ public class SessionItem implements FilmstripItem {
     }
 
     @Override
-    public View getView(Optional<View> optionalView, int viewWidthPx, int viewHeightPx,
-          LocalFilmstripDataAdapter adapter, boolean isInProgress,
-          VideoClickedCallback videoClickedCallback) {
+    public View getView(Optional<View> optionalView, LocalFilmstripDataAdapter adapter,
+          boolean isInProgress, VideoClickedCallback videoClickedCallback) {
         ImageView imageView;
 
         if (optionalView.isPresent()) {
@@ -80,7 +81,7 @@ public class SessionItem implements FilmstripItem {
         if (placeholder != null) {
             imageView.setImageBitmap(placeholder);
         } else {
-            imageView.setImageResource(DEFAULT_PLACEHOLDER_RESOURCE);
+            imageView.setImageResource(GlideFilmstripManager.DEFAULT_PLACEHOLDER_RESOURCE);
         }
         imageView.setContentDescription(mContext.getResources().getString(
                 R.string.media_processing_content_description));
@@ -93,8 +94,16 @@ public class SessionItem implements FilmstripItem {
     }
 
     @Override
-    public void loadFullImage(int width, int height, View view) {
-    }
+    public void setSuggestedSize(int widthPx, int heightPx) { }
+
+    @Override
+    public void renderTiny(@Nonnull View view) { }
+
+    @Override
+    public void renderThumbnail(@Nonnull View view) { }
+
+    @Override
+    public void renderFullRes(@Nonnull View view) { }
 
     @Override
     public boolean delete() {
@@ -138,7 +147,7 @@ public class SessionItem implements FilmstripItem {
     }
 
     @Override
-    public void recycle(View view) {
+    public void recycle(@Nonnull View view) {
         Glide.clear(view);
     }
 
index 1e465f0..f12b66d 100644 (file)
@@ -19,7 +19,6 @@ package com.android.camera.data;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.net.Uri;
 import android.provider.MediaStore;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -32,6 +31,8 @@ import com.android.camera2.R;
 import com.bumptech.glide.Glide;
 import com.google.common.base.Optional;
 
+import javax.annotation.Nonnull;
+
 /**
  * Backing data for a single video displayed in the filmstrip.
  */
@@ -63,8 +64,9 @@ public class VideoItem extends FilmstripItemBase<VideoItemData> {
 
     private Size mCachedSize;
 
-    public VideoItem(Context context, VideoItemData data, VideoItemFactory videoItemFactory) {
-        super(context, data, VIDEO_ITEM_ATTRIBUTES);
+    public VideoItem(Context context, GlideFilmstripManager manager, VideoItemData data,
+          VideoItemFactory videoItemFactory) {
+        super(context, manager, data, VIDEO_ITEM_ATTRIBUTES);
         mVideoItemFactory = videoItemFactory;
     }
 
@@ -140,15 +142,16 @@ public class VideoItem extends FilmstripItemBase<VideoItemData> {
     }
 
     @Override
-    public View getView(Optional<View> optionalView, int viewWidthPx, int viewHeightPx,
+    public View getView(Optional<View> optionalView,
           LocalFilmstripDataAdapter adapter, boolean isInProgress,
-          VideoClickedCallback videoClickedCallback) {
+          final VideoClickedCallback videoClickedCallback) {
+
         View view;
         VideoViewHolder viewHolder;
 
         if (optionalView.isPresent()) {
             view = optionalView.get();
-            viewHolder = (VideoViewHolder) view.getTag(R.id.mediadata_tag_target);
+            viewHolder = getViewHolder(view);
         } else {
             view = LayoutInflater.from(mContext).inflate(R.layout.filmstrip_video, null);
             view.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
@@ -159,46 +162,50 @@ public class VideoItem extends FilmstripItemBase<VideoItemData> {
             view.setTag(R.id.mediadata_tag_target, viewHolder);
         }
 
-        fillVideoView(view, viewHolder, viewWidthPx, viewHeightPx, videoClickedCallback);
-
-        return view;
-    }
+        if (viewHolder != null) {
+            // ImageView for the play icon.
+            viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    videoClickedCallback.playVideo(mData.getUri(), mData.getTitle());
+                }
+            });
 
-    private void fillVideoView(View view, VideoViewHolder viewHolder, final int viewWidthPx,
-          final int viewHeightPx, final VideoClickedCallback videoClickedCallback) {
+            view.setContentDescription(mContext.getResources().getString(
+                  R.string.video_date_content_description,
+                  mDateFormatter.format(mData.getLastModifiedDate())));
 
-        //TODO: Figure out why these can be <= 0.
-        if (viewWidthPx <= 0 || viewHeightPx <=0) {
-            return;
+            renderTiny(viewHolder);
+        } else {
+            Log.w(TAG, "getView called with a view that is not compatible with VideoItem.");
         }
 
-        Uri uri = mData.getUri();
-
-        glideFilmstripThumb(uri, viewWidthPx, viewHeightPx)
-              .thumbnail(glideMediaStoreThumb(uri))
-              .placeholder(DEFAULT_PLACEHOLDER_RESOURCE)
-              .dontAnimate()
-              .into(viewHolder.mVideoView);
+        return view;
+    }
 
-        // ImageView for the play icon.
-        viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                videoClickedCallback.playVideo(mData.getUri(), mData.getTitle());
-            }
-        });
+    @Override
+    public void renderTiny(@Nonnull View view) {
+        renderTiny(getViewHolder(view));
+    }
 
-        view.setContentDescription(mContext.getResources().getString(
-              R.string.video_date_content_description,
-              mDateFormatter.format(mData.getLastModifiedDate())));
+    @Override
+    public void renderThumbnail(@Nonnull View view) {
+        mGlideManager.loadScreen(mData.getUri(), generateSignature(mData),
+              mSuggestedWidthPx, mSuggestedHeightPx)
+              .thumbnail(mGlideManager.loadMediaStoreThumb(mData.getUri(),
+                    generateSignature(mData)))
+              .into(getViewHolder(view).mVideoView);
     }
 
+    @Override
+    public void renderFullRes(@Nonnull View view) { }
 
     @Override
-    public void recycle(View view) {
-        VideoViewHolder videoViewHolder =
-              (VideoViewHolder) view.getTag(R.id.mediadata_tag_target);
-        Glide.clear(videoViewHolder.mVideoView);
+    public void recycle(@Nonnull View view) {
+        VideoViewHolder holder = getViewHolder(view);
+        if (holder != null) {
+            Glide.clear(getViewHolder(view).mVideoView);
+        }
     }
 
     @Override
@@ -215,4 +222,18 @@ public class VideoItem extends FilmstripItemBase<VideoItemData> {
     public String toString() {
         return "VideoItem: " + mData.toString();
     }
+
+    private void renderTiny(@Nonnull VideoViewHolder viewHolder) {
+        mGlideManager.loadMediaStoreThumb(mData.getUri(), generateSignature(mData))
+              .into(viewHolder.mVideoView);
+    }
+
+    private VideoViewHolder getViewHolder(@Nonnull View view) {
+        Object container = view.getTag(R.id.mediadata_tag_target);
+        if (container instanceof VideoViewHolder) {
+            return (VideoViewHolder) container;
+        }
+
+        return null;
+    }
 }
index 87df3bc..d045cd8 100644 (file)
@@ -33,12 +33,14 @@ public class VideoItemFactory implements CursorToFilmstripItemFactory<VideoItem>
           + " DESC, " + MediaStore.Video.VideoColumns._ID + " DESC";
 
     private final Context mContext;
+    private final GlideFilmstripManager mGlideManager;
     private final ContentResolver mContentResolver;
     private final VideoDataFactory mVideoDataFactory;
 
-    public VideoItemFactory(Context context, ContentResolver contentResolver,
-          VideoDataFactory videoDataFactory) {
+    public VideoItemFactory(Context context, GlideFilmstripManager glideManager,
+          ContentResolver contentResolver, VideoDataFactory videoDataFactory) {
         mContext = context;
+        mGlideManager = glideManager;
         mContentResolver = contentResolver;
         mVideoDataFactory = videoDataFactory;
     }
@@ -47,7 +49,7 @@ public class VideoItemFactory implements CursorToFilmstripItemFactory<VideoItem>
     public VideoItem get(Cursor c) {
         VideoItemData data = mVideoDataFactory.fromCursor(c);
         if (data != null) {
-            return new VideoItem(mContext, data, this);
+            return new VideoItem(mContext, mGlideManager, data, this);
         } else {
             Log.w(TAG, "skipping item with null data, returning null for item");
             return null;
index f0f4307..8e20a24 100644 (file)
@@ -97,17 +97,6 @@ public interface FilmstripDataAdapter {
     public int getItemViewType(int index);
 
     /**
-     * Resizes the view used to visually present the image data.  This is
-     * useful when the view contains a bitmap.
-     *
-     * @param index The ID of the resize data to be presented.
-     * @param view The view to update that was created by getView().
-     * @param w Width in pixels of rendered view.
-     * @param h Height in pixels of rendered view.
-     */
-    public void resizeView(int index, View view, int w, int h);
-
-    /**
      * Returns the {@link FilmstripItem} specified by the ID.
      *
      * @param index The ID of the {@link FilmstripItem}.
index 0d3cee4..1bb80d9 100644 (file)
@@ -150,18 +150,25 @@ public class FilmstripView extends ViewGroup {
      * A helper class to tract and calculate the view coordination.
      */
     private static class ViewItem {
+        private static enum RenderSize {
+            TINY,
+            THUMBNAIL,
+            FULL_RES
+        }
+
+        private final FilmstripView mFilmstrip;
+        private final View mView;
+        private final RectF mViewArea;
+
         private int mIndex;
         /** The position of the left of the view in the whole filmstrip. */
         private int mLeftPosition;
-        private final View mView;
         private FilmstripItem mData;
-        private final RectF mViewArea;
-        private boolean mMaximumBitmapRequested;
+        private RenderSize mRenderSize;
 
         private ValueAnimator mTranslationXAnimator;
         private ValueAnimator mTranslationYAnimator;
         private ValueAnimator mAlphaAnimator;
-        private final FilmstripView mFilmstrip;
 
         /**
          * Constructor.
@@ -171,28 +178,50 @@ public class FilmstripView extends ViewGroup {
          * @param v The {@code View} representing the data.
          */
         public ViewItem(int index, View v, FilmstripItem data, FilmstripView filmstrip) {
-            v.setPivotX(0f);
-            v.setPivotY(0f);
+            mFilmstrip = filmstrip;
+            mView = v;
+            mViewArea = new RectF();
+
             mIndex = index;
             mData = data;
-            mView = v;
-            mMaximumBitmapRequested = false;
             mLeftPosition = -1;
-            mViewArea = new RectF();
-            mFilmstrip = filmstrip;
+            mRenderSize = RenderSize.TINY;
+
+            mView.setPivotX(0f);
+            mView.setPivotY(0f);
         }
 
         public void setData(FilmstripItem item) {
             mData = item;
-            mMaximumBitmapRequested = false;
+
+            renderTiny();
         }
 
-        public boolean isMaximumBitmapRequested() {
-            return mMaximumBitmapRequested;
+        public void renderTiny() {
+            if (mRenderSize != RenderSize.TINY) {
+                mRenderSize = RenderSize.TINY;
+
+                Log.i(TAG, "[ViewItem:" + mIndex + "] mData.renderTiny()");
+                mData.renderTiny(mView);
+            }
         }
 
-        public void setMaximumBitmapRequested() {
-            mMaximumBitmapRequested = true;
+        public void renderThumbnail() {
+            if (mRenderSize != RenderSize.THUMBNAIL) {
+                mRenderSize = RenderSize.THUMBNAIL;
+
+                Log.i(TAG, "[ViewItem:" + mIndex + "] mData.renderThumbnail()");
+                mData.renderThumbnail(mView);
+            }
+        }
+
+        public void renderFullRes() {
+            if (mRenderSize != RenderSize.FULL_RES) {
+                mRenderSize = RenderSize.FULL_RES;
+
+                Log.i(TAG, "[ViewItem:" + mIndex + "] mData.renderFullRes()");
+                mData.renderFullRes(mView);
+            }
         }
 
         /**
@@ -368,14 +397,6 @@ public class FilmstripView extends ViewGroup {
         }
 
         /**
-         * Notifies the {@link com.android.camera.filmstrip.FilmstripDataAdapter} to
-         * resize the view.
-         */
-        public void resizeView(int w, int h) {
-            mFilmstrip.mDataAdapter.resizeView(mIndex, mView, w, h);
-        }
-
-        /**
          * Adds the view of the data to the view hierarchy if necessary.
          */
         public void addViewToHierarchy() {
@@ -503,50 +524,6 @@ public class FilmstripView extends ViewGroup {
             return Math.round(mViewArea.left);
         }
 
-        public void copyAttributes(ViewItem item) {
-            setLeftPosition(item.getLeftPosition());
-            // X
-            setTranslationX(item.getTranslationX());
-            if (item.mTranslationXAnimator != null) {
-                mTranslationXAnimator = item.mTranslationXAnimator;
-                mTranslationXAnimator.removeAllUpdateListeners();
-                mTranslationXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                        // We invalidate the filmstrip view instead of setting the
-                        // translation X because the translation X of the view is
-                        // touched in onLayout(). See the documentation of
-                        // animateTranslationX().
-                        mFilmstrip.invalidate();
-                    }
-                });
-            }
-            // Y
-            setTranslationY(item.getTranslationY());
-            if (item.mTranslationYAnimator != null) {
-                mTranslationYAnimator = item.mTranslationYAnimator;
-                mTranslationYAnimator.removeAllUpdateListeners();
-                mTranslationYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                        setTranslationY((Float) valueAnimator.getAnimatedValue());
-                    }
-                });
-            }
-            // Alpha
-            setAlpha(item.getAlpha());
-            if (item.mAlphaAnimator != null) {
-                mAlphaAnimator = item.mAlphaAnimator;
-                mAlphaAnimator.removeAllUpdateListeners();
-                mAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                        ViewItem.this.setAlpha((Float) valueAnimator.getAnimatedValue());
-                    }
-                });
-            }
-        }
-
         /**
          * Apply a scale factor (i.e. {@code postScale}) on top of current scale at
          * pivot point ({@code focusX}, {@code focusY}). Visually it should be the
@@ -850,14 +827,14 @@ public class FilmstripView extends ViewGroup {
         // Always scale by fixed filmstrip scale, since we only show items when
         // in filmstrip. Preloading images with a different scale and bounds
         // interferes with caching.
-        int width = Math.round(FILM_STRIP_SCALE * getWidth());
-        int height = Math.round(FILM_STRIP_SCALE * getHeight());
+        int width = Math.round(FULL_SCREEN_SCALE * getWidth());
+        int height = Math.round(FULL_SCREEN_SCALE * getHeight());
+
         Log.v(TAG, "suggesting item bounds: " + width + "x" + height);
         mDataAdapter.suggestViewSizeBound(width, height);
 
         View recycled = getRecycledView(index);
-        View v = mDataAdapter.getView(recycled, index,
-              mVideoClickedCallback);
+        View v = mDataAdapter.getView(recycled, index, mVideoClickedCallback);
         if (v == null) {
             return null;
         }
@@ -866,22 +843,27 @@ public class FilmstripView extends ViewGroup {
         return item;
     }
 
-    private void ensureItemAtMaxSize(int bufferIndex) {
+    private void renderFullRes(int bufferIndex) {
         ViewItem item = mViewItems[bufferIndex];
-        if (item == null || item.isMaximumBitmapRequested()) {
+        if (item == null) {
             return;
         }
-        item.setMaximumBitmapRequested();
-        // Request full size bitmap, or max that DataAdapter will create.
-        int index = item.getAdapterIndex();
-        int h = mDataAdapter.getFilmstripItemAt(index).getDimensions().getHeight();
-        int w = mDataAdapter.getFilmstripItemAt(index).getDimensions().getWidth();
-        item.resizeView(w, h);
+
+        item.renderFullRes();
+    }
+
+    private void renderThumbnail(int bufferIndex) {
+        ViewItem item = mViewItems[bufferIndex];
+        if (item == null) {
+            return;
+        }
+
+        item.renderThumbnail();
     }
 
-    private void ensureBufferItemsAtMaxSize() {
+    private void renderAllThumbnails() {
         for(int i = 0; i < BUFFER_SIZE; i++) {
-            ensureItemAtMaxSize(i);
+            renderThumbnail(i);
         }
     }
 
@@ -1600,7 +1582,7 @@ public class FilmstripView extends ViewGroup {
         }
 
         mViewItems[bufferIndex] = viewItem;
-        ensureItemAtMaxSize(bufferIndex);
+        renderThumbnail(bufferIndex);
         viewItem.setAlpha(0f);
         viewItem.setTranslationY(getHeight() / 8);
         slideViewBack(viewItem);
@@ -1637,7 +1619,7 @@ public class FilmstripView extends ViewGroup {
                     mListener.onDataFocusChanged(index, getCurrentItemAdapterIndex());
                 }
                 Log.d(TAG, "onFilmstripItemInserted()");
-                ensureBufferItemsAtMaxSize();
+                renderAllThumbnails();
             }
 
             @Override
@@ -1647,7 +1629,7 @@ public class FilmstripView extends ViewGroup {
                     mListener.onDataFocusChanged(index, getCurrentItemAdapterIndex());
                 }
                 Log.d(TAG, "onFilmstripItemRemoved()");
-                ensureBufferItemsAtMaxSize();
+                renderAllThumbnails();
             }
         });
     }
@@ -1770,7 +1752,7 @@ public class FilmstripView extends ViewGroup {
         // is unreliable. Load the full resolution if either value
         // reports that the item is not scrolling.
         if (!mController.isScrolling() || !mIsUserScrolling) {
-            ensureItemAtMaxSize(bufferIndex);
+            renderThumbnail(bufferIndex);
         }
 
         adjustChildZOrder();
@@ -1898,7 +1880,7 @@ public class FilmstripView extends ViewGroup {
         adjustChildZOrder();
 
         Log.d(TAG, "reload() - Ensure all items are loaded at max size.");
-        ensureBufferItemsAtMaxSize();
+        renderAllThumbnails();
         invalidate();
 
         if (mListener != null) {
@@ -2017,10 +1999,10 @@ public class FilmstripView extends ViewGroup {
 
                             Log.d(TAG, "[fling] onScrollEnd() - Ensuring that items are at"
                                   + " full resolution.");
-                            ensureItemAtMaxSize(BUFFER_CENTER);
-                            ensureItemAtMaxSize(BUFFER_CENTER + 1);
-                            ensureItemAtMaxSize(BUFFER_CENTER - 1);
-                            ensureItemAtMaxSize(BUFFER_CENTER + 2);
+                            renderThumbnail(BUFFER_CENTER);
+                            renderThumbnail(BUFFER_CENTER + 1);
+                            renderThumbnail(BUFFER_CENTER - 1);
+                            renderThumbnail(BUFFER_CENTER + 2);
                         }
 
                         if (isCurrentItemCentered()
@@ -2726,7 +2708,7 @@ public class FilmstripView extends ViewGroup {
             }
             if (inFullScreen()) {
                 mController.zoomAt(current, x, y);
-                ensureItemAtMaxSize(BUFFER_CENTER);
+                renderFullRes(BUFFER_CENTER);
                 return true;
             } else if (mScale > FULL_SCREEN_SCALE) {
                 // In zoom view.
@@ -3072,6 +3054,7 @@ public class FilmstripView extends ViewGroup {
                     onLeaveZoomView();
                 }
                 mScale = newScale;
+                renderThumbnail(BUFFER_CENTER);
                 onEnterFilmstrip();
                 invalidate();
             } else {
@@ -3094,7 +3077,7 @@ public class FilmstripView extends ViewGroup {
                 } else {
                     onEnterZoomView();
                 }
-                ensureItemAtMaxSize(BUFFER_CENTER);
+                renderFullRes(BUFFER_CENTER);
             }
             return true;
         }