Android官方开发文档Training系列课程中文版:高效显示位图之在UI中展示位图
原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/display-bitmap.html
這節(jié)課會將前面的知識點整合到一起,展示如何使用后臺線程、位圖緩存來加載多張圖片到ViewPager或者GridView中,并會涉及并發(fā)處理及配置更改的相關知識。
加載位圖到ViewPager
swipe view pattern是畫廊應用詳情頁之間來回跳轉的最佳演示。你可以使用ViewPager來實現(xiàn)這種模式。無論如何,FragmentStatePagerAdapter是ViewPager支撐適配器的最佳選擇,它可以隨著Fragment在屏幕上的消失自動銷毀與保存Fragment的狀態(tài),降低內存的使用。
Note: 如果你有少量的圖片并且可以保證它們一起被加入內存之后不會超出內存的限制,那么使用正規(guī)的PagerAdapter或者FragmentPagerAdapter可能更合適些。
下面是帶有ImageView的ViewPager實現(xiàn)。MainActivity持有了ViewPager及Adapter的引用:
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 adapterpublic 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};@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.image_detail_pager); // Contains just a ViewPagermAdapter = 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;}@Overridepublic int getCount() {return mSize;}@Overridepublic Fragment getItem(int position) {return ImageDetailFragment.newInstance(position);}} }下面是詳情Fragment的實現(xiàn),它持有了ImageView的引用。這可能看起來是一種非常合理的方式,但是你可以看出這個實現(xiàn)的缺陷嗎?如何改進它?
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 docspublic ImageDetailFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {// image_detail_fragment.xml contains just an ImageViewfinal View v = inflater.inflate(R.layout.image_detail_fragment, container, false);mImageView = (ImageView) v.findViewById(R.id.imageView);return v;}@Overridepublic void onActivityCreated(Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);final int resId = ImageDetailActivity.imageResIds[mImageNum];mImageView.setImageResource(resId); // Load image into ImageView} }希望你會注意到這個問題:讀取圖片資源的過程是在UI線程中執(zhí)行的,這會導致程序的卡頓及被強制關閉。使用在Processing Bitmaps Off the UI Thread課程中所描述的AsyncTask,它可以簡單的將圖片的加載與處理過程交給后臺線程:
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 BitmapWorkerTask class } public class ImageDetailFragment extends Fragment {...@Overridepublic 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);}} }任何額外的處理,比如調整圖片尺寸或者從網絡獲取圖像,這些處理過程可以放入BitmapWorkerTask而不會影響到主線程的響應能力。如果后臺線程做的不僅僅是從磁盤上直接加載圖像,那么它還益于處理添加內存緩存及磁盤緩存。下面是針對于內存緩存的專門修改版本:
public class ImageDetailActivity extends FragmentActivity {...private LruCache<String, Bitmap> mMemoryCache;@Overridepublic void onCreate(Bundle savedInstanceState) {...// initialize LruCache as per Use a Memory Cache 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 Use a Memory Cache section }以最小的加載延遲將這些塊提供給ViewPager,并盡可能的將處理圖片的工作放入后臺線程。
加載位圖到GridView
grid list building block對展示圖片數據集很有幫助,它主要通過GridView來實現(xiàn),它可以將很多圖片在同一時間顯示出來,如果用戶要來回滑動,那可能會有更多的圖片需要準備展示出來。當實現(xiàn)這種控制類型時,你必須確保UI依舊流暢,內存的使用依舊在控制范圍之內,并發(fā)的處理依舊正確(因為GridView會回收它的子View)。
首先,這里有一個標準的GridView實現(xiàn)。再者,這可能看起來非常完美,但是它還有可以改進的地方嗎?
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {private ImageAdapter mAdapter;// A static dataset to back the GridView adapterpublic 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 docspublic ImageGridFragment() {}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mAdapter = new ImageAdapter(getActivity());}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic int getCount() {return imageResIds.length;}@Overridepublic Object getItem(int position) {return imageResIds[position];}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup container) {ImageView imageView;if (convertView == null) { // if it's not recycled, initialize some attributesimageView = new ImageView(mContext);imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);imageView.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));} else {imageView = (ImageView) convertView;}imageView.setImageResource(imageResIds[position]); // Load image into ImageViewreturn imageView;}} }再提示一次,這個實現(xiàn)的問題圖像的設置過程位于UI線程。雖然這可能只是應用小圖像、簡單圖像,但如果有任何的額外工作需要處理,那么UI線程就會戛然而止。
前一節(jié)中的一步處理及緩存圖像可以實現(xiàn)到這里。無論如何,你還需要對由GridView所引起的并發(fā)問題提高警惕。如果要處理這個問題,可以使用在Processing Bitmaps Off the UI Thread課程中所提到的技巧。下面是更改后的版本:
public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {...private class ImageAdapter extends BaseAdapter {...@Overridepublic View getView(int position, View convertView, ViewGroup container) {...loadBitmap(imageResIds[position], imageView)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 taskbitmapWorkerTask.cancel(true);} else {// The same work is already in progressreturn false;}}// No task associated with the ImageView, or an existing task was cancelledreturn 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 BitmapWorkerTask classNote: 上面的代碼也可以應用于ListView。
這個實現(xiàn)對如何處理及加載圖像是很靈活的,并不會妨礙UI的流暢性。后臺任務可以從網絡加載圖像并且可以調整數碼相片的尺寸,并會在任務結束的時候提供處理后的圖像。
對于本示例的全部代碼以及這節(jié)課中討論到的其它概念,可以查看包含這部分功能的示例應用。
總結
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:高效显示位图之在UI中展示位图的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Python自然语言处理】中文分词技术
- 下一篇: Android官方开发文档Trainin