9977dddee3
Changes: -Updated code sample (see http://ag/214812) -Updated code snippets to match updated sample -Fixed <> in code snippets -Updated disk cache section -Some other minor updates Change-Id: Id7ca4d161f165814d71f238f940b2c5bfbc220aa
404 lines
16 KiB
Plaintext
404 lines
16 KiB
Plaintext
page.title=Displaying Bitmaps in Your UI
|
||
parent.title=Displaying Bitmaps Efficiently
|
||
parent.link=index.html
|
||
|
||
trainingnavtop=true
|
||
previous.title=Caching Bitmaps
|
||
previous.link=cache-bitmap.html
|
||
|
||
@jd:body
|
||
|
||
<div id="tb-wrapper">
|
||
<div id="tb">
|
||
|
||
<h2>This lesson teaches you to</h2>
|
||
<ol>
|
||
<li><a href="#viewpager">Load Bitmaps into a ViewPager Implementation</a></li>
|
||
<li><a href="#gridview">Load Bitmaps into a GridView Implementation</a></li>
|
||
</ol>
|
||
|
||
<h2>You should also read</h2>
|
||
<ul>
|
||
<li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li>
|
||
<li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li>
|
||
</ul>
|
||
|
||
<h2>Try it out</h2>
|
||
|
||
<div class="download-box">
|
||
<a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a>
|
||
<p class="filename">BitmapFun.zip</p>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<p></p>
|
||
|
||
<p>This lesson brings together everything from previous lessons, showing you how to load multiple
|
||
bitmaps into {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView}
|
||
components using a background thread and bitmap cache, while dealing with concurrency and
|
||
configuration changes.</p>
|
||
|
||
<h2 id="viewpager">Load Bitmaps into a ViewPager Implementation</h2>
|
||
|
||
<p>The <a href="{@docRoot}design/patterns/swipe-views.html">swipe view pattern</a> is an excellent
|
||
way to navigate the detail view of an image gallery. You can implement this pattern using a {@link
|
||
android.support.v4.view.ViewPager} component backed by a {@link
|
||
android.support.v4.view.PagerAdapter}. However, a more suitable backing adapter is the subclass
|
||
{@link android.support.v4.app.FragmentStatePagerAdapter} which automatically destroys and saves
|
||
state of the {@link android.app.Fragment Fragments} in the {@link android.support.v4.view.ViewPager}
|
||
as they disappear off-screen, keeping memory usage down.</p>
|
||
|
||
<p class="note"><strong>Note:</strong> If you have a smaller number of images and are confident they
|
||
all fit within the application memory limit, then using a regular {@link
|
||
android.support.v4.view.PagerAdapter} or {@link android.support.v4.app.FragmentPagerAdapter} might
|
||
be more appropriate.</p>
|
||
|
||
<p>Here’s an implementation of a {@link android.support.v4.view.ViewPager} with {@link
|
||
android.widget.ImageView} children. The main activity holds the {@link
|
||
android.support.v4.view.ViewPager} and the adapter:</p>
|
||
|
||
<pre>
|
||
public class ImageDetailActivity extends FragmentActivity {
|
||
public static final String EXTRA_IMAGE = "extra_image";
|
||
|
||
private ImagePagerAdapter mAdapter;
|
||
private ViewPager mPager;
|
||
|
||
// A static dataset to back the ViewPager adapter
|
||
public final static Integer[] imageResIds = new Integer[] {
|
||
R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
|
||
R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
|
||
R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
|
||
|
||
@Override
|
||
public void onCreate(Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
setContentView(R.layout.image_detail_pager); // Contains just a ViewPager
|
||
|
||
mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);
|
||
mPager = (ViewPager) findViewById(R.id.pager);
|
||
mPager.setAdapter(mAdapter);
|
||
}
|
||
|
||
public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
|
||
private final int mSize;
|
||
|
||
public ImagePagerAdapter(FragmentManager fm, int size) {
|
||
super(fm);
|
||
mSize = size;
|
||
}
|
||
|
||
@Override
|
||
public int getCount() {
|
||
return mSize;
|
||
}
|
||
|
||
@Override
|
||
public Fragment getItem(int position) {
|
||
return ImageDetailFragment.newInstance(position);
|
||
}
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>Here is an implementation of the details {@link android.app.Fragment} which holds the {@link android.widget.ImageView} children. This might seem like a perfectly reasonable approach, but can
|
||
you see the drawbacks of this implementation? How could it be improved?</p>
|
||
|
||
<pre>
|
||
public class ImageDetailFragment extends Fragment {
|
||
private static final String IMAGE_DATA_EXTRA = "resId";
|
||
private int mImageNum;
|
||
private ImageView mImageView;
|
||
|
||
static ImageDetailFragment newInstance(int imageNum) {
|
||
final ImageDetailFragment f = new ImageDetailFragment();
|
||
final Bundle args = new Bundle();
|
||
args.putInt(IMAGE_DATA_EXTRA, imageNum);
|
||
f.setArguments(args);
|
||
return f;
|
||
}
|
||
|
||
// Empty constructor, required as per Fragment docs
|
||
public ImageDetailFragment() {}
|
||
|
||
@Override
|
||
public void onCreate(Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;
|
||
}
|
||
|
||
@Override
|
||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||
Bundle savedInstanceState) {
|
||
// image_detail_fragment.xml contains just an ImageView
|
||
final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
|
||
mImageView = (ImageView) v.findViewById(R.id.imageView);
|
||
return v;
|
||
}
|
||
|
||
@Override
|
||
public void onActivityCreated(Bundle savedInstanceState) {
|
||
super.onActivityCreated(savedInstanceState);
|
||
final int resId = ImageDetailActivity.imageResIds[mImageNum];
|
||
<strong>mImageView.setImageResource(resId);</strong> // Load image into ImageView
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>Hopefully you noticed the issue: the images are being read from resources on the UI thread,
|
||
which can lead to an application hanging and being force closed. Using an
|
||
{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps
|
||
Off the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a
|
||
background thread:</p>
|
||
|
||
<pre>
|
||
public class ImageDetailActivity extends FragmentActivity {
|
||
...
|
||
|
||
public void loadBitmap(int resId, ImageView imageView) {
|
||
mImageView.setImageResource(R.drawable.image_placeholder);
|
||
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
|
||
task.execute(resId);
|
||
}
|
||
|
||
... // include <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> class
|
||
}
|
||
|
||
public class ImageDetailFragment extends Fragment {
|
||
...
|
||
|
||
@Override
|
||
public void onActivityCreated(Bundle savedInstanceState) {
|
||
super.onActivityCreated(savedInstanceState);
|
||
if (ImageDetailActivity.class.isInstance(getActivity())) {
|
||
final int resId = ImageDetailActivity.imageResIds[mImageNum];
|
||
// Call out to ImageDetailActivity to load the bitmap in a background thread
|
||
((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView);
|
||
}
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>Any additional processing (such as resizing or fetching images from the network) can take place
|
||
in the <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> without affecting
|
||
responsiveness of the main UI. If the background thread is doing more than just loading an image
|
||
directly from disk, it can also be beneficial to add a memory and/or disk cache as described in the
|
||
lesson <a href="cache-bitmap.html#memory-cache">Caching Bitmaps</a>. Here's the additional
|
||
modifications for a memory cache:</p>
|
||
|
||
<pre>
|
||
public class ImageDetailActivity extends FragmentActivity {
|
||
...
|
||
private LruCache<String, Bitmap> mMemoryCache;
|
||
|
||
@Override
|
||
public void onCreate(Bundle savedInstanceState) {
|
||
...
|
||
// initialize LruCache as per <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section
|
||
}
|
||
|
||
public void loadBitmap(int resId, ImageView imageView) {
|
||
final String imageKey = String.valueOf(resId);
|
||
|
||
final Bitmap bitmap = mMemoryCache.get(imageKey);
|
||
if (bitmap != null) {
|
||
mImageView.setImageBitmap(bitmap);
|
||
} else {
|
||
mImageView.setImageResource(R.drawable.image_placeholder);
|
||
BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
|
||
task.execute(resId);
|
||
}
|
||
}
|
||
|
||
... // include updated BitmapWorkerTask from <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section
|
||
}
|
||
</pre>
|
||
|
||
<p>Putting all these pieces together gives you a responsive {@link
|
||
android.support.v4.view.ViewPager} implementation with minimal image loading latency and the ability
|
||
to do as much or as little background processing on your images as needed.</p>
|
||
|
||
<h2 id="gridview">Load Bitmaps into a GridView Implementation</h2>
|
||
|
||
<p>The <a href="{@docRoot}design/building-blocks/grid-lists.html">grid list building block</a> is
|
||
useful for showing image data sets and can be implemented using a {@link android.widget.GridView}
|
||
component in which many images can be on-screen at any one time and many more need to be ready to
|
||
appear if the user scrolls up or down. When implementing this type of control, you must ensure the
|
||
UI remains fluid, memory usage remains under control and concurrency is handled correctly (due to
|
||
the way {@link android.widget.GridView} recycles its children views).</p>
|
||
|
||
<p>To start with, here is a standard {@link android.widget.GridView} implementation with {@link
|
||
android.widget.ImageView} children placed inside a {@link android.app.Fragment}. Again, this might
|
||
seem like a perfectly reasonable approach, but what would make it better?</p>
|
||
|
||
<pre>
|
||
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
|
||
private ImageAdapter mAdapter;
|
||
|
||
// A static dataset to back the GridView adapter
|
||
public final static Integer[] imageResIds = new Integer[] {
|
||
R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
|
||
R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
|
||
R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
|
||
|
||
// Empty constructor as per Fragment docs
|
||
public ImageGridFragment() {}
|
||
|
||
@Override
|
||
public void onCreate(Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
mAdapter = new ImageAdapter(getActivity());
|
||
}
|
||
|
||
@Override
|
||
public View onCreateView(
|
||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||
final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);
|
||
final GridView mGridView = (GridView) v.findViewById(R.id.gridView);
|
||
mGridView.setAdapter(mAdapter);
|
||
mGridView.setOnItemClickListener(this);
|
||
return v;
|
||
}
|
||
|
||
@Override
|
||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||
final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
|
||
i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
|
||
startActivity(i);
|
||
}
|
||
|
||
private class ImageAdapter extends BaseAdapter {
|
||
private final Context mContext;
|
||
|
||
public ImageAdapter(Context context) {
|
||
super();
|
||
mContext = context;
|
||
}
|
||
|
||
@Override
|
||
public int getCount() {
|
||
return imageResIds.length;
|
||
}
|
||
|
||
@Override
|
||
public Object getItem(int position) {
|
||
return imageResIds[position];
|
||
}
|
||
|
||
@Override
|
||
public long getItemId(int position) {
|
||
return position;
|
||
}
|
||
|
||
@Override
|
||
public View getView(int position, View convertView, ViewGroup container) {
|
||
ImageView imageView;
|
||
if (convertView == null) { // if it's not recycled, initialize some attributes
|
||
imageView = new ImageView(mContext);
|
||
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||
imageView.setLayoutParams(new GridView.LayoutParams(
|
||
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
|
||
} else {
|
||
imageView = (ImageView) convertView;
|
||
}
|
||
<strong>imageView.setImageResource(imageResIds[position]);</strong> // Load image into ImageView
|
||
return imageView;
|
||
}
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
<p>Once again, the problem with this implementation is that the image is being set in the UI thread.
|
||
While this may work for small, simple images (due to system resource loading and caching), if any
|
||
additional processing needs to be done, your UI grinds to a halt.</p>
|
||
|
||
<p>The same asynchronous processing and caching methods from the previous section can be implemented
|
||
here. However, you also need to wary of concurrency issues as the {@link android.widget.GridView}
|
||
recycles its children views. To handle this, use the techniques discussed in the <a
|
||
href="process-bitmap.html#concurrency">Processing Bitmaps Off the UI Thread</a> lesson. Here is the
|
||
updated
|
||
solution:</p>
|
||
|
||
<pre>
|
||
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
|
||
...
|
||
|
||
private class ImageAdapter extends BaseAdapter {
|
||
...
|
||
|
||
@Override
|
||
public View getView(int position, View convertView, ViewGroup container) {
|
||
...
|
||
<strong>loadBitmap(imageResIds[position], imageView)</strong>
|
||
return imageView;
|
||
}
|
||
}
|
||
|
||
public void loadBitmap(int resId, ImageView imageView) {
|
||
if (cancelPotentialWork(resId, imageView)) {
|
||
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
|
||
final AsyncDrawable asyncDrawable =
|
||
new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
|
||
imageView.setImageDrawable(asyncDrawable);
|
||
task.execute(resId);
|
||
}
|
||
}
|
||
|
||
static class AsyncDrawable extends BitmapDrawable {
|
||
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
|
||
|
||
public AsyncDrawable(Resources res, Bitmap bitmap,
|
||
BitmapWorkerTask bitmapWorkerTask) {
|
||
super(res, bitmap);
|
||
bitmapWorkerTaskReference =
|
||
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
|
||
}
|
||
|
||
public BitmapWorkerTask getBitmapWorkerTask() {
|
||
return bitmapWorkerTaskReference.get();
|
||
}
|
||
}
|
||
|
||
public static boolean cancelPotentialWork(int data, ImageView imageView) {
|
||
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
|
||
|
||
if (bitmapWorkerTask != null) {
|
||
final int bitmapData = bitmapWorkerTask.data;
|
||
if (bitmapData != data) {
|
||
// Cancel previous task
|
||
bitmapWorkerTask.cancel(true);
|
||
} else {
|
||
// The same work is already in progress
|
||
return false;
|
||
}
|
||
}
|
||
// No task associated with the ImageView, or an existing task was cancelled
|
||
return true;
|
||
}
|
||
|
||
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
|
||
if (imageView != null) {
|
||
final Drawable drawable = imageView.getDrawable();
|
||
if (drawable instanceof AsyncDrawable) {
|
||
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
|
||
return asyncDrawable.getBitmapWorkerTask();
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
... // include updated <a href="process-bitmap.html#BitmapWorkerTaskUpdated">{@code BitmapWorkerTask}</a> class
|
||
</pre>
|
||
|
||
<p class="note"><strong>Note:</strong> The same code can easily be adapted to work with {@link
|
||
android.widget.ListView} as well.</p>
|
||
|
||
<p>This implementation allows for flexibility in how the images are processed and loaded without
|
||
impeding the smoothness of the UI. In the background task you can load images from the network or
|
||
resize large digital camera photos and the images appear as the tasks finish processing.</p>
|
||
|
||
<p>For a full example of this and other concepts discussed in this lesson, please see the included
|
||
sample application.</p>
|