一、前言
事情的起因是這樣的踩娘,因?yàn)樾枨蟮脑蚬涡蹋幸粋€(gè)頁面的cell分割線需要自定義,于是我的同事很順其自然地用了個(gè)view养渴,并將其高度設(shè)為1雷绢,來作為cell分割線使用。一切看起來都那么平靜理卑,直到有一天翘紊,產(chǎn)品大大的出現(xiàn),打破了所有的寧靜藐唠。他帆疟,一個(gè)帶有血輪眼的男人,看出了高度為1的線與系統(tǒng)cell分割線的不同宇立。如果他要是看了這篇文章的話踪宠,他就應(yīng)該能明白我說的那句話的含義。3倍屏上高度為1的線與高度為1像素的線差別只是在2個(gè)像素的粗細(xì)妈嘹,基本上已經(jīng)超出了肉眼的識別能力柳琢。他,是一個(gè)傳奇,一個(gè)可以與水哥抗衡的真漢子
~.png
好吧柬脸,言歸正傳他去,下面就來分析下高度為1的view線與系統(tǒng)cell分割線的區(qū)別在哪兒吧。在此之前先要搞懂下面這兩坨東西倒堕。
二灾测、Point與Pixel
2.1 Point與Pixel的概念
- 先來說下Pixel,翻譯過來就是像素垦巴,屏幕上顯示的最小單位媳搪。
- Point,翻譯過來就是點(diǎn)骤宣,是一個(gè)標(biāo)準(zhǔn)的長度單位蛾号。在編程中,frame涯雅、bounds、center等設(shè)置的坐標(biāo)位置就是以point為單位展运。
2.2 Point與Pixel的關(guān)系
iPhone 4之前 non-retina 屏幕的設(shè)備,一個(gè)point 就代表一個(gè)像素活逆,在此就不做過多說明。之后的retina屏幕(視網(wǎng)膜屏)拗胜,兩者之間的關(guān)系見下表:
設(shè)備 | 尺寸 | scale |
---|---|---|
iPhone4s | 320,480 | 2 |
iPhone5/5s | 320,568 | 2 |
iPhone6 | 375,667 | 2 |
iPhone6s | 414,736 | 2 |
iPhone6plus | 414,736 | 3 |
... | ... | 3 |
scale根據(jù)代碼可以獲取:
CGFloat scale = [UIScreen mainScreen].scale
基于以上信息可以看出蔗候,我們大部分情況下都不需要去關(guān)注pixel,然而存在部分情況需要考慮像素的轉(zhuǎn)化埂软。比如說繪制一個(gè)1像素粗細(xì)的線锈遥。
看到這個(gè)問題,第一想法就是根據(jù)當(dāng)前屏幕的縮放因子scale計(jì)算出1像素線對應(yīng)的點(diǎn)勘畔,然后將其設(shè)置成線的粗細(xì)即可所灸。
沒錯(cuò),我當(dāng)時(shí)就這么干了炫七。代碼寫完爬立,編譯運(yùn)行發(fā)現(xiàn)在設(shè)備上有的線并沒有顯示出來。
我在萬能的互聯(lián)網(wǎng)上找到了原因:
為了獲得良好的視覺效果万哪,繪圖系統(tǒng)通常都會采用一個(gè)叫antialiasing(反鋸齒)的技術(shù)侠驯,iOS也不例外。顯示屏幕有很多個(gè)顯示單元(即像素)組成奕巍,如果要畫一條黑線吟策,這條線剛好落在了一列或者一行顯示單位之內(nèi),將會渲染出標(biāo)準(zhǔn)的一個(gè)像素的黑線的止。如下圖所示:
但是如果線落在樂兩個(gè)行或列的中間時(shí)檩坚,那么會得到一條失真的線,如下圖所示:
官方給出的解釋與解決辦法是這樣的
解釋:
Positions defined by whole-numbered points fall at the midpoint between pixels. For example, if you draw a one-pixel-wide vertical line from (1.0, 1.0) to (1.0, 10.0), you get a fuzzy grey line. If you draw a two-pixel-wide line, you get a solid black line because it fully covers two pixels (one on either side of the specified point). As a rule, lines that are an odd number of physical pixels wide appear softer than lines with widths measured in even numbers of physical pixels unless you adjust their position to make them cover pixels fully.
奇數(shù)像素寬度的線在渲染的時(shí)候?qū)憩F(xiàn)為柔和的寬度擴(kuò)展到向上的整數(shù)寬度的線,除非你手動的調(diào)整線的位置效床,使線剛好落在一行或列的顯示單元內(nèi)睹酌。
解決辦法
On a low-resolution display (with a scale factor of 1.0), a one-point-wide line is one pixel wide. To avoid antialiasing when you draw a one-point-wide horizontal or vertical line, if the line is an odd number of pixels in width, you must offset the position by 0.5 points to either side of a whole-numbered position. If the line is an even number of points in width, to avoid a fuzzy line, you must not do so.
On a high-resolution display (with a scale factor of 2.0), a line that is one point wide is not antialiased at all because it occupies two full pixels (from -0.5 to +0.5). To draw a line that covers only a single physical pixel, you would need to make it 0.5 points in thickness and offset its position by 0.25 points. A comparison between the two types of screens is shown in Figure 1-4.
1.在non-retina屏上,一個(gè)Point對應(yīng)一個(gè)像素剩檀。為了防止antialiasing導(dǎo)致的奇數(shù)像素的線渲染時(shí)出現(xiàn)失真憋沿,你需要設(shè)置偏移0.5個(gè)點(diǎn)。
2.在retina屏上沪猴,要繪制一個(gè)像素的線辐啄,需要設(shè)置線寬為0.5個(gè)點(diǎn),同時(shí)設(shè)置偏移為0.25個(gè)點(diǎn)运嗜。
3.如果線寬為偶數(shù)Point的話壶辜,則不要去設(shè)置偏移,否則線條也會失真担租。
至此似懂非懂地貌似察覺到了解決辦法砸民,但是上面給出的解決方法需要偏移的點(diǎn)數(shù)也只是在二倍屏的基礎(chǔ)上。如果為三倍屏奋救,又該偏移多少呢岭参?
下面就來分析下偏移量的計(jì)算。
首先設(shè)置為1Pixel的線尝艘,其所用的點(diǎn)為1 / [UIScreen mainScreen].scale演侯。渲染的時(shí)候,如果我們檢測到線的落點(diǎn)并沒有完美顯示在顯示單元上(奇數(shù)單元點(diǎn))背亥,只需要將其移動半個(gè)單元到偶數(shù)單元點(diǎn)秒际,即可使線渲染的時(shí)候躲過** antialiasing**這個(gè)門檻。
即1像素的線需要的偏移量為1 / [UIScreen mainScreen].scale/2
三狡汉、核心代碼
下面貼上繪制1像素線的核心代碼:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
CGFloat pixelAdjustOffset = 0;
// 落在奇數(shù)位置的顯示單元上
if (((int)(1* [UIScreen mainScreen].scale) + 1) % 2 == 0)
{
pixelAdjustOffset = SINGLE_LINE_ADJUST_OFFSET;
}
// 設(shè)置畫線y值
CGFloat yPos = 1 - pixelAdjustOffset;
// 如果想在view的最底部畫線娄徊,需設(shè)置lineMode為EUCPixelViewLineModeBottom
if (self.lineMode == EUCPixelViewLineModeBottom)
{
while (yPos + 1 < self.bounds.size.height) {
yPos ++;
}
}
CGContextMoveToPoint(context, 0, yPos);
CGContextAddLineToPoint(context, self.bounds.size.width, yPos);
CGContextSetLineWidth(context, 1);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextStrokePath(context);
}
有時(shí)間補(bǔ)上github上的demo地址
謝謝觀看!