開源UI界面佈局框架MyLayout1.9發佈

MyLayout是一套功能全面的iOS開源UI界面佈局框架。它囊括了前端全部流行的界面佈局技術和解決方案,同時具備以下七大特色:

  • 功能強大。它能夠減小咱們在開發UI界面時所花費的時間以及減小須要適配多種設備而所消耗的時間。實踐代表使用MyLayout進行界面佈局時能夠減小几乎50%的工做量。前端

  • 性能優越。MyLayout內部實現是基於frame計算來完成佈局的,因此同等界面下性能是AutoLayout的5倍左右,所以複雜界面選擇MyLayout將是最佳實踐。git

  • 佈局體系豐富。MyLayout提供了iOS、Android、HTML/CSS等前端中的全部流行佈局實現。所以不管你以前工做在何種平臺上均可以選擇熟悉的佈局類上手進行開發操做。MyLayout還支持從服務器進行動態佈局下發的能力。github

  • 系統結合緊密。MyLayout能夠同時和AutoLayout技術進行結合使用,同時能夠用在XIB和Storyboard中進行可視化佈局,同時還支持SizeClass技術用於多設備適配處理。數組

  • 多語言實現。MyLayout提供了OC語言版本的實現,同時也提供了Swift語言版本的實現:TangramKit。兩者的語法和使用方式類似,您能夠任意選擇一種語言進行代碼佈局。bash

  • 國際化支持。MyLayout支持LTR和RTL兩種方向的佈局,其中的RTL模式能夠用來支持希伯來語系的佈局方式。服務器

  • 無版本限制。MyLayout並無操做系統版本上的使用限制,理論上它最低甚至能夠支持到iOS5.0。框架

下面的表格列出的是MyLayout所提供的九大布局類所實現的功能以及和其它系統的對標能力:ide

佈局類名 功能介紹 對標功能
MyLinearLayout 線性佈局
提供視圖依次從上往下或者從左往右進行單行單列排列的能力
iOS:UIStackView
Android:LinearLayout
Flutter:Row、Column
SwiftUI:HStack、VStack
MyFloatLayout 浮動佈局:
提供視圖經過上下左右浮動停靠而進行排列布局的能力
CSS: float
MyFlowLayout 流式佈局:
提供視圖按垂直或者水平方向依次進行排列而且在知足特定條件(一行內的數量和尺寸值知足約定值)後會換行進行繼續排列布局的能力
獨有
MyFlexLayout 彈性佈局:
提供一個盒內的子視圖能夠進行伸縮對齊和換行排列而且知足flex規約的佈局能力
CSS:flexbox
MyGridLayout 柵格佈局:
提供了一種基於單元格進行垂直和水平的無限拆分而進行佈局的能力,柵格佈局同時具備佈局動態下發的能力
CSS:相似Bootstrap、Grid
MyTableLayout 表格佈局:
提供基於行列控制的表格佈局的能力
Android: TableLayout、GridLayout
HTML: table、tr、td
MyRelativeLayout 相對佈局:
提供一種經過設置視圖之間的尺寸和位置的相互依賴約束來實現佈局的能力
iOS:AutoLayout
Android:RelativeLayout、PercentRelativeLayout、ConstraintLayout
MyFrameLayout 框架佈局:
提供視圖在父視圖上某個方位進行停靠以及層疊擺放佈局的能力
Android:FrameLayout
MyPathLayout 路徑佈局:
提供子視圖的位置經過數學函數運算而進行定位排列的能力
獨有
SizeClass 提供了根據屏幕尺寸和橫豎屏而進行差別佈局設置的能力。上述全部佈局都支持SizeClass的功能 iOS:SizeClass
CSS: 相似Bootstrap

在這些衆多佈局類中有些佈局類提供了子視圖的有規律的佈局排列,好比線性佈局、流式佈局、表格佈局、浮動佈局、路徑佈局、彈性佈局、柵格佈局。有些佈局類則提供了經過子視圖之間的約束限制來實現佈局排列,好比浮動佈局、相對佈局、框架佈局。有些佈局類則須要經過多個層次嵌套來實現界面需求,好比線性佈局、流式佈局、表格佈局、彈性佈局。有些佈局類則能夠把界面需求拍平而只用單層排版就能實現所需功能,好比浮動佈局、相對佈局、柵格佈局。有些佈局類則能夠實現一些特殊排列,好比路徑佈局能夠根據提供的數學函數來實現視圖根據特定路徑曲線來進行排列展現。有些佈局類則能夠提供從服務器進行動態下發以及用JSON進行佈局描述的能力,好比柵格佈局。有些佈局類則能夠實現和HTML/CSS對標的能力,好比浮動佈局和彈性佈局。所以在實現界面需求時,咱們能夠靈活運用。在選擇佈局時我將使用佈局類的優先級列出來,供你們參考:函數


浮動佈局->流式佈局->線性佈局->彈性佈局->柵格佈局->相對佈局->框架佈局->表格佈局->路徑佈局佈局


您能夠從以下地址下載這兩個版本的工程DEMO:

👉OC語言版本MyLayout:  github.com/youngsoft/M… 👉Swift語言版本TangramKit: github.com/youngsoft/T…

1.9.0新特性

此次1.9.0版本的升級不管是新功能的添加、代碼的重構、性能的提高都作了大量的改進,新增和改進的功能主要有:

  • 彈性佈局flexbox的實現MyFlexLayout
  • 最值約束
  • 視圖尺寸和位置的壓縮
  • 環繞和拉伸停靠的支持
  • 拖放類MyLayoutDragger實現佈局內視圖的拖放
  • iOS13的黑白模式的適配支持
  • 流式佈局自定義行內對齊
  • 流式佈局和浮動佈局對基線對齊的支持
  • 重構和添加了對佈局視圖進行佈局時的動畫支持能力
  • 完善和擴充對佈局和視圖尺寸自適應設置支持
  • 重構了流式佈局和相對佈局的實現,提高了全部佈局的性能
  • 修復了線上的BUG

下面是新版本的上述功能的詳細介紹:

1. 彈性佈局MyFlexLayout

flexbox是目前Web前端比較流行的佈局框架。它提供了一種在一個盒子內子視圖依次排列並能夠進行換行排列和進行拉伸和壓縮的功能。目前也有不少將flexbox移植到native客戶端的解決方案。固然flexbox也有必定的缺陷:好比不支持重疊覆蓋、不支持相對間距、不支持行和列間距的統一設置、不支持不規則排列等等問題。

在之前的版本中流式佈局MyFlowLayout就能夠實現flexbox的大多數特性而且在此基礎上進行了更多複雜功能的擴展。由於其語法和設置方式和flexbox不兼容,所以對於flexbox的喜好者來講是增長了學習和使用的成本。而此次的新版本則提供了一個新的佈局類:彈性佈局MyFlexLayout:

/*
 * 彈性佈局是爲了兼容flexbox語法而創建了一個佈局,它是從MyFlowLayout派生。在MyFlowLayout中也是支持相似flexbox的一些特性的
 * 由於它的屬性和flexbox不兼容,因此提供一個新的類MyFlexLayout來徹底支持flexbox.
 */
@interface MyFlexLayout:MyFlowLayout


/**
 用於彈盒佈局視圖自身的佈局設置
 */
@property(nonatomic, strong, readonly) id<MyFlexBox> myFlex;

@end
複製代碼

從上面的類定義中能夠看出這個佈局類是從流式佈局MyFlowLayout類派生,咱們能夠經過類中的myFlex屬性來進行彈性佈局視圖的相關屬性設置。myFlex中提供了鏈式語法以及屬性設置語法兩種操做形式,您能夠選擇喜歡的方式來操做和使用彈性佈局。下面是屬性myFlex的接口MyFlexBox的詳細定義:

@protocol MyFlexBox <MyFlexItem>

@property(nonatomic, strong) id<MyFlexBoxAttrs> attrs;

/**
 設置或檢索伸縮盒對象的子元素在父容器中的位置。默認值:MyFlexDirection_Row
 */
-(id<MyFlexBox> (^)(MyFlexDirection))flex_direction;
/**
 設置或檢索伸縮盒對象的子元素超出父容器時是否換行。默認值:MyFlexWrap_NoWrap
 */
-(id<MyFlexBox> (^)(MyFlexWrap))flex_wrap;
/**
 同時設置檢索伸縮盒對象的子元素在父容器中的位置和伸縮盒對象的子元素超出父容器時是否換行。兩者經過 | 運算進行組合
 */
-(id<MyFlexBox> (^)(int))flex_flow;
/**
 設置或檢索彈性盒子元素在主軸(橫軸)方向上的對齊方式。可選值爲:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Space_Between | MyFlexGravity_Space_Around 中的一個,默認值爲MyFlexGravity_Flex_Start
 */
-(id<MyFlexBox> (^)(MyFlexGravity))justify_content;
/**
 設置或檢索彈性盒子元素在側軸(縱軸)方向上的對齊方式。可選值爲:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一個,默認值爲MyFlexGravity_Flex_Start
 */
-(id<MyFlexBox> (^)(MyFlexGravity))align_items;
/**
 設置或檢索彈性盒堆疊伸縮行的對齊方式。可選值爲:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Between | MyFlexGravity_Around | MyFlexGravity_Stretch中的一個,默認值爲MyFlexGravity_Stretch
 */
-(id<MyFlexBox> (^)(MyFlexGravity))align_content;


/**
 指定主軸的子條目的數量。只有在flex_wrap設置爲wrap時纔有效。默認值是0表示會根據條目的尺寸自動進行換行。
 */
-(id<MyFlexBox> (^)(NSInteger))item_size;
/**
 指定佈局視圖中每頁的條目數量。這個值必須是item_size的倍數。
 */
-(id<MyFlexBox> (^)(NSInteger))page_size;
/**
 指定佈局會根據條目的尺寸自動排列,默認值是NO。
 */
-(id<MyFlexBox> (^)(BOOL))auto_arrange;


/**
 設置彈性盒的內邊距
 */
-(id<MyFlexBox> (^)(UIEdgeInsets))padding;
/**
 設置彈性盒內全部條目視圖之間的垂直間距
 */
-(id<MyFlexBox> (^)(CGFloat))vert_space;
/**
 設置彈性盒內全部條目視圖之間的水平間距
 */
-(id<MyFlexBox> (^)(CGFloat))horz_space;

@end
複製代碼

而對於彈性盒視圖中的條目子視圖(item)來講則能夠經過UIView的一個分類擴展提供的myFlex進行屬性設置:

@interface UIView(MyFlexLayout)

/**
 用於彈盒視圖中的子視圖的佈局設置。
 */
@property(nonatomic, strong, readonly) id<MyFlexItem> myFlex;

@end
複製代碼

條目視圖的myFlex屬性的實現接口:MyFlexItem的定義以下:

@protocol MyFlexItem

@property(nonatomic, strong, readonly) id<MyFlexItemAttrs> attrs;
@property(nonatomic, weak, readonly) __kindof UIView *view;


/**
 視圖的寬度設置,若是寬度設置爲大於0小於1則代表是相對於父視圖寬度的比重值,若是是MyLayoutSize.wrap則代表寬度自適應,若是是MyLayoutSize.fill則代表寬度和父視圖相等,若是是MyLayoutSize.empty則代表不設置寬度值。 其餘的值就是一個固定寬度值。
 */
-(id<MyFlexItem> (^)(CGFloat))width;


/**
 視圖的寬度設置,percent代表佔用父視圖寬度的百分比值,inc代表在百分比值的基礎上的增量值。
 */
-(id<MyFlexItem> (^)(CGFloat percent, CGFloat inc))width_percent;


/**
 最小寬度限制設置
 */
-(id<MyFlexItem> (^)(CGFloat))min_width;

/**
 最大寬度限制設置
 */
-(id<MyFlexItem> (^)(CGFloat))max_width;
/**
 視圖的高度設置,若是高度設置爲大於0小於1則代表是相對於父視圖高度的比重值,若是是MyLayoutSize.wrap則代表高度自適應,若是是MyLayoutSize.fill則代表高度和父視圖相等,若是是MyLayoutSize.empty則代表不設置高度值,其餘的值就是一個固定高度值。
 */
-(id<MyFlexItem> (^)(CGFloat))height;

/**
 視圖的高度設置,percent代表佔用父視圖高度的百分比值,inc代表在百分比值的基礎上的增量值。
 */
-(id<MyFlexItem> (^)(CGFloat percent, CGFloat inc))height_percent;


/**
 最小高度限制設置
 */
-(id<MyFlexItem> (^)(CGFloat))min_height;

/**
 最大高度限制設置
 */
-(id<MyFlexItem> (^)(CGFloat))max_height;

//視圖的外間距設置。
/**
 視圖的頂部外間距設置
 */
-(id<MyFlexItem> (^)(CGFloat))margin_top;
/**
 視圖的底部外間距設置
 */
-(id<MyFlexItem> (^)(CGFloat))margin_bottom;
/**
 視圖的左邊外間距設置
 */
-(id<MyFlexItem> (^)(CGFloat))margin_left;
/**
 視圖的右邊外間距設置
 */
-(id<MyFlexItem> (^)(CGFloat))margin_right;
/**
 視圖的四周外間距設置
 */
-(id<MyFlexItem> (^)(CGFloat))margin;
/**
 視圖的可視設置
 */
-(id<MyFlexItem> (^)(MyVisibility))visibility;


//添加到父視圖中
-(__kindof UIView* (^)(UIView*))addTo;

//添加子視圖
-(id<MyFlexItem> (^)(UIView*))add;

/**
 條目在彈盒中的排列順序,值越大越日後排。
 */
-(id<MyFlexItem> (^)(NSInteger))order;
/**
 設置或檢索彈性盒的擴展比率。默認值爲0表示不擴展
 */
-(id<MyFlexItem> (^)(CGFloat))flex_grow;
/**
 設置或檢索彈性盒的收縮比率。默認值爲1表示當條目尺寸超過彈性盒尺寸後會進行壓縮。值越大壓縮比越大
 */
-(id<MyFlexItem> (^)(CGFloat))flex_shrink;
/**
 設置或檢索彈性盒伸縮基準值。默認值爲MyFlex_Auto表示由其餘屬性決定,若是值爲大於0小於1則表示相對值,其餘爲一個固定的尺寸值。
 */
-(id<MyFlexItem> (^)(CGFloat))flex_basis;
/**
 設置或檢索彈性盒子元素自身在側軸(縱軸)方向上的對齊方式。可選值爲:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一個,默認值爲MyFlex_Auto
 */
-(id<MyFlexItem> (^)(MyFlexGravity))align_self;

@end
複製代碼

從上面的定義中能夠看出由於其設置和使用方法都和flexbox規約幾乎保持一致,所以對於熟悉flexbox的人來講使用幾乎是零成本。好比咱們用MyFlexLayout來實現下面這個界面:

彈性盒示例

代碼實現以下:

-(void)viewDidLoad{
    [super viewDidLoad];

   //用鏈式語法建立一個彈性佈局,寬度和父視圖一致,高度自適應
   MyFlexLayout *layout = MyFlexLayout.new.myFlex
  .flex_direction(MyFlexDirection_Row)
  .flex_wrap(MyFlexWrap_Wrap)
  .align_content(MyFlexGravity_Center)
  .align_items(MyFlexGravity_Flex_End)
  .vert_space(10)
  .horz_space(10)
  .padding(UIEdgeInsetsMake(10, 10, 10, 10))
  .marign_top(50)
  .width(MyLayoutSize.fill)
  .height(MyLayoutSize.wrap)
  .addTo(self.view);


 UILabel *itemA = UILabel.new.myFlex
.width(MyLayoutSize.fill)
.height(30)
.addTo(layout);

UILabel *itemB = UILabel.new.myFlex
.flex_grow(1)
.align_self(MyFlexGravity_Flex_Start)
.height(30)
.addTo(layout);

UILabel *itemC = UILabel.new.myFlex
.flex_grow(1)
.height(40)
.addTo(layout);

UILabel *itemD = UILabel.new.myFlex
.flex_grow(1)
.height(50)
.addTo(layout);

layout.backgroundColor = [UIColor grayColor];
itemA.text = @"A";
itemA.backgroundColor = [UIColor redColor];
itemB.text = @"B";
itemB.backgroundColor = [UIColor greenColor];
itemC.text = @"C";
itemC.backgroundColor = [UIColor blueColor];
itemD.text = @"D";
itemD.backgroundColor = [UIColor yellowColor];
}

複製代碼

除了使用鏈式語法進行佈局和條目樣式設置外,還能夠直接經過屬性賦值來進行樣式設置。您能夠經過MyFlexBox中的attrs以及MyFlexItem中的attrs這兩個數據成員來以屬性值的形式進行佈局的和條目的樣式設置。爲了更好的演示MyFlexLayout的使用,我在MyLayout的Demo工程中創建了一個Flex佈局(FlexLayout)。您能夠在那裏看到彈性佈局相關的全部操做。

2.最值約束

👉設想一個場景:某個視圖的寬度在豎屏下是屏幕寬度的一半,而在橫屏下則是屏幕高度的一半。換句話說就是視圖的寬度是屏幕寬度和高度中的最小值的一半。 👉再設想一個場景:某個視圖的右邊位置但願跟另外兩個視圖中最靠右的那個位置對齊,換句話說就是視圖的右邊位置是另外兩個視圖右邊位置的最大值。

咱們稱這種某個視圖的位置或者尺寸是一個位置集合或者尺寸集合中的最大值或者最小值的約束爲最值約束。用表達式以下:

位置 = MAX(位置1,位置2,位置3,...) 或者 位置 = MIN(位置1,位置2,位置3,...)
尺寸 = MAX(尺寸1,尺寸2,尺寸3,...)或者 尺寸 = MIN(尺寸1,尺寸2,尺寸3,...)
複製代碼

位置最值約束

MyLayout爲了實現對位置最值的支持,在數組類NSArray上創建了一個擴展分類:

//位置最值擴展分類
@interface NSArray(MyLayoutMostPos)
//從數組中獲得最小的位置值。要求數組的元素必須是MyLayoutPos或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostPos *myMinPos;
//從數組中獲得最小的位置值。要求數組的元素必須是MyLayoutPos或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostPos *myMaxPos;

@end

複製代碼

咱們能夠經過數組中的myMinPos和myMaxPos兩個只讀屬性來分別獲取最小值和最大值的最值對象,獲取位置最值對象時要求數組中的元素只能是NSNumber以及MyLayoutPos類的實例對象,它代表最值是這些具體數字或者位置對象中的最大或者最小值。好比下面的代碼:

//A視圖的左邊位置是B視圖左邊位置,C視圖右邊位置,100這三個值中的最小的一個
A.leftPos.equalTo(@[B.leftPos, C.rightPos, @100].myMinPos);
//A視圖的垂直居中位置是B視圖頂部位置、100、C視圖底部位置這三個值中的最大一個。
A.centerYPos.equalTo(@[B.topPos, @100, C.bottomPos].myMaxPos);

//A視圖的左邊位置是B視圖左邊位置+20、C視圖右邊位置-20 這兩個位置中的最大一個。
A.leftPos.equalTo(@[B.leftPos.clone(20), C.rightPos.clone(20)].myMaxPos);

複製代碼

在上面的最後一個例子中咱們看到使用了MyLayoutPos對象的clone方法,這個方法的做用是clone一個新的對象並帶上必定的偏移值。MyLayoutPos中的clone方法就是專門爲最值約束使用的,主要爲了解決那些獲取最值時但願在某個位置的偏移的場景。

目前只有相對佈局下的子視圖才支持位置最值約束設置,其餘佈局下的子視圖不支持。同時在設置位置最值約束的時候,要求數組內的元素的位置約束計算必需要在當前視圖的位置約束計算以前完成,不然獲得的結果將未可知。

尺寸最值

MyLayout爲了實現對尺寸最值的支持,在數組類NSArray上創建了一個擴展分類:

//尺寸最值擴展分類
@interface NSArray(MyLayoutMostSize)
//從數組中獲得最小的尺寸值。要求數組的元素必須是MyLayoutSize或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostSize *myMinSize;
//從數組中獲得最大的尺寸值。要求數組的元素必須是MyLayoutSize或者NSNumber類型
@property(nonatomic, readonly) MyLayoutMostSize *myMaxSize;

@end

複製代碼

咱們能夠經過數組中的myMinSize和myMaxSize兩個只讀屬性來分別獲取最小值和最大值的最值對象。獲取尺寸最值對象時要求數組中的元素只能是NSNumber以及MyLayoutSize類的實例對象,它代表最值是這些具體數字或者尺寸對象中的最大或者最小值。好比下面的例子:

//A視圖的寬度是B視圖的寬度,C視圖的高度,100這三個值中的最小的一個
A.widthSize.equalTo(@[B.widthSize, C.heightSize, @100].myMinSize);
//A視圖的高度是A視圖自身高度,B視圖高度的一半加20,100這三個值中的最大一個。
A.heightSize.equalTo(@[@(MyLayoutSize.wrap), B.heightSize.clone(20, 0.5), @100].myMaxSize);

複製代碼

在上面的最後一個例子中咱們看到使用了MyLayoutSize對象的clone方法,這個方法的做用是clone一個新的尺寸對象並帶上必定的倍數和增量值。咱們還能夠用一個特殊的尺寸值MyLayoutSize.wrap在最值數組中,它代表自身的尺寸也參與最值比較中。

最值尺寸約束設置,能夠應用在全部佈局下的視圖中以及佈局自己。可是在使用最值約束時,要求數組內的元素的尺寸約束計算必需要在當前視圖的尺寸約束計算以前完成,不然獲得的結果將未可知。

3.視圖尺寸和位置的壓縮

在一些場景中咱們但願當全部子視圖的尺寸總和超過佈局視圖的尺寸時爲了能讓全部子視圖都獲得徹底的顯示而須要對子視圖的尺寸進行適當的壓縮,對於位置也是如此。這時候就須要應用到視圖尺寸和位置的壓縮技術了。舉例來講:假如一個橫向的水平線性佈局的寬度是120,裏面的三個子視圖A,B,C的寬度和間距分別爲:A左間距20,A寬度30, B左間距10,B寬度60, C左間距20,C寬度40。在不進行壓縮時界面顯示的效果以下:

未壓縮前

爲了實現壓縮的能力在MyLayoutSize和MyLayoutPos兩個類中分別提供了一個新的屬性shrink。這個屬性值的意義代表當位置和尺寸超過佈局視圖時的壓縮比重值。值越大代表被壓縮的比重越大,值爲0代表不會被壓縮。系統默認的壓縮比重值被設置爲0。就以上面的例子來講假如咱們分別設置視圖A,B,C的寬度和間距的壓縮比例值以下:

A.leftPos.equalTo(@20).shrink = 1;
A.widthSize.equalTo(@30).shrink = 1;
B.leftPos.equalTo(@10);
B.widthSize.equalTo(@50).shrink = 2;
C.leftPos.equalTo(@20).shrink = 1;
C.widthSize.equalTo(@40);
複製代碼

這樣在不壓縮的狀況全部子視圖的間距和寬度總和爲:20+30+10+50+20+40 = 170 ,減去佈局視圖的寬度120後超出了50。而上述設置的壓縮比重值的總和爲:1+1+2+1 = 5。所以最終的每一個位置和尺寸被壓縮後的結果值分別爲:

A的左間距 = 20 - 50 * (1/5.0) = 10
A的寬度 = 30 - 50 *(1/5.0) = 20
B的左間距 = 10  不會被壓縮
B的寬度 = 50 - 50 *(2/5.0) = 30
C的左間距 = 20 - 50 *(1/5.0) = 10
C的寬度 = 40 不會被壓縮
複製代碼

最終界面展現的效果以下:

位置和尺寸壓縮後的界面

目前只有線性佈局、框架佈局、流式佈局、表格佈局、彈性佈局下的子視圖的寬度和尺寸才支持壓縮特性,其餘佈局中的子視圖不支持。並且壓縮的特性只有在全部子視圖的尺寸超出的時候才生效不然是不生效的。

須要注意的是彈性佈局中的子視圖的壓縮特性通常不經過直接設置shrink屬性來實現,而是經過設置flex_shrink來實現。

視圖的壓縮屬性和視圖的weight屬性的區別是前者是用於視圖尺寸的壓縮,然後者則是用於視圖尺寸的拉伸。具體的weight屬性的使用請參考相關的文檔和DEMO。

4.環繞和拉伸停靠

咱們能夠經過設置佈局視圖的gravity屬性來設置佈局內子視圖的總體停靠和對齊特性。在新版本中爲了實現flexbox中的一些能力,特別增長了4個停靠屬性:

MyGravity_Horz_Around
MyGravity_Horz_Stretch
MyGravity_Vert_Around
MyGravity_Vert_Stretch
複製代碼

位置的拉伸和環繞

在之前的版本中若是咱們但願拉伸子視圖之間的間距時能夠經過MyGravity_Horz_Between或者MyGravity_Vert_Between來實現。拉伸間距時第一個以及最後一個子視圖離父佈局視圖的間距將是0,而子視圖之間的間距將會平分剩餘的空間。而MyGravity_Horz_Around和MyGravity_Vert_Around則是第一個和最後一個子視圖離父佈局視圖的間距是子視圖之間的間距的一半。下面的界面展現了Between和Around的區別:

位置

尺寸的拉伸和環繞

在之前的版本中若是咱們但願填充拉伸全部子視圖之間的尺寸來佔滿佈局視圖的尺寸時咱們能夠經過MyGravity_Horz_Fill或者MyGravity_Vert_Fill來實現。這兩個停靠屬性的功能會將佈局視圖中的剩餘空間均勻的分配到全部子視圖(設置有尺寸自適應的佈局視圖除外)的尺寸之上,而無論子視圖是否設置了尺寸約束與否,從而實現子視圖之間的尺寸拉伸效果。 而MyGravity_Horz_Stretch以及MyGravity_Vert_Stretch則效果和填充是同樣的,只不過它只會拉伸那些沒有設置尺寸約束的子視圖以及設置了尺寸自適應的子視圖(設置了尺寸自適應的佈局視圖除外)。下面的界面展現了Fill 和Stretch的區別:

尺寸

目前只有線性佈局、流式佈局、浮動佈局、框架佈局、彈性佈局中才具備總體停靠和對齊設置的效果,其餘佈局不支持。

5.佈局中子視圖的拖放

在一些應用中咱們能夠經過拖放功能來調整子視圖的位置或者進行一些其餘處理。MyLayout之前的版本中實現了這麼一個DEMO。新版本中咱們將DEMO中拖放的能力進行了抽象而造成了一個新的拖放類:MyLayoutDragger。 在使用拖放類實現拖放功能時須要以下幾個步驟:

  1. 從佈局視圖類中經過createLayoutDragger方法建立一個拖放類實例對象,並保存起來。

  2. 對添加到佈局視圖中的子視圖分別添加以下事件:

[能夠被拖放的子視圖 addTarget:self action:@selector(handleTouchDrag:withEvent:) forControlEvents:UIControlEventTouchDragInside]; //註冊拖動事件。
    [能夠被拖放的子視圖 addTarget:self action:@selector(handleTouchDrag:withEvent:) forControlEvents:UIControlEventTouchDragOutside]; //註冊外面拖動事件。
    [能夠被拖放的子視圖 addTarget:self action:@selector(handleTouchDown:withEvent:) forControlEvents:UIControlEventTouchDown]; //註冊按下事件
    [能夠被拖放的子視圖 addTarget:self action:@selector(handleTouchUp:withEvent:) forControlEvents:UIControlEventTouchUpInside]; //註冊擡起事件
    [能夠被拖放的子視圖 addTarget:self action:@selector(handleTouchUp:withEvent:) forControlEvents:UIControlEventTouchCancel]; //註冊終止事件
複製代碼
  1. 分別在對應的事件處理方法中,調用拖放器對象的相關方法:
- (IBAction)handleTouchDown:(id)sender withEvent:(UIEvent*)event {
    //拖動子視圖開始處理。
    [self.dragger dragView:sender withEvent:event];
}

- (IBAction)handleTouchUp:(id)sender withEvent:(UIEvent*)event {
    //中止子視圖拖動處理。
    [self.dragger dropView:sender withEvent:event];
}

- (IBAction)handleTouchDrag:(id)sender withEvent:(UIEvent*)event {
    //子視圖拖動中處理。
    [self.dragger dragginView:sender withEvent:event];
}

複製代碼

這樣就能夠自動實現對子視圖的拖放功能了。咱們還能夠經過拖放器對象來進行一些特性化設置,好比能夠設置拖放的動畫時長、能夠設置哪些子視圖在拖放時不會移動、以及是否能夠在拖放時實現懸停效果等等。具體的演示代碼請參考DEMO工程中的:FLLTest3ViewController

6.iOS13的黑白模式適配

iOS13之後提供了黑白模式適配的能力。對於MyLayout來講由於具備對邊界線的支持的能力,邊界線內部實現是採用的CALayer來實現,而CALayer對顏色的輸入是CGColorRef對象,所以爲了支持黑白模式適配也進行版本升級,以便讓邊界線也能實現黑白模式適配的能力。

7.流式佈局的行內對齊控制

在流式佈局中咱們能夠經過設置gravity屬性和arrangedGravity屬性來設置佈局內子視圖的總體停靠特性以及行內子視圖之間的對齊特性。然而在實際中咱們可能但願某些行的停靠對齊屬性和其餘行是不同的,也就是但願可以定製每行的停靠對齊屬性。這樣經過行的停靠對齊屬性就能夠不經過插入佔位視圖或者不須要進行多層嵌套來實現咱們的界面需求。(若是用線性佈局來實現多行多列則須要進行多個佈局層次的嵌套處理)。就好比下面的這個界面:

流式佈局的行對齊自定義

爲了支持行內對齊停靠自定義處理,流式佈局提供了一個新的屬性:

/**
 單獨爲某一行定製的水平和垂直停靠對齊屬性,默認狀況下佈局視圖的gravity和arrangedGravity做用於全部行以及行內的停靠對齊。若是你想單獨定製某一行的停靠對齊方式時
 能夠經過設置這個block屬性。
 lineGravity的入參分別是佈局對象、當前行的索引(0開始)、當前行的條目視圖數量、是不是最後一行四個參數。
 函數返回的是此行以及行內的停靠對齊方式,若是返回MyGravity_None則表示使用佈局默認的gravity和arrangedGravity停靠對齊屬性。
 */
@property(nonatomic, copy) MyGravity (^lineGravity)(MyFlowLayout *layout, NSInteger lineIndex, NSInteger itemCount, BOOL isLastLine);

複製代碼

咱們能夠經過這個block的形式的屬性來進行行內停靠對齊的自定義處理。具體的行內對齊停靠的使用能夠參考DEMO工程中的FLLTest4ViewControllerFLLTest9ViewController

8.流式佈局和浮動佈局對基線對齊的支持

新版本中對於垂直流式佈局以及垂直浮動佈局中的每一行子視圖之間新增長了對基線對齊的支持。你能夠經過設置流式佈局的arrangedGravity的值爲MyGravity_Vert_Baseline。以及設置浮動佈局的gravity的值爲MyGravity_Vert_Baseline來實現行內的基線對齊。其中基線的標準視圖是行內的第一個文本視圖。這樣整個佈局體系中水平線性佈局、相對佈局、垂直流式佈局、垂直浮動佈局、彈性佈局均可以實現行內基線對齊的能力了。

9.佈局動畫的支持和擴展

動畫的適當使用會加強用戶的體驗效果。MyLayout中若是咱們調整了子視圖的約束後但願有動畫效果,那麼能夠調用佈局視圖的方法:

/**
 *設置佈局時的動畫。並指定時間,選項,和完成時的處理,這個動畫只會在調用後的下次佈局時執行一次。
 @param duration 指定動畫的時間間隔
 */
-(void)layoutAnimationWithDuration:(NSTimeInterval)duration;
-(void)layoutAnimationWithDuration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion;
複製代碼

上述的方法調用後系統會在下一個佈局週期發生時自動執行動畫效果。在使用動畫方法時咱們能夠指定動畫的時長以及一些選項還有動畫完成後的回調處理。

10.完善和擴充視圖尺寸的自適應設置支持

所謂尺寸自適應就是視圖的尺寸根據自身的內容和視圖內的子視圖的尺寸來動態肯定自身的尺寸,從而造成所謂的包裹的效果。尺寸自適應的目的是爲了讓視圖中的全部內容都獲得徹底的展現。

老版本的尺寸自適應設置

視圖的自適應尺寸也算是一種特殊的尺寸。在老版本中若是咱們想讓某個視圖的寬度自適應時能夠經過設置wrapContentWidth 屬性爲YES便可,而讓視圖的高度自適應時則能夠經過設置wrapContentHeight屬性爲YES便可。而要設置視圖的具體尺寸時則須要經過widthSize或者heightSize來實現。爲了設置尺寸而分別使用兩個屬性來操做這是不合理的方式。所以新版本中再也不建議使用wrapContentWidth和wrapContentHeight以及wrapContentSize來設置尺寸自適應了,而是建議使用新的設置方式。

新版本的尺寸自適應設置

新版本中將尺寸的自適應設置合併到了widthSize和heightSize中。由於自適應也是一種尺寸值,只不過是特殊值。下面的代碼是老版本和新版本的設置方法:

//老的方法
A.wrapContentWidth = YES;  
//新的方法1
A.widthSize.equalTo(@(MyLayoutSize.wrap));
//新的方法2
A.myWidth = MyLayoutSize.wrap;

//老的方法:
B.wrapContentSize = YES;
//新的方法:
B.mySize = CGSizeMake(MyLayoutSize.wrap, MyLayoutSize.wrap);

//老的讀取和判斷的方法
if (A.wrapContentWidth) {}
//新的判斷和讀取的方法1
if (A.widthSize.isWrap){}
//新的判斷和讀取的方法2
if (A.myWidth == MyLayoutSize.wrap){}

複製代碼

在新版本中咱們除了能夠設置MyLayoutSize.wrap爲尺寸自適應外,在MyLayoutSize類中還定義了另外兩個類屬性:MyLayoutSize.fill和MyLayoutSize.empty。它們也能夠用來簡化尺寸的設置。

  • MyLayoutSize.wrap:表明尺寸自適應
  • MyLayoutSize.fill: 表明尺寸佔用父視圖的剩餘空間
  • MyLayoutSize.empty: 表明清除尺寸約束

好比下面的代碼是等價的:

A.widthSize.equalTo(@(MyLayoutSize.wrap)) <==> A.myWidth = MyLayoutSize.wrap;
A.widthSize.equalTo(A.superview.widthSize) <==> A.myWidth = MyLayoutSize.fill;
A.widthSize.equalTo(nil) <==> A.myWidth = MyLayoutSize.empty;
複製代碼

結束語


因爲語法篇幅MyLayout中的不少功能都沒有介紹到,若是您想進一步瞭解的話能夠到github中下載對應的工程demo來進行詳細瞭解。

👉MyLayout: github.com/youngsoft/M…

👉TangramKit: github.com/youngsoft/T…

👉個人掘金主頁: juejin.im/user/593fb4…

相關文章
相關標籤/搜索