普通视图

发现新文章,点击刷新页面。
昨天 — 2026年1月30日首页

Flutter最佳实践:Sliver族网络刷新组件NCustomScrollView

作者 SoaringHeart
2026年1月30日 21:07

一、需求来源

最近需要实现嵌套和吸顶Header滚动下的下拉刷新及上拉加载。最终实现基于 CustomScrollView 的刷新视图组件。

simulator_screenshot_5C4883E4-F919-4FFD-BE3D-97E0BCD5C40D.png

二、使用示例

Widget buildBodyNew() {
  return NCustomScrollView<String>(
    onRequest: (bool isRefresh, int page, int pageSize, pres) async {
      final length = isRefresh ? 0 : pres.length;
      final list = List<String>.generate(pageSize, (i) => "item${length + i}");
      DLog.d([isRefresh, list.length]);
      return list;
    },
    headerSliverBuilder: (context, bool innerBoxIsScrolled) {
      return [
        buildPersistentHeader(),
      ];
    },
    itemBuilder: (_, i, e) {
      return ListTile(
        title: Text('Item $i'),
      );
    },
  );
}

三、源码

//
//  NCustomScrollView.dart
//  projects
//
//  Created by shang on 2026/1/28 14:41.
//  Copyright © 2026/1/28 shang. All rights reserved.
//

import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_templet_project/basicWidget/n_placeholder.dart';
import 'package:flutter_templet_project/basicWidget/n_sliver_decorated.dart';
import 'package:flutter_templet_project/basicWidget/refresh/easy_refresh_mixin.dart';
import 'package:flutter_templet_project/basicWidget/refresh/n_refresh_view.dart';

/// 基于 CustomScrollView 的下拉刷新,上拉加载更多的滚动列表
class NCustomScrollView<T> extends StatefulWidget {
  const NCustomScrollView({
    super.key,
    this.title,
    this.placeholder = const NPlaceholder(),
    this.contentDecoration = const BoxDecoration(),
    this.contentPadding = const EdgeInsets.all(0),
    required this.onRequest,
    required this.headerSliverBuilder,
    required this.itemBuilder,
    this.separatorBuilder,
    this.headerBuilder,
    this.footerBuilder,
    this.builder,
  });

  final String? title;

  final Widget? placeholder;

  final Decoration contentDecoration;

  final EdgeInsets contentPadding;

  /// 请求方法
  final RequestListCallback<T> onRequest;

  /// 列表表头
  final NestedScrollViewHeaderSliversBuilder? headerSliverBuilder;

  /// ListView 的 itemBuilder
  final ValueIndexedWidgetBuilder<T> itemBuilder;

  final IndexedWidgetBuilder? separatorBuilder;

  /// 列表表头
  final List<Widget> Function(int count)? headerBuilder;

  /// 列表表尾
  final List<Widget> Function(int count)? footerBuilder;

  final Widget Function(List<T> items)? builder;

  @override
  State<NCustomScrollView<T>> createState() => _NCustomScrollViewState<T>();
}

class _NCustomScrollViewState<T> extends State<NCustomScrollView<T>>
    with AutomaticKeepAliveClientMixin, EasyRefreshMixin<NCustomScrollView<T>, T> {
  @override
  bool get wantKeepAlive => true;

  final scrollController = ScrollController();

  @override
  late RequestListCallback<T> onRequest = widget.onRequest;

  @override
  List<T> items = <T>[];

  @override
  void didUpdateWidget(covariant NCustomScrollView<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.title != oldWidget.title ||
        widget.placeholder != oldWidget.placeholder ||
        widget.contentDecoration != oldWidget.contentDecoration ||
        widget.contentPadding != oldWidget.contentPadding ||
        widget.onRequest != oldWidget.onRequest ||
        widget.itemBuilder != oldWidget.itemBuilder ||
        widget.separatorBuilder != oldWidget.separatorBuilder) {
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    if (items.isEmpty) {
      return GestureDetector(onTap: onRefresh, child: Center(child: widget.placeholder));
    }

    final child = EasyRefresh.builder(
      controller: refreshController,
      onRefresh: onRefresh,
      onLoad: onLoad,
      childBuilder: (_, physics) {
        return CustomScrollView(
          physics: physics,
          slivers: [
            ...(widget.headerBuilder?.call(items.length) ?? []),
            buildContent(),
            ...(widget.footerBuilder?.call(items.length) ?? []),
          ],
        );
      },
    );
    if (widget.headerSliverBuilder == null) {
      return child;
    }

    return NestedScrollView(
      headerSliverBuilder: widget.headerSliverBuilder!,
      body: child,
    );
  }

  Widget buildContent() {
    if (items.isEmpty) {
      return SliverToBoxAdapter(child: widget.placeholder);
    }

    return NSliverDecorated(
      decoration: widget.contentDecoration,
      sliver: SliverPadding(
        padding: widget.contentPadding,
        sliver: widget.builder?.call(items) ?? buildSliverList(),
      ),
    );
  }

  Widget buildSliverList() {
    return SliverList.separated(
      itemBuilder: (_, i) => widget.itemBuilder(context, i, items[i]),
      separatorBuilder: (_, i) => widget.separatorBuilder?.call(context, i) ?? const SizedBox(),
      itemCount: items.length,
    );
  }
}

源码:EasyRefreshMixin.dart

//
//  EasyRefreshMixin.dart
//  projects
//
//  Created by shang on 2026/1/28 14:37.
//  Copyright © 2026/1/28 shang. All rights reserved.
//

import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_templet_project/basicWidget/refresh/n_refresh_view.dart';

/// EasyRefresh刷新 mixin
mixin EasyRefreshMixin<W extends StatefulWidget, T> on State<W> {
  late final refreshController = EasyRefreshController(
    controlFinishRefresh: true,
    controlFinishLoad: true,
  );


  /// 请求方式
  late RequestListCallback<T> _onRequest;
  RequestListCallback<T> get onRequest => _onRequest;
  set onRequest(RequestListCallback<T> value) {
    _onRequest = value;
  }

  // 数据列表
  List<T> _items = [];
  List<T> get items => _items;
  set items(List<T> value) {
    _items = value;
  }

  int page = 1;
  final int pageSize = 20;
  var indicator = IndicatorResult.success;

  @override
  void dispose() {
    refreshController.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();

    WidgetsBinding.instance.addPostFrameCallback((_) {
      // DLog.d([widget.title, widget.key, hashCode]);
      if (items.isEmpty) {
        onRefresh();
      }
    });
  }

  Future<void> onRefresh() async {
    try {
      page = 1;
      final list = await onRequest(true, page, pageSize, <T>[]);
      items.replaceRange(0, items.length, list);
      page++;

      final noMore = list.length < pageSize;
      if (noMore) {
        indicator = IndicatorResult.noMore;
      }
      refreshController.finishRefresh();
      refreshController.resetFooter();
    } catch (e) {
      refreshController.finishRefresh(IndicatorResult.fail);
    }
    setState(() {});
  }

  Future<void> onLoad() async {
    if (indicator == IndicatorResult.noMore) {
      refreshController.finishLoad();
      return;
    }

    try {
      final start = (items.length - pageSize).clamp(0, pageSize);
      final prePages = items.sublist(start);
      final list = await onRequest(false, page, pageSize, prePages);
      items.addAll(list);
      page++;

      final noMore = list.length < pageSize;
      if (noMore) {
        indicator = IndicatorResult.noMore;
      }
      refreshController.finishLoad(indicator);
    } catch (e) {
      refreshController.finishLoad(IndicatorResult.fail);
    }
    setState(() {});
  }
}

最后、总结

1、当页面比较复杂,需要吸顶或者嵌套滚动时就必须使用 Sliver 相关组件,否则会有滚动行文冲突。

2、NCustomScrollView 支持顶部吸顶组件自定义;底部列表头,列表尾设置,支持sliver 设置 Decoration。

3、支持下拉刷新,上拉加载更多,代码极简,使用方便。

4、刷新逻辑封装在 EasyRefreshMixin 混入里,方便多组件可共用。

github

追觅科技亮相2026中英企业家委员会会议

2026年1月30日 20:53
1月29日,2026中英企业家委员会会议在京举行,追觅科技作为行业代表受邀参会。追觅科技创立于2017年,目前业务已覆盖全球超120个国家和地区,开设超6500家旗舰店。在英国市场,其扫地机品类市占率近30%,伯明翰旗舰店位于核心商圈New Street。此次受邀参与会议,彰显其全球化实力与品牌国际影响力,将进一步助力其全球智能生态布局。

荃银高科:涉嫌信息披露违法违规,被中国证监会立案调查

2026年1月30日 20:51
36氪获悉,荃银高科公告,公司于2026年1月30日收到中国证监会下发的《立案告知书》,因公司涉嫌信息披露违法违规,中国证监会决定对公司立案。目前公司生产经营正常,立案调查不会对公司正常生产经营活动产生重大影响。公司将积极配合中国证监会的工作,严格履行信息披露义务。

莫高股份:公司股票可能被实施退市风险警示

2026年1月30日 20:47
36氪获悉,莫高股份公告,经公司财务部门初步测算,预计公司2025年年度利润总额、净利润或者扣除非经常性损益后的净利润孰低者为负值,且扣除与主营业务无关的业务收入和不具备商业实质的收入后的营业收入低于3亿元。公司股票在2025年年度报告披露后可能被实施退市风险警示(在公司股票简称前冠以“*ST”字样)。

flutter添加间隙gap源码解析

作者 Nicholas68
2026年1月30日 20:46

Flutter 小部件,可轻松在 Flex 小部件(如列和行)或滚动视图中添加间隙。

Gap的核心原理是使用RenderObject自定义实现布局。

Gap


class Gap extends StatelessWidget {
  const Gap(
    this.mainAxisExtent, {
    Key? key,
    this.crossAxisExtent,
    this.color,
  })  : assert(mainAxisExtent >= 0 && mainAxisExtent < double.infinity),
        assert(crossAxisExtent == null || crossAxisExtent >= 0),
        super(key: key);

  const Gap.expand(
    double mainAxisExtent, {
    Key? key,
    Color? color,
  }) : this(
          mainAxisExtent,
          key: key,
          crossAxisExtent: double.infinity,
          color: color,
        );

  final double mainAxisExtent;

  final double? crossAxisExtent;

  final Color? color;

  @override
  Widget build(BuildContext context) {
    final scrollableState = Scrollable.maybeOf(context);
    final AxisDirection? axisDirection = scrollableState?.axisDirection;
    final Axis? fallbackDirection =
        axisDirection == null ? null : axisDirectionToAxis(axisDirection);

    return _RawGap(
      mainAxisExtent,
      crossAxisExtent: crossAxisExtent,
      color: color,
      fallbackDirection: fallbackDirection,
    );
  }
}

从源码看, 它提供了两个构造方法, GapGap.expand方便用户按需使用。

_RawGap

_RawGap是核心类, 它继承了LeafRenderObjectWidget.

class _RawGap extends LeafRenderObjectWidget {
  const _RawGap(
    this.mainAxisExtent, {
    Key? key,
    this.crossAxisExtent,
    this.color,
    this.fallbackDirection,
  })  : assert(mainAxisExtent >= 0 && mainAxisExtent < double.infinity),
        assert(crossAxisExtent == null || crossAxisExtent >= 0),
        super(key: key);

  final double mainAxisExtent;

  final double? crossAxisExtent;

  final Color? color;

  final Axis? fallbackDirection;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderGap(
      mainAxisExtent: mainAxisExtent,
      crossAxisExtent: crossAxisExtent ?? 0,
      color: color,
      fallbackDirection: fallbackDirection,
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderGap renderObject) {
    if (kDebugMode) {
      debugPrint(
        '[Gap] updateRenderObject '
        'mainAxisExtent=$mainAxisExtent '
        'crossAxisExtent=${crossAxisExtent ?? 0} '
        'color=$color '
        'fallbackDirection=$fallbackDirection',
      );
    }
    renderObject
      ..mainAxisExtent = mainAxisExtent
      ..crossAxisExtent = crossAxisExtent ?? 0
      ..color = color
      ..fallbackDirection = fallbackDirection;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('mainAxisExtent', mainAxisExtent));
    properties.add(
        DoubleProperty('crossAxisExtent', crossAxisExtent, defaultValue: 0));
    properties.add(ColorProperty('color', color));
    properties.add(EnumProperty<Axis>('fallbackDirection', fallbackDirection));
  }
}

RenderGap

class RenderGap extends RenderBox {
  RenderGap({
    required double mainAxisExtent,
    double? crossAxisExtent,
    Axis? fallbackDirection,
    Color? color,
  })  : _mainAxisExtent = mainAxisExtent,
        _crossAxisExtent = crossAxisExtent,
        _color = color,
        _fallbackDirection = fallbackDirection {
    if (kDebugMode) {
      debugPrint(
        '🆕 RenderGap<init> '
        'mainAxisExtent=$mainAxisExtent '
        'crossAxisExtent=$crossAxisExtent '
        'color=$color '
        'fallbackDirection=$fallbackDirection',
      );
    }
  }

  double get mainAxisExtent => _mainAxisExtent;
  double _mainAxisExtent;
  set mainAxisExtent(double value) {
    if (_mainAxisExtent != value) {
      if (kDebugMode) {
        debugPrint('📏 mainAxisExtent set: $_mainAxisExtent -> $value');
      }
      _mainAxisExtent = value;
      markNeedsLayout();
    }
  }

  double? get crossAxisExtent => _crossAxisExtent;
  double? _crossAxisExtent;
  set crossAxisExtent(double? value) {
    if (_crossAxisExtent != value) {
      if (kDebugMode) {
        debugPrint('📐 crossAxisExtent set: $_crossAxisExtent -> $value');
      }
      _crossAxisExtent = value;
      markNeedsLayout();
    }
  }

  Axis? get fallbackDirection => _fallbackDirection;
  Axis? _fallbackDirection;
  set fallbackDirection(Axis? value) {
    if (_fallbackDirection != value) {
      if (kDebugMode) {
        debugPrint('🧭 fallbackDirection set: $_fallbackDirection -> $value');
      }
      _fallbackDirection = value;
      markNeedsLayout();
    }
  }

  Axis? get _direction {
    final parentNode = parent;
    if (parentNode is RenderFlex) {
      return parentNode.direction;
    } else {
      return fallbackDirection;
    }
  }

  Color? get color => _color;
  Color? _color;
  set color(Color? value) {
    if (_color != value) {
      if (kDebugMode) {
        debugPrint('🎨 color set: $_color -> $value');
      }
      _color = value;
      markNeedsPaint();
    }
  }

  @override
  double computeMinIntrinsicWidth(double height) {
    final result = _computeIntrinsicExtent(
      Axis.horizontal,
      () => super.computeMinIntrinsicWidth(height),
    )!;
    if (kDebugMode) {
      debugPrint('🔹 computeMinIntrinsicWidth(height=$height) => $result');
    }
    return result;
  }

  @override
  double computeMaxIntrinsicWidth(double height) {
    final result = _computeIntrinsicExtent(
      Axis.horizontal,
      () => super.computeMaxIntrinsicWidth(height),
    )!;
    if (kDebugMode) {
      debugPrint('🔷 computeMaxIntrinsicWidth(height=$height) => $result');
    }
    return result;
  }

  @override
  double computeMinIntrinsicHeight(double width) {
    final result = _computeIntrinsicExtent(
      Axis.vertical,
      () => super.computeMinIntrinsicHeight(width),
    )!;
    if (kDebugMode) {
      debugPrint('🔸 computeMinIntrinsicHeight(width=$width) => $result');
    }
    return result;
  }

  @override
  double computeMaxIntrinsicHeight(double width) {
    final result = _computeIntrinsicExtent(
      Axis.vertical,
      () => super.computeMaxIntrinsicHeight(width),
    )!;
    if (kDebugMode) {
      debugPrint('🔶 computeMaxIntrinsicHeight(width=$width) => $result');
    }
    return result;
  }

  double? _computeIntrinsicExtent(Axis axis, double Function() compute) {
    final Axis? direction = _direction;
    if (direction == axis) {
      final result = _mainAxisExtent;
      if (kDebugMode) {
        debugPrint(
          '📐 _computeIntrinsicExtent(axis=$axis, direction=$direction) => $result',
        );
      }
      return result;
    } else {
      if (_crossAxisExtent!.isFinite) {
        final result = _crossAxisExtent;
        if (kDebugMode) {
          debugPrint(
            '📐 _computeIntrinsicExtent(axis=$axis, direction=$direction) => $result',
          );
        }
        return result;
      } else {
        final result = compute();
        if (kDebugMode) {
          debugPrint(
            '📐 _computeIntrinsicExtent(axis=$axis, direction=$direction) => $result',
          );
        }
        return result;
      }
    }
  }

  @override
  Size computeDryLayout(BoxConstraints constraints) {
    final Axis? direction = _direction;

    if (direction != null) {
      if (direction == Axis.horizontal) {
        final s =
            constraints.constrain(Size(mainAxisExtent, crossAxisExtent!));
        if (kDebugMode) {
          debugPrint(
            '💧 computeDryLayout(constraints=$constraints, direction=$direction) => $s',
          );
        }
        return s;
      } else {
        final s =
            constraints.constrain(Size(crossAxisExtent!, mainAxisExtent));
        if (kDebugMode) {
          debugPrint(
            '💧 computeDryLayout(constraints=$constraints, direction=$direction) => $s',
          );
        }
        return s;
      }
    } else {
      throw FlutterError(
        'A Gap widget must be placed directly inside a Flex widget '
        'or its fallbackDirection must not be null',
      );
    }
  }

  @override
  void performLayout() {
    size = computeDryLayout(constraints);
    if (kDebugMode) {
      debugPrint('🛠 performLayout(constraints=$constraints) size=$size');
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (color != null) {
      final Paint paint = Paint()..color = color!;
      context.canvas.drawRect(offset & size, paint);
      if (kDebugMode) {
        debugPrint('🎨 paint(offset=$offset, size=$size, color=$color)');
      }
    } else {
      if (kDebugMode) {
        debugPrint('🎨 paint(offset=$offset, size=$size, color=null)');
      }
    }
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    if (kDebugMode) {
      debugPrint('🧾 debugFillProperties()');
    }
    properties.add(DoubleProperty('mainAxisExtent', mainAxisExtent));
    properties.add(DoubleProperty('crossAxisExtent', crossAxisExtent));
    properties.add(ColorProperty('color', color));
    properties.add(EnumProperty<Axis>('fallbackDirection', fallbackDirection));
  }
}

截屏2026-01-30 19.30.39.png

真正绘制原理, RenderGapRenderBox的子类, 不需要子类, 绘制时, 只与自身sizecolor有关。

mainAxisExtentcrossAxisExtent属性set方法触发后, 会执行markNeedsLayout, 标记该渲染对象需要重新布局, 并请求(requestVisualUpdate)调度下一帧执行布局。

布局阶段,会执行computeDryLayoutperformLayout方法,更新size

绘制阶段paint,在 offset & size 的矩形内填充颜色(color 为 null 时不绘制)。

  • 矩形范围:offset & size 等价于 Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),保证绘制严格位于本组件区域。
  • 无子节点与图层:RenderGap 不 push 额外 Layer,也不绘制子内容;仅把一个矩形指令提交到当前画布。

markNeedsLayout布局阶段不同的是, markNeedsPaint绘制阶段不参与尺寸计算, 它在size确定后才执行。

与标记方法的关系:

  • markNeedsPaint:当 color 变更时由属性 setter 调用,标记本节点需要在下一帧重绘;不会触发布局。
  • markNeedsLayout:当 mainAxisExtent/crossAxisExtent/fallbackDirection 变更引起尺寸或方向变化时调用;下一帧会重新布局,布局完成后若绘制区域或内容也需更新才会出现 paint。
  • 执行链路示例:属性变更 → 标记(layout/paint)→ 布局(computeDryLayout/performLayout)→ 绘制(paint)。

新易盛:2025年净利同比预增231.24%-248.86%

2026年1月30日 20:45
36氪获悉,新易盛发布2025年业绩预告。报告显示,预计2025年归属于上市公司股东的净利润为94亿元-99亿元,比上年同期增长231.24%-248.86%。报告期内,受益于算力投资持续增长,高速率产品需求快速提升,公司销售收入和净利润大幅增加。

市场监管总局公布4起直播电商领域典型案例,成都快购被罚超2600万

2026年1月30日 20:41
36氪获悉,市场监管总局今天(30日)集中发布第五批直播电商领域典型案例。其中,市场监管总局查处成都快购科技有限公司违法案。2025年12月,市场监管总局综合考量当事人的违法事实、案件性质、违法情节以及社会危害程度,依法对当事人的违法行为作出行政处罚。责令当事人立即改正违法行为,并处罚没款26692904.62元。

上纬新材:2025年净利润同比减少37%-54%

2026年1月30日 20:39
36氪获悉,上纬新材发布2025年业绩预告。报告显示,预计2025年年度实现归属于母公司所有者的净利润与上年同期(法定披露数据)相比,将减少3,300万元到4,800万元,同比减少37%到54%。上年同期归属于母公司所有者的净利润为8,868.14万元。净利润较上年同期减少主要系新材料业务加大研发投入、计提投资损失和信用减值损失、探索新方向增加费用投入等。

顾地科技:预计2025年度期末净资产为负值,公司股票交易可能被实施退市风险警示

2026年1月30日 20:34
36氪获悉,顾地科技公告,预计2025年归属于上市公司股东的净利润为亏损3亿元-5.77亿元,预计2025年度期末净资产为负值。若公司2025年度经审计的期末净资产为负值,根据相关规定,公司股票交易将在2025年年度报告披露后被实施退市风险警示(股票简称前冠以“*ST”字样)。

中国车企和特斯拉的下一战,战场已定

2026年1月30日 20:22

出品|虎嗅汽车组

作者|王亚骏

头图|视觉中国



在发布了“建设富足非凡世界”的新使命后,特斯拉又用两款旗舰车型,来为这个新使命“祭旗”。


在Q4财报电话会开场白环节上,特斯拉CEO马斯克表示,是时候让Model S和Model X“光荣退役(honorable discharge)”了,这两款特斯拉的功勋车型将于下季度基本停产。


Model S/X的成功,为特斯拉走辆车型Model 3/Y带来了研发资金。同时,它们还极大程度上促进了新能源汽车从"环保政策工具"向"大型科技类消费品"的跃迁。


原本生产Model S和Model X的弗里蒙特工厂,将改为生产Optimus机器人。这款产品可以视为特斯拉新使命中最为重要的一环,马斯克认为未来特斯拉80%的市值都由Optimus机器人支撑。不过,他的梦想正面临着来自大洋彼岸的威胁。


在财报电话会上,马斯克直言,中国擅长AI,也擅长制造,在人形机器人领域,特斯拉最大的竞争肯定来自中国。

 

从中国车企在人形机器人领域的动作来看,马斯克这番话并非什么“高情商”之语或杞人忧天。目前在国内,不管是新势力还是传统车企,均已在机器人产业进行了布局。


同时,中国车企的入场步伐也在不断加快。在特斯拉发布财报的三天前,理想汽车开了一次线上全员会,公司CEO李想表示,理想一定会做人形机器人,并会尽快让该产品落地亮相。



这意味着,在FSD尚未入华、双方智驾战还是“隔海叫阵”的情况下,中国车企和特斯拉的下一战已经确定将在机器人领域展开。


中国车企和特斯拉,为何同时盯上了人形机器人?当下国内汽车行业的内卷式竞争,对战局又将有何影响?


低投入+高回报预期,让中美车企同时盯上这块蛋糕


车企做人形机器人,很难算“跨界”。


首先,车企人形机器人专门搭建新的研发和制造团队。何小鹏曾表示,“车企70%的技术储备能直接复用到机器人身上。”


两者的技术复用度之所以如此之高,原因是在感知、决策、执行三大核心环节,智能汽车和人形机器人的技术架构高度重叠,甚至可以被视为“同一套技术栈的双线部署”。以Optimus机器人为例:


  • 感知层,Optimus机器人直接使用了FSD(特斯拉完全自动驾驶)的纯视觉感知方案;

  • 决策层,两者的算法相似度60%(华西证券测算);

  • 执行层,Cybertruck的线控转向与Optimus关节驱动存在复用。


除了软件外,两者在硬件上也有着极高的关联度,比如传感器、芯片、雷达、摄像头等,这进一步推高了供应链重合度。


中国电动汽车百人会副理事长兼秘书长张永伟认为,在供应链方面,智能汽车与人形机器人的重合度超过了60%(车企纷纷布局的飞行汽车也是同理)。


同时,在制造方面,两者装配逻辑、质量控制体系等方面也具备一定的通用性。这意味着,车企完全可以将自己过剩的汽车生产线改装成人形机器人生产线,无需再找地建厂。


如此高的关联度,让汽车不必因拓展人形机器人业务而损耗太多元气。而机器人市场的广阔前景,更是让车企在当下的布局有望变成一个“低成本、高回报”的投资。


据摩根士丹利预测,到了2050年,全球机器人销售额达到25万亿美元,是2025年销售额的250倍。而在未来的25年,汽车销量恐怕难有如此规模的增长。


不只有中国车企和特斯拉外想瓜分这片淘金地。日韩和欧洲车企也在人形领域进行了布局,其中进展最快的是现代汽车。 2021年6月,现代汽车收购了知名人形机器人公司波士顿动力。


不过在财报电话会上,马斯克并未提及丰田现代们。而在两年前,他曾公开预测,未来全球只有十家车企能存活,分别是九家中国车企和特斯拉。


这场仗会怎么打?


从各家车企在机器人量产方面的规划来看,中国车企和特斯拉之间的“机器人战争”极有可能在2027年被点燃。


在财报电话会上,马斯克表示特斯拉“可能会在几个月内发布Optimus 3。”在瑞士达沃斯世界经济论坛上,特斯拉CEO埃隆·马斯克宣布,公司计划于2027年底前向公众销售其Optimus人形机器人。


在大洋彼岸,奇瑞已经开始交付首款人形机器人“墨茵”(2025年交付量超300台)。同时,小鹏汽车也计划在2026年年底实现人形机器人的量产。


小鹏汽车人形机器人IRON


在这场竞争中,双方各有各的优势


得益于长期积累的自动驾驶AI算法和庞大的真实世界数据(数百万辆特斯拉汽车每天采集的端到端驾驶视频数据),特斯拉在技术上实现了领先。


马斯克在财报电话会上详细解释了自身技术和数据带来的产品优势。目前,人形机器人有三大难点:打造像人手一样灵活的机械手(手是机器人身上最难的部分),其次是现实世界AI,最后是规模化生产。这三点是目前最大的难题,“我认为特斯拉是唯一一家同时具备这三大要素的公司。”


面对特斯拉的技术壁垒,中国车企并非没有还手之力。在智能汽车的竞争中,中国车企已经证明了自己的快速迭代和成本控制能力。摩根士丹利曾发布研报表示,中国在人形机器人产业供应链中占据主导地位,占比达到63%,“中国庞大的市场规模正在把人形机器人的制造成本打下来”。


在这种局面下,谁能吸引到更多更好的人才,谁的获胜概率就更大。何小鹏曾表示,吸引最顶尖人才,是做好人形机器人的关键。


不过,当下汽车行业的价格战和内卷,一定程度上扯了中国车企招募人才的后腿。


在线上全员会中,李想在谈及人形机器人业务时表示,公司要招聘这方面最好的人才,要从机器人创业公司那里挖人。


当下,车企和机器人公司之间的人才流动的确存在。但虎嗅汽车在与多位行业人士交流中发现,这种流动的主要方向却并非从机器人公司流向车企,而是反过来,从车企“逃到”机器人公司。


车企员工们之所以选择逃离,重要原因之一便是为了躲避内卷。同时,由于汽车和机器人,他们跳槽后的学习和适应成本也比较低。


与之对比,虽然特斯拉在2025年遭遇了业绩下滑,马斯克形象受损等风波,但这家新能源汽车先行者仍对人才保持了不俗的吸引力。据雇主品牌调研机构Universum调研,在2025年美国最具吸引力雇主排名中,特斯拉位列工程类学生就业选择第九名,是排名最高的汽车制造商(马斯克一直强调特斯拉是AI公司而非汽车公司,不过在2025年,汽车业务的收入仍占公司总收入的73.4%,为696亿美元)。


从这方面来看,当下的汽车行业内卷,难以对人形机器人业务起到什么正面作用。好在,中国车企与特斯拉的机器人之战,也并非短时间内便会走到决战时刻。

下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

万科:我爸又来救我了,羡慕吗?

2026年1月30日 20:00

喜大普奔!万科终于开始还钱了,这段时间万科还活着吗?郁亮是真退休还是被带走了?万科是不是要爆雷了?万科的房子还能不能买?这些问题在评论区反复出现,而这些问题在今天都有了阶段性的答案,我将用三期视频将这些坑全部填上。

下载虎嗅APP,第一时间获取深度独到的商业科技资讯,连接更多创新人群与线下活动

润欣科技:决定放弃行使意向协议所约定的进行后续投资的权利

2026年1月30日 20:26
36氪获悉,润欣科技公告,根据《合作与投资意向协议》约定,后续投资计划(公司有权选择增持奇异摩尔股权至不超过20%)以奇异摩尔达到约定的经营目标为前提条件之一,即其2025年度合并报表的主营业务收入需达到6亿元人民币。近日,公司收到奇异摩尔出具的告知函,确认其2025年度合并报表的主营业务收入未达到上述经营目标。奇异摩尔当前的融资估值增长较快,经协商,双方未就将经营目标顺延至2026年度达成一致,公司决定放弃行使意向协议所约定的进行后续投资的权利。

现货白银跌幅收窄至10%

2026年1月30日 20:21
36氪获悉,国际贵金属收窄跌幅,现货黄金跌幅收窄至4.61%,报5129.17美元/盎司;现货白银跌幅收窄至10.11%,报103.72美元/盎司。

苏宁易购:预计2025年净利润5000万-7500万元

2026年1月30日 20:17
36氪获悉,苏宁易购发布2025年度业绩预告。报告显示,公司预计全年实现归属于上市公司股东的净利润5000万元-7500万元。面对复杂的市场变化,苏宁易购连续两年保持盈利,聚焦零售服务商战略,持续夯实家电主业与经营基本盘。

豫园股份:预计2025年归母净亏损48亿元左右

2026年1月30日 20:15
36氪获悉,豫园股份发布2025年业绩预告。报告显示,经财务部门初步测算,预计2025年年度实现归属于母公司所有者的净利润为-480,000万元左右,净利润出现亏损。公司预计2025年年度实现归属于母公司所有者的扣除非经营性损益的净利润为-470,000万元左右。

富满微:预计2025年归母净亏损1.5亿元-1.9亿元

2026年1月30日 20:07
36氪获悉,富满微发布2025年业绩预告。报告显示,预计2025年度实现营业收入8.5亿元-8.7亿元,预计2025年度归母净利润亏损1.5亿元-1.9亿元。报告期,公司着力加大研发投入、加速新品推进进程、加快技术迭代、优化产品结构、加强渠道建设、优化营销策略,促使报告期较2024年销售量、销售收入双双增长。基于谨慎性原则,报告期公司计提存货跌价减值损失约6,745.13万元;应收账款坏账冲回1,982.08万元。2025年公司计提股份支付费用2,856.89万元。
❌
❌