IOS開發之瀑布流照片牆實現
阿新 • • 發佈:2019-01-05
想必大家已經對網際網路傳統的照片佈局方式司空見慣了,這種行列分明的佈局雖然對使用者來說簡潔明瞭,但是長久的使用難免會產生審美疲勞。現在網上流行一種叫做“瀑布流”的照片佈局樣式,這種行與列參差不齊的狀態著實給使用者眼前一亮的感覺,這種不規則的方式也吸引著我,現在我們就來一起實現它吧 :)
首先我們來看一下這種樣式佈局是如何體現的,請看示意圖:
別看這種介面的佈局好像毫無規律,其實它的排列還是很有規則的。我們拿手機螢幕舉個例子,我們把螢幕等寬的劃分為幾個區域,由於手機螢幕比較小,我們就按照網上
最普遍的把螢幕分為等寬的三列,然後將圖片載入在每一列中,在加入到列之前,要先判斷哪一列的高度最低,然後把圖片加到列高度最低的那列中。聽起來是不是有些拗口,說簡單點就是“哪列高度低就加哪”,這樣聽我一說是不是覺得原理其實很簡單嘛!
下面我們就來用程式去實現他吧!首先看一張效果圖。
新建一個Xcode工程,名字隨便取吧(或者叫Album都可以),然後新建三個類,分別是:MainViewController用於控制主介面, MyScrollView繼承自UIScrollView用於控制介面滾動,以及圖片的管理類ImageLoader。
我們先來看一下ImageLoader類,它是一個圖片的處理類,負責對取到的圖片進行等比例壓縮,以至於載入到UImageView中時不會失真,程式碼如下:
#import "ImageLoader.h" @interface ImageLoader () @end @implementation ImageLoader @synthesize imagesArray = _imagesArray; + (ImageLoader *)shareInstance{ static ImageLoader *loader = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ loader = [[ImageLoader alloc] init]; }); return loader; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. } /*載入圖片*/ - (void)loadImage:(NSMutableArray *)array{ self.imagesArray = array; } /* 壓縮圖片,根據圖片的大小按比例壓縮 width:每列試圖的寬度 返回一個UIImageView */ - (UIImageView *)compressImage:(float)width imageName:(NSString *)name{ UIImageView *imgView = [[UIImageView alloc] init]; imgView.image = [UIImage imageNamed:name]; float orgi_width = [imgView image].size.width; float orgi_height = [imgView image].size.height; //按照每列的寬度,以及圖片的寬高來按比例壓縮 float new_width = width - 5; float new_height = (width * orgi_height)/orgi_width; //重置imageView的尺寸 [imgView setFrame:CGRectMake(0, 0, new_width, new_height)]; return imgView; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)dealloc{ [self.imagesArray release]; [super dealloc]; } @end
這裡將ImageLoader類設定成了單例,以便於在MainViewContrioller和MyScrollView中獲取。
下面是MyScrollView的程式碼片段:
#import "MyScrollView.h" #define COORDINATE_X_LEFT 5 #define COORDINATE_X_MIDDLE MY_WIDTH/3 + 5 #define COORDINATE_X_RIGHT MY_WIDTH/3 * 2 + 5 #define PAGESIZE 21 @interface MyScrollView () @end @implementation MyScrollView @synthesize mainScroll = _mainScroll; @synthesize isOnce = _isOnce; @synthesize imagesName = _imagesName; @synthesize loadedImageDic = _loadedImageDic; @synthesize leftColumHeight = _leftColumHeight; @synthesize midColumHeight = _midColumHeight; @synthesize rightColumHeight = _rightColumHeight; @synthesize loadedImageArray = _loadedImageArray; @synthesize imgTag = _imgTag; @synthesize imgTagDic = _imgTagDic; @synthesize imageLoad = _imageLoad; @synthesize page = _page; + (MyScrollView *)shareInstance{ static MyScrollView *instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] initWithFrame:CGRectMake(0, 0, MY_WIDTH, MY_HEIGHT)]; }); return instance; } /* 初始化scrollView的委託以及背景顏色,不顯示它的水平,垂直顯示條 */ - (id)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if(self){ self.delegate = self; self.backgroundColor = [UIColor blackColor]; self.pagingEnabled = NO; self.showsHorizontalScrollIndicator = NO; self.showsVerticalScrollIndicator = NO; self.isOnce = YES; self.loadedImageDic = [[NSMutableDictionary alloc] init]; self.loadedImageArray = [[NSMutableArray alloc] init]; self.imgTagDic = [[NSMutableDictionary alloc] init]; //初始化列的高度 self.leftColumHeight = 3.0f; self.midColumHeight = 3.0f; self.rightColumHeight = 3.0f; self.imgTag = 10086; self.page = 1; [self initWithPhotoBox]; } return self; } /* 將scrollView介面分為大小相等的3個部分,每個部分為一個UIView, 並設定每一個UIView的tag */ - (void)initWithPhotoBox{ UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, MY_WIDTH/3, self.frame.size.height)]; UIView *middleView = [[UIView alloc] initWithFrame:CGRectMake(leftView.frame.origin.x + MY_WIDTH/3, 0, MY_WIDTH/3, self.frame.size.height)]; UIView *rightView = [[UIView alloc] initWithFrame:CGRectMake(middleView.frame.origin.x + MY_WIDTH/3, 0, MY_WIDTH/3, self.frame.size.height)]; //設定三個部分的tag leftView.tag = 100; middleView.tag = 101; rightView.tag = 102; //設定背景顏色 [leftView setBackgroundColor:[UIColor clearColor]]; [middleView setBackgroundColor:[UIColor clearColor]]; [rightView setBackgroundColor:[UIColor clearColor]]; [self addSubview:leftView]; [self addSubview:middleView]; [self addSubview:rightView]; //第一次載入圖片 self.imageLoad = [ImageLoader shareInstance]; for(int i = 0; i < PAGESIZE; i++){ NSString *imageName = [self.imageLoad.imagesArray objectAtIndex:i]; UIImageView *imgView = [self.imageLoad compressImage:MY_WIDTH/3 imageName:imageName]; [self addImage:imgView name:imageName]; [self checkImageIsVisible]; } //第一頁 self.page = 1; [self adjustContentSize:NO]; } /*調整scrollview*/ - (void)adjustContentSize:(BOOL)isEnd{ UIView *leftView = [self viewWithTag:100]; UIView *middleView = [self viewWithTag:101]; UIView *rightView = [self viewWithTag:102]; if(_leftColumHeight >= _midColumHeight && _leftColumHeight >= _rightColumHeight){ self.contentSize = leftView.frame.size; }else{ if(_midColumHeight >= _rightColumHeight){ self.contentSize = middleView.frame.size; }else{ self.contentSize = rightView.frame.size; } } } /* 得到最短列的高度 */ - (float)getTheShortColum{ if(_leftColumHeight <= _midColumHeight && _leftColumHeight <= _rightColumHeight){ return _leftColumHeight; }else{ if(_midColumHeight <= _rightColumHeight){ return _midColumHeight; }else{ return _rightColumHeight; } } } /* 新增一張圖片 規則:根據每一列的高度來決定,優先載入列高度最短的那列 重新設定圖片的x,y座標 imageView:圖片檢視 imageName:圖片名 */ - (void)addImage:(UIImageView *)imageView name:(NSString *)imageName{ //圖片是否載入 if([self.loadedImageDic objectForKey:imageName]){ return; } //若圖片還未載入則儲存 [self.loadedImageDic setObject:imageView forKey:imageName]; [self.loadedImageArray addObject:imageView]; [self imageTagWithAction:imageView name:imageName]; float width = imageView.frame.size.width; float height = imageView.frame.size.height; //判斷哪一列的高度最低 if(_leftColumHeight <= _midColumHeight && _leftColumHeight <= _rightColumHeight){ UIView *leftView = [self viewWithTag:100]; [leftView addSubview:imageView]; //重新設定座標 [imageView setFrame:CGRectMake(2, _leftColumHeight, width, height)]; _leftColumHeight = _leftColumHeight + height + 3; [leftView setFrame:CGRectMake(0, 0, MY_WIDTH/3, _leftColumHeight)]; }else{ if(_midColumHeight <= _rightColumHeight){ UIView *middleView = [self viewWithTag:101]; [middleView addSubview:imageView]; [imageView setFrame:CGRectMake(2, _midColumHeight, width, height)]; _midColumHeight = _midColumHeight + height + 3; [middleView setFrame:CGRectMake(MY_WIDTH/3, 0, MY_WIDTH/3, _midColumHeight)]; }else{ UIView *rightView = [self viewWithTag:102]; [rightView addSubview:imageView]; [imageView setFrame:CGRectMake(2, _rightColumHeight, width, height)]; _rightColumHeight = _rightColumHeight + height + 3; [rightView setFrame:CGRectMake(2 * MY_WIDTH/3, 0, MY_WIDTH/3, _rightColumHeight)]; } } } /* 將圖片tag儲存,以及為UIImageView新增事件響應 */ - (void)imageTagWithAction:(UIImageView *)imageView name:(NSString *)imageName{ //將要顯示圖片的tag儲存 imageView.tag = self.imgTag; [self.imgTagDic setObject:imageName forKey:[NSString stringWithFormat:@"%d", imageView.tag]]; self.imgTag++; //圖片新增事件響應 UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageClickWithTag:)]; tapRecognizer.delegate = self; imageView.userInteractionEnabled = YES; [imageView addGestureRecognizer:tapRecognizer]; [tapRecognizer release]; } /* //若三列中最短列距離底部高度超過30畫素,則請求載入新的圖片 */ - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ //可視檢查 [self checkImageIsVisible]; if((self.contentOffset.y + self.frame.size.height) - [self getTheShortColum] > 30){ [self pullRefreshImages]; } } /* 上拉時載入新的圖片 */ - (void)pullRefreshImages{ int index = self.page *PAGESIZE; int imgNum = [self.imageLoad.imagesArray count]; if(index >= imgNum){ //圖片載入完畢 [self adjustContentSize:YES]; [MyToast showWithText:@"沒有更多圖片"]; }else{ if((imgNum - self.page*PAGESIZE) > PAGESIZE){ for (int i = index; i < PAGESIZE; i++) { NSString *imageName = [self.imageLoad.imagesArray objectAtIndex:i]; UIImageView *imgView = [self.imageLoad compressImage:MY_WIDTH/3 imageName:imageName]; [self addImage:imgView name:imageName]; [self checkImageIsVisible]; } }else{ for (int i = index; i < imgNum; i++) { NSString *imageName = [self.imageLoad.imagesArray objectAtIndex:i]; UIImageView *imgView = [self.imageLoad compressImage:MY_WIDTH/3 imageName:imageName]; [self addImage:imgView name:imageName]; [self checkImageIsVisible]; } } self.page++; } [self adjustContentSize:NO]; } /* 檢查圖片是否可見,如果不在可見視線內,則把圖片替換為nil */ - (void)checkImageIsVisible{ for (int i = 0; i < [self.loadedImageArray count]; i++) { UIImageView *imgView = [self.loadedImageArray objectAtIndex:i]; if((self.contentOffset.y - imgView.frame.origin.y) > imgView.frame.size.height || imgView.frame.origin.y > (self.frame.size.height + self.contentOffset.y)){ //不顯示圖片 imgView.image = nil; }else{ //重新根據tag值顯示圖片 NSString *imageName = [self.imgTagDic objectForKey:[NSString stringWithFormat:@"%d", imgView.tag]]; if((NSNull *)imageName == [NSNull null]){ return; } UIImageView *view = [self.imageLoad compressImage:MY_WIDTH/3 imageName:imageName]; imgView.image = view.image; } } } //點選圖片事件響應 - (void)imageClickWithTag:(UITapGestureRecognizer *)sender{ UIImageView *view = (UIImageView *)sender.view; NSString *imageName = [self.imgTagDic objectForKey:[NSString stringWithFormat:@"%d", view.tag]]; NSLog(@"%@", imageName); PhotoViewController *photoView = [[PhotoViewController alloc] init]; photoView.imageName = imageName; [self addSubview:photoView.view]; } - (void)dealloc{ [self.imagesName release]; [self.imgTagDic release]; [self.loadedImageArray release]; [super dealloc]; } @end
在這個類中,我將三列等寬的UIView加入到ScrollView中,每次加入一張圖片的時候都會去判斷一下哪一列的高度最低。為了避免手機被圖片的佔用記憶體過高導致程式
崩潰的問題,我還作了一個照片可見性的邏輯判斷,具體函式名為checkImageIsVisible,當照片不可見時,將imageView中的image設為nil,一旦照片出現了就根據照片的tag
獲取到照片名重新設定image。
最後,當照片加載出來以後當然不能少了點選檢視相簿的功能啊!於是新建一個UIViewController類取名為:PhotoViewController,程式碼如下:
#import "PhotoViewController.h"
@interface PhotoViewController ()
@end
@implementation PhotoViewController
@synthesize headView = _headView;
@synthesize mainView = _mainView;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view setFrame:CGRectMake(0, 0, MY_WIDTH, MY_HEIGHT)];
[self.view setBackgroundColor:[UIColor blackColor]];
self.headView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, MY_WIDTH, 50)];
UIButton *cancelBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 20, 40, 30)];
[cancelBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[cancelBtn setTitle:@"取消" forState:UIControlStateNormal];
[self.headView addSubview:cancelBtn];
[cancelBtn addTarget:self action:@selector(closePhotoView) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.headView];
self.mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 80, MY_WIDTH, MY_HEIGHT)];
[self.view addSubview:self.mainView];
}
- (void)viewWillAppear:(BOOL)animated{
UIImageView *imageV = [self compressImage:MY_WIDTH imageName:_imageName];
[self.mainView addSubview:imageV];
}
- (UIImageView *)compressImage:(float)width imageName:(NSString *)name{
UIImageView *imgView = [[UIImageView alloc] init];
imgView.image = [UIImage imageNamed:name];
float orgi_width = [imgView image].size.width;
float orgi_height = [imgView image].size.height;
//按照每列的寬度,以及圖片的寬高來按比例壓縮
float new_width = width - 5;
float new_height = (width * orgi_height)/orgi_width;
//重置imageView的尺寸
[imgView setFrame:CGRectMake(0, 0, new_width, new_height)];
return imgView;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//關閉
- (void)closePhotoView{
[self.view removeFromSuperview];
}
- (void)dealloc{
[self.headView release];
[self.mainView release];
[super dealloc];
}
@end