1. 程式人生 > >IOS開發之瀑布流照片牆實現

IOS開發之瀑布流照片牆實現

想必大家已經對網際網路傳統的照片佈局方式司空見慣了,這種行列分明的佈局雖然對使用者來說簡潔明瞭,但是長久的使用難免會產生審美疲勞。現在網上流行一種叫做“瀑布流”的照片佈局樣式,這種行與列參差不齊的狀態著實給使用者眼前一亮的感覺,這種不規則的方式也吸引著我,現在我們就來一起實現它吧 :)
首先我們來看一下這種樣式佈局是如何體現的,請看示意圖:

別看這種介面的佈局好像毫無規律,其實它的排列還是很有規則的。我們拿手機螢幕舉個例子,我們把螢幕等寬的劃分為幾個區域,由於手機螢幕比較小,我們就按照網上
最普遍的把螢幕分為等寬的三列,然後將圖片載入在每一列中,在加入到列之前,要先判斷哪一列的高度最低,然後把圖片加到列高度最低的那列中。聽起來是不是有些拗口,說簡單點就是“哪列高度低就加哪”,這樣聽我一說是不是覺得原理其實很簡單嘛!
下面我們就來用程式去實現他吧!首先看一張效果圖。


新建一個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

這個類主要是用於顯示大圖片所用,使用者點選小圖片後,程式根據圖片的tag來獲取點選圖片的名字,然後將名字傳遞給該類,完成對相應圖片的顯示,當然這裡也要做對圖片的壓縮處理,使它顯示的時候不失真。(由於本人有點懶,這個類的介面美觀什麼的就懶得調了,多多包涵)示意圖如下:


以上就是這個例子的大部分程式碼了,如果要下載完整工程的話,請到我的上傳中下載。

現在看一下整個效果;