【Google官方教程】第四課:在UI中顯示Bitmap
在Google最新的文件中,提供了一系列含金量相當高的教程。因為種種原因而鮮為人知,真是可惜!Ryan將會細心整理,將之翻譯成中文,希望對開發者有所幫助。
本系列是Google關於展示大Bitmap(點陣圖)的官方演示,可以有效的解決記憶體限制,更加有效的載入並顯示圖片,同時避免讓人頭疼的OOM(Out Of Memory)。
-------------------------------------------------------------------------------------
譯文:
這節課將我們前面幾節課學習的東西都整合起來,向你展示如何使用後臺執行緒和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介面中。
要了解在本節課中討論到的概念和完整程式碼,請參閱示例程式。
BitmapFun:http://vdisk.weibo.com/s/hNgFB
相關文章
- 【Google官方教程】第二課:在非UI執行緒處理BitmapGoUI執行緒
- 【Google官方教程】第三課:快取BitmapGo快取
- 【Google官方教程】第一課:高效地載入大Bitmap(點陣圖)Go
- 在SAP WebClient UI裡顯示倒數計時的UIWebclientUI
- 【Qt】UI顯示中文QTUI
- Google Guava官方教程(中文版)GoGuava
- 元素在div中底部顯示
- 在sql*plus中顯示長字元SQL字元
- flash在jsp中不能顯示?JS
- jQuery UI Datepicker中文顯示jQueryUI
- 在sqlserver中顯示錶的結構SQLServer
- Extjs 在GridPanel中顯示圖示的方法JS
- Xamarin XAML語言教程將XAML設計的UI顯示到介面UI
- Mac顯示/不顯示隱藏檔案教程!Mac
- 不同size class顯示不同的UIUI
- 在 CRM WebClient UI 中使用純 JavaScript 顯示 3D 足球效果WebclientUIJavaScript3D
- macbook 外接顯示器教程Mac
- C#教程第四課:迴圈控制語句C#
- Android UI 顯示原理分析小結AndroidUI
- win10在工作列顯示觸控板圖示教程_win10怎麼在工作列右下角顯示觸控板圖示Win10
- 在工作列上顯示圖示 (轉)
- 通過Behavior在RecycleView中隱藏顯示FABView
- Mac技巧:在macOS中訪達顯示路徑?Mac
- thinkphp控制器變數在模板中顯示PHP變數
- echarts 在 vue2 中的顯示問題EchartsVue
- 直播系統開發,實現在進度條中顯示文字顯示進度
- Janrain:資料顯示社交登入Google與Facebook的差距在縮小AIGo
- chat-gpt-google-extension: 在谷歌搜尋結果中同時顯示ChatGPT結果的chrome外掛Go谷歌ChatGPTChrome
- GitHub#C#:在終端裡面顯示一個UI視窗(TerminalGfx)GithubC#UI
- Easy UI Combobox顯示拼接欄位UI
- FloatingActionButton在RecycleView中滑動隱藏顯示View
- 在vim中顯示並編輯十六進位制
- 直播商城原始碼,PopupWindow選單在ListView中顯示原始碼View
- 在 Laravel 中動態 隱藏 / 顯示 API 欄位LaravelAPI
- number資料型別在查詢中的顯示資料型別
- bootstrap datepicker 在bootstrap modal中不顯示問題boot
- Word表格在WPS中顯示不全的解決
- js將選擇的圖片顯示在img中JS