写点什么

贝塞尔曲线在 iOS 端的绘图实践

  • 2020-09-15
  • 本文字数:9102 字

    阅读完需:约 30 分钟

贝塞尔曲线在iOS端的绘图实践

1 前言

在这个大数据的时代,很多信息只有通过图形才能更好的展示给用户。例如:房屋的历史价格、基金股票的历史增长、数据占比分析图等。如何做图形?需要用到什么知识?本文将从 建模、显示 两方面来展开介绍。


2 建模

建模是一切图形的基础,其他内容的前提,要用代码展示一个图形,首先要有它的几何模型表达。目前在客户端二维图形建模上,Bézier curve(贝塞尔曲线)可以称为 经典 和 主流 并重的数学曲线。


对于贝塞尔曲线来说,最重要的是 起始点、终止点(也称锚点)、控制点。控制点决定了一条路径的弯曲轨迹,根据控制点的个数,贝塞尔曲线被分为:一阶贝塞尔曲线(0 个控制点)、二阶贝塞尔曲线(1 个控制点)、三阶贝塞尔曲线(2 个控制点)、N 阶贝塞尔曲线(n - 1 个控制点)。

2.1 贝塞尔曲线原理

以二阶贝塞尔曲线为例 解释说明:


起始点:P0 ; 控制点:P1 ; 终止点:P2



  1. 连接 P0P1 线 和 P1P2 线。

  2. 在 P0P1 线上找到点 A,在 P1P2 线上找到点 B,使得 P0A/AP1 = P1B/BP2

  3. 连接 AB,在 AB 上找到点 X,X 点满足:AX/XB = P0A/AP1 = P1B/BP2

  4. 找出所有满足公式:AX/XB = P0A/AP1 = P1B/BP2 的 X 点。(从 P0 到 P2 的红色曲线点为所有 X 点的连线)这条由所有 X 点组成的连线 即为 贝塞尔曲线。


二阶贝塞尔曲线 起始点:P0 ; 控制点:P1 ; 终止点:P2



三阶贝塞尔曲线 起始点:P0 ; 控制点:P1、P2; 终止点:P3



四阶贝塞尔曲线 起始点:P0 ; 控制点:P1、P2、P3 ; 终止点:P4


2.2 UIBezierPath 类

系统给我们提供了一个叫做 UIBezierPath 类,用它可以画简单的圆形,椭圆,矩形,圆角矩形,也可以通过添加点去生成任意的图形,还可以简单的创建一条二阶贝塞尔曲线和三阶贝塞尔曲线。我们来了解一下它的常用方法:

2.2.1 初始化方法

// 创建UIBezierPath对象+ (instancetype)bezierPath;// 创建在rect内的矩形+ (instancetype)bezierPathWithRect:(CGRect)rect;// 设定特定的角为圆角的矩形,corners:指定的角为圆角,其他角不变,cornerRadii:圆角的大小+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;// 创建圆弧+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;// 通过已有路径创建路径+ (instancetype)bezierPathWithCGPath:(CGPathRef)CGPath;// 创建三次贝塞尔曲线 endPoint:终点 controlPoint1:控制点1 controlPoint2:控制点2- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;- // 创建二次贝塞尔曲线 endPoint:终点 controlPoint:控制点- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
复制代码

2.2.2 使用方法

// 移动到某一点- (void)moveToPoint:(CGPoint)point;// 绘制一条线- (void)addLineToPoint:(CGPoint)point;// 闭合路径,即在终点和起点连一根线- (void)closePath;// 清空路径- (void)removeAllPoints;// 填充- (void)fill;// 描边,路径创建需要描边才能显示出来- (void)stroke;
复制代码

2.2.3 常用属性

// 将UIBezierPath类转换成CGPath,类似于UIColor的CGColor@property(nonatomic) CGPathRef CGPath;// path线的宽度@property(nonatomic) CGFloat lineWidth;// path端点样式@property(nonatomic) CGLineCap lineCapStyle;// 拐角样式@property(nonatomic) CGLineJoin lineJoinStyle;
复制代码

2.2.4 举个栗子🌰

先看效果👇:



代码如下:


- (void)drawRect:(CGRect)rect {
[[UIColor redColor] set];
// 右边第一个图 UIBezierPath* maskPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(30, 30)]; maskPath.lineWidth = 20.f; maskPath.lineJoinStyle = kCGLineJoinBevel; [maskPath stroke]; // 中间第二个图 UIBezierPath* maskFillPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(200, 50, 100, 100) byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(30, 30)]; maskFillPath.lineWidth = 20.f; maskFillPath.lineJoinStyle = kCGLineJoinBevel; [maskFillPath fill]; [maskFillPath stroke]; // 右边第三个图 UIBezierPath *maskLinePath = [UIBezierPath bezierPath]; maskLinePath.lineWidth = 20.f; maskLinePath.lineCapStyle = kCGLineCapRound; [maskLinePath moveToPoint:CGPointMake(250.0, 50)]; [maskLinePath addLineToPoint:CGPointMake(300.0, 100.0)]; [maskLinePath stroke];}
复制代码


上图中:


1)图一和图二 唯一的不同是[maskFillPath fill]方法,fill 方法要在封闭的曲线调用。


2)图一和图二 为设定特定的角为圆角的矩形,corners 为 UIRectCornerTopLeft 左上角,cornerRadii 圆角大小为 30,绿色的箭头 表示的设定的这个角。


corners 为下面五种类型typedef NS_OPTIONS(NSUInteger, UIRectCorner) {    UIRectCornerTopLeft     = 1 << 0,    // 左上角    UIRectCornerTopRight    = 1 << 1,    // 右上角    UIRectCornerBottomLeft  = 1 << 2,    // 左下角    UIRectCornerBottomRight = 1 << 3,    // 右下角    UIRectCornerAllCorners  = ~0UL        // 全部};
复制代码


3)图一和图二 黄色的箭头 设置的属性 拐角样式:lineJoinStyle kCGLineJoinBevel(缺角)


lineJoinStyle 为下面三种类型typedef CF_ENUM(int32_t, CGLineJoin) {    kCGLineJoinMiter,    // 尖角    kCGLineJoinRound,    // 圆角    kCGLineJoinBevel    // 缺角};
复制代码


4)图三 白色的箭头 设置的属性 path 端点样式:lineCapStyle kCGLineCapRound(圆形端点)


lineCapStyle 为下面三种类型typedef CF_ENUM(int32_t, CGLineCap) {    kCGLineCapButt,        // 无端点    kCGLineCapRound,    // 圆形端点    kCGLineCapSquare    // 方形端点};
复制代码


有兴趣的 可以试试别的方法属性~

2.3 波浪曲线实现

如何实现 N 阶 波浪式曲线?如何找到 N-1 个对应的控制点?


有两个方法,下图为同数据,方案一 和 方案二 分别所得曲线图。


方案一 为左边(三阶贝塞尔)图 其中 第二条的红点 为数据的位置


方案二 为右边(CatmullRom)图 其中 第二条的红点 为数据的位置



方案一:根据 创建三次贝塞尔曲线 方法 实现波浪曲线


控制点的选取方案不唯一,以下为我选择控制点的方案:


控制点 P1:CGPointMake((PrePonit.x+NowPoint.x)/2, PrePonit.y)


控制点 P2:CGPointMake((PrePonit.x+NowPoint.x)/2, NowPoint.y)


可以根据前一个点 PrePonit 和 现在的点 NowPoint 进行计算 控制点。


主要代码如下:


UIBezierPath *path = [UIBezierPath bezierPath];[path moveToPoint:[self pointAtIndex:0]];NSInteger count = self.points.count;CGPoint PrePonit;
for (NSInteger i = 0; i < count; i++) {
CGPoint NowPoint = [self pointAtIndex:i]; if(i == 0) { PrePonit = NowPoint; } else { // 利用三次曲线 形成波浪曲线 [path addCurveToPoint:point controlPoint1:CGPointMake((PrePonit.x+NowPoint.x)/2, PrePonit.y) controlPoint2:CGPointMake((PrePonit.x+NowPoint.x)/2, NowPoint.y)]; PrePonit = NowPoint; }}return path;
复制代码


方案二:使用 CatmullRom 插值样条。(有兴趣的可以百度一下~这里只简单介绍)


要点分析:


1)给定一组控制点而得到一条曲线,曲线经过给定所有数据点。


2)Catmull-Rom 公式:P(t) = 0.5 * (2*p1 + (p2 - p0) *t + (2*p0 - 5*p1 + 4*p2 - p3)* t * t + (3*p1 - p0 - 3*p2 + p3) * t * t * t);


注:t 为分割的最小粒尺寸,根据 P0 P1 P2 P3 顺序的四个点 求得 P1P2 曲线公式。


主要代码如下:


void getPointsFromBezier(void *info, const CGPathElement *element) {    NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;    CGPathElementType type = element->type;    CGPoint *points = element->points;
if (type != kCGPathElementCloseSubpath) { [bezierPoints addObject:VALUE(0)]; if ((type != kCGPathElementAddLineToPoint) && (type != kCGPathElementMoveToPoint)) [bezierPoints addObject:VALUE(1)]; } if (type == kCGPathElementAddCurveToPoint) [bezierPoints addObject:VALUE(2)];}
NSArray *pointsFromBezierPath(UIBezierPath *bpath) { NSMutableArray *points = [NSMutableArray array]; // 获取贝塞尔曲线上所有的点 CGPathApply(bpath.CGPath, (__bridge void *)points, getPointsFromBezier); return points;}
- (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity path:(UIBezierPath *)path { NSMutableArray *points = [pointsFromBezierPath(path) mutableCopy];
if (points.count < 4) return [path copy]; [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]];
UIBezierPath *smoothedPath = [path copy]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)];
for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2);
for (int i = 1; i < granularity; i++) { // granularity 这里按照 20 粒度划分的 float t = (float) i * (1.0f / (float) granularity); float tt = t * t; float ttt = tt * t;
CGPoint pi; // 根据 CatmullRom 公式 根据 P0 P1 P2 P3 获取点的坐标 pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); if (pi.x <= self.width) { [smoothedPath addLineToPoint:pi]; } }
if (p2.x <= self.width) { [smoothedPath addLineToPoint:p2]; } }
return smoothedPath;}
复制代码


对比总结:


方案一 控制点的选取比较难,曲线的弯度 也取决于控制点,操作简单,易理解。


方案二 曲线更顺滑,但实现更复杂,不易理解。


这里推荐两个好用的网站:


a 这个网站提供了丰富的曲线类型可供选择,非常直观。


http://easings.net


b 这个网站提供了可视化的修改两个控制点,来生成一条三阶贝塞尔曲线,并切右边还可以看到这条曲线产生的动画会做怎样的速度改变。


http://www.roblaplaca.com/examples/bezierBuilder/#

3 显示

当 layer 与贝塞尔曲线相遇,会发生什么样的神奇反应?

3.1 CALayer

苹果官网注释:“An object that manages image-based content and allows you to perform animations on that content.” 管理基于图像的内容并允许您对该内容执行动画的对象。


CALayer 主要就两方面作用:


1) 管理展示内容


2)内容可执行动画


CALayer 自身有很多情况下自带隐式动画,但是 UIView 的根 Layer 是没有隐式动画的。

3.1.1 常用属性

// 图层大小 支持隐式动画@property CGRect bounds;// 图层位置 支持隐式动画@property CGPoint position;// 在z轴上的位置 支持隐式动画@property CGFloat zPosition;// 沿z轴位置的锚点 支持隐式动画@property CGFloat anchorPointZ;// 锚点 默认在layer的中心点 取值范围(0~1) 支持隐式动画@property CGPoint anchorPoint;// 图层变换 支持隐式动画@property CATransform3D transform;// 图层大小和位置 不支持隐式动画@property CGRect frame;// 是否隐藏 支持隐式动画@property(getter=isHidden) BOOL hidden;// 图层背景是否显示 支持隐式动画@property(getter=isDoubleSided) BOOL doubleSided;// 父图层 支持隐式动画@property(nullable, readonly) CALayer *superlayer;// 子图层 支持隐式动画@property(nullable, copy) NSArray<__kindof CALayer *> *sublayers;// 子图层变换 支持隐式动画@property CATransform3D sublayerTransform;// 图层蒙版 支持隐式动画@property(nullable, strong) __kindof CALayer *mask;// 子图层是否裁切超出父图层的部分,默认为NO@property BOOL masksToBounds;// 图层显示内容 设置layer的contents可以为layer添加显示内容 支持隐式动画@property(nullable, strong) id contents;// 图层显示内容的大小和位置 支持隐式动画@property CGRect contentsRect;// 用于指定层的内容如何在其范围内定位或缩放@property(copy) CALayerContentsGravity contentsGravity;// 是否包含完全不透明内容的布尔值@property(getter=isOpaque) BOOL opaque;// 背景色 支持隐式动画@property(nullable) CGColorRef backgroundColor;// 圆角半径 支持隐式动画@property CGFloat cornerRadius;// 边框宽度 支持隐式动画@property CGFloat borderWidth;// 边框颜色 支持隐式动画@property(nullable) CGColorRef borderColor;// 透明度 支持隐式动画@property float opacity;// 阴影颜色 支持隐式动画@property(nullable) CGColorRef shadowColor;// 阴影透明度 默认为0 需要显示阴影 必须设置值 支持隐式动画@property float shadowOpacity;// 阴影偏移量 支持隐式动画@property CGSize shadowOffset;// 阴影半径 支持隐式动画@property CGFloat shadowRadius;// 阴影形状 支持隐式动画@property(nullable) CGPathRef shadowPath;
复制代码

3.1.2 子类


CALayer 的子类有很多,下面说几个比较常用的。

3.2 CAShapeLayer

苹果官网注释:“A layer that draws a cubic Bezier spline in its coordinate space.” 专门用于绘制贝塞尔曲线的 layer。

3.2.1 看一下它独特的属性:

// path属性是曲线的路径,也是它和贝塞尔曲线紧密连接一个入口,决定了图层上画的是什么形状。@property(nullable) CGPathRef path;// 填充颜色@property(nullable) CGColorRef fillColor;// 曲线 指定哪块区域为内部,内部会被填充颜色@property(copy) CAShapeLayerFillRule fillRule;// 线的颜色@property(nullable) CGColorRef strokeColor;// strokeStart 和 strokeEnd 两者的取值都是0~1,决定贝塞尔曲线的划线百分比@property CGFloat strokeStart;@property CGFloat strokeEnd;// 虚线开始的位置@property CGFloat lineDashPhase;// 虚线设置,数组中奇数位实线长度,偶数位带遍空白长度@property(nullable, copy) NSArray<NSNumber *> *lineDashPattern;// 线的宽度@property CGFloat lineWidth;// 最大斜接长度 只有lineJoin属性为kCALineJoinMiter时miterLimit才有效@property CGFloat miterLimit;// 线端点样式(样式与 贝塞尔曲线的CGLineCap 属性一致)@property(copy) CAShapeLayerLineCap lineCap;// 拐角样式(样式与 贝塞尔曲线的CGLineJoin 属性一致)@property(copy) CAShapeLayerLineJoin lineJoin;
复制代码

3.2.2 举个栗子🌰

使用上面的一些属性,再结合贝塞尔曲线,我们实现了如下一些效果:



其中图五的效果,代码实现如下:


UIBezierPath *maskPath = [UIBezierPath bezierPath];for (NSInteger i = 1; i < 9; i++) {    UIBezierPath *tempPath = [UIBezierPath bezierPathWithRect:CGRectMake(190 - 20 * i, 550 - 10 * i, 40 * i, 20 * i)];    [maskPath appendPath:tempPath];}[maskPath stroke];
// CAShapeLayerCAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];maskLayer.frame = self.view.bounds;maskLayer.path = maskPath.CGPath;maskLayer.lineWidth = 5;maskLayer.strokeColor = [UIColor purpleColor].CGColor;maskLayer.fillRule = kCAFillRuleEvenOdd;maskLayer.fillColor = [UIColor cyanColor].CGColor;maskLayer.strokeStart = 0.2;maskLayer.strokeEnd = 0.5;maskLayer.lineDashPattern = @[@(10), @(10), @(30), @(30)];maskLayer.lineDashPhase = 0;[self.view.layer addSublayer:maskLayer];
复制代码

3.3 CAGradientLayer

苹果官网注释:“A layer that draws a color gradient over its background color, filling the shape of the layer (including rounded corners)” 专门用于在背景色上绘制颜色渐变的图层,填充图层的形状。

3.3.1 看一下它独特的属性:

// colors属性是CAGradientLayer的特殊属性,完美实现几种颜色的过渡。@property(nullable, copy) NSArray *colors;// 定义每个梯度停止的位置。取值范围为0~1递增@property(nullable, copy) NSArray<NSNumber *> *locations;// 决定了变色范围的起始点@property CGPoint startPoint;// 决定了变色范围的结束点@property CGPoint endPoint;// startPoint 和 endPoint两者的连线决定变色的趋势
复制代码

3.3.2 举个栗子🌰

使用上面的一些属性我们实现了如下一些效果:



其中图五的效果,代码实现如下:


CAGradientLayer *gradientLayer = [CAGradientLayer layer];gradientLayer.frame = CGRectMake(20, 450, 150, 150);gradientLayer.locations = @[@(0.2), @(0.5), @(0.6), @(0.8)];gradientLayer.startPoint = CGPointMake(0, 0);gradientLayer.endPoint = CGPointMake(1, 1);gradientLayer.colors = @[(id)[UIColor purpleColor].CGColor, (id)[UIColor greenColor].CGColor, (id)[UIColor orangeColor].CGColor, (id)[UIColor blackColor].CGColor];[self.view.layer addSublayer:gradientLayer];
复制代码

3.4 再举个栗子🌰🌰

当 CAGradientLayer + CAShapeLayer + 贝塞尔曲线 会有什么效果?上代码~


- (void)setupUI {    // 贝塞尔曲线    UIBezierPath *maskPath = [UIBezierPath bezierPath];    [maskPath moveToPoint:CGPointMake(100, 220)];    [maskPath addLineToPoint:CGPointMake(200, 150)];    [maskPath addLineToPoint:CGPointMake(300, 220)];    [maskPath stroke];
UIBezierPath *maskBottomPath = [UIBezierPath bezierPath]; [maskBottomPath moveToPoint:CGPointMake(280, 250)]; [maskBottomPath addCurveToPoint:CGPointMake(120, 250) controlPoint1:CGPointMake(250, 320) controlPoint2:CGPointMake(150, 320)]; [maskBottomPath stroke];
[maskPath appendPath:maskBottomPath];
// CAShapeLayer CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; maskLayer.frame = self.view.bounds; maskLayer.path = maskPath.CGPath; maskLayer.lineWidth = 20; maskLayer.strokeColor = UIColorFromRGB(0xF0F5FF).CGColor; maskLayer.lineCap = kCALineCapRound; maskLayer.lineJoin = kCALineJoinRound; maskLayer.fillColor = [UIColor clearColor].CGColor; maskLayer.strokeStart = 0; maskLayer.strokeEnd = 0; [self.view.layer addSublayer:maskLayer];
// CAGradientLayer NSMutableArray *colorArray = [NSMutableArray new]; for (NSInteger i = 0; i < 6; i++) { [colorArray addObject:[self arc4randomColor]]; }
CAGradientLayer *gradientLayer = [CAGradientLayer layer]; gradientLayer.frame = self.view.bounds; gradientLayer.colors = colorArray; gradientLayer.startPoint = CGPointMake(0, 0.5); gradientLayer.endPoint = CGPointMake(1, 0.5); gradientLayer.mask = maskLayer; [self.view.layer addSublayer:gradientLayer];
// 创建全局并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 创建定时器 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // 设置定时器,每N秒触发 int64_t intervalInNanoseconds = (int64_t)(0.3 * NSEC_PER_SEC); dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), (uint64_t)intervalInNanoseconds, 0); // 设置定时器处理事件 dispatch_source_set_event_handler(timer, ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (maskLayer.strokeEnd < 0.6) { maskLayer.strokeEnd += 0.4; } else if (maskLayer.strokeEnd < 0.8){ maskLayer.strokeEnd += 0.2; } else if (maskLayer.strokeEnd < 1){ maskLayer.strokeEnd += 0.1; } else { maskLayer.strokeEnd = 1; if (maskLayer.strokeStart < 0.6) { maskLayer.strokeStart += 0.4; } else if (maskLayer.strokeStart < 0.8){ maskLayer.strokeStart += 0.2; } else if (maskLayer.strokeStart < 1){ maskLayer.strokeStart += 0.1; } else { [colorArray removeObjectAtIndex:0]; [colorArray addObject:[self arc4randomColor]]; gradientLayer.colors = colorArray;
maskLayer.strokeStart = 0; maskLayer.strokeEnd = 0; } } }); });
_timer = timer;
// 开启定时器 dispatch_resume(_timer);}
- (id)arc4randomColor { return (id)[UIColor colorWithRed:arc4random()%255/255.f green:arc4random()%255/255.f blue:arc4random()%255/255.f alpha:1].CGColor;}
复制代码


运行结果👇


<https://v.qq.com/x/page/l3146iykm06.html


其他 layer 怎么使用?贝塞尔曲线 + Layer 还可以组合出更多神奇的反应!感兴趣的可以去试试哦~本文仅为抛砖引玉~~


本文转载自公众号贝壳产品技术(ID:beikeTC)。


原文链接


贝塞尔曲线在iOS端的绘图实践


2020-09-15 10:083764

评论

发布
暂无评论
发现更多内容

软件架构语录

烟雨濛濛

那些会阻碍程序员成长的细节[3]

MavenTalker

程序员 职业规划 职业成长

iOS 动画 - 窗景篇(三·完结)

柯烂

ios swift 动画 移动互联网 动效

了不起的 TypeScript 入门教程 [1.2 w字]

阿宝哥

Java typescript 大前端 Web

为什么建议你使用枚举?

王磊

Java 枚举

深入浅出kubernetes之WorkQueue详解

博文视点Broadview

Kubernetes 源码分析 k8s 队列 延迟队列

告别静默式看房 融云音视频助力上海中原 App 上线 VR 带看服务

Geek_116789

计算机操作系统基础(五)---Linux的进程管理

书旅

php 线程 多线程 操作系统 进程

海阅优品致力打造新零售蓝海

Geek_116789

MySQL实战45讲笔记(1)

程序员老王

msyql

【写作群星榜】6.20~6.26 写作平台优秀作者 & 文章排名

InfoQ写作社区官方

写作平台 排行榜 热门活动

融云年中大促钜惠来袭 IM+RTC 超值套餐最低6折起

Geek_116789

一二线城市知名 IT 互联网公司名单(新版)

程序员生活志

互联网 IT 大厂

ARTS - Week 4

Khirye

ARTS 打卡计划 arts

限频/限流的一些思考

i风语

Java redis 微服务 sentinel ratelimiter

Dart vs Swift

柠檬水

swift dart

创新管理体系标准ISO56002介绍

涛哥 数字产品和业务架构

数字化转型 创新

玩转Java8中的 Stream 之从零认识 Stream

Java小咖秀

学习 面试 stream java8 经验

重学 Java 设计模式:实战中介者模式「按照Mybatis原理手写ORM框架,给JDBC方式操作数据库增加中介者场景」

小傅哥

设计模式 小傅哥 代码优化 代码规范 中介者模式

来了!8M/S+速度,Pdown复活!

程序员生活志

如何学 Java,我说点不太一样的学习方式

四猿外

学习 程序员 个人成长

揭秘!中国人一定要知道的北斗卫星系统

程序那些事

北斗卫星 北斗系统 卫星定位 卫星授时 黑科技

势能造就下的互联网大厂程序员为什么去开滴滴了?

非著名程序员

程序员 程序人生 提升认知

一群龙舟划手 “拍了拍” 你:端午节安康~

博睿数据

IM聊天教程:发送图片/视频/语音/表情

GoEasy消息推送

websocket 即时通讯 聊天室 聊天

MySQL系列 - SQL查询与修改执行过程

俊俊哥

MySQL 性能优化 关系型数据库 存储

从0开始设计Flutter独立APP | 第二篇: 完整的国际化语言支持

渔子长

flutter 大前端

架构师训练营第四周作业

烟雨濛濛

对直播带货的一点思考

Neco.W

直播 直播带货

架构师训练营第四周作业

张明森

架构师训练营第四周学习总结

张明森

贝塞尔曲线在iOS端的绘图实践_大数据_孙齐_InfoQ精选文章