2014年3月8日

Picture Viewer - android 版 以 ViewPager 搭配 FragmentStatePagerAdapter 來實現


如何在 android 中實現與系統相仿的照片瀏覽功能呢?這裡以 ViewPager 搭配 FragmentStatePagerAdapter 來實現系統 picture viewer 功能

ViewPager

  1. 是一個在 android-support-v4 才有的元件
  2. 其用法就像 ListView、GridView 一樣,需要一個 Adapter 來呈現畫面
  3. 可達到左右滑動就換頁的效果
  4. 通常與 Fragment 一起使用

FragmentStatePagerAdapter 與 FragmentPagerAdapter

  1. 都繼承至 PagerAdapter
  2. 每個 Item 都為 Fragment
  3. FragmentStatePagerAdapter:只保留當前 Fragment ( Item ),當頁面離開畫面後,就會被消毀,在頁面需要顯示時,生成新的頁面
  4. FragmentPagerAdapter:每個生成後的 Fragment ( Item ) 都會在内存中,適用於內容較少的靜態頁面

code解說

  1. view 包裝:ViewPager > FragmentStatePagerAdapter > Fragment > ProgressBar 與 ImageView
  2. ViewPager 的用法就像 ListView 與 GridView 一樣,這裡就不特別列出說明
  3. FragmentStatePagerAdapter 需實作 getCount 與 getItem method ( 這裡只列出 getItem method 說明)

@Override
public Fragment getItem(int position) {
 String vo = (String) this.list.get(position);
 Log.i("ruby", "Fragment getItem:" + position + " imageID:" + vo);
 
 ImageFragment fragment = ImageFragment.newInstance(vo);
 fragment.setContext(this.context);  //用 setContext 將資料放入 fragment
 return fragment;
}

※ 這裡值得注意的是,如何在 init 時將資料傳入 Fragment,我曾試過直接用 Bundle 傳遞,事實證明無法直接用 Bundle 傳遞,而 newInstance 這個 method,是我自訂的 method,主要用來實現將資料傳入 Fragment,method 內容稍後解釋

4. Fragment 的部分除了需要實現上面所述的 newInstance method 外,還必須複寫 onCreate、onCreateView 與 onActivityCreated method ( method 執行順序 newInstance -> onCreate -> onCreateView -> onActivityCreated )

public static ImageFragment newInstance(String path) {
 ImageFragment imageFragment = new ImageFragment();
    Bundle args = new Bundle();
    args.putString("path", path);
    imageFragment.setArguments(args);  //將 args 參數綁在 Fragment 物件上
    return imageFragment;
}

//初始化(onCreate → onCreateView → onActivityCreated)
@Override
public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 path = getArguments() != null ? getArguments().getString("path") : "";
 Log.i("ruby", "ImageFragment path:"+path);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 Log.i("ruby", "onCreateView");
 //先取得View才能載入元件
 view = inflater.inflate(R.layout.fragment_image, container, false);
 
 //載入物件
 initComponent();
 
 //初始化
 initServerData();
 
 rl_fragmentImage_bg.setOnClickListener(new OnClickListener() {
  
  @Override
  public void onClick(View v) {
   Log.i("ruby", "background click");
  }
 });
 
    return view;
}

@Override  
public void onActivityCreated(Bundle savedInstanceState) { 
 Log.i("ruby", "onActivityCreated");
    super.onActivityCreated(savedInstanceState);  
    loadBitmap();  
} 

※ 這部分要注意 setArguments 這個 method,他會將參數綁在 Fragment 物件上,然後可以用 getArguments method 承接
PS. Fragment 生命週期:onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart -> onResume

5. 載入圖片的部分

public void loadBitmap() {  
 bitmap = BitmapFactory.decodeResource(getResources(), Integer.parseInt(path));  //從Resource取image
 
 //開另外的thread載入圖片
 BitmapWorkerTask task = new BitmapWorkerTask(this.context, imgv_fragmentImage_imageView, pg_fragmentImage_progressBar, bitmap.getWidth(), bitmap.getHeight());  
 task.execute(Integer.valueOf(path));
 
 //設定圖片的縮放功能
 new ImageViewHelper(context.getResources().getDisplayMetrics(), imgv_fragmentImage_imageView , bitmap);
} 

※ 這裡要要注意的是 BitmapWorkerTask,之前直接在 main thread 加載圖片發現不管怎麼試都試不出來,後來才想到應該開另一個 thread 來載入圖片,而這裡使用 AsyncTask 來加載圖片
另外,ImageViewHelper 是做圖片縮放的部分,詳細介紹可參考 "Android 寫一個 ImageViewHelper 來處理圖片放大縮小等功能 (matrix)" 這篇文章,這裡就不再解說

由於代碼過長,這裡就不全部列出,只列出較重要與較難懂的部分,有興趣的朋友可以下載範例邊測試邊看解說,比較容易看懂!

References