resizableImageWithCapInsets實現登錄按鈕、膠囊tab按鈕和聊天氣泡貼圖效果

1.關於UIImage的resizable capInsets屬性

This technique is often used to create variable-width buttons, which retain the same rounded corners but whose center region grows or shrinks as needed. 

For example, you can use this method to create a background image for a button with borders and corners: when the button is resized, the corners of the image remain unchanged, but the borders and center of the image expand to cover the new size.


當我們在創建變寬按鈕時,往往保持按鈕的圓角,只是讓中心部分進行必要地伸縮。

例如,你可以使用 stretchable 或 resizable 來設置圓角按鈕的背景貼圖,當按鈕尺寸發生改變時,背景貼圖的圓角保持不變,中心部分進行必要伸縮來適應新的尺寸。


調用 stretchableImage 或 resizableImage 後,返回一個帶有縮放封蓋屬性的UIImage對象(Return a new image object with the specified cap insets)。
當我們對超出圖片size的UIImageView/UIButton進行setImage或setBackgroundImage時,就會按照預設CapInsets屬性進行填充、拉伸適配

2.-[UIImage(UIImageDeprecated) stretchableImageWithLeftCapWidth:topCapHeight:]

NS_DEPRECATED_IOS(2_0, 5_0, "Use -resizableImageWithCapInsets:")
【Summary】
Creates and returns a new image object with the specified cap values.
【Discussion】

specifying cap insets such that the interior is a1x1 area.

During scaling or resizing of the image, areas covered by a cap are not scaled or resized
Instead, the 1-pixel wide area not covered by the cap in each direction is what is scaled or resized

【解說】
    它的功能是創建一個內容可拉伸,而邊角不拉伸的圖片,需要兩個參數:

@property(nonatomic,readonlyNSInteger leftCapWidth:horiz. stretchable. 左邊不拉伸區域的寬度。

@property(nonatomic,readonlyNSInteger topCapHeight:vert. stretchable. 上面不拉伸區域的高度。

    根據UIKit/UIImage.h對該屬性的描述

  •     rightCapWidth=width-leftCapWidth-1
  •     bottomCapHeight=height-topCapWidth-1

    UIEdgeInsets capInsets =  UIEdgeInsetsMake(topCapHeight,leftCapWidth,bottomCapHeight,rightCapWidth)

    因此,實際stretchable的interior area是capInsets封蓋圈起來的部分——一個1x1點陣。也即座標爲{left+1,top+1}的這個點(one point)會像貼瓷磚那樣進行復制填充(UIImageResizingModeTile:the interior is tiled when drawn),capInsets封蓋的周邊外圍保持不變。

    爲視效均勻起見,一般選取圖片的中心點(leftCapWidth=width/2,topCapHeight=height/2)進行復制填充。

// create a resizable version of this image. the interior is tiled when drawn.

3.-[UIImage resizableImageWithCapInsets:]

@property(nonatomic,readonlyUIEdgeInsets capInsets;

NS_AVAILABLE_IOS(5_0)

【Summary】
create a resizable version of this image. the interior is tiled when drawn.
【Discussion】

For best performance, use a tiled area that is a 1x1 pixel area in size.

iOS uses different rendering techniques, with different performance characteristics, depending on the size of each resizable area in the image:

  • If resizable areas have a width or height of 1 pixel—that is, a horizontally resizable area is 1 pixel wide, a vertically resizable area is 1 pixel tall, or the center region of the image is 1 x 1 pixel—iOS draws the image by stretching the 1-pixel region. This mode provides the fastest performance (for nonzero cap insets).
  • If resizable areas have a width or height greater than 1 pixel, iOS draws the image by tiling the region. This mode provides reduced performance, but can be useful for images with textured (rather than solid-color) content in their resizable areas.
  • If the entire image is resizable—that is, the capInsets parameter is UIEdgeInsetsZero—and its size is greater than 1 x 1 pixel, iOS draws the image by tiling the entire image. This mode is faster than the tiling mode for nonzero cap insets.
【解說】
    其中 UIEdgeInsets 參數的格式是(top,left,bottom,right),從上、左、下、右分別在圖片上畫了一道線,這樣就給一個圖片加了一個逆向包圍圈,圈內部分會被拉伸圈外部分則保持不變

    (1)若選取圖片的中心點(height/2,width/2,height/2+1,width/2+1)進行復制填充時,效果同stretchableImageWithLeftCapWidth:width/2 topCapHeight:height/2,對 1x1 pixel area 進行拉伸(stretching)適配。對於純色圖片,選取中心點進行拉伸時效能最優。

    (2)若capInsets圍封區域大於1x1,則對尺寸超出部分,像貼瓷磚一樣複製填充(tile/spread/extend)。對於紋理貼圖(textured),通常採用此種方式。

    (3)若capInsets=UIEdgeInsetsZero,則圍封區域爲整個原始貼圖(width*height),對於尺寸超出部分,同上也像貼瓷磚一樣複製填充(tile/spread/extend)。但效能優於非零capInsets。


// the interior is resized according to the resizingMode

4.-[UIImage resizableImageWithCapInsets:resizingMode:]

NS_AVAILABLE_IOS(6_0)
================================================================================
【Discussion】

This method is exactly the same as its counterpart resizableImageWithCapInsets: except that the resizing mode of the new image object can be explicitly declared

You should only call this method in place of its counterpart if you specifically want your image to be resized with the UIImageResizingModeStretch resizing mode.

【解說】
    UIImageResizingModeTile:平鋪模式,通過拉伸或鋪排UIEdgeInsets指定的矩形區域來填充圖片。

       resizableImageWithCapInsets: resizingMode:對於圍封區域大於1x1,UIImageResizingModeTile相當於resizableImageWithCapInsets: ,一般直接調用resizableImageWithCapInsets:。對於圍封區域等於1x1,若調用resizableImageWithCapInsets默認使用stretching方式,若指定resizingMode=UIImageResizingModeTile則可式指定使用tiling模式。

    UIImageResizingModeStretch:拉伸模式,通過拉伸UIEdgeInsets(圍封區域大於1x1)指定的矩形區域來填充圖片。

對於圍封區域大於1x1,若調用resizableImageWithCapInsets默認使用tiling模式;若指定resizingMode=UIImageResizingModeStretch則可顯式指定使用stretching模式。

        拉伸UIEdgeInsets排除外圍指定的內部矩形區域(interior resizable area is a rectangle)。
  1. 橫向拉伸:UIEdgeInsetsMake(0, coreRadius, 0, coreRadius),例如圓角/橫向膠囊按鈕貼圖。
  2. 縱向拉伸:UIEdgeInsetsMake(coreRadius, 0, coreRadius, 0),例如類似溫度計的圓角/縱向膠囊按鈕貼圖。
  3. 居中拉伸:UIEdgeInsetsMake(coreRadius, coreRadius,coreRadius, coreRadius),例如帶corner的圓角登錄按鈕。


5.resizableImage & EdgeInsets 綜合例程

    例如,我們要用貼圖[email protected](39x20)和貼圖[email protected](39x20) 實現如下貼圖效果:


    其中分割線上半部由[email protected]貼出頂部圓角效果,分割線下半部由[email protected]貼出底部圓角效果。其拉伸屬性設置代碼如下:

// 圓角半徑爲6pixel=3point
#define CORNER_RADIUS 3
// [email protected]拉伸矩形區域
#define TOP_STRETCH_CAP_INSETS  UIEdgeInsetsMake(CORNER_RADIUS,CORNER_RADIUS,0,CORNER_RADIUS)
// [email protected]拉伸矩形區域
#define BOT_STRETCH_CAP_INSETS  UIEdgeInsetsMake(0,CORNER_RADIUS,CORNER_RADIUS,CORNER_RADIUS)

UIImage* topBgImage = [UIImage imageNamed:@"top_half_bg.png"];
topBgImage = [topBgImage resizableImageWithCapInsets:TOP_STRETCH_CAP_INSETS];

UIImage* botBgImage = [UIImage imageNamed:@"bot_half_bg.png"];
botBgImage = [botBgImage resizableImageWithCapInsets:BOT_STRETCH_CAP_INSETS];


    最後,給出一個關於UIImageView和UIButton貼圖的綜合示例(fan2/resizableImage):

    (1)第一組(testVerticalResizable):例程演示了縱向半橢圓封蓋屬性的應用。上邊一個左上、右上帶圓角貼圖,下邊一個左下、右下帶圓角貼圖。

    (2)第二組(testRoundedRectButton):例程演示了橫向圓角按鈕效果的實現。

    (3)第三組(testResizableBubble):例程演示了類微信聊天氣泡拉伸效果的實現。

    (4)第四組(testCapsuleRoundedButton):例程演示了橫向半橢圓封蓋屬性的應用:

  • 初始膠囊按鈕,背景爲藍圈白底,字體爲藍色。
  • 左右按鈕初始圖標分別爲大拇指豎起和大拇指向下。 
  • 選擇左膠囊按鈕,背景變藍,字體變白,圖標由大拇指豎起變爲一朵鮮花。 
  • 選擇右膠囊按鈕,背景變藍,字體變白,圖標由大拇指向下變爲一朵枯萎。 


    以下爲UIImageView和UIButton貼圖的CapInsets標註:



testVerticalResizable

testVerticalResizable 例程演示了縱向半橢圓封蓋屬性的應用。

上半部 topBgImage 基於中心點拉伸貼圖。

topBgImage = [topBgImage resizableImageWithCapInsets:CENTER_PIXEL_CAPINSETS_OF_IMAGE(topBgImage)

下半部 botBgImage 基於封蓋 tiling 圍封部分填充貼圖。
// tiling底部橢圓上的圍封矩形部分:
botBgImage = [botBgImage resizableImageWithCapInsets:BOT_IMGVIEW_CAPINSETS /*resizingMode:UIImageResizingModeStretch*/]; // 默認Tile,可測試Stretch


testRoundedRectButton

testRoundedRectButton 例程演示了橫向圓角按鈕效果的實現。
CapInsets 封蓋剔除四周圓角部分,tiling 或 streching 圍封矩形。

方式1:stretching 中心點
// stretching the is 1 x 1 pixel region, provides the fastest performance.
// loginBtnBgImg = [loginBtnBgImg resizableImageWithCapInsets:CENTER_PIXEL_CAPINSETS_OF_IMAGE(loginBtnBgImg) /*resizingMode:UIImageResizingModeTile*/]; // 默認Stretch,可測試Tile

RESIZABLE_IMAGE_BY_STRETCHING_CENTER_PIXEL(loginBtnBgImg);


方式2:stretching 橢圓中間封圍的矩形部分:

// stretching the interior area not covered by the cap
loginBtnBgImg = [loginBtnBgImg resizableImageWithCapInsets:LOGIN_BTN_CAPINSETS resizingMode:UIImageResizingModeStretch]; // 默認Tile,測試Stretch


通過 UIView.CALayer 的 setCornerRadius 接口設置邊框圓角半徑。  
通過 `+[UIImage(UIColor) imageFromColor:]` 擴展接口可基於顏色繪製純色背景,免用貼圖資源

    [_loginButton.layer setMasksToBounds:YES];
    [_loginButton.layer setCornerRadius:LOGIN_BUTTON_CORNER_RADIUS];
    [_loginButton setBackgroundImage:[UIImage resizableImageFromColor:RGBCOLOR(54, 187, 72)] forState:UIControlStateNormal];


testResizableBubble

testResizableBubble 例程演示了類微信聊天氣泡拉伸效果的實現。

方式1:stretching 中心點,箭頭中心點會被縱向拉伸!
friendBubbleBgImg = [friendBubbleBgImg resizableImageWithCapInsets:CENTER_PIXEL_CAPINSETS_OF_IMAGE(friendBubbleBgImg) /*resizingMode:UIImageResizingModeTile*/]; // 默認Stretch,可測試Tile

方式2:tiling 圍封的中部矩形部分(4,11,4,4):

默認 tiling 模式下左側縱向填充出現3個箭頭;指定 stretch 模式,則箭頭會沿中心點縱向拉伸鈍化。
friendBubbleBgImg = [friendBubbleBgImg resizableImageWithCapInsets:UIEdgeInsetsMake(4, 11, 4, 4) /*resizingMode:UIImageResizingModeStretch*/]; // 默認Tile,可測試Stretch


方式3:tiling 圍封的右下矩形部分(21,11,4,4):  

保留箭頭所在左上部分,右下方向填充/拉伸,實現預期拉伸效果!
friendBubbleBgImg = [friendBubbleBgImg resizableImageWithCapInsets:UIEdgeInsetsMake(21, BUBBLE_ANGLE_SIDE_MARGIN, BUBBLE_CORNER_RADIUS, BUBBLE_CORNER_RADIUS) /*resizingMode:UIImageResizingModeStretch*/]; // 默認Tile,可測試Stretch


testCapsuleRoundedButton

testCapsuleRoundedButton 例程演示了橫向半橢圓封蓋屬性的應用。

膠囊按鈕綜合演示了:

+ 按鈕背景貼圖(setBackgroundImage):  
> -[UIImage resizableImageWithCapInsets:resizingMode:]  
+ 按鈕圖標及標題的間距控制(imageView/titleLabel EdgeInsets):  
  > -[UIButton setImageEdgeInsets:]   
> -[UIButton setTitleEdgeInsets:]   

點擊按鈕爲選擇態,背景變藍,字體變白,圖標改變。
左按鈕爲左圖標右標題,右按鈕爲左標題右圖標,圖標和標題間距爲 8pt。

resizable Background Image

方式1:stretching 中心點,縱向拉伸橢圓圓角部分至頂,導致右側縱向飽滿部分也向上鼓脹。
RESIZABLE_IMAGE_BY_STRETCHING_CENTER_PIXEL(leftNorBgImg);

方式2:stretching 右側點,保留左側橢圓,右側正確拉伸。但是按下態不正常!
RESIZABLE_IMAGE_BY_INTERIOR_PIXEL(leftNorBgImg, BARBTN_CORNER_RADIUS, BARBTN_CORNER_RADIUS);

方式3:tiling 右側圍封的非橢圓區矩形部分,實現預期拉伸效果!
leftNorBgImg = [leftNorBgImg resizableImageWithCapInsets:LEFT_BARBTN_CAPINSETS /*resizingMode:UIImageResizingModeStretch*/]; // 默認Tile,可測試Stretch


setImageEdgeInsets & setTitleEdgeInsets

iOS 系統按鈕默認只有一個 titleLabel 居中。當用戶指定 imageView 作爲按鈕圖標時,默認圖標在左側,標題在右側,間隔0.5pt(1pixel),整體居中(UIControlContentVerticalAlignmentCenter/UIControlContentHorizontalAlignmentCenter)。
用戶根據排版需求可設置 contentHorizontalAlignment=UIControlContentHorizontalAlignmentLeft/Center 使按鈕控件(UIControl)中的元素(imageView+titleLabel)橫向左/中對齊,然後通過 contentEdgeInsets 調整控件內元素組合的邊緣來設置周邊留白。
橫向基於 contentHorizontalAlignmentcontentEdgeInsets 排版後,可進一步設置按鈕控件子元素(imageView/titleLabel)的 UIEdgeInsets(titleEdgeInsets/imageEdgeInsets){top, left, bottom, right}。UIEdgeInsets的偏移效果是右移left/2,左移right/2;如爲負數,則反向偏移。如此,可精確配置按鈕控件子元素(imageView+titleLabel)之間的間隔。

關於 setImageEdgeInsets/setTitleEdgeInsets 的排版佈局效果,參見下圖:



左標題右圖標

下面重點講解“左標題右圖標”效果的實現:

標題左移至圖標位置

// test41:標題只設置左邊距爲負圖標寬度(24pt),實際左移半圖標
[_rightAgainstButton setTitleEdgeInsets:UIEdgeInsetsMake(0, -rightBtnImageWidth, 0, 0)];

// test42:標題只設置右邊距爲圖標寬度(24pt),實際左移半圖標
[_rightAgainstButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 0, 0, rightBtnImageWidth)];

// test43:test41+test42,標題實際左移整個圖標寬度
[_rightAgainstButton setTitleEdgeInsets:UIEdgeInsetsMake(0, -rightBtnImageWidth, 0, rightBtnImageWidth)];
圖標右移至標題位置

// 圖標設置左邊距爲按鈕標題寬度,右邊距爲負按鈕標題寬度,實際右移整個按鈕標題寬度
[_rightAgainstButton setImageEdgeInsets:UIEdgeInsetsMake(0, rightBtnTitleWidth, 0, -rightBtnTitleWidth)];
標題再左移4pt,圖標再右移4pt
// test44:基於test43,標題繼續左移4pt
[_rightAgainstButton setTitleEdgeInsets:UIEdgeInsetsMake(0, -rightBtnImageWidth-4, 0, rightBtnImageWidth+4)];

// 圖標繼續右移4pt,最終左側標題和右側圖標間距8pt
[_rightAgainstButton setImageEdgeInsets:UIEdgeInsetsMake(0, rightBtnTitleWidth+4, 0, -rightBtnTitleWidth-4)];


參考:

iOS圖片拉伸技巧

[UIImage resizableImageWithCapInsets:]使用注意

iOS 不規則的ImageView》《iOS 實現聊天剪裁氣泡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章