3.0 android ui布局 - CSDN

2021-01-17 CSDN技術社區

作者 | 歐陽大哥2013

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

• 功能強大。它可以減少我們在開發UI界面時所花費的時間以及減少需要適配多種設備而所消耗的時間。實踐表明使用MyLayout進行界面布局時可以減少幾乎50%的工作量。

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

• 布局體系豐富。MyLayout提供了iOS、Android、HTML/CSS等前端中的所有流行布局實現。因此無論你之前工作在何種平臺上都可以選擇熟悉的布局類上手進行開發操作。MyLayout還支持從伺服器進行動態布局下發的能力。

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

• 多語言實現。MyLayout提供了OC語言版本的實現,同時也提供了Swift語言版本的實現:TangramKit。二者的語法和使用方式相似,您可以任意選擇一種語言進行代碼布局。

• 國際化支持。MyLayout支持LTR和RTL兩種方向的布局,其中的RTL模式可以用來支持希伯來語系的布局方式。

• 無版本限制。MyLayout並沒有作業系統版本上的使用限制,理論上它最低甚至可以支持到iOS5.0。

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

布局類名功能介紹對標功能MyLinearLayout線性布局:提供視圖依次從上往下或者從左往右進行單行單列排列的能力iOS:UIStackView 
Android:LinearLayoutFlutter:Row、Column SwiftUI:HStack、VStackMyFloatLayout浮動布局:提供視圖通過上下左右浮動停靠而進行排列布局的能力CSS: floatMyFlowLayout流式布局:提供視圖按垂直或者水平方向依次進行排列並且在滿足特定條件(一行內的數量和尺寸值滿足約定值)後會換行進行繼續排列布局的能力獨有MyFlexLayout彈性布局:提供一個盒內的子視圖可以進行伸縮對齊和換行排列並且滿足flex規約的布局能力CSS:flexboxMyGridLayout柵格布局:提供了一種基於單元格進行垂直和水平的無限拆分而進行布局的能力,柵格布局同時具有布局動態下發的能力CSS:類似Bootstrap、GridMyTableLayout表格布局:提供基於行列控制的表格布局的能力Android: TableLayout、GridLayoutHTML: table、tr、tdMyRelativeLayout相對布局:提供一種通過設置視圖之間的尺寸和位置的相互依賴約束來實現布局的能力iOS:AutoLayoutAndroid:RelativeLayout、PercentRelativeLayout、ConstraintLayoutMyFrameLayout框架布局:提供視圖在父視圖上某個方位進行停靠以及層疊擺放布局的能力Android:FrameLayoutMyPathLayout路徑布局:提供子視圖的位置通過數學函數運算而進行定位排列的能力獨有SizeClass提供了根據屏幕尺寸和橫豎屏而進行差異布局設置的能力。上述所有布局都支持SizeClass的功能iOS:SizeClassCSS: 類似Bootstrap

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

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

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

?OC語言版本MyLayout:  https://github.com/youngsoft/MyLinearLayout.git
?Swift語言版本TangramKit: https://github.com/youngsoft/TangramKit.git

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 myFlex;@end

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

@protocol MyFlexBox <MyFlexItem>

@property(nonatomic, strong) id attrs;/**
設置或檢索伸縮盒對象的子元素在父容器中的位置。默認值:MyFlexDirection_Row
*/
-(id (^)(MyFlexDirection))flex_direction;/**
設置或檢索伸縮盒對象的子元素超出父容器時是否換行。默認值:MyFlexWrap_NoWrap
*/
-(id (^)(MyFlexWrap))flex_wrap;/**
同時設置檢索伸縮盒對象的子元素在父容器中的位置和伸縮盒對象的子元素超出父容器時是否換行。二者通過 | 運算進行組合
*/
-(id (^)(int))flex_flow;/**
設置或檢索彈性盒子元素在主軸(橫軸)方向上的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Space_Between | MyFlexGravity_Space_Around 中的一個,默認值為MyFlexGravity_Flex_Start
*/
-(id (^)(MyFlexGravity))justify_content;/**
設置或檢索彈性盒子元素在側軸(縱軸)方向上的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一個,默認值為MyFlexGravity_Flex_Start
*/
-(id (^)(MyFlexGravity))align_items;/**
設置或檢索彈性盒堆疊伸縮行的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Between | MyFlexGravity_Around | MyFlexGravity_Stretch中的一個,默認值為MyFlexGravity_Stretch
*/
-(id (^)(MyFlexGravity))align_content;/**
指定主軸的子條目的數量。只有在flex_wrap設置為wrap時才有效。默認值是0表示會根據條目的尺寸自動進行換行。
*/
-(id (^)(NSInteger))item_size;/**
指定布局視圖中每頁的條目數量。這個值必須是item_size的倍數。
*/
-(id (^)(NSInteger))page_size;/**
指定布局會根據條目的尺寸自動排列,默認值是NO。
*/
-(id (^)(BOOL))auto_arrange;/**
設置彈性盒的內邊距
*/
-(id (^)(UIEdgeInsets))padding;/**
設置彈性盒內所有條目視圖之間的垂直間距
*/
-(id (^)(CGFloat))vert_space;/**
設置彈性盒內所有條目視圖之間的水平間距
*/
-(id (^)(CGFloat))horz_space;@end

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

@interface UIView(MyFlexLayout)

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

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

@protocol MyFlexItem

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


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


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


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

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

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


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

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

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


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

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

/**
條目在彈盒中的排列順序,值越大越往後排。
*/
-(id (^)(NSInteger))order;
/**
設置或檢索彈性盒的擴展比率。默認值為0表示不擴展
*/
-(id (^)(CGFloat))flex_grow;
/**
設置或檢索彈性盒的收縮比率。默認值為1表示當條目尺寸超過彈性盒尺寸後會進行壓縮。值越大壓縮比越大
*/
-(id (^)(CGFloat))flex_shrink;
/**
設置或檢索彈性盒伸縮基準值。默認值為MyFlex_Auto表示由其他屬性決定,如果值為大於0小於1則表示相對值,其他為一個固定的尺寸值。
*/
-(id (^)(CGFloat))flex_basis;
/**
設置或檢索彈性盒子元素自身在側軸(縱軸)方向上的對齊方式。可選值為:MyFlexGravity_Flex_Start | MyFlexGravity_Flex_End | MyFlexGravity_Center | MyFlexGravity_Baseline | MyFlexGravity_Stretch中的一個,默認值為MyFlex_Auto
*/
-(id (^)(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。在使用拖放類實現拖放功能時需要如下幾個步驟:

• 從布局視圖類中通過createLayoutDragger方法創建一個拖放類實例對象,並保存起來。

• 對添加到布局視圖中的子視圖分別添加如下事件:

[可以被拖放的子視圖 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]; //註冊終止事件

• 分別在對應的事件處理方法中,調用拖放器對象的相關方法:

- (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工程中的FLLTest4ViewController和FLLTest9ViewController

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來進行詳細了解。

推薦閱讀• 如何對 iOS 啟動階段耗時進行分析• NNPopObjc:在 Objective-C 上進行面向協議的編程原理解析• Pecker:自動檢測項目中不用的代碼• Flutter 狀態管理指南篇 —— Provider• 5 個酷炫的 iOS 庫

相關焦點

  • android 布局 覆蓋 - CSDN
    項目中listview中嵌套checkbox,將父控制項設置為android:descendantFocusability="blocksDescendants",這樣設置為的是:會覆蓋子類控制項而直接獲得焦點,即點擊listview的item區域即可選中checkbox。
  • android啟動頁設計專題及常見問題 - CSDN
    轉載請註明出處:http://blog.csdn.net/wangjihuanghun/article/details/63255144啟動頁幾乎成為了每個app的標配,有些商家在啟動頁中增加了開屏廣告以此帶來更多的收入。
  • android開發 自我優勢 - CSDN
    3、能夠有效避免APP運行過程中遇到的內存洩漏和內存溢出問題。4、熟練掌握線程之間通過Handler傳遞消息的機制原理5、熟練掌握自定義控制項,自定義組合控制項中View所經過的測量,布局,繪製的流程,以及Touch事件分發機制。
  • android 自定義view大小 - CSDN
    --場景1-->android:layout_width="match_parent"android:layout_height="match_parent"那麼按照我們的期望,希望子View的尺寸要是300dp*300dp,如果子View的布局參數是<!
  • Android - android xml 層級專題及常見問題 - CSDN
    它們被保存在res/drawable/文件夾下,通過R.drawable類訪問layout/定義用戶界面布局的XML文件。它們被保存在res/layout/文件夾下,通過R.layout類訪問menu/定義應用程式菜單的XML文件,如選項菜單,上下文菜單,子菜單等。
  • Android ConstraintLayout約束布局可視化工具使用~
    ,今天我們簡單看看這個布局的使用。ContraintLayout 是一個 Google 發布的一個新布局( 翻譯成中文 「約束布局「 )它的使用方式有兩種在 AS 2.3 以上版本都可以使用滑鼠拖動使用代碼書寫控制項之間的約束
  • android 監聽屏幕鎖屏專題及常見問題 - CSDN
    https://github.com/android/platform_frameworks_policies_base/blob/master/phone/com/android/internal/policy/impl/KeyguardViewManager.javaAndroid QQ音樂/酷狗音樂鎖屏控制實現原理,酷狗鎖屏- https://blog.csdn.net/yangxi_pekin
  • 如何利用 Android 自定義控制項實現炫酷的動畫?|CSDN 博文精選
    2、 張旭童的掌握自定義LayoutManager(一) 系列開篇 常見誤區、問題、注意事項,常用APIhttps://blog.csdn.net/zxt0601/article/details/529480093、張旭童的掌握自定義LayoutManager(二) 實現流式布局https://blog.csdn.net/zxt0601/article
  • Android Studio 4.0 發布
    Android Studio 4.0 已經發布。此版本亮點包括新的 Motion 編輯器;構建分析器,可用於分析構建速度較慢的原因。
  • android app被殺原因專題及常見問題 - CSDN
    for (int i = 0; i < procsToKill.size(); i++) { ProcessRecord pr = procsToKill.get(i); if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { pr.kill("remove task"
  • 了解Android開發規範:性能及UI優化是什麼樣的?
    本文引用地址:http://www.eepw.com.cn/article/201808/385611.htm一、Android編碼規範1.java代碼中不出現中文,最多注釋中可以出現中文2.局部變量命名、靜態成員變量命名只能包含字母,單詞首字母出第一個外,都為大寫,其他字母都為小寫3.常量命名只能包含字母和_,字母全部大寫
  • 5.0 android 平板 - CSDN
    OpenGL ES 3.1 添加了計算著色器、模具紋理、加速視覺效果、高質量 ETC2/EAC 紋理壓縮、高級紋理渲染、標準化紋理尺寸和 render-buffer 格式以及其他功能。Android 5.0部分主要的新功能包括:1.預先 (AOT) 編譯 2.改進的垃圾回收 (GC) 3.改進的調試支持。大多數 Android 應用無需任何更改就可以在 ART 下工作。不過,部分適合 Dalvik 的技術並不適用於 ART。如存在以下情況,應特別注意:您的應用使用 Java 原生接口 (JNI) 運行 C/C++ 代碼。
  • 谷歌Android Studio Arctic Fox (2020.3.1) 預覽版發布,附更新內容
    IT之家12月11日消息 谷歌表示,第一版 Android Studio Arctic Fox (2020.3.1) 以及 Android Gradle 插件 (AGP) 的 7.0.0-alpha01 版雙雙在 Canary 通道已正式發布。
  • 谷歌Android Studio 4.0 發布:新的 Motion 編輯器
    Android Studio 4.0 已經發布。此版本亮點包括新的 Motion 編輯器;構建分析器,可用於分析構建速度較慢的原因。同時對 CPU Profiler 用戶界面進行了大修,提供更加直觀的工作流和簡單的線程並行分析。
  • android 啟動頁慢專題及常見問題 - CSDN
    3.3處有兩個選項:wall clock time:代碼在線程上執行的真正時間[有一部分是等待cpu輪詢時間]thread time :cpu執行的時間一般是優化的是cpu執行時間結合業務代碼走查發現Controller0線程為一個線程,因此主線程一些操作可以放進去執行,從而減少main線程的耗時。
  • 加載布局專題及常見問題 - CSDN
    但是把項目中動態加載布局這塊學習了一下,把項目這塊寫了,然後還學了下如何動態添加item這塊。什麼叫動態布局這就是動態加載布局,根據我選擇的卡片類型,創建不同的布局,加載到下面 動態布局就是動態加載的布局啊emmmmmmm 有動態布局肯定有相應的靜態布局 那兩者有什麼區別啊靜態布局是之前就寫好的
  • xPlugin 1.3.0 發布,Android 消息化插件方案
    最新版本: 1.3.0更新內容: 優化部分組件加載性能 修復應用後臺被回收後activity棧未完全恢復的問題gradle添加依賴:
  • 對抗學習專題及常見問題 - CSDN
    /isMarvellous/article/details/75900055】3 resnet & inception【https://blog.csdn.net/zouxy09/article/details
  • Android-x86 9.0-r1 穩定版發布,在 PC 上運行 Android - OSCHINA...
    Android-x86 9.0-r1 已發布,此版本是基於 Android-x86 9.0 (pie-x86) 的首個穩定版。