• 手把手教你写移动端瀑布流控件布局篇


    1.了解每个平台的基础的布局控件:

    Android:FrameLayout,LinearLayout,AbsoluteLayout(已废弃) (http://developer.android.com/guide/topics/ui/declaring-layout.html)以及强大的ViewGroup。

    Windows Phone:Grid,StackPanel,Canvas (http://msdn.microsoft.com/zh-cn/library/windowsphone/develop/jj207042(v=vs.105).aspx

    iOS:强大的UIView给开发者自己够构造布局的空间。

    2.思路设计图

    3.思路剖析

    这里先给两个图起个名字:第一个叫LinearLayout,第二个叫CanvasLayout 。这是现在比较流行的布局方式,Android有些人的博客写是采用第一中方式写的。我不做评判,先简单介绍下。

    LinearLayout 是按照列数来构造列数个垂直排列的容器控件,然后当数据来的时候,先拿到高度最低的列,根据计算好的列宽,把图片等比例缩放,然后把数据插入到相应的列中,维护多个容器。

    CanvasLayout是按照列数和控件的宽度,计算出每列的宽度,然后根据计算好的列宽,对图片进行等比例缩放得到每个Item的宽高,然后计算Item的坐标,有了Point和Size就可以放入布局中。维护坐标系统。

    我个人更倾向于第二种方式。(其实我也没试过第一种,有兴趣者可以跟帖讨论)

    猜测原因:

    LinearLayout要维护多个Container对象,在后面会提到的deleteItem,SelectItem或者在ItemAnimation时,都要找相应的Item进行位置变化或者UI 重新渲染等,而这些又是对相应的Container进行的,这样就要不断维持和判断Item和Container的关系,项目久了之后可能就出现混乱和难以维护的情况。

    CanvasLayout维护的就是一个值类型或者一个结构体,相应的操作也是在一个容器中进行的,再加上有自己的坐标系统,操作起来也比较方便。

    4.代码部分

    好了,说了这么多,看看具体代码是怎么来写吧。

    iOS:

    UIPinterestView.h

     1 #import <UIKit/UIKit.h>
     2 
     3 @class UIPinterestViewCell;
     4 @class UIPinterestView;
     5 @protocol UIPinterestViewDataSource;
     6 
     7 @protocol UIPinterestViewDelegate <NSObject,UIScrollViewDelegate>
     8 
     9 @required
    10 - (CGFloat)pinterestView:(UIPinterestView *)pinterestView heightForItemAtIndex:(NSUInteger)index;
    11 @optional
    12 - (void)pinterestView:(UIPinterestView *)pinterestView didSelectItemAtIndex:(NSUInteger)index;
    13 
    14 @end
    15 
    16 @interface UIPinterestView : UIScrollView
    17 {
    18     NSMutableArray *_columnHeightRecoder;
    19     NSUInteger _oldItemsCount;
    20     BOOL _needLayout;
    21 }
    22 
    23 @property(nonatomic,assign) id<UIPinterestViewDelegate> delegate;
    24 @property(nonatomic,assign) id<UIPinterestViewDataSource> dataSource;
    25 @property(nonatomic,assign) CGFloat itemWidth;
    26 
    27 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier;
    28 - (void)reloadData;
    29 
    30 @end
    31 
    32 @protocol UIPinterestViewDataSource <NSObject>
    33 
    34 @required
    35 - (NSUInteger)numberOfItemsInGridView:(UIPinterestView *)pinterestView;
    36 - (NSUInteger)columnsNumPerRow:(UIPinterestView *)pinterestView;
    37 - (CGFloat)marginPerCell:(UIPinterestView *)pinterestView;
    38 - (UIPinterestViewCell *)pinterestView:(UIPinterestView *)pinterestView cellForItemAtIndex:(NSUInteger)index;
    39 
    40 @end

    UIPinterestView.m

      1 #import "UIPinterestView.h"
      2 #import "UIPinterestViewCell.h"
      3 #import "UIView+UITappedBlocks.h"
      4 
      5 @implementation UIPinterestView
      6 
      7 - (id)initWithFrame:(CGRect)frame
      8 {
      9     self = [super initWithFrame:frame];
     10     if (self) {
     11         [self shareInit];
     12     }
     13     return self;
     14 }
     15 
     16 - (void)dealloc
     17 {
     18     if (_columnHeightRecoder != nil) {
     19         [_columnHeightRecoder release];
     20         _columnHeightRecoder = nil;
     21     }
     22     [super dealloc];
     23 }
     24 
     25 - (void)shareInit
     26 {
     27     if(_columnHeightRecoder == nil) {
     28         _columnHeightRecoder = [[NSMutableArray alloc] init];
     29     } else {
     30         [_columnHeightRecoder release];
     31         _columnHeightRecoder = nil;
     32         [self shareInit];
     33     }
     34     _oldItemsCount = 0;
     35     _needLayout = YES;
     36 }
     37 
     38 -(void)layoutSubviews
     39 {
     40     [super layoutSubviews];
     41     if(!_needLayout) {
     42         return;
     43     }
     44     [self layoutCells];
     45     _needLayout = NO;
     46 }
     47 
     48 - (void)layoutCells
     49 {
     50     NSUInteger itemCount = [self.dataSource numberOfItemsInGridView:self];
     51     NSUInteger columnCount = [self columnCount];
     52     CGFloat columnMargin = [self columnMargin];
     53     self.itemWidth = (self.frame.size.width - (columnCount + 1) * columnMargin) / columnCount;
     54     
     55     for (int i = _oldItemsCount; i < itemCount; i++) {
     56         
     57         UIPinterestViewCell *cell = [self.dataSource pinterestView:self cellForItemAtIndex:i];
     58         [cell whenTapped:^{
     59             if([self.delegate respondsToSelector:@selector(pinterestView:didSelectItemAtIndex:)]) {
     60                 [self.delegate pinterestView:self didSelectItemAtIndex:i];
     61             }
     62         }];
     63         CGFloat itemHeight = [self.delegate pinterestView:self heightForItemAtIndex:i];
     64         int minYColumnNum = [self getMinHeightColumnNum:columnCount];//获取最小列的列号
     65         CGFloat minYHeight = [[_columnHeightRecoder objectAtIndex:minYColumnNum] floatValue];
     66         CGFloat x = minYColumnNum * (self.itemWidth + columnMargin) + columnMargin;
     67         CGFloat y = minYHeight + columnMargin;
     68         cell.frame = CGRectMake(x, y, self.itemWidth, itemHeight);
     69         [self addSubview:cell];
     70         _columnHeightRecoder[minYColumnNum] = [NSNumber numberWithFloat:(y + itemHeight)];
     71         
     72     }
     73     int maxYColumnNum = [self getMaxHeightColumnNum:columnCount];
     74     CGFloat maxHeight = [[_columnHeightRecoder objectAtIndex:maxYColumnNum] floatValue];
     75     CGSize gridSize = CGSizeMake(self.contentSize.width, maxHeight);
     76     [self updateContentSize:gridSize];
     77 }
     78 
     79 - (void)updateContentSize:(CGSize)gridSize
     80 {
     81     self.contentSize = gridSize;
     82 }
     83 
     84 - (int)columnCount
     85 {
     86     return [self.dataSource columnsNumPerRow:self];
     87 }
     88 
     89 - (CGFloat)columnMargin
     90 {
     91     return [self.dataSource marginPerCell:self];
     92 }
     93 
     94 
     95 - (int)getMinHeightColumnNum:(NSUInteger)columnCount
     96 {
     97     if(_columnHeightRecoder.count == 0) {
     98         for (int i=0; i<columnCount; i++) {
     99             [_columnHeightRecoder insertObject:[NSNumber numberWithFloat:0.0f] atIndex:i];
    100         }
    101     }
    102     int num = 0;
    103     for (int i=0; i<columnCount; i++) {
    104         CGFloat minY = [[_columnHeightRecoder objectAtIndex:num] floatValue];
    105         CGFloat y = [[_columnHeightRecoder objectAtIndex:i] floatValue];
    106         if(minY > y) {
    107             num = i;
    108         }
    109     }
    110     return num;
    111 }
    112 
    113 - (int)getMaxHeightColumnNum:(NSUInteger)columnCount
    114 {
    115     int num = 0;
    116     for (int i=0; i<columnCount; i++) {
    117         CGFloat maxY = [[_columnHeightRecoder objectAtIndex:num] floatValue];
    118         CGFloat y = [[_columnHeightRecoder objectAtIndex:i] floatValue];
    119         if(maxY < y) {
    120             num = i;
    121         }
    122     }
    123     return num;
    124 }
    125 
    126 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
    127 {
    128     return nil;
    129 }
    130 
    131 
    132 - (void)reloadData
    133 {
    134     _needLayout = YES;
    135     [self setNeedsLayout];
    136 }
    137 
    138 @end

    UIPinterestViewCell.h

    1 #import <UIKit/UIKit.h>
    2 
    3 @interface UIPinterestViewCell : UIView
    4 
    5 @property(nonatomic,retain) NSString *identifier;
    6 
    7 - (id)initWithReuseIndentifier:(NSString *)identifier;
    8 
    9 @end

    UIPinterestViewCell.m

     1 #import "UIPinterestViewCell.h"
     2 
     3 @implementation UIPinterestViewCell
     4 
     5 - (void)dealloc
     6 {
     7     self.identifier = nil;
     8     [super dealloc];
     9 }
    10 
    11 - (id)initWithReuseIndentifier:(NSString *)identifier
    12 {
    13     if (self = [super init]) {
    14         self.identifier = identifier;
    15     }
    16     return self;
    17 }
    18 
    19 @end

    MainViewController.h

     1 #import <UIKit/UIKit.h>
     2 #import "UIPinterestView.h"
     3 
     4 @interface MainViewController : UIViewController<UIPinterestViewDelegate,UIPinterestViewDataSource>
     5 {
     6     UIPinterestView *_pinterestView;
     7     NSMutableArray *_itemsArray;
     8 }
     9 
    10 
    11 @end

    MainViewController.m

      1 #import "MainViewController.h"
      2 #import "UIPinterestViewCell.h"
      3 #import "ItemModel.h"
      4 
      5 @interface MainViewController ()
      6 
      7 @end
      8 
      9 @implementation MainViewController
     10 
     11 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
     12 {
     13     self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
     14     if (self) {
     15         // Custom initialization
     16     }
     17     return self;
     18 }
     19 
     20 - (void)viewDidLoad
     21 {
     22     [super viewDidLoad];
     23     [self setUpViews];
     24     [self initData];
     25 }
     26 
     27 - (void)didReceiveMemoryWarning
     28 {
     29     [super didReceiveMemoryWarning];
     30     // Dispose of any resources that can be recreated.
     31 }
     32 
     33 - (void)dealloc
     34 {
     35     if(_pinterestView != nil) {
     36         [_pinterestView release];
     37         _pinterestView = nil;
     38     }
     39     if(_itemsArray != nil) {
     40         [_itemsArray release];
     41         _itemsArray = nil;
     42     }
     43     [super dealloc];
     44 }
     45 
     46 #pragma mark - View
     47 - (void)setUpViews
     48 {
     49     _pinterestView = [[UIPinterestView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
     50     _pinterestView.dataSource = self;
     51     _pinterestView.delegate = self;
     52     [self.view addSubview:_pinterestView];
     53 }
     54 
     55 #pragma mark - Delegate
     56 
     57 - (CGFloat)pinterestView:(UIPinterestView *)pinterestView heightForItemAtIndex:(NSUInteger)index
     58 {
     59     ItemModel *model = (ItemModel *)[_itemsArray objectAtIndex:index];
     60     if (model == nil) {
     61         return 0.0f;
     62     } else {
     63         return model.ratio * pinterestView.itemWidth;
     64     }
     65 }
     66 
     67 - (NSUInteger)numberOfItemsInGridView:(UIPinterestView *)pinterestView
     68 {
     69     return _itemsArray.count;
     70 }
     71 
     72 - (NSUInteger)columnsNumPerRow:(UIPinterestView *)pinterestView
     73 {
     74     return 3;
     75 }
     76 
     77 - (CGFloat)marginPerCell:(UIPinterestView *)pinterestView
     78 {
     79     return 5.0f;
     80 }
     81 
     82 - (UIPinterestViewCell *)pinterestView:(UIPinterestView *)pinterestView cellForItemAtIndex:(NSUInteger)index
     83 {
     84     NSString *cellIdentifier = @"cell";
     85     UIPinterestViewCell *cell = [pinterestView dequeueReusableCellWithIdentifier:cellIdentifier];
     86     if (cell == nil) {
     87         cell = [[UIPinterestViewCell alloc] init];
     88         cell.backgroundColor = [UIColor greenColor];
     89     }
     90     return cell;
     91 }
     92     
     93 #pragma mark - Data
     94 - (void)initData
     95 {
     96     _itemsArray = [[NSMutableArray alloc] init];
     97     
     98     for (int i=0; i<2; i++) {
     99         ItemModel *model = [[ItemModel alloc] init];
    100         model.name = @"Test1";
    101         model.ratio = 0.5f;
    102         [_itemsArray addObject:model];
    103         [model release];
    104         
    105         model = [[ItemModel alloc] init];
    106         model.name = @"Test2";
    107         model.ratio = 0.8f;
    108         [_itemsArray addObject:model];
    109         [model release];
    110         
    111         model = [[ItemModel alloc] init];
    112         model.name = @"Test3";
    113         model.ratio = 1.2f;
    114         [_itemsArray addObject:model];
    115         [model release];
    116         
    117         model = [[ItemModel alloc] init];
    118         model.name = @"Test4";
    119         model.ratio = 1.5f;
    120         [_itemsArray addObject:model];
    121         [model release];
    122         
    123         model = [[ItemModel alloc] init];
    124         model.name = @"Test5";
    125         model.ratio = 1.2f;
    126         [_itemsArray addObject:model];
    127         [model release];
    128     }
    129 }
    130 
    131 @end

    这部分代码是我已经实现过的一个版本的简略版,基本上只是表达出布局的概念。

    效果图:

    ===================================屌丝的分割线====================================

    Android:

    首先,类似ListView一样构造PinterestView继承自ViewGroup,由于我的android水平还是小白级别,所以写的比较简单,对onMeasure和onLayout的处理可能也不完善,但是毕竟是实现了,欢迎拍砖。代码:

    PinterestView.java

     1 public class PinterestView extends ViewGroup {
     2     
     3     private static String PinterestViewLog = "PinterestViewLog";
     4     
     5     //datasource
     6     private int _columnCount = 3;
     7     private int _columnMargin = 5;
     8     private PinterestBaseAdapter _adapter = null;
     9     private int _columnRecorder[] =  new int[_columnCount];
    10     
    11     //flag
    12     private boolean _isLayouting = false;
    13     private boolean _needsLayout = true;
    14 
    15     public PinterestView(Context context) {
    16         super(context);
    17     }
    18     
    19     public PinterestView(Context context,AttributeSet attrs) {
    20         super(context,attrs);
    21     }
    22     
    23     public PinterestView(Context context,AttributeSet attrs,int defStyle) {
    24         super(context,attrs,defStyle);
    25     }
    26     
    27     @Override
    28     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    29         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    30         
    31         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    32         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    33         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    34         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    35         
    36         if (_adapter != null) {
    37 //            Log.i(PinterestViewLog,"widthMode:" + widthMode + "heightMode:" + heightMode + "widthSize:" + widthSize + "heightSize:" + heightSize);
    38 //            Log.i(PinterestViewLog,"onMeasure");
    39         }
    40     }
    41 
    42     @Override
    43     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    44         _isLayouting = true;
    45         if (_needsLayout) {
    46             Log.i(PinterestViewLog,"onLayout");
    47             layoutChildren();
    48             _needsLayout = false;
    49         }
    50         _isLayouting = false;
    51     }
    52     
    53     public void setAdapter(PinterestBaseAdapter adapter) {
    54         Log.i(PinterestViewLog,"setAdapter");
    55         _adapter = adapter;
    56     }
    57     
    58     public PinterestBaseAdapter getAdapter() {
    59         return _adapter;
    60     }
    61     
    62     private void layoutChildren() {
    63         
    64         int itemCount = _adapter.getCount();
    65         int totalWidth = this.getWidth();
    66         int itemWidth = (totalWidth - _columnMargin * (_columnCount + 1)) / _columnCount;
    67         for(int i=0; i<itemCount; i++) {
    68             View childView = this.obtainView(i);
    69             this.addViewInLayout(childView, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
    70 //            this.addView(childView);            
    71             int minColumnIndex = this.getMinHeightColumn();
    72             int itemHeight = _adapter.getItemHeight(i,itemWidth);
    73             int left = itemWidth * minColumnIndex + _columnMargin * (minColumnIndex + 1);
    74             int top = _columnRecorder[minColumnIndex] + _columnMargin;
    75             int right = left + itemWidth;
    76             int bottom = top + itemHeight;
    77 //            Log.i(PinterestViewLog,"left:" + left + "top:" + top + "right:" + right + "bottom:" + bottom);
    78             childView.measure(itemWidth, itemHeight);//设置view 宽高
    79             childView.layout(left, top, right, bottom); //设置view 位置
    80             _columnRecorder[minColumnIndex] = bottom;
    81         }
    82     }
    83     
    84     private View obtainView(int position) {
    85         return _adapter.getView(position, null, this);
    86     }
    87     
    88     private int getMinHeightColumn() {
    89         int minIndex = 0;
    90         for (int i = 0; i < _columnRecorder.length; i++) {
    91             int minHeight = _columnRecorder[minIndex];
    92             int newHeight = _columnRecorder[i];
    93             if(minHeight > newHeight) {
    94                 minIndex = i;
    95             }
    96         }
    97         return minIndex;
    98     }

    需要一个Adapter.

    PinterestAdapter.java:

     1 public class PinterestBaseAdapter implements Adapter{
     2 
     3     private Context _context = null;
     4     private LayoutInflater _inflater = null;
     5     private ArrayList _models = null;
     6     
     7     public PinterestBaseAdapter(Context context,ArrayList models) {
     8         _context = context;
     9         _inflater = LayoutInflater.from(context);
    10         _models = models;
    11     }
    12     
    13     @Override
    14     public int getCount() {
    15         return _models.size();
    16     }
    17 
    18     @Override
    19     public Object getItem(int position) {
    20         return _models.get(position);
    21     }
    22 
    23     @Override
    24     public long getItemId(int position) {
    25         return 0;
    26     }
    27 
    28     @Override
    29     public int getItemViewType(int position) {
    30         return 0;
    31     }
    32 
    33     @Override
    34     public View getView(int position, View convertView, ViewGroup parent) {
    35         //这里的ConvertView是在PinterestView中获取的,目前全部是null,等到后面讲道virtualize的时候会有改变
    36         //由于这里先不考虑性能,所以也没有使用placehoder等机制
    37         if (convertView == null) {
    38             convertView = _inflater.inflate(R.layout.item_pinterest, null);
    39         }
    40         ItemModel model = (ItemModel) this.getItem(position);
    41         TextView nameTv = (TextView) convertView.findViewById(R.id.name_tv);
    42 //        nameTv.setText(model.getName());
    43         return convertView;
    44     }
    45     
    46     public int getItemHeight(int position,int itemWidth) {
    47         ItemModel model = (ItemModel) this.getItem(position);
    48         return (int)(itemWidth * model.getRatio());
    49     }
    50 
    51     @Override
    52     public int getViewTypeCount() {
    53         return 0;
    54     }
    55 
    56     @Override
    57     public boolean hasStableIds() {
    58         return false;
    59     }
    60 
    61     @Override
    62     public boolean isEmpty() {
    63         return false;
    64     }
    65 
    66     @Override
    67     public void registerDataSetObserver(DataSetObserver observer) {
    68         
    69     }
    70 
    71     @Override
    72     public void unregisterDataSetObserver(DataSetObserver observer) {
    73         
    74     }
    75 
    76 }

    item_pinterest.xml

     1 <?xml version="1.0" encoding="utf-8"?>
     2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     3     android:layout_width="match_parent"
     4     android:layout_height="match_parent"
     5     android:background="#FF00FF00"
     6     android:orientation="vertical" >
     7     
     8     <TextView android:id="@+id/name_tv"
     9         android:layout_width="match_parent"
    10         android:layout_height="match_parent"
    11         android:layout_gravity="center"
    12         android:textSize="18sp"/>
    13 
    14 </LinearLayout>

    最后Activity和main.xml:

    Activity_Pinterest.java和Activity_Pinterest.xml

     1 public class PinterestActivity extends Activity {
     2 
     3     private ArrayList<ItemModel> _models = new ArrayList<ItemModel>();
     4 
     5     private PinterestView _pinterestView = null;
     6 
     7     @Override
     8     protected void onCreate(Bundle savedInstanceState) {
     9         super.onCreate(savedInstanceState);
    10         this.setContentView(R.layout.activity_pinterest);
    11         for (int i = 0; i < 2; i++) {
    12             initData();
    13         }
    14         initView();
    15 
    16     }
    17 
    18     private void initView() {
    19         _pinterestView = (PinterestView) this.findViewById(R.id.pinterest_list);
    20         PinterestBaseAdapter adapter = new PinterestBaseAdapter(this, _models);
    21         _pinterestView.setAdapter(adapter);
    22     }
    23 
    24     private void initData() {
    25 
    26         ItemModel model1 = new ItemModel();
    27         model1.setName("Test1");
    28         model1.setRatio(0.5);
    29         _models.add(model1);
    30 
    31         model1 = new ItemModel();
    32         model1.setName("Test2");
    33         model1.setRatio(0.8);
    34         _models.add(model1);
    35 
    36         model1 = new ItemModel();
    37         model1.setName("Test3");
    38         model1.setRatio(1.2);
    39         _models.add(model1);
    40 
    41         model1 = new ItemModel();
    42         model1.setName("Test4");
    43         model1.setRatio(1.5);
    44         _models.add(model1);
    45 
    46         model1 = new ItemModel();
    47         model1.setName("Test5");
    48         model1.setRatio(1.2);
    49         _models.add(model1);
    50 
    51     }
    52 }
    1 <?xml version="1.0" encoding="utf-8"?>
    2  <com.nextstep.pinterestview.controls.PinterestView
    3          xmlns:android="http://schemas.android.com/apk/res/android"
    4         android:id="@+id/pinterest_list"
    5         android:layout_width="match_parent"
    6         android:layout_height="match_parent"/>

    效果图:

    这只是个简单的demo,有很多需要注意的地方在后续我的学习伴随着我的文章中会慢慢优化的。谢谢大家指点。

    ===================================屌丝的分割线====================================

    Windows Phone:

    关于windows phone我更喜欢用MVVM,以及微软的一套控件绑定机制,如果喜欢把Android的MVC硬套过来的同学,可以对比下我前面Android实现模式,俗话说,入乡随俗,是吧。

    PinterestView.cs

      1 using System;
      2 using System.Collections;
      3 using System.Collections.Generic;
      4 using System.Diagnostics;
      5 using System.Reflection;
      6 using System.Windows;
      7 using System.Windows.Controls;
      8 namespace Pinterest.Controls
      9 {
     10     public class PinterestView : Control
     11     {
     12 
     13         #region Fields
     14 
     15         private List<object> _items = new List<object>();
     16         private DataTemplate _itemTemplate = null;
     17         private int _columnCount = 0;
     18         private double _columnMargin = 0.0;
     19         private List<double> _columnRecorder = new List<double>();
     20         private double _showWidth = 480;//need changed more good????
     21         
     22         private Canvas _itemContainer;
     23         
     24         #endregion
     25 
     26         #region Constructor
     27         public PinterestView()
     28         {
     29             base.DefaultStyleKey = typeof(PinterestView);
     30             ShareInitData();
     31         }
     32 
     33         #endregion
     34 
     35         #region Override
     36 
     37         public override void OnApplyTemplate()
     38         {
     39             base.OnApplyTemplate();
     40             GetViews();
     41             LoadView();
     42         }
     43 
     44         #endregion
     45 
     46         #region Method
     47 
     48         private void GetViews()
     49         {
     50             _itemContainer = (Canvas)base.GetTemplateChild("PART_ItemsContainer");
     51         }
     52 
     53         private void ShareInitData()
     54         {
     55             InitColumnRecorder();
     56         }
     57 
     58         private void InitColumnRecorder()
     59         {
     60             //Init ColumnRecorder
     61             for (int i = 0; i < _columnCount; i++)
     62             {
     63                 _columnRecorder.Add(0.0);
     64             }
     65         }
     66 
     67         private void LoadView()
     68         {
     69             double totalWidth = this.Width;
     70             double itemWidth = (_showWidth - _columnMargin * (_columnCount + 1)) / _columnCount;
     71             
     72             for (int i = 0; i < _items.Count; i++)
     73             {
     74                 object obj = _items[i];
     75                 double ratio = this.GetModelRatio(obj);
     76                 double itemHeight = itemWidth * ratio;
     77                 int minColumnIndex = GetMinColumnIndex();
     78                 double x = minColumnIndex * itemWidth + (minColumnIndex + 1) * _columnMargin;
     79                 double y = _columnRecorder[minColumnIndex] + _columnMargin;
     80 
     81                 Debug.WriteLine("x:" + x + "y:" + y + "" + itemWidth + "height:" + itemHeight);
     82 
     83                 //Make ItemView
     84                 PinterestViewItem itemView = GenerateItemView();
     85                 //Bind To Model
     86                 BindToModel(itemView,obj);
     87                 //Set ItemViewSize
     88                 SetItemSize(itemView, new Rect(x, y, itemWidth, itemHeight));
     89                 //Add To Container
     90                 _itemContainer.Children.Add(itemView);
     91                 _columnRecorder[minColumnIndex] = y + itemHeight;
     92             }
     93         }
     94 
     95         private PinterestViewItem GenerateItemView()
     96         {
     97             return new PinterestViewItem();//no virtualize
     98         }
     99 
    100         private void BindToModel(PinterestViewItem itemView,object model) 
    101         {
    102             if(model == null) return;
    103             itemView.SetValue(FrameworkElement.DataContextProperty,model);
    104             if (_itemTemplate != null)
    105             {
    106                 itemView.Content = model;
    107                 itemView.ContentTemplate = _itemTemplate;
    108             }
    109         }
    110 
    111         private void SetItemSize(FrameworkElement element,Rect rect)
    112         {
    113             element.SetValue(FrameworkElement.WidthProperty, rect.Width);
    114             element.SetValue(FrameworkElement.HeightProperty, rect.Height);
    115             Canvas.SetLeft(element, rect.X);
    116             Canvas.SetTop(element, rect.Y);
    117         }
    118 
    119         private int GetMinColumnIndex()
    120         {
    121             int min = 0;
    122             for (int i = 0; i < _columnCount; i++)
    123             {
    124                 double minHeight = _columnRecorder[min];
    125                 double newHeight = _columnRecorder[i];
    126                 if (newHeight < minHeight)
    127                 {
    128                     min = i;
    129                 }
    130             }
    131             return min;
    132         }
    133 
    134         private double GetModelRatio(object obj)
    135         {
    136             double ratio = 1.0;
    137             PropertyInfo property = obj.GetType().GetProperty("Ratio");
    138             if (property != null && property.CanRead)
    139             {
    140                 ratio = (double)property.GetValue(obj);
    141             }
    142             else
    143             {
    144                 throw new InvalidOperationException("The model must has the Ratio property.");
    145             }
    146             return ratio;
    147         }
    148 
    149         private void AddItems(IEnumerable dataSource)
    150         {
    151             if (dataSource != null)
    152             {
    153                 IEnumerator enumerator = dataSource.GetEnumerator();
    154                 try
    155                 {
    156                     while (enumerator.MoveNext())
    157                     {
    158                         object current = enumerator.Current;
    159                         _items.Add(current);
    160                     }
    161                 }
    162                 finally
    163                 {
    164                     IDisposable disposable = enumerator as IDisposable;
    165                     if (disposable != null)
    166                     {
    167                         disposable.Dispose();
    168                     }
    169                 }
    170                
    171             }
    172         }
    173 
    174         #endregion
    175 
    176         #region DependencyProperites
    177 
    178         #region ItemsSource DependencyProperty
    179         public IEnumerable ItemsSource
    180         {
    181             get { return (IEnumerable)GetValue(ItemsSourceProperty); }
    182             set { SetValue(ItemsSourceProperty, value); }
    183         }
    184 
    185         public static readonly DependencyProperty ItemsSourceProperty =
    186             DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(PinterestView), new PropertyMetadata(null, OnItemsSourceChanged));
    187 
    188         private static void OnItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    189         {
    190             PinterestView view = (PinterestView)sender;
    191             view.OnItemsSourceChanged(e);
    192         }
    193 
    194         private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
    195         {
    196             IEnumerable dataSource = e.NewValue as IEnumerable;
    197             this.AddItems(dataSource);
    198         }
    199         #endregion
    200        
    201 
    202         #region ItemTemplate DependencyProperty
    203         
    204         #endregion
    205         public DataTemplate ItemTemplate
    206         {
    207             get { return (DataTemplate)GetValue(ItemTemplateProperty); }
    208             set { SetValue(ItemTemplateProperty, value); }
    209         }
    210 
    211         public static readonly DependencyProperty ItemTemplateProperty =
    212             DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(PinterestView), new PropertyMetadata(null,OnItemTemplateChanged));
    213 
    214         private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    215         {
    216             PinterestView view = (PinterestView)d;
    217             view.OnItemTemplateChanged(e);
    218         }
    219 
    220         private void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e)
    221         {
    222              _itemTemplate = (DataTemplate)e.NewValue;
    223         }
    224 
    225         #region ColumnMargin DependencyProperty
    226 
    227         public double ColumnMargin
    228         {
    229             get { return (double)GetValue(ColumnMarginProperty); }
    230             set { SetValue(ColumnMarginProperty, value); }
    231         }
    232 
    233         public static readonly DependencyProperty ColumnMarginProperty =
    234             DependencyProperty.Register("ColumnMargin", typeof(double), typeof(PinterestView), new PropertyMetadata(-1.0,OnColumnMarginChanged));
    235 
    236         private static void OnColumnMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    237         {
    238             ((PinterestView)d).OnColumnMarginChanged(e);
    239         }
    240 
    241         private void OnColumnMarginChanged(DependencyPropertyChangedEventArgs e)
    242         {
    243             _columnMargin = (double)e.NewValue;
    244         }
    245 
    246         #endregion
    247 
    248         #region ColumnCount DependecyProperty
    249 
    250         public int ColumnCount
    251         {
    252             get { return (int)GetValue(ColumnCountProperty); }
    253             set { SetValue(ColumnCountProperty, value); }
    254         }
    255 
    256         public static readonly DependencyProperty ColumnCountProperty =
    257             DependencyProperty.Register("ColumnCount", typeof(int), typeof(PinterestView), new PropertyMetadata(-1,OnColumnCountChanged));
    258 
    259         private static void OnColumnCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    260         {
    261             PinterestView view = (PinterestView)d;
    262             view.OnColumnCountChanged(e);
    263         }
    264 
    265         private void OnColumnCountChanged(DependencyPropertyChangedEventArgs e)
    266         {
    267             _columnCount = (int)e.NewValue;
    268             InitColumnRecorder();
    269         }
    270 
    271         #endregion
    272         
    273         #endregion
    274 
    275 
    276 
    277     }
    278 }

    PinterestView.xaml

    <Style TargetType="Controls:PinterestView">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Controls:PinterestView">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Canvas x:Name="PART_ItemsContainer" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    PinterestViewItem.cs

     1 using System.Windows.Controls;
     2 
     3 namespace Pinterest.Controls
     4 {
     5     public class PinterestViewItem : ContentControl
     6     {
     7         public PinterestViewItem()
     8         {
     9             base.DefaultStyleKey = typeof(PinterestViewItem);
    10         }
    11     }
    12 }

    PinterestViewItem.xaml

     1 <Style TargetType="Controls:PinterestViewItem">
     2         <Setter Property="Background"
     3                 Value="Transparent" />
     4         <Setter Property="BorderThickness"
     5                 Value="0" />
     6         <Setter Property="BorderBrush"
     7                 Value="Transparent" />
     8         <Setter Property="Padding"
     9                 Value="0" />
    10         <Setter Property="HorizontalContentAlignment"
    11                 Value="Stretch" />
    12         <Setter Property="VerticalContentAlignment"
    13                 Value="Stretch" />
    14         <Setter Property="CacheMode"
    15                 Value="BitmapCache" />
    16         <Setter Property="Template">
    17             <Setter.Value>
    18                 <ControlTemplate TargetType="Controls:PinterestViewItem">
    19                     <Grid x:Name="PART_ContainerLayout"
    20                           HorizontalAlignment="Stretch"
    21                           VerticalAlignment="Stretch">
    22                         <ContentControl x:Name="PART_ContainerHolder"
    23                                         Margin="{TemplateBinding Margin}"
    24                                         HorizontalAlignment="Stretch"
    25                                         VerticalAlignment="Stretch"
    26                                         HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
    27                                         VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
    28                                         Content="{TemplateBinding Content}"
    29                                         ContentTemplate="{TemplateBinding ContentTemplate}" />
    30                     </Grid>
    31                 </ControlTemplate>
    32             </Setter.Value>
    33         </Setter>
    
    34 </Style>

    MainPage.xaml

     1 <phone:PhoneApplicationPage x:Class="Pinterest.MainPage"
     2                             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4                             xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
     5                             xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
     6                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     7                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     8                             mc:Ignorable="d"
     9                             FontFamily="{StaticResource PhoneFontFamilyNormal}"
    10                             FontSize="{StaticResource PhoneFontSizeNormal}"
    11                             Foreground="{StaticResource PhoneForegroundBrush}"
    12                             SupportedOrientations="Portrait"
    13                             xmlns:CT="clr-namespace:Pinterest.Controls"
    14                             Orientation="Portrait"
    15                             shell:SystemTray.IsVisible="True"
    16                             DataContext="{Binding MainVm, Mode=OneWay, Source={StaticResource Locator}}">
    17     <!--LayoutRoot is the root grid where all page content is placed-->
    18     <Grid x:Name="LayoutRoot" Background="Transparent">
    19         <Grid.RowDefinitions>
    20             <RowDefinition Height="Auto"/>
    21             <RowDefinition Height="*"/>
    22         </Grid.RowDefinitions>
    23 
    24         <!-- LOCALIZATION NOTE:
    25             To localize the displayed strings copy their values to appropriately named
    26             keys in the app's neutral language resource file (AppResources.resx) then
    27             replace the hard-coded text value between the attributes' quotation marks
    28             with the binding clause whose path points to that string name.
    29 
    30             For example:
    31 
    32                 Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}"
    33 
    34             This binding points to the template's string resource named "ApplicationTitle".
    35 
    36             Adding supported languages in the Project Properties tab will create a
    37             new resx file per language that can carry the translated values of your
    38             UI strings. The binding in these examples will cause the value of the
    39             attributes to be drawn from the .resx file that matches the
    40             CurrentUICulture of the app at run time.
    41          -->
    42 
    43         <!--TitlePanel contains the name of the application and page title-->
    44         <StackPanel x:Name="TitlePanel"
    45                     Grid.Row="0"
    46                     Margin="12,17,0,28">
    47             <TextBlock Text="MY APPLICATION"
    48                        Style="{StaticResource PhoneTextNormalStyle}"
    49                        Margin="12,0" />
    50             <TextBlock Text="page name"
    51                        Margin="9,-7,0,0"
    52                        Style="{StaticResource PhoneTextTitle1Style}" />
    53         </StackPanel>
    54 
    55         <!--ContentPanel - place additional content here-->
    56         <Grid x:Name="ContentPanel"
    57               Grid.Row="1">
    58             <CT:PinterestView x:Name="PinterestView"
    59                               ItemsSource="{Binding Items}"
    60                               ColumnCount="3"
    61                               ColumnMargin="5.0">
    62                 <CT:PinterestView.ItemTemplate>
    63                     <DataTemplate>
    64                         <Grid Background="Green" />
    65                     </DataTemplate>
    66                 </CT:PinterestView.ItemTemplate>
    67             </CT:PinterestView>
    68         </Grid>
    69         <!--Uncomment to see an alignment grid to help ensure your controls are
    70             aligned on common boundaries.  The image has a top margin of -32px to
    71             account for the System Tray. Set this to 0 (or remove the margin altogether)
    72             if the System Tray is hidden.
    73 
    74             Before shipping remove this XAML and the image itself.-->
    75         <!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />-->
    76     </Grid>
    77 
    78 </phone:PhoneApplicationPage>

    MainPage.xaml.cs 

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Net;
     5 using System.Windows;
     6 using System.Windows.Controls;
     7 using System.Windows.Navigation;
     8 using Microsoft.Phone.Controls;
     9 using Microsoft.Phone.Shell;
    10 using Pinterest.Resources;
    11 using Pinterest.ViewModels;
    12 
    13 namespace Pinterest
    14 {
    15     public partial class MainPage : PhoneApplicationPage
    16     {
    17         private MainViewModel _mainViewModel;
    18 
    19         // Constructor
    20         public MainPage()
    21         {
    22             InitializeComponent();
    23             _mainViewModel = (MainViewModel)this.DataContext;
    24             // Sample code to localize the ApplicationBar
    25             //BuildLocalizedApplicationBar();
    26         }
    27 
    28         protected override void OnNavigatedTo(NavigationEventArgs e)
    29         {
    30             base.OnNavigatedTo(e);
    31             _mainViewModel.NavigateTo(this, e);
    32         }
    33 
    34         protected override void OnNavigatedFrom(NavigationEventArgs e)
    35         {
    36             _mainViewModel.NavigateFrom(this, e);
    37             base.OnNavigatedFrom(e);
    38         }
    39 
    40         // Sample code for building a localized ApplicationBar
    41         //private void BuildLocalizedApplicationBar()
    42         //{
    43         //    // Set the page's ApplicationBar to a new instance of ApplicationBar.
    44         //    ApplicationBar = new ApplicationBar();
    45 
    46         //    // Create a new button and set the text value to the localized string from AppResources.
    47         //    ApplicationBarIconButton appBarButton = new ApplicationBarIconButton(new Uri("/Assets/AppBar/appbar.add.rest.png", UriKind.Relative));
    48         //    appBarButton.Text = AppResources.AppBarButtonText;
    49         //    ApplicationBar.Buttons.Add(appBarButton);
    50 
    51         //    // Create a new menu item with the localized string from AppResources.
    52         //    ApplicationBarMenuItem appBarMenuItem = new ApplicationBarMenuItem(AppResources.AppBarMenuItemText);
    53         //    ApplicationBar.MenuItems.Add(appBarMenuItem);
    54         //}
    55     }
    56 }

    MainViewModel: 

     1 using Microsoft.Phone.Controls;
     2 using Pinterest.Helpers;
     3 using Pinterest.Models;
     4 using System.Collections.Generic;
     5 using System.Collections.ObjectModel;
     6 using System.Windows.Navigation;
     7 
     8 namespace Pinterest.ViewModels
     9 {
    10     public class MainViewModel : ViewModelBase
    11     {
    12 
    13         #region Fields
    14 
    15         private ObservableCollection<ItemModel> _items = new ObservableCollection<ItemModel>();
    16 
    17         #endregion
    18 
    19         #region Constructor
    20         public MainViewModel()
    21         {
    22 
    23         }
    24 
    25         #endregion
    26 
    27         #region Properties
    28         
    29 
    30         public ObservableCollection<ItemModel> Items
    31         {
    32             get { return _items; }
    33             set
    34             {
    35                 if (value != _items)
    36                 {
    37                     _items = value;
    38                     this.NotifyOfPropertyChange(()=>this.Items);
    39                 }
    40             }
    41         }
    42         
    43         #endregion
    44 
    45         #region Method
    46 
    47         private void InitData()
    48         {
    49             List<ItemModel> list = new List<ItemModel>();
    50 
    51             for (int i = 0; i < 2; i++)
    52             {
    53                 ItemModel model1 = new ItemModel { Name = "Test1", Ratio = 0.5 };
    54                 ItemModel model2 = new ItemModel { Name = "Test1", Ratio = 0.8 };
    55                 ItemModel model3 = new ItemModel { Name = "Test1", Ratio = 1.2 };
    56                 ItemModel model4 = new ItemModel { Name = "Test1", Ratio = 1.5 };
    57                 ItemModel model5 = new ItemModel { Name = "Test1", Ratio = 1.2 };
    58                 list.Add(model1);
    59                 list.Add(model2);
    60                 list.Add(model3);
    61                 list.Add(model4);
    62                 list.Add(model5);
    63             }
    64 
    65             list.ToObservable<ItemModel>(this.Items);
    66         }
    67 
    68         #endregion
    69 
    70         #region Override
    71 
    72         public override void NavigateTo(PhoneApplicationPage page, NavigationEventArgs e)
    73         {
    74             InitData();
    75         }
    76 
    77 
    78         public override void NavigateFrom(PhoneApplicationPage page, NavigationEventArgs e)
    79         {
    80 
    81         }
    82 
    83         #endregion
    84 
    85     }
    86 }

    效果图

     

    5.代码下载地址:

    http://pan.baidu.com/s/1AB4TT(百度网盘)



  • 相关阅读:
    【洛谷 P1896】[SCOI2005]互不侵犯(状压dp)
    【洛谷 P4289】[HAOI2008]移动玩具(搜索)
    【洛谷 SP283】NAPTIME
    【洛谷 P4342】[IOI1998]Polygon(DP)
    【洛谷 SP2878】Knights of the Round Table(双联通分量)
    【洛谷 P4168】[Violet]蒲公英(分块)
    【洛谷 P4180】【模板】严格次小生成树[BJWC2010](倍增)
    数学总结
    个人码风
    【洛谷 P3304】[SDOI2013]直径(树的直径)
  • 原文地址:https://www.cnblogs.com/AFIBlogs/p/3377890.html
Copyright © 2020-2023  润新知