Use collectionview to implement waterfall stream-transfer and collectionview waterfall
General idea of Algorithms
Let's talk about the general idea. Since the image size and position are different, we naturally think of the need to calculate the frame of each item, and then assign these frames to the UICollectionViewLayoutAttributes of the current item.
The two key steps for customizing UICollectionViewLayout are to reload the following methods:
-(Void) prepareLayout;
And
-(NSArray *) layoutAttributesForElementsInRect :( CGRect) rect;
So our idea is to calculate the frame of all items in the-(void) prepareLayout; method, and assign the value to the UICollectionViewLayoutAttributes of the current item. The image format is more intuitive:
Then the question is how to calculate the frame of each item.
Here we abstract the concept of a column:
In addition, we also need to maintain an array of storage heights COLUMNSHEIGHTS. The array contains n elements. n indicates all columns, for example, n = 3. The cached value indicates the height of the current column. In this example, COLUMNSHEIGHTS = [104,123, 89].
Then we put the items into the columns one by one, with the rule: from left to right, the items are preferentially placed in the shortest COLUMNSHEIGHTS column.
For example, the next item should be placed in the shortest column, that is, the third column:
This rule is used to loop down until all items are placed completely.
Details
Item. frame. origin:
Describe in natural language,
The coordinate x should be like this: (the number of the shortest column-1) x column width
The coordinate y should be like this: retrieve the height of the shortest column from COLUMNSHEIGHTS
Therefore, we need an algorithm to find the shortest column in the current COLUMNSHEIGHTS. The most direct method is the loop comparison of 0 (n) time complexity. Here, it is better because the data volume is small, if there is a large amount of data, you may need to consider the division and control method.
// Search for the column with the shortest height. The first column is 0.
-(NSUInteger) findShortestColumn {
NSUInteger shortestIndex = 0;
CGFloat shortestValue = MAXFLOAT;
NSUInteger index = 0; // cursor
For (NSNumber * columnHeight in self. COLUMNSHEIGHTS ){
If ([columnHeight floatValue] <shortestValue ){
ShortestValue = [columnHeight floatValue];
ShortestIndex = index;
}
Index ++;
}
Return shortestIndex;
}
After finding the shortest column, it is easy to express the x and y coordinates of the item:
NSUInteger origin_x = [self findShortestColumn] * [self columnWidth];
NSUInteger origin_y = [self. COLUMNSHEIGHTS [shtIndex] integerValue];
Item. frame. size. width:
The number of columns is determined by the user. Therefore, it is a variable and the column width columnWidth = self. collectionView. bounds. size. width/self. columnsCount can be obtained.
Then we stipulate that,By default, the width of an item is equal to columnWidth. When the height of the current column and the next column (such as the Red Square, that is, the current column is located in column 2, the next column is 3) is equal, it can span two columns. (Then look at the Red Square, because before it is put in, the height of the second column is 0, and the height of the third column is also 0, meeting the cross condition)
But!
If the following problem occurs:
That is to say, simply meetCurrent column and next columnHeight equality is not enough, because once this condition is met, it will continue to be in a cross state. Therefore, we need to add another condition to filter the results, even ifThe height of the current column is equal to that of the next column.. We can use a random number:
NSUInteger randomOfWhetherDouble = arc4random () % 100; // the random number indicates whether two rows are required.
Arc4random () % 100; A Random 0 ~ An integer of 100. Then we set a threshold, for example, 40. Only when both conditions are metThe height of the current column is equal to that of the next column.AndRandomOfWhetherDouble <40To implement cross-row. In other words, even if the current column and the next column are of the same height, there is only a 40% probability of a cross-row item. This ensures that items with different widths are randomly displayed!
The code for width is:
If (shtIndex <self. columnsCount-1 & [self. COLUMNSHEIGHTS [shtIndex] floatValue] = [self. COLUMNSHEIGHTS [shtIndex + 1] floatValue] & randomOfWhetherDouble <40 ){
Size_width = 2 * [self columnWidth];
} Else {
Size_width = [self columnWidth];
}
Item. frame. size. height:
This can be set as there is no special limit on the vertical height. For example, I stipulated that:
1. If it is cross, the height = width * (0.75 ~ 1 random)
Float extraRandomHeight = arc4random () % 25;
RetVal = 0.75 + (extraRandomHeight/100 );
Size_height = size_width * retVal;
2. If it is a single column, the height = width * (0.75 ~ 1.25 random)
Float extraRandomHeight = arc4random () % 50;
RetVal = 0.75 + (extraRandomHeight/100 );
Size_height = size_width * retVal; // The height is 0.75 ~ 1.25 times
Supplement
In actual tests, it is found that, even if the threshold value across items is adjusted to 0, that is, if the current column and the next column are equal to the height, 100% is displayed across, there are only a few scenarios that span each other. Why? The reason lies in the data type. The data types I used previously were CGFloat or float floating point. The probability of two floating point numbers being equal can be imagined. It is much better to change it to NSUInteger. In addition, in order to increase the probability of cross-scenario, I also used rounding. Take the figure as an example:
Let's take the remainder of an integer from the height of the item, for example, in the unit of 40, let the height of 40 get the remainder, and then let the height of the item cut off the remainder. The remaining height must be an integer multiple of 40.
The code is simple:
Size_height = size_height-(size_height % 40 );
This can reduce the heights in a certain range to a single height, which increases the probability of equal heights between the left and right columns and the possibility of cross-item conversion.
Then, assign the frame value of each item to the corresponding attributes during the loop process, and save the attributes to an array.
// Assign a value to attributes. frame and save it to self. itemsAttributes
NSIndexPath * indexPath = [NSIndexPath indexPathForItem: I inSection: 0];
UICollectionViewLayoutAttributes * attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath: indexPath];
Attributes. frame = CGRectMake (origin_x, origin_y, size_width, size_height );
[Self. itemsAttributes addObject: attributes];
Then return the following in the layoutAttributesForElementsInRect method:
-(NSArray *) layoutAttributesForElementsInRect :( CGRect) rect {
Return self. itemsAttributes;
}
Last
To slide the collectionView, we also need to set its ContentSize. In fact, we only need to change the height of ContentSize to the height of the longest column in COLUMNSHEIGHTS. The algorithm used to calculate the longest column in the array is similar to the algorithm used to calculate the shortest column.
-(CGSize) collectionViewContentSize {
CGSize size = self. collectionView. bounds. size;
NSUInteger longstIndex = [self findLongestColumn];
Float columnMax = [self. COLUMNSHEIGHTS [longstIndex] floatValue];
Size. height = columnMax;
Return size;
}
Effect
Vertical scroll 1
Vertical scroll 2
Horizontal scroll 1
Horizontal scroll 2