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 + "width:" + 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(百度網盤)
