如何對使用了autolayout的UIView添加動畫,autolayoutuiview

來源:互聯網
上載者:User

如何對使用了autolayout的UIView添加動畫,autolayoutuiview

         當我們對一個UIView使用了autolayout自動布局之後,也就意味著我們放棄了

傳統的通過設定view的frame等方式手動的修改、確定這個view的位置、尺寸屬性。

甚至從某種程度上講,我們應該忘記view的frame屬性:它的確定不再取決於我

(手動的直接修改),而是通過我們在storyboard或者code中提供的約束條件

(constraints),通過一個自動布局引擎(蘋果為autolayout採用的是Cassowary

布局引擎,參考文檔:點擊開啟連結),計算出這個view的frame。因此我們可以

認為使用了autolayout的view的frame屬性是一個唯讀屬性。在代碼裡認為的改動

這個view的frame並不能對這個view的frame產生真正的效果(事實也確實如此)。

      現在問題就來了,在以前我們經常通過對一個view的frame的修改產生view移動

的動畫效果,那麼在使用了autolayout的view世界中我們該如何?相同的效果呢?

答案是,我們“將計就計”,通過改變這個view上的某個約束constraint然後在uiview的

animation block中觸發layout來實現。

一、預期效果

        下面我們以一個簡單的例子來進行詳細的說明:

        如所示,整個介面都使用了autolayout,現在我們想實現這樣一個效果:

當我們點擊顯示生日的按鈕的時候,整個view向上滑動,同時向上推出一個日期

選取器(date picker),類似於點擊textfield,彈出鍵盤後整個介面為了避免被遮

住而向上移動的效果。選取完成日期後點擊生日日期按鈕或者完成按鈕整個view向

下縮回,同時date picker向下滑出可視範圍。

二、實現細節

        首先來看一眼storyboard中view的層級結構:如所示,我們可以

看到,整個view的布局相當簡單,就兩級:根view和我們的date picker view,其

中date picker view包含了一個完成按鈕和系統的date picker。這樣的話,要實現

整個view和date picker view同時上移的效果,我們只需要對根view和date picker

 view同時做動畫即可。


        考慮如何?根view的動畫效果,這裡我們可以巧妙的通過修改根view的

bounds屬性來實現根view的上移效果。注意這裡我們需要明白view的bounds屬性

和frame屬性的區別,前者是相對於當前view的本地座標系而言的,而後者則是相

對於當前view的父view的座標系而言的。

        簡單的講,frame決定了一個view相對於父view的position和size資訊。而

bounds則決定了當前view展示的內容相對於本地座標系的位置。這裡我們將view

自身的可視內容和subviews可以看做一頁紙上的內容資訊,而view本身可以看成

是一枚放於紙上的放大鏡,放大鏡的大小不一定是和紙(content size)相同大小

的。bounds屬性的作用就是確定這枚放大鏡相對於紙的位置:一個bounds =

(0, 200, 300, 300)就意味著我們要將這枚放大鏡向紙的下方移動200個points,但

放大鏡相對於父view的位置仍是保持不變的,這樣給我們的效果就是這個view(顯

示的內容)向上移動了200個points.


        改動bounds的origin屬性並不會改動這個view的frame,通過這種展示內容的

移動給我們產生一種view向上移動了的幻覺。如中,“哪個位置...”為成為我們

放大鏡中看到的第一行。

        根view上移動畫的效果解決了,下面我們再來看日期選擇器date picker,在

storyboard中對其增加的約束如下:定高207、trailing/leading/top相對於super 

view (根view)的位置。


        確定date picker view y軸方向上下移動的約束顯然是top約束,點開top約束,

可以看到該約束的詳細內容:

        一個約束可以描述為:firstItem.attributeA = secondItem.attributeB * multipler 

+ constant。

結合我們可以得出date picker view的top約束為

datePickerView.Top = topLayoutGuide.bottom * 1 + 400

我們可以通過修改這裡的constant值來修改這個top約束以達到預期效果,事實

上通過修改而不是刪除舊的constraint再添加新的constraint也正是蘋果所推薦的,

在NSLayoutConstraint.h標頭檔中有如下說明:


        這樣,date picker view的上下移動就可以通過擷取並修改其top約束來實現。

需要注意的是在代碼中擷取datepicker view的top約束實際上是要在其父view的

constraints數組中尋找,這是因為每個view的constraints數組中儲存的實際上是

layout 子view所需的約束的集合。

我們還要定義個輔助BOOL變數,已判斷date picker view是否以彈出:

<span style="font-size:18px;">@property (nonatomic, assign) BOOL hasShowPickerView;</span>

       接下來定義一個輔助函數,用於尋找date picker view的top約束並修改其

constant屬性為給定的值:

- (void)replacePickerContainerViewTopConstraintWithConstant:(CGFloat)constant{    for (NSLayoutConstraint *constraint in self.pickerContainerView.superview.constraints) {        if (constraint.firstItem == self.pickerContainerView && constraint.firstAttribute == NSLayoutAttributeTop) {            constraint.constant = constant;        }    }}


        代碼裡我們在picker container view (即文中的date picker view)的

superview的constraints屬性中尋找,如果發現firstItem和firstAttribute屬性分別是

date picker view和top,則該constraint即為目標約束,然後修改其constant屬性。

在view首次被載入的時候我們想確保date picker view 處於整個view的最底部即隱

藏的狀態,因而我們在viewcontroller的viewDidLoad方法中調用輔助方法修改一下

date picker view的top約束:

<span style="font-size:18px;">[self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];</span>

        在首次點擊birthday button的時候動畫修改根view的bounds和date picker 

view的top constraint,注意上移gap的計算。再次點擊birthday button的時候將根

view的bounds恢複到正常值,date picker view的top constraint也恢複到viewDidLoad

中設定的值:

<span style="font-size:18px;">- (IBAction)didTapOnBirthdayButton:(id)sender{    self.hasShowPickerView = !self.hasShowPickerView;    if (self.hasShowPickerView) {        CGRect birthdayButtonFrame = self.birthdayButton.frame;        birthdayButtonFrame = [self.view convertRect:birthdayButtonFrame fromView:self.birthdayButton.superview];        CGFloat birthdayButtonYOffset = birthdayButtonFrame.origin.y + birthdayButtonFrame.size.height;        CGFloat gap = birthdayButtonYOffset - (self.view.frame.size.height - self.pickerContainerView.frame.size.height);        CGRect bounds = self.view.bounds;        if (gap > 0) {            bounds.origin.y = gap;        } else {            gap = 0;        }        [self replacePickerContainerViewTopConstraintWithConstant:birthdayButtonYOffset];        [UIView animateWithDuration:0.25 animations:^{            self.view.bounds = bounds;            [self.view layoutIfNeeded];        }];    } else {        [self replacePickerContainerViewTopConstraintWithConstant:self.view.frame.size.height];        CGRect bounds = self.view.bounds;        bounds.origin.y = 0;        [UIView animateWithDuration:0.25 animations:^{            self.view.bounds = bounds;            [self.view layoutIfNeeded];        }];    }}</span>


        上述代碼中的[self.view layoutIfNeed]去掉也是沒問題的。可能比較費解的是

根view.bounds.origin.y的上移gap的計算以及top constraint的constant值的計算,

關鍵實在真正理解view的frame和bounds的意義。

        至此程式達到了預期的效果。

三、小結

        在使用autolayout之前我們寫程式控制介面的構成就好比是開一輛手動擋的汽

車,雖然頻繁換擋(修改frame)很繁瑣,卻也很享受那種可以完全控制汽車檔位的

自由感。使用了autolayout之後則一下子升級為了自動擋汽車,切換檔位的活不再由

我們直接操作,而只能通過油門(constraints)的大小來間接的改變汽車的檔位。在

自動擋汽車裡,我們必須要放棄直接控制檔位的想法,那是不可能的了,我們必須要

學會通過熟練掌握腳下的油門和刹車來控制車速!在習慣了自動擋之後,相信大家也

一樣能夠得心應手的做自己想做的事情。



使用AutoLayout的時怎實現動畫

先解除約束,然後添加約束,然後layoutIfNeeded
 
可以同時執行多個UIView動畫?

可以用CABasicAnimation來給CALayer做動畫Cocos2D的動畫還是基於layer的
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.