27、自定义视图绘制与代码重构:打造高效美观的图像视图

自定义视图绘制与代码重构:打造高效美观的图像视图

在开发过程中,我们常常需要对视图进行自定义绘制,以实现特定的视觉效果。本文将详细介绍如何为图像视图添加背景渐变、光泽效果,并对代码进行重构,以提高代码的可读性和可维护性。

1. 添加背景渐变和基本属性设置

首先,我们需要设置图像视图的基本属性,包括主图像、背景颜色、边框颜色、阴影和背景渐变。以下是相关代码:

imageView.mainImage           = [NSImage imageNamed:@"SpaceShuttle"];
imageView.backgroundColor     = [NSColor darkGrayColor];
imageView.borderColor         = [NSColor whiteColor];
imageView.shouldAddShadow     = YES;
imageView.backgroundGradient  = gradient;
[gradient release];
// resize with the window.
NSInteger resizingMask = ( NSViewWidthSizable | NSViewHeightSizable );

保存所有文件并重新运行项目,你将看到主图像后面出现背景渐变。

2. 绘制图像光泽效果

绘制图像光泽效果可以模拟玻璃表面材质。关键是要使用从透明度约为 0.8 的白色渐变到透明度为 0.0 的白色的渐变,并使用多停止渐变来优化外观。

2.1 计算光泽路径

创建一个新的 Objective-C 类别 NSBezierPath-Utilities.m ,并修改头文件 NSBezierPath-Utilities.h 如下:

#import <Cocoa/Cocoa.h>
@interface NSBezierPath (Utilities)
+ (NSBezierPath*) sheenPathForRect:(NSRect)myRect;
@end

实现文件 NSBezierPath-Utilities.m 的代码如下:

#import "NSBezierPath-Utilities.h"
@implementation NSBezierPath (Utilities)
+ (NSBezierPath*) sheenPathForRect:(NSRect)myRect {
    CGFloat minX = NSMinX(myRect);
    CGFloat maxX = NSMaxX(myRect);
    CGFloat maxY = NSMaxY(myRect);
    // scale the base of the sheen with the image.
    CGFloat bottomLeftY  = myRect.origin.y + (myRect.size.height * 0.25);
    CGFloat bottomRightY = myRect.origin.y + (myRect.size.height * 0.9);

    NSPoint point1  = NSMakePoint ( minX, bottomLeftY );
    NSPoint point2  = NSMakePoint ( maxX, bottomRightY );
    NSPoint point3  = NSMakePoint ( maxX, maxY );
    NSPoint point4  = NSMakePoint ( minX, maxY );
    // for the arc that crosses the image.
    NSPoint control1 = NSMakePoint ( minX * 0.9, maxY * 0.9 );
    NSPoint control2 = NSMakePoint ( maxX * 0.9, maxY * 0.9 );
    // create the path.
    NSBezierPath* sheenPath = [NSBezierPath bezierPath];
    // starting point.
    [sheenPath moveToPoint: point1];
    // arc to the other side.
    [sheenPath curveToPoint: point2
              controlPoint1: control1
              controlPoint2: control2];
    [sheenPath lineToPoint:point3];
    [sheenPath lineToPoint:point4];
    [sheenPath closePath];
    return sheenPath;    
}
@end

这个类方法创建了一个新的 NSBezierPath 实例,可用于绘制光泽。

2.2 添加光泽属性和方法

StyledImageView.h 中添加公共属性以启用光泽:

@interface StyledImageView : NSView
@property (retain) NSImage* mainImage;
@property (retain) NSColor* backgroundColor;
@property (retain) NSColor* borderColor;
@property (assign) BOOL shouldAddShadow;
@property (retain) NSGradient* backgroundGradient;
@property (assign) BOOL shouldAddSheen;
@end

StyledImageView.m 中添加私有光泽属性和方法:

@interface StyledImageView ()
// private properties.
@property (retain) NSShadow* imageShadow;
@property (retain) NSGradient* imageSheen;
// private methods.
+ (NSShadow*) defaultImageShadow;
+ (NSGradient*) defaultImageSheen;
- (void) drawBackgroundInRect:(NSRect)rect;
- (void) drawImageSheenInRect:(NSRect)rect;
@end

添加合成语句:

@implementation StyledImageView
@synthesize mainImage;
@synthesize backgroundColor;
@synthesize borderColor;
@synthesize shouldAddShadow;
@synthesize imageShadow;
@synthesize backgroundGradient;
@synthesize shouldAddSheen;
@synthesize imageSheen;

dealloc 方法中释放光泽渐变:

- (void) dealloc {
    self.mainImage          = nil;
    self.backgroundColor    = nil;
    self.borderColor        = nil;
    self.imageShadow        = nil;
    self.backgroundGradient = nil;
    self.imageSheen         = nil;
    [super dealloc];
}

重写 setShouldAddSheen: 方法,以便在启用光泽时重新生成渐变:

- (void) setShouldAddSheen:(BOOL)shouldAdd {
    // if the new value is the same, just return.
    if ( shouldAddSheen == shouldAdd ) return;
    // set the new value.
    shouldAddSheen = shouldAdd;
    if ( shouldAddSheen )
        self.imageSheen = [[self class] defaultImageSheen]; 
    else
        self.imageSheen = nil;
    // redraw.
    [self setNeedsDisplay:YES];
}

添加私有类方法以生成光泽渐变:

+ (NSGradient*) defaultImageSheen {
    NSColor* color1 = [NSColor colorWithDeviceWhite:1.0 alpha:0.80];
    NSColor* color2 = [NSColor colorWithDeviceWhite:1.0 alpha:0.10];
    NSColor* color3 = [NSColor colorWithDeviceWhite:1.0 alpha:0.0];
    // a location of 0.0 is the 'start' of the gradient; 1.0 is the 'end'.
    NSGradient* sheen = [[NSGradient alloc] initWithColorsAndLocations:
                    color1, 0.0, color2, 0.4, color3, 0.8, nil];
    return [sheen autorelease];
}

实现绘制光泽的方法:

- (void) drawImageSheenInRect:(NSRect)rect {
    NSBezierPath* sheenPath = [NSBezierPath sheenPathForRect:rect];
    // draw at 280.0 degees to simulate a light source from the upper-left.
    [self.imageSheen drawInBezierPath:sheenPath angle:280.0];
}

drawRect: 方法中调用绘制光泽的方法:

// restore the context so the frame doesn't get a shadow.
[NSGraphicsContext restoreGraphicsState];
// draw the sheen.
[self drawImageSheenInRect:imageRect];
// draw a border around the image.
[self.borderColor set];
NSBezierPath* imageFrame = [NSBezierPath bezierPathWithRect:imageRect];
imageFrame.lineWidth = 4;
[imageFrame stroke];

最后,在 BasicCocoaDrawingAppDelegate.m 中激活光泽:

imageView.shouldAddShadow       = YES;
imageView.backgroundGradient    = gradient;
imageView.shouldAddSheen        = YES;

保存所有文件并重新运行项目,你将看到图像上应用了光泽。

3. 代码重构的必要性和目标

随着代码的增加,代码变得难以理解和维护。此时,我们有三种选择:
- 允许代码混乱 :这是最简单的方法,但会导致代码难以调试和扩展。
- 重写代码 :虽然可以解决问题,但风险较大,可能会引入新的错误。
- 重构代码 :这是一种折中的方法,通过重新组织现有代码,使其更易于理解和维护。

重构 StyledImageView 类的目标包括:
- 使代码更易于阅读。
- 使视图更易于定制。
- 使方法更对称。

4. 重构示例:方法对称性

方法对称性是指具有相似功能的方法应该具有相似的名称和输入输出类型。例如,以下是不对称的代码:

- (NSArray*)itemsSortedByName;
- (NSArray*)dateSortedItems;

对称的版本如下:

- (NSArray*)itemsSortedByName;
- (NSArray*)itemsSortedByDate;

这样可以使代码更易于理解和使用。

5. 重构后的头文件

StyledImageView.h 替换为以下重构版本:

#import <Cocoa/Cocoa.h>
#pragma mark -
#pragma mark Delegate Protocols
 // allows another class to calculate the geometry.
@protocol StyleImageViewGeometryDelegate <NSObject>
- (NSRect)imageRectForContentRect:(NSRect)contentRect;
@end
#pragma mark -
#pragma mark Class Definiton
@interface StyledImageView : NSView
#pragma mark -
#pragma mark Main Properties
 // content.
@property (copy) NSImage* mainImage;
 // colors and gradients.
@property (retain) NSGradient* backgroundGradient;
@property (retain) NSColor*    backgroundColor;
@property (retain) NSColor*    borderColor;
 // display options.
@property (assign) BOOL shouldAddShadow;
@property (assign) BOOL shouldAddSheen;
 // delegate.
@property (assign) id<StyleImageViewGeometryDelegate> delegate;
#pragma mark -
#pragma mark Drawing Methods
 // drawing methods for subclasses to override.
- (void) drawBackground;
- (void) drawImage;
- (void) drawImageBorder;
- (void) drawImageSheen;
@end

这个头文件分为三个主要部分:委托协议、视图的公共属性和视图子类可以重写的方法。

6. 重构后的实现文件

重构 StyledImageView.m 如下:

#import "StyledImageView.h"
#import "NSImage-Utilities.h"
#import "NSBezierPath-Utilities.h"
@interface StyledImageView ()
// private properties.
@property (assign)   NSRect         contentRect;
@property (readonly) NSRect         imageRect;
@property (retain)   NSShadow*      imageShadow;
@property (retain)   NSGradient*    imageSheen;
// private methods.
+ (NSShadow*)   defaultImageShadow;
+ (NSGradient*) defaultImageSheen;
@end
@implementation StyledImageView
// content.
@synthesize mainImage;
@synthesize contentRect;
// colors and gradients.
@synthesize backgroundGradient;
@synthesize backgroundColor;
@synthesize borderColor;
// display options.
@synthesize shouldAddShadow;
@synthesize shouldAddSheen;
@synthesize imageShadow;
@synthesize imageSheen;
// delegate.
@synthesize delegate;
- (id)initWithFrame:(NSRect)frame {
    if ( self = [super initWithFrame:frame] ) {
    }
    return self;
}
- (void) dealloc {
    self.mainImage          = nil;
    self.backgroundGradient = nil;
    self.backgroundColor    = nil;
    self.borderColor        = nil;  
    self.imageShadow        = nil;
    self.imageSheen         = nil;
    self.delegate           = nil;
    [super dealloc];
}

访问器方法实现:

#pragma mark -
#pragma mark Property Accessors
- (void) setShouldAddShadow:(BOOL)shouldAdd {
    if ( shouldAddShadow == shouldAdd ) return;
    shouldAddShadow = shouldAdd;
    if ( shouldAddShadow )
        self.imageShadow = [[self class] defaultImageShadow];   
    else
        self.imageShadow = nil;
    [self setNeedsDisplay:YES];
}
- (void) setShouldAddSheen:(BOOL)shouldAdd {
    if ( shouldAddSheen == shouldAdd ) return;
    shouldAddSheen = shouldAdd;
    if ( shouldAddSheen )
        self.imageSheen = [[self class] defaultImageSheen]; 
    else
        self.imageSheen = nil;
    [self setNeedsDisplay:YES];
}
#pragma mark -
#pragma mark Geometry
- (NSRect) imageRect {
    // first, check with the delegate.
    if ( [self.delegate respondsToSelector:@selector(imageRectForContentRect:)] )
        return [self.delegate imageRectForContentRect:self.contentRect];
    // if delegate didn't return a value, calculate it.        
    NSImage* image = self.mainImage;
    return [image proportionalRectForTargetRect:self.contentRect];
}

绘制方法实现:

#pragma mark -
#pragma mark Drawing
- (void)drawRect:(NSRect)rect {
    // set up the content rect that will be used by the other
    // drawing methods.
    NSRect  bounds    = self.bounds;    
    CGFloat insetX    = NSWidth  ( bounds ) * 0.10;
    CGFloat insetY    = NSHeight ( bounds ) * 0.10;
    self.contentRect  = NSInsetRect ( bounds, insetX, insetY );
    // call each drawing method separately.
    [self drawBackground];
    [self drawImage];
    [self drawImageBorder];
    [self drawImageSheen];
}
- (void) drawBackground {
    [self.backgroundColor set]; 
    NSRectFill ( self.bounds );
    [self.backgroundGradient drawInRect:self.bounds angle:90.0];
}
- (void) drawImage {
    NSImage* image = self.mainImage;
    [NSGraphicsContext saveGraphicsState];
        [self.imageShadow set];
        [image drawInRect: self.imageRect
                 fromRect: NSZeroRect
                operation: NSCompositeSourceOver
                 fraction: 1.0];
    [NSGraphicsContext restoreGraphicsState];
}
- (void) drawImageBorder {
    [self.borderColor set];
    NSBezierPath* imageFrame = [NSBezierPath bezierPathWithRect:self.imageRect];
    imageFrame.lineWidth = 4;
    [imageFrame stroke];
}
- (void) drawImageSheen {
    NSRect rect = self.imageRect;
    NSBezierPath* sheenPath = [NSBezierPath sheenPathForRect:rect];
    // draw at 280.0 degrees to simulate a light source from the upper-left.
    [self.imageSheen drawInBezierPath:sheenPath angle:280.0];
}

默认值方法:

#pragma mark -
#pragma mark Default Values
+ (NSShadow*) defaultImageShadow {
    NSShadow* newShadow         = [[NSShadow alloc] init];      
    newShadow.shadowBlurRadius  = 8.0;
    newShadow.shadowOffset      = NSMakeSize(0,-6);
    return newShadow;
}

通过以上步骤,我们不仅为图像视图添加了背景渐变和光泽效果,还对代码进行了重构,提高了代码的可读性和可维护性。在实际开发中,我们应该养成定期重构代码的习惯,以确保代码的质量和可扩展性。

自定义视图绘制与代码重构:打造高效美观的图像视图

7. 重构带来的好处分析

重构代码虽然需要花费一定的时间和精力,但从长远来看,它能带来诸多显著的好处。以下是详细分析:

7.1 易于调试

当代码结构清晰、方法和变量命名规范且具有对称性时,调试过程会变得更加高效。例如,在查找某个属性或方法的问题时,我们可以快速定位到相关代码,而不需要在混乱的代码中四处寻找。

7.2 便于他人理解

在团队协作开发或者将项目交接给其他开发者时,整洁的代码能够让他人迅速理解代码的功能和逻辑。如果代码杂乱无章,新接手的开发者可能需要花费大量时间去梳理代码,甚至可能因为理解错误而引入新的问题。

7.3 提高开发效率

干净的代码就像一条畅通的道路,能够让我们快速地添加新功能。相反,在杂乱的代码基础上添加新功能,就如同在泥泞的道路上行走,不仅速度慢,还容易陷入困境。例如,在重构后的 StyledImageView 类中,我们可以轻松地修改或添加绘制方法,而不用担心会影响到其他部分的代码。

7.4 避免遗忘代码逻辑

随着项目的发展和时间的推移,我们可能会忘记自己编写的代码的具体实现细节。重构后的代码具有良好的可读性和逻辑性,即使过了一段时间后再回过头来看,也能快速理解代码的意图。

以下是一个简单的表格,总结了重构代码的好处:
| 好处 | 描述 |
| — | — |
| 易于调试 | 快速定位问题,提高调试效率 |
| 便于他人理解 | 降低团队协作和项目交接的难度 |
| 提高开发效率 | 更轻松地添加新功能,减少错误发生的概率 |
| 避免遗忘代码逻辑 | 即使长时间后也能快速理解代码意图 |

8. 代码灵活性与性能的权衡

在重构代码的过程中,我们需要在代码的灵活性和性能之间进行权衡。例如,在 drawRect: 方法中,我们选择将 contentRect 保存为属性,而不是直接传递给每个绘制方法。虽然这样做会带来微小的性能损失,但却提高了代码的灵活性。

以下是 drawRect: 方法的代码示例:

- (void)drawRect:(NSRect)rect {
    // set up the content rect that will be used by the other
    // drawing methods.
    NSRect  bounds    = self.bounds;    
    CGFloat insetX    = NSWidth  ( bounds ) * 0.10;
    CGFloat insetY    = NSHeight ( bounds ) * 0.10;
    self.contentRect  = NSInsetRect ( bounds, insetX, insetY );
    // call each drawing method separately.
    [self drawBackground];
    [self drawImage];
    [self drawImageBorder];
    [self drawImageSheen];
}

如果我们直接将 contentRect 传递给每个方法,当需要修改传递的参数类型(如从 NSRect 改为 NSSize )时,就需要修改所有相关的方法。而将其保存为属性,我们只需要在一处进行修改,其他方法仍然可以正常使用。

在实际开发中,我们应该根据具体的场景来决定如何权衡灵活性和性能。如果代码需要被其他开发者复用和定制,那么灵活性往往更为重要;如果对性能要求极高,那么可以适当牺牲一些灵活性来换取性能的提升。

9. 定制化与复杂度的平衡

在设计类时,我们希望类既易于使用,又具有一定的定制性。然而,过多的定制选项可能会导致类变得过于复杂,增加使用的难度。以下是一些关于平衡定制化和复杂度的建议:

9.1 明确核心功能

在设计类之前,我们需要明确类的核心功能是什么。例如, StyledImageView 类的核心功能是显示图像,并添加一些特效(如背景渐变、光泽等)。我们应该围绕这些核心功能来设计类的接口和属性。

9.2 提供必要的定制选项

对于一些常见的定制需求,我们可以提供相应的属性和方法。例如,在 StyledImageView 类中,我们提供了 shouldAddShadow shouldAddSheen 属性,让用户可以选择是否添加阴影和光泽效果。

9.3 避免过度定制

如果一个类有太多的属性和方法,用户在使用时可能会感到困惑。我们应该只提供那些真正必要的定制选项,避免让类变得过于复杂。例如,在 StyledImageView 类中,我们没有提供过多的绘制细节选项,而是将这些细节封装在类内部,让用户只需要关注一些关键的属性和方法。

以下是一个简单的 mermaid 流程图,展示了定制化和复杂度的平衡过程:

graph LR
    A[明确核心功能] --> B[提供必要的定制选项]
    B --> C{是否过度定制?}
    C -- 是 --> D[减少定制选项]
    C -- 否 --> E[完成设计]
    D --> B
10. 总结

通过为图像视图添加背景渐变、光泽效果,并对代码进行重构,我们实现了高效美观的图像视图。在开发过程中,我们应该注重代码的质量和可维护性,定期对代码进行重构,以提高开发效率和代码的可扩展性。同时,我们需要在代码的灵活性和性能、定制化和复杂度之间找到平衡,以设计出既实用又易于使用的类。

在未来的开发中,我们可以进一步扩展 StyledImageView 类的功能,例如添加更多的特效、支持不同的图像格式等。同时,我们也可以将重构的经验应用到其他项目中,不断提高自己的开发水平。

AI 代码审查Review工具 是一个旨在自动化代码审查流程的工具。它通过集成版本控制系统(如 GitHub 和 GitLab)的 Webhook,利用大型语言模型(LLM)对代码变更进行分析,并将审查意见反馈到相应的 Pull Request 或 Merge Request 中。此外,它还支持将审查结果通知到企业微信等通讯工具。 一个基于 LLM 的自动化代码审查助手。通过 GitHub/GitLab Webhook 监听 PR/MR 变更,调用 AI 分析代码,并将审查意见自动评论到 PR/MR,同时支持多种通知渠道。 主要功能 多平台支持: 集成 GitHub 和 GitLab Webhook,监听 Pull Request / Merge Request 事件。 智能审查模式: 详细审查 (/github_webhook, /gitlab_webhook): AI 对每个变更文件进行分析,旨在找出具体问题。审查意见会以结构化的形式(例如,定位到特定代码行、问题分类、严重程度、分析和建议)逐条评论到 PR/MR。AI 模型会输出 JSON 格式的分析结果,系统再将其转换为多条独立的评论。 通用审查 (/github_webhook_general, /gitlab_webhook_general): AI 对每个变更文件进行整体性分析,并为每个文件生成一个 Markdown 格式的总结性评论。 自动化流程: 自动将 AI 审查意见(详细模式下为多条,通用模式下为每个文件一条)发布到 PR/MR。 在所有文件审查完毕后,自动在 PR/MR 中发布一条总结性评论。 即便 AI 未发现任何值得报告的问题,也会发布相应的友好提示和总结评论。 异步处理审查任务,快速响应 Webhook。 通过 Redis 防止对同一 Commit 的重复审查。 灵活配置: 通过环境变量设置基
【直流微电网】径向直流微电网的状态空间建模线性化:一种耦合DC-DC变换器状态空间平均模型的方法 (Matlab代码实现)内容概要:本文介绍了径向直流微电网的状态空间建模线性化方法,重点提出了一种基于耦合DC-DC变换器的状态空间平均模型的建模策略。该方法通过数学建模手段对直流微电网系统进行精确的状态空间描述,并对其进行线性化处理,以便于系统稳定性分析控制器设计。文中结合Matlab代码实现,展示了建模仿真过程,有助于研究人员理解和复现相关技术,推动直流微电网系统的动态性能研究工程应用。; 适合人群:具备电力电子、电力系统或自动化等相关背景,熟悉Matlab/Simulink仿真工具,从事新能源、微电网或智能电网研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握直流微电网的动态建模方法;②学习DC-DC变换器在耦合条件下的状态空间平均建模技巧;③实现系统的线性化分析并支持后续控制器设计(如电压稳定控制、功率分配等);④为科研论文撰写、项目仿真验证提供技术支持代码参考。; 阅读建议:建议读者结合Matlab代码逐步实践建模流程,重点关注状态变量选取、平均化处理和线性化推导过程,同时可扩展应用于更复杂的直流微电网拓扑结构中,提升系统分析设计能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值