顯示具有 Picture Viewer 標籤的文章。 顯示所有文章
顯示具有 Picture Viewer 標籤的文章。 顯示所有文章

2014/03/08

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

Picture Viewer - ios版 以雙層 scrollView 實現


照片瀏覽是很常見的功能,但如何實現與系統相仿的照片瀏覽功能呢?這裡就介紹一下如何在 ios 中實現系統照片瀏覽功能 ( 包含照片換頁,照片縮放功能 )
ios 做圖片縮放與 android 不同的地方在於,ios 是對 scrollView 做縮放即可,android 則需縮放 imageView

外層 scrollView

  1. init 設定 ( View 定義在 MainView.xib 中,這裡只設置部分屬性 )

scrView.contentSize = CGSizeMake(1700, 480);  //設定顯示內容大小
scrView.showsHorizontalScrollIndicator = NO;  //scroll 時不顯示橫向 scroll bar

2. 實作 Delegate 判斷目前頁數

//scroll結束後調用
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
 CGFloat pageWidth = scrollView.frame.size.width;
 NSInteger page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
 
 if (lastPage != page) 
 {
  MyScrollView *aView = (MyScrollView *)[scrView viewWithTag:100+lastPage];
  aView.zoomScale = 1.0;
  
  lastPage = page;
 }
}

3. 加入內層 scrollView

for (int i=0; i<5; i++)
{
 MyScrollView *ascrView = [[MyScrollView alloc] initWithFrame:CGRectMake(340*i, 0, 320, 480)];
 NSString *imgPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%d", i+1] ofType:@"jpg"];
        
 [ascrView showProgressView];
        ascrView.tag = 100+i;
        
 [subviewArray addObject:ascrView];
 [pathArray addObject:imgPath];
        
 //設定 timmer,用來顯示 ProgressView
 theTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(countTotalFrames:) userInfo:nil repeats:YES];

 [scrView addSubview:ascrView];
}

4. 設定 timmer 執行的 method

-(void)countTotalFrames:(NSTimer*)sender{
    NSObject *obj = sender.userInfo;
    
    MyScrollView *ascrView = [subviewArray objectAtIndex:lastPage];
    NSString *imgPath = [pathArray objectAtIndex:lastPage];
    
    //模擬 loading 畫面
    if(ascrView.progressView.progress < 1){
        ascrView.progressView.progress += 0.1;  //設定 progressView 進度,進度範圍為 0~1
    }else{
        [ascrView closeProgressView];
//        [theTimer invalidate];  //關閉 timmer
        ascrView.image = [UIImage imageWithContentsOfFile:imgPath];
    }
}

內層 scrollView

  1. init scrollView、imageView 與 progressView

self.delegate = self;
self.minimumZoomScale = 0.1;  //最小縮小比例
self.maximumZoomScale = 2;  //最大放大比例
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;

imageView  = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[self addSubview:imageView];

//init progressView
CGFloat widthSpace_iPhone = 20;
CGFloat widthSpace_iPad = 50;
CGFloat widthSpace;

isPad = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad;  //判斷是否為 pad

if(isPad){
    widthSpace = widthSpace_iPad;
}else{
    widthSpace = widthSpace_iPhone;
}

self.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(10, (self.frame.size.height-10)/2, self.frame.size.width-widthSpace, 30)];
self.progressView.progressViewStyle = UIProgressViewStyleDefault;
self.progressView.progress = 0;  //初始化進度

//將progressView縮放改變其高度
if(isPad){
    CGAffineTransform transform = CGAffineTransformMakeScale(1.0f, 5.0f);
    self.progressView.transform = transform;
}

2. 複寫 setImage method

- (void)setImage:(UIImage *)img
{
 imageView.image = img;
    
    //將原圖縮小符合螢幕大小
    imageView.frame = CGRectMake(0, 10, img.size.width, img.size.height);
    
    //設定原圖比例為最小縮小比例,即最小不能小於原圖
    self.zoomScale = [self getZoomScale];
    self.minimumZoomScale = self.zoomScale;
}

3. 設定 touch 縮放事件 ( 這裡是做 scrollView 的縮放來實現圖片縮放 )

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 UITouch *touch = [touches anyObject];
 
 if ([touch tapCount] == 2) //兩指click時觸發
 {
  CGFloat zs = self.zoomScale;
  zs = (zs == 1.0) ? self.maximumZoomScale : self.minimumZoomScale;
  
  [UIView beginAnimations:nil context:NULL];  //設置動畫
  [UIView setAnimationDuration:0.3];  //動畫總時間
  self.zoomScale = zs;  //設定滾動條默認的尺度
  [UIView commitAnimations];  //結束動畫
 }
}

※ minimumZoomScale 為滾動條最小尺度;maximumZoomScale 為滾動條最大尺度
4. 設定 scrollView 縮放完成後,重設滾動條的最大與最小值 ( 可縮放的最大為小值 )

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
 //縮放完觸發
 CGFloat zs = scrollView.zoomScale;
 zs = MAX(zs, self.minimumZoomScale);
 zs = MIN(zs, self.maximumZoomScale);
 
 [UIView beginAnimations:nil context:NULL];
 [UIView setAnimationDuration:0.3];
 scrollView.zoomScale = zs;
 [UIView commitAnimations];
}

接著就可以來測試一下摟! ( 以上只擷取部分代碼說明,如果有遇到問題的可以下載範例測試查看 )

References