现在的位置: 首页 > 综合 > 正文

在UI中显示Bitmap

2014年09月05日 ⁄ 综合 ⁄ 共 10357字 ⁄ 字号 评论关闭

  这节课将我们前面几节课学习的东西都整合起来,向你展示如何使用后台线程和Bitmap缓存加载多个Bitmap(位图)到ViewPager和GridView组件中,并学习如何处理并发和配置变化问题。

实现加载Bitmap到ViewPager 

        滑动浏览模式(Swipe
View Pattern
)是一种很好的浏览详细图片的方式。你可以使用ViewPager组件配合PagerAdapter(适配器)来实现这种模式。然而,更加合适的适配器是FragmentStatePagerAdapter,它可以在ViewPager退出屏幕的时候自动销毁并存储Fragments的状态,使得内存依然保留下来。

        注意如果你只有少量的图片,并且确信它们不会超出程序的内存限制,使用常规的PagerAdapter或者FragmentPagerAdapter或许更加合适。

        这里有一个包含ImageView的ViewPager的实现类,Main Activity(主活动)持有这个ViewPager和Adapter。

01 public class ImageDetailActivity extends FragmentActivity
{
02     public static final String
EXTRA_IMAGE = 
"extra_image";
03  
04     private ImagePagerAdapter
mAdapter;
05     private ViewPager
mPager;
06  
07     //
A static dataset to back the ViewPager adapter
08     public final static Integer[]
imageResIds = 
new Integer[]
{
09             R.drawable.sample_image_1,
R.drawable.sample_image_2, R.drawable.sample_image_3,
10             R.drawable.sample_image_4,
R.drawable.sample_image_5, R.drawable.sample_image_6,
11             R.drawable.sample_image_7,
R.drawable.sample_image_8, R.drawable.sample_image_9};
12  
13     @Override
14     public void onCreate(Bundle
savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.image_detail_pager); //
Contains just a ViewPager
17  
18         mAdapter
= 
new ImagePagerAdapter(getSupportFragmentManager(),
imageResIds.length);
19         mPager
= (ViewPager) findViewById(R.id.pager);
20         mPager.setAdapter(mAdapter);
21     }
22  
23     public static class ImagePagerAdapter extends FragmentStatePagerAdapter
{
24         private final int mSize;
25  
26         public ImagePagerAdapter(FragmentManager
fm, 
int size)
{
27             super(fm);
28             mSize
= size;
29         }
30  
31         @Override
32         public int getCount()
{
33             return mSize;
34         }
35  
36         @Override
37         public Fragment
getItem(
int position)
{
38             return ImageDetailFragment.newInstance(position);
39         }
40     }
41 }

        这里有一个用来持有ImageView并显示详细信息的Fragment的实现类。看起来这似乎是非常合理的方法,但是你能否看到这个方案的缺点呢?应该如何改善它呢?

01 public class ImageDetailFragment extends Fragment
{
02     private static final String
IMAGE_DATA_EXTRA = 
"resId";
03     private int mImageNum;
04     private ImageView
mImageView;
05  
06     static ImageDetailFragment
newInstance(
int imageNum)
{
07         final ImageDetailFragment
f = 
new ImageDetailFragment();
08         final Bundle
args = 
new Bundle();
09         args.putInt(IMAGE_DATA_EXTRA,
imageNum);
10         f.setArguments(args);
11         return f;
12     }
13  
14     //
Empty constructor, required as per Fragment docs
15     public ImageDetailFragment()
{}
16  
17     @Override
18     public void onCreate(Bundle
savedInstanceState) {
19         super.onCreate(savedInstanceState);
20         mImageNum
= getArguments() != 
null ?
getArguments().getInt(IMAGE_DATA_EXTRA) : -
1;
21     }
22  
23     @Override
24     public View
onCreateView(LayoutInflater inflater, ViewGroup container,
25             Bundle
savedInstanceState) {
26         //
image_detail_fragment.xml contains just an ImageView
27         final View
v = inflater.inflate(R.layout.image_detail_fragment, container, 
false);
28         mImageView
= (ImageView) v.findViewById(R.id.imageView);
29         return v;
30     }
31  
32     @Override
33     public void onActivityCreated(Bundle
savedInstanceState) {
34         super.onActivityCreated(savedInstanceState);
35         final int resId
= ImageDetailActivity.imageResIds[mImageNum];
36         mImageView.setImageResource(resId); //
Load image into ImageView
37     }
38 }

        希望你能注意到:这些图片是在UI线程从资源中读取过来的,而这极有可能导致应用挂起甚至被强制关闭。使用在“非UI线程处理Bitmap”一课中提到的AsyncTask,直接将图片加载和处理移到后台线程中。

        任何额外的处理(例如调整大小或者从网络获取图片)可以放在BitmapWorkerTask中而不会影响到主UI线程的响应性。如果后台线程做的不仅仅是直接从硬盘直接加载图片,那么如“缓存Bitmap”一课中说的,将图片缓存到内存或者硬盘是有利于程序优化的。这里是对内存缓存的一些额外修改:

01 public class ImageDetailActivity extends FragmentActivity
{
02     ...
03     private LruCache<String,
Bitmap> mMemoryCache;
04  
05     @Override
06     public void onCreate(Bundle
savedInstanceState) {
07         ...
08         //
initialize LruCache as per Use a Memory Cache section
09     }
10  
11     public void loadBitmap(int resId,
ImageView imageView) {
12         final String
imageKey = String.valueOf(resId);
13  
14         final Bitmap
bitmap = mMemoryCache.get(imageKey);
15         if (bitmap
!= 
null)
{
16             mImageView.setImageBitmap(bitmap);
17         } else {
18             mImageView.setImageResource(R.drawable.image_placeholder);
19             BitmapWorkerTask
task = 
new BitmapWorkerTask(mImageView);
20             task.execute(resId);
21         }
22     }
23  
24     ... //
include updated BitmapWorkerTask from Use a Memory Cache section
25 }

        将上面的代码片段整合在一起会让你的ViewPager具备优良的响应性能,可以实现最小的加载延迟,根据你的图片加载需要或多或少的进行后台处理。

实现加载Bitmap到GridView

        网格列表控件(Grid
List Building Block
)对于显示图片数据集非常有用,也可以使用GridView组件来实现,如果用户上下滚动的话,有很多图片处于就绪状态,随时可以显示在屏幕上。如果要实现这种类型的控制,你必须确保UI保持流畅性,内存使用处于控制之中而且并发也要被正确地处理(取决于GridView回收子视图的方式)。

        首先,这里有一个标准的GridView实现,将ImageView子控件存放在Fragment中。我们再一次思考这个问题,这个方法看起来似乎非常完美且合乎情理,但是有没有办法让它便得更好呢?

01 public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener
{
02     private ImageAdapter
mAdapter;
03  
04     //
A static dataset to back the GridView adapter
05     public final static Integer[]
imageResIds = 
new Integer[]
{
06             R.drawable.sample_image_1,
R.drawable.sample_image_2, R.drawable.sample_image_3,
07             R.drawable.sample_image_4,
R.drawable.sample_image_5, R.drawable.sample_image_6,
08             R.drawable.sample_image_7,
R.drawable.sample_image_8, R.drawable.sample_image_9};
09  
10     //
Empty constructor as per Fragment docs
11     public ImageGridFragment()
{}
12  
13     @Override
14     public void onCreate(Bundle
savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         mAdapter
= 
new ImageAdapter(getActivity());
17     }
18  
19     @Override
20     public View
onCreateView(
21             LayoutInflater
inflater, ViewGroup container, Bundle savedInstanceState) {
22         final View
v = inflater.inflate(R.layout.image_grid_fragment, container, 
false);
23         final GridView
mGridView = (GridView) v.findViewById(R.id.gridView);
24         mGridView.setAdapter(mAdapter);
25         mGridView.setOnItemClickListener(this);
26         return v;
27     }
28  
29     @Override
30     public void onItemClick(AdapterView<?>
parent, View v, 
int position, long id)
{
31         final Intent
i = 
new Intent(getActivity(),
ImageDetailActivity.
class);
32         i.putExtra(ImageDetailActivity.EXTRA_IMAGE,
position);
33         startActivity(i);
34     }
35  
36     private class ImageAdapter extends BaseAdapter
{
37         private final Context
mContext;
38  
39         public ImageAdapter(Context
context) {
40             super();
41             mContext
= context;
42         }
43  
44         @Override
45         public int getCount()
{
46             return imageResIds.length;
47         }
48  
49         @Override
50         public Object
getItem(
int position)
{
51             return imageResIds[position];
52         }
53  
54         @Override
55         public long getItemId(int position)
{
56             return position;
57         }
58  
59         @Override
60         public View
getView(
int position,
View convertView, ViewGroup container) {
61             ImageView
imageView;
62             if (convertView
== 
null)
{ 
//
if it's not recycled, initialize some attributes
63                 imageView
= 
new ImageView(mContext);
64                 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
65                 imageView.setLayoutParams(new GridView.LayoutParams(
66                         LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
67             } else {
68                 imageView
= (ImageView) convertView;
69             }
70             imageView.setImageResource(imageResIds[position]); //
Load image into ImageView
71             return imageView;
72         }
73     }
74 }

        当然,问题还是这个方法在UI线程中处理图片。这种方式或许是和处理小而简单的图片(系统资源的加载和缓存),如果需要做任何的处理,UI就会被阻塞(甚至引起ANR(Application
Not Responding)
)。

        和前一节相同的处理方式,我们在异步线程中进行处理和缓存。然而,考虑到GridView回收子视图的方式,你需要谨慎处理并发问题。可以使用“在非UI线程中处理Bitmap”一课中提到的技巧。这里是更新的解决方案:

01 public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener
{
02     ...
03  
04     private class ImageAdapter extends BaseAdapter
{
05         ...
06  
07         @Override
08         public View
getView(
int position,
View convertView, ViewGroup container) {
09             ...
10             loadBitmap(imageResIds[position],
imageView)
11             return imageView;
12         }
13     }
14  
15     public void loadBitmap(int resId,
ImageView imageView) {
16         if (cancelPotentialWork(resId,
imageView)) {
17             final BitmapWorkerTask
task = 
new BitmapWorkerTask(imageView);
18             final AsyncDrawable
asyncDrawable =
19                     new AsyncDrawable(getResources(),
mPlaceHolderBitmap, task);
20             imageView.setImageDrawable(asyncDrawable);
21             task.execute(resId);
22         }
23     }
24  
25     static class AsyncDrawable extends BitmapDrawable
{
26         private final WeakReference<BitmapWorkerTask>
bitmapWorkerTaskReference;
27  
28         public AsyncDrawable(Resources
res, Bitmap bitmap,
29                 BitmapWorkerTask
bitmapWorkerTask) {
30             super(res,
bitmap);
31             bitmapWorkerTaskReference
=
32                 new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
33         }
34  
35         public BitmapWorkerTask
getBitmapWorkerTask() {
36             return bitmapWorkerTaskReference.get();
37         }
38     }
39  
40     public static boolean cancelPotentialWork(int data,
ImageView imageView) {
41         final BitmapWorkerTask
bitmapWorkerTask = getBitmapWorkerTask(imageView);
42  
43         if (bitmapWorkerTask
!= 
null)
{
44             final int bitmapData
= bitmapWorkerTask.data;
45             if (bitmapData
!= data) {
46                 //
Cancel previous task
47                 bitmapWorkerTask.cancel(true);
48             } else {
49                 //
The same work is already in progress
50                 return false;
51             }
52         }
53         //
No task associated with the ImageView, or an existing task was cancelled
54         return true;
55     }
56  
57     private static BitmapWorkerTask
getBitmapWorkerTask(ImageView imageView) {
58        if (imageView
!= 
null)
{
59            final Drawable
drawable = imageView.getDrawable();
60            if (drawable instanceof AsyncDrawable)
{
61                final AsyncDrawable
asyncDrawable = (AsyncDrawable) drawable;
62                return asyncDrawable.getBitmapWorkerTask();
63            }
64         }
65         return null;
66     }
67  
68     ... //
include updated BitmapWorkerTask class

        注意:相同的代码也可以很好的适配ListView。

        这里的实现方法允许灵活地处理和加载图片,并且不会影响UI的流畅性。在后台线程中,你可以从网络加载图片,调整大幅的数码相机照片的大小,并在处理任务结束的时候将图片显示在UI界面中。

转至:http://my.oschina.net/ryanhoo/blog/88484

抱歉!评论已关闭.