iOS中動畫操作多個UIView的兩種不同思路

前段時間在網(wǎng)上看到一篇關于AutoLayout 約束動畫方法的文章:《Animating Autolayout Constraints》殿怜,譯文詼諧幽默廉涕,寫得很不錯泻云。但是初次看完此文章,有種奇怪的感覺:雖然主旨是在描述怎樣在runtime處理約束狐蜕,但是總感覺這種方法非常的“離奇”宠纯,有化簡為繁多此一舉之嫌〔闶停可是既然Apple提供了這種方法婆瓜,應該有比較理想的使用場景。所以看完之后仍然不斷回味,然后突然就想到到了一種合適的場景能夠完美的解釋這種看似“無聊”的方法廉白。這也為我們將來處理類似的需求時多了一種思路个初。這里我把上述文章和我自己的思路總結(jié)出來,給大家一個參考猴蹂。歡迎看過的朋友討論院溺。

首先,我利用原文中的例子磅轻,在這里給大家介紹下達到同樣的效果珍逸,有哪兩種不同的思路。先看預期效果:

statictable.gif

當打開開關時瓢省,藍色的View會從右邊推入到黃色的View右邊弄息,然后兩個View會均分屏幕并列排列。兩者之間的Spacing是10 pt勤婚。
注意這里有個小細節(jié)摹量,藍色的View在推入時,是由遠及近的馒胆,并不是一直都是挨在黃色的View旁邊10pt的地方缨称。

好,這個效果實現(xiàn)起來祝迂,我們一般會:

思路一:直接操作兩個View的Frame:

(以下示例以IB操作為例睦尽,如果不用IB也是一樣的思路)

1)IB中View的初始化

首先在IB中畫好黃色的View:


InterfaceBuilder.png
2)Code創(chuàng)建另一個View

在Code中動態(tài)創(chuàng)建藍色的View:

- (void)viewDidLoad {
  [super viewDidLoad];    
  self.blueView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 360, 360)];
  self.blueView.backgroundColor = [UIColor blueColor];
  [self.view addSubview:self.blueView];
  self.blueView.hidden = YES;
}

-(void)viewDidLayoutSubviews
{
  float blueViewx = self.yellowView.frame.origin.x + self.yellowView.frame.size.width + 100;
  float blueViewy = self.yellowView.frame.origin.y;
  self.blueView.frame = CGRectMake(blueViewx, blueViewy, 360, 360);
  self.blueView.hidden = NO;
}

當然,我這里為了演示方便型雳,用了360這樣的magic number当凡,而且是在viewDidLoad的時候就把blueView創(chuàng)建好了,在實際開發(fā)中纠俭,你完全可以根據(jù)實際需要對blueView進行l(wèi)azy load沿量。

注意:這里blueView的位置在viewDidLayoutSubViews中進行處理而不是在viewDidLoad中的原因是,blueView的初始狀態(tài)時必須要和yellowView處在同一個水平線上冤荆,也就是說必須等到y(tǒng)ellowView的layout完全確定以后朴则。而在viewDidLoad時,yellowView的layout并沒有完全確定钓简。當然你也可以在viewDidAppear中進行處理乌妒,但是顯然不如在viewDidlayoutSubViews中更合理。

3)動畫處理兩個View

在Switcher的開關Action中處理兩個View的變化:

- (IBAction)fancyMode:(id)sender {
    if (self.s.on){
      // yellow view縮小
      [UIView animateWithDuration:1.0 animations:^{
        self.yellowView.frame = CGRectMake(self.yellowView.frame.origin.x,
                                           self.yellowView.frame.origin.y, 
                                           (self.yellowView.frame.size.width - 10.0) / 2, 
                                           self.yellowView.frame.size.height);
      }];
      // 計算blue view frame的x使其靠近
      [UIView animateWithDuration:1.0 animations:^{
        self.blueView.frame = CGRectMake(self.yellowView.frame.origin.x + self.yellowView.frame.size.width + 10,
                                         self.yellowView.frame.origin.y, 
                                         self.yellowView.frame.size.width,
                                         self.yellowView.frame.size.height);
      }];
    }
    else {
      // yellow view擴展
      [UIView animateWithDuration:1.0 animations:^{
        self.yellowView.frame = CGRectMake(self.yellowView.frame.origin.x, 
                                           self.yellowView.frame.origin.y,
                                           self.yellowView.frame.size.width * 2 + 10.0, 
                                           self.yellowView.frame.size.height);
      }];
      // 計算blue view frame的x使得距離移開
      [UIView animateWithDuration:1.0 animations:^{
        self.blueView.frame = CGRectMake(self.yellowView.frame.origin.x + self.yellowView.frame.size.width + 100.0,
                                         self.yellowView.frame.origin.y, 
                                         self.blueView.frame.size.width * 2, 
                                         self.blueView.frame.size.height);
      }];
    }
}

好外邓,以上是我們常用的方法撤蚊,非常直觀簡單。


那么下面來看下譯文中提到的另一個思路:

思路二:通過兩個View的Autolayout Constraints來間接操作Frame

這個思路的核心是對View的變化不是直接修改Frame损话,而是通過autolayout的設定拴魄,讓UIKit自行處理Frame的layout設定從而達到間接操作Frame的效果。同樣是上述需求,我們來看下處理步驟(具體詳情可以參考譯文):

1)創(chuàng)建IB和約束

在IB上拖出視圖匹中,拉上約束。這個時候倆視圖都是可見的豪诲。



黃圖有五個約束:左邊相對父視圖間隔顶捷,右邊相對藍圖間隔,上邊相對switch間隔屎篱,下邊相對父視圖間隔服赎,以及和藍圖寬度相等約束。



藍圖和黃圖的約束差不多交播,除了藍圖是右邊相對父視圖間隔重虑。

非必需約束優(yōu)先級
在只有黃圖可見的時候(真是不錯),我們需要加另一個約束秦士,也就是它右側(cè)相對父視圖的間隔約束缺厉。如果在上面我加上這個約束,那么他就和那個"右側(cè)相對藍圖約束"沖突了隧土,因為他倆同時有優(yōu)先級1000提针。為了避免沖突以及移動藍圖,我們可以改變一下黃藍圖間隔的那個約束的優(yōu)先級曹傀。
必需約束優(yōu)先級是這個UILayoutPriorityRequired(1000)辐脖,你不能在運行時改變一個必需約束的優(yōu)先級。優(yōu)先級比UILayoutPriorityRequired小的皆愉,就是一個可選或者非必需的約束嗜价,類似這種,只要你別把優(yōu)先級設置為UILayoutPriorityRequired幕庐,你就可以改久锥。
所以首先,我們把藍圖右側(cè)相對父視圖約束的優(yōu)先級搞低一點翔脱,搞到750.


2)關聯(lián)約束

然后我們在給黃圖加一個它右側(cè)相對父視圖的約束(就像上面提到的)奴拦,優(yōu)先級也搞到750.


為了在運行時改變藍圖右側(cè)約束我們得先把這個約束拖到代碼中。

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *yellowViewConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *blueViewConstraint;

為了確保我們把藍圖推出屏幕届吁,我們也得調(diào)整黃圖和藍圖中間的間隔約束错妖,所以我們把這個約束也整到代碼中粪躬。

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *viewSpacingContraint;
3) 更新約束

現(xiàn)在可以很容易的寫一個方法來根據(jù)模式開關設置藍圖約束想要的優(yōu)先級皿桑。這里我對原文的代碼做了一些修改:

- (void)updateConstraintsForMode { 
    if (self.modeSwitch.isOn) { 
        self.viewSpacingContraint.constant = 10.0; 
        // ---
        // 原文: self.blueViewConstraint.priority = UILayoutPriorityDefaultHigh+1; 
        // +++
        self.yellowViewConstraint.priority = UILayoutPriorityDefaultLow;
        self.blueViewConstraint.priority = UILayoutPriorityDefaultHigh;
    } else { 
        self.viewSpacingContraint.constant = 100.0;
        // ---
        // 原文:self.blueViewConstraint.priority = UILayoutPriorityDefaultHigh-1;  
        // +++
        self.yellowViewConstraint.priority = UILayoutPriorityDefaultHigh;
        self.blueViewConstraint.priority = UILayoutPriorityDefaultLow;
    }
}

我們在storyboard中把黃圖右側(cè)相對父視圖的約束也設定了優(yōu)先級UILayoutPriorityDefaultHigh(750)熏纯。為了使藍圖可見贫悄,我們需要把藍圖的右側(cè)約束優(yōu)先級設定的比750高一些香椎,而隱藏起藍圖時我們得把它設定的低一些陨仅。

我們在視圖第一次加載時也應該配置下約束味榛。
- (void)viewDidLoad {
// ...
[self updateConstraintsForMode];
}

4) 約束動畫:

蘋果的 Auto Layout Guide描述了autoLayout搞動畫的基本方法朵夏,這樣:

- (IBAction)enableMode:(UISwitch *)sender { 
    // ...
    [self.view layoutIfNeeded]; 
    [UIView animateWithDuration:1.0 animations:^{ [self updateConstraintsForMode]; 
        [self.view layoutIfNeeded];
    }];
}

思考

好,到這里辣吃,相信很多人會和我有一樣的問題动遭,明明思路1更簡潔直接,為什么偏偏非要使用思路2這么晦澀的方法神得。更何況有很多人對在IB中使用AutoLayout非常的痛恨厭惡厘惦,更是無法接受去操作constraints來間接操作Frame。那好哩簿,我們來考慮下面這個場景:

ASample.gif

當滑動Slider Bar的時候宵蕉,紅View的大小會隨之變化,然后下面的所有View都會跟著一起移動节榜。
那么我們來看這種場景在上述兩種思路下需要怎樣實現(xiàn)羡玛。

由于兩種思路View的初始化狀態(tài)都是一樣的,不同的關鍵是在Slider變化時候的操作不同:

思路一:調(diào)整Frame

1)首先在IB中設定View宗苍,做好AutoLayout適配稼稿;
2)頭文件中定位:

@property (weak, nonatomic) IBOutlet UIView *redView;
@property (weak, nonatomic) IBOutlet UIView *yellowView;
@property (weak, nonatomic) IBOutlet UIView *blueView;
@property (weak, nonatomic) IBOutlet UIView *greenView;
@property (weak, nonatomic) IBOutlet UISlider *slider;

3)處理Slider Bar的變化:
- (IBAction)spaceChanged:(id)sender {
static float old = 0.0;
float value = self.slider.value;
float delta = value - old;
old = value;

    [self updateMainViewWithDelta:delta];
    [self updateOtherView:self.yellowView WithDelta:delta];
    [self updateOtherView:self.blueView WithDelta:delta];
    [self updateOtherView:self.greenView WithDelta:delta];
}

- (void) updateMainViewWithDelta:(float)delta
{
    self.redView.frame = CGRectMake(self.redView.frame.origin.x,
                                    self.redView.frame.origin.y, 
                                    self.redView.frame.size.width,             
                                    self.redView.frame.size.height + delta);
}

- (void) updateOtherView:(UIView *)view WithDelta:(float)delta
{
    view.frame = CGRectMake(view.frame.origin.x, 
                            view.frame.origin.y + delta,                                  
                            view.frame.size.width, 
                            view.frame.size.height);
}
思路二:調(diào)整Constraints

1)首先設定Constraints:
除了紅色View之外其他的View都有4個約束:leading space, trailing space, top space, height;紅色View沒有height約束,但是由一個Bottom space to Bottom Layout浓若;所有constraints priority均為默認(1000)渺杉。

2)拽出紅色View的Bottom space約束到redViewbottomConstraint;

IB.png

這個時候挪钓,頭文件里只需要有這2個property就可以:

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *redViewbottomConstraint;
@property (weak, nonatomic) IBOutlet UISlider *slider;

3)我們來看spaceChanged:方法里需要怎么寫……

- (IBAction)spaceChanged:(id)sender {
    static float old = 0.0;
    float value = self.slider.value;
    float delta = value - old;
    old = value;
    
    self.redViewbottomConstraint.constant -= delta;
}

塔塔J窃健!~~只要1行有木有碌上?倚评!所有東西全部搞定!
可以看到這種場景下方法二的優(yōu)勢沒有馏予?

也許你會說:“不對天梧!方法二還是要花一部分精力去設定constraints!方法一就不需要霞丧!” 可是我會說呢岗,如果你用IB去創(chuàng)建View,方法一還是需要花精力在IB中設定AutoLayout蛹尝,就算你用code去創(chuàng)建View后豫,方法一還是需要花精力去算每一個View的初始狀態(tài)(當然Github的神器Masonary能夠大大簡化設定Autolayout的過程)。而且憑心而論突那,但就這種多個View的場景挫酿,在IB中繪制View顯然要比Code來得簡單的多。

如果你還不服氣愕难,那么我們思考下這個場景:


Sample2.gif

這里早龟,我使用中間紅色方塊的width和height constraints惫霸,全部代碼只有這些:

// ViewController.h
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *width;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *height;
@property (weak, nonatomic) IBOutlet UISlider *slider;

// ViewController.m
- (IBAction)changed:(id)sender {
    self.width.constant = self.slider.value;
    self.height.constant = self.slider.value;
}

當然,在此之前葱弟,需要在IB中設定好這些View的約束壹店,但是非常簡單,大家自己可以試試芝加,在此不表茫打。

然和你可以思考下如果用常規(guī)的思路一,去調(diào)整每一個方塊的Frame妖混,需要多少代碼。

總結(jié)

我把原作者提到的一種操作UIView Layout的技術(shù)進行引申轮洋,總結(jié)了2種操作UIView的方法制市。

當然,我并不是說思路2就一定比思路1好弊予,所有的技術(shù)都是工具祥楣,這里只是利用這個例子給大家拓寬思路,在某些場合下汉柒,退一步換個思路误褪,從約束的角度去考慮問題可能能帶來意想不到的效果。

希望能幫到您碾褂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兽间,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子正塌,更是在濱河造成了極大的恐慌嘀略,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乓诽,死亡現(xiàn)場離奇詭異帜羊,居然都是意外死亡,警方通過查閱死者的電腦和手機鸠天,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門讼育,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人稠集,你說我怎么就攤上這事奶段。” “怎么了巍杈?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵忧饭,是天一觀的道長。 經(jīng)常有香客問我筷畦,道長词裤,這世上最難降的妖魔是什么刺洒? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吼砂,結(jié)果婚禮上逆航,老公的妹妹穿的比我還像新娘。我一直安慰自己渔肩,他們只是感情好因俐,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著周偎,像睡著了一般抹剩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蓉坎,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天澳眷,我揣著相機與錄音,去河邊找鬼蛉艾。 笑死钳踊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的勿侯。 我是一名探鬼主播拓瞪,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼助琐!你這毒婦竟也來了祭埂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤弓柱,失蹤者是張志新(化名)和其女友劉穎沟堡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矢空,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡航罗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了屁药。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粥血。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酿箭,靈堂內(nèi)的尸體忽然破棺而出复亏,到底是詐尸還是另有隱情,我是刑警寧澤缭嫡,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布缔御,位于F島的核電站,受9級特大地震影響妇蛀,放射性物質(zhì)發(fā)生泄漏耕突。R本人自食惡果不足惜笤成,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眷茁。 院中可真熱鬧炕泳,春花似錦、人聲如沸上祈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽登刺。三九已至籽腕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纸俭,已是汗流浹背节仿。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掉蔬,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓矾瘾,卻偏偏與公主長得像女轿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子壕翩,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容