我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么用Flutter实现酷狗流畅Tabbar效果

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么用Flutter实现酷狗流畅Tabbar效果

本文小编为大家详细介绍“怎么用Flutter实现酷狗流畅Tabbar效果”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么用Flutter实现酷狗流畅Tabbar效果”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

分析效果

研究酷狗Tabbar的动画可以发现,默认状态下在当前Tab的中心处展示圆点,滑动时的效果拆分成两个以下部分:

  • 从单个Tab A的中心根据X轴平移到Tab B的中心位置;

  • 指示器的长度从圆点变长,再缩短为圆点。其中最大长度是可变的,跟两个Tab的大小和距离都有关系;

  • 指示器虽然依赖Tab的size和offset来变换,但和Tab却基本是同一时间渲染的,整个过程非常顺滑;

  • 总的来说,酷狗的效果就是改变了指示器的渲染动画而已。

开发思路

从上面的分析可以明确,指示器的滑动效果一定跟每个Tab的size和offset相关。那在Flutter中,获取渲染信息我们马上能想到GlobalKey,通过GlobalKey的currentContext对象获取Rander信息,但这必须在视图渲染完成后才能获取,也就是说Tab渲染完才能开始计算并渲染指示器。很显然不符合体验要求,同时频繁使用GlobalKey也会导致性能较差。

转变思路,我们需要在Tab渲染的不断把信息传给指示器,然后更新指示器,这种方式自然想到了CustomPainter。在Tab updateWidget的时候,不断把Rander的信息传给画笔Painter,然后更新绘制,理论上这样做是完全行得通的。

Flutter Tabbar 解析源码

为了验证我的思路,我开始研究官方Tabbar是如何写的:

  • 进入TabBar类,直接查看build方法,可以看到为每个Tab加入了Globalkey,然后指示器用CustomPaint进行绘制;

Widget build(BuildContext context) {    // ...此处省略部分代码...    final List<Widget> wrappedTabs = List<Widget>.generate(widget.tabs.length, (int index) {    const double verticalAdjustment = (_kTextAndIconTabHeight - _kTabHeight)/2.0;    EdgeInsetsGeometry? adjustedPadding;    // 这里为tab加入Globalkey,以便后续获取Tab的渲染信息    if (widget.tabs[index] is PreferredSizeWidget) {      final PreferredSizeWidget tab = widget.tabs[index] as PreferredSizeWidget;      if (widget.tabHasTextAndIcon && tab.preferredSize.height == _kTabHeight) {        if (widget.labelPadding != null || tabBarTheme.labelPadding != null) {          adjustedPadding = (widget.labelPadding ?? tabBarTheme.labelPadding!).add(const EdgeInsets.symmetric(vertical: verticalAdjustment));        }        else {          adjustedPadding = const EdgeInsets.symmetric(vertical: verticalAdjustment, horizontal: 16.0);        }      }    }        // ...此处省略部分代码...        // 可以看到指示器是CustomPaint对象    Widget tabBar = CustomPaint(        painter: _indicatorPainter,        child: _TabStyle(            animation: kAlwaysDismissedAnimation,            selected: false,            labelColor: widget.labelColor,            unselectedLabelColor: widget.unselectedLabelColor,            labelStyle: widget.labelStyle,            unselectedLabelStyle: widget.unselectedLabelStyle,            child: _TabLabelBar(              onPerformLayout: _saveTabOffsets,              children: wrappedTabs,        ),      ),    );
  • 绘制指示器用CustomPaint跟我们的预想一致,那如何把绘制的size和offset传进去呢。我们来看_TabLabelBar继承于Flex,而Flex又继承自MultiChildRenderObjectWidget,重写其createRenderObject方法;

class _TabLabelBar extends Flex {  _TabLabelBar({    Key? key,    List<Widget> children = const <Widget>[],    required this.onPerformLayout,  }) : super(    key: key,    children: children,    direction: Axis.horizontal,    mainAxisSize: MainAxisSize.max,    mainAxisAlignment: MainAxisAlignment.start,    crossAxisAlignment: CrossAxisAlignment.center,    verticalDirection: VerticalDirection.down,  );  final _LayoutCallback onPerformLayout;  @override  RenderFlex createRenderObject(BuildContext context) {    // 查看下_TabLabelBarRenderer    return _TabLabelBarRenderer(      direction: direction,      mainAxisAlignment: mainAxisAlignment,      mainAxisSize: mainAxisSize,      crossAxisAlignment: crossAxisAlignment,      textDirection: getEffectiveTextDirection(context)!,      verticalDirection: verticalDirection,      onPerformLayout: onPerformLayout,    );  }  @override  void updateRenderObject(BuildContext context, _TabLabelBarRenderer renderObject) {    super.updateRenderObject(context, renderObject);    renderObject.onPerformLayout = onPerformLayout;  }}

查看真实的渲染对象:_TabLabelBarRenderer,在performLayout中返回渲染的size和offset,并通过TabBar传入的_saveTabOffsets方法保存到_indicatorPainter中;_saveTabOffsets尤为重要,把Tabbar的渲染位移通知给Painter,从而让Painter可以轻松算出tab之间的宽度差

class _TabLabelBarRenderer extends RenderFlex {  _TabLabelBarRenderer({    List<RenderBox>? children,    required Axis direction,    required MainAxisSize mainAxisSize,    required MainAxisAlignment mainAxisAlignment,    required CrossAxisAlignment crossAxisAlignment,    required TextDirection textDirection,    required VerticalDirection verticalDirection,    required this.onPerformLayout,  }) : assert(onPerformLayout != null),       assert(textDirection != null),       super(         children: children,         direction: direction,         mainAxisSize: mainAxisSize,         mainAxisAlignment: mainAxisAlignment,         crossAxisAlignment: crossAxisAlignment,         textDirection: textDirection,         verticalDirection: verticalDirection,       );  _LayoutCallback onPerformLayout;  @override  void performLayout() {    super.performLayout();    // xOffsets will contain childCount+1 values, giving the offsets of the    // leading edge of the first tab as the first value, of the leading edge of    // the each subsequent tab as each subsequent value, and of the trailing    // edge of the last tab as the last value.    RenderBox? child = firstChild;    final List<double> xOffsets = <double>[];    while (child != null) {      final FlexParentData childParentData = child.parentData! as FlexParentData;      xOffsets.add(childParentData.offset.dx);      assert(child.parentData == childParentData);      child = childParentData.nextSibling;    }    assert(textDirection != null);    switch (textDirection!) {      case TextDirection.rtl:        xOffsets.insert(0, size.width);        break;      case TextDirection.ltr:        xOffsets.add(size.width);        break;    }    onPerformLayout(xOffsets, textDirection!, size.width);  }}
  • 通过Tabbar中的didChangeDependencies和didUpdateWidget生命周期,更新指示器;

@overridevoid didChangeDependencies() {  super.didChangeDependencies();  assert(debugCheckHasMaterial(context));  final TabBarTheme tabBarTheme = TabBarTheme.of(context);  _updateTabController();  _initIndicatorPainter(adjustedPadding, tabBarTheme);}@overridevoid didUpdateWidget(KuGouTabBar oldWidget) {  super.didUpdateWidget(oldWidget);  final TabBarTheme tabBarTheme = TabBarTheme.of(context);  if (widget.controller != oldWidget.controller) {    _updateTabController();    _initIndicatorPainter(adjustedPadding, tabBarTheme);  } else if (widget.indicatorColor != oldWidget.indicatorColor ||      widget.indicatorWeight != oldWidget.indicatorWeight ||      widget.indicatorSize != oldWidget.indicatorSize ||      widget.indicator != oldWidget.indicator) {    _initIndicatorPainter(adjustedPadding, tabBarTheme);  }  if (widget.tabs.length > oldWidget.tabs.length) {    final int delta = widget.tabs.length - oldWidget.tabs.length;    _tabKeys.addAll(List<GlobalKey>.generate(delta, (int n) => GlobalKey()));  } else if (widget.tabs.length < oldWidget.tabs.length) {    _tabKeys.removeRange(widget.tabs.length, oldWidget.tabs.length);  }}
  • 然后重点就在指示器_IndicatorPainter如何进行绘制了。

实现步骤

通过理解Flutter Tabbar的实现思路,大体跟我们预想的差不多。不过官方继承了Flex来计算Offset和size,实现起来很优雅。所以我也不班门弄斧了,直接改动官方的Tabbar就可以了。

  • 创建KuGouTabbar,复制官方代码,修改引用,删除无关的类,只保留Tabbar相关的代码。

怎么用Flutter实现酷狗流畅Tabbar效果

重点修改_IndicatorPainter,根据我们的需求来绘制指示器。在painter方法中,我们可以通过controller拿到当前tab的index以及animation!.value, 我们模拟下切换的过程,当tab从第0个移到第1个,动画的值从0变成1,然后动画走到0.5时,tab的index会从0突然变为1,指示器应该是先变长,然后在动画走到0.5时,再变短。因此动画0.5之前,我们用动画的value-index作为指示器缩放的倍数,指示器不断增大;动画0.5之后,用index-value作为缩放倍数,不断缩小。

final double index = controller.index.toDouble();final double value = controller.animation!.value;/// 改动 ltr为false,表示索引还是0,动画执行未超过50%;ltr为true,表示索引变为1,动画执行超过50%final bool ltr = index > value;final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex);final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex);/// 改动 通过ltr来决定是放大还是缩小倍数,可以得出公式:ltr ? (index - value) : (value - index)final Rect fromRect =    indicatorRect(size, from, ltr ? (index - value) : (value - index));/// 改动final Rect toRect =    indicatorRect(size, to, ltr ? (index - value) : (value - index));_currentRect = Rect.lerp(fromRect, toRect, (value - from).abs());

而指示器接收缩放倍数的前提还需要计算指示器最大的宽度,并且上面是根据动画的0.5作为最大的宽度,也就是移动到一半的时候,指示器应该达到最大宽度。因此指示器最大的宽度是需要✖️2的。请看下面代码:

class _IndicatorPainter extends CustomPainter {  ......此处省略部分代码......  void saveTabOffsets(List<double>? tabOffsets, TextDirection? textDirection) {    _currentTabOffsets = tabOffsets;    _currentTextDirection = textDirection;  }  // _currentTabOffsets[index] is the offset of the start edge of the tab at index, and  // _currentTabOffsets[_currentTabOffsets.length] is the end edge of the last tab.  int get maxTabIndex => _currentTabOffsets!.length - 2;  double centerOf(int tabIndex) {    assert(_currentTabOffsets != null);    assert(_currentTabOffsets!.isNotEmpty);    assert(tabIndex >= 0);    assert(tabIndex <= maxTabIndex);    return (_currentTabOffsets![tabIndex] + _currentTabOffsets![tabIndex + 1]) /        2.0;  }  /// 接收上面代码分析中传入的倍数 scale  Rect indicatorRect(Size tabBarSize, int tabIndex, double scale) {    assert(_currentTabOffsets != null);    assert(_currentTextDirection != null);    assert(_currentTabOffsets!.isNotEmpty);    assert(tabIndex >= 0);    assert(tabIndex <= maxTabIndex);    double tabLeft, tabRight, tabWidth = 0;    switch (_currentTextDirection!) {      case TextDirection.rtl:        tabLeft = _currentTabOffsets![tabIndex + 1];        tabRight = _currentTabOffsets![tabIndex];        break;      case TextDirection.ltr:        tabLeft = _currentTabOffsets![tabIndex];        tabRight = _currentTabOffsets![tabIndex + 1];        break;    }    /// 改动,通过GlobalKey计算出渲染的文本的宽度    tabWidth = tabKeys[tabIndex].currentContext!.size!.width;    final double delta = ((tabRight - tabLeft) - tabWidth) / 2.0;    tabLeft += delta;    tabRight -= delta;    final EdgeInsets insets = indicatorPadding.resolve(_currentTextDirection);    /// 改动,算出指示器的最大宽度,记得*2    double maxLen = (tabRight - tabLeft + insets.horizontal) * 2;    double res =        scale == 0 ? minWidth : maxLen * (scale < 0.5 ? scale : 1 - scale);    /// 改动    final Rect rect = Rect.fromLTWH(tabLeft + tabWidth / 2 - minWidth / 2, 0.0, res > minWidth ? res : minWidth, tabBarSize.height);    if (!(rect.size >= insets.collapsedSize)) {      throw FlutterError(        'indicatorPadding insets should be less than Tab Size\n'        'Rect Size : ${rect.size}, Insets: ${insets.toString()}',      );    }    return insets.deflateRect(rect);   }}
  • 如上,指示器的宽度我们根据controller切换时的index和动画值进行转化,实现宽度的变化。而Offset的最小值和最大值分别是切换前后两个Tab的中心点,这里应该做下相应的的限制,然后传给Rect.fromLTWH。

【由于时间和精力问题,我并没有去做这一步的实现,而且酷狗那边动画跟滑动逻辑的关系需要UI给出具体的公式,才能百分百还原。】

最后就是加多一个参数,让业务方传入指示器的最小宽度。

/// 指示器的最小宽度final double indicatorMinWidth;

业务使用

在上面我们已经把简单的动画效果改完了,接下来就是传入圆角的indicator、最小宽度indicatorMinWidth,就可以正常使用啦。

  • 圆角的指示器,我直接上源码

import 'package:flutter/material.dart';class RRecTabIndicator extends Decoration {  const RRecTabIndicator(      {this.borderSide = const BorderSide(width: 2.0, color: Colors.white),        this.insets = EdgeInsets.zero,        this.radius = 0,        this.color = Colors.white});  final double radius;  final Color color;  final BorderSide borderSide;  final EdgeInsetsGeometry insets;  @override  Decoration? lerpFrom(Decoration? a, double t) {    if (a is RRecTabIndicator) {      return RRecTabIndicator(        borderSide: BorderSide.lerp(a.borderSide, borderSide, t),        insets: EdgeInsetsGeometry.lerp(a.insets, insets, t)!,      );    }    return super.lerpFrom(a, t);  }  @override  Decoration? lerpTo(Decoration? b, double t) {    if (b is RRecTabIndicator) {      return RRecTabIndicator(        borderSide: BorderSide.lerp(borderSide, b.borderSide, t),        insets: EdgeInsetsGeometry.lerp(insets, b.insets, t)!,      );    }    return super.lerpTo(b, t);  }  @override  _UnderlinePainter createBoxPainter([VoidCallback? onChanged]) {    return _UnderlinePainter(this, onChanged);  }  Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {    final Rect indicator = insets.resolve(textDirection).deflateRect(rect);    return Rect.fromLTWH(      indicator.left,      indicator.bottom - borderSide.width,      indicator.width,      borderSide.width,    );  }  @override  Path getClipPath(Rect rect, TextDirection textDirection) {    return Path()..addRect(_indicatorRectFor(rect, textDirection));  }}class _UnderlinePainter extends BoxPainter {  _UnderlinePainter(this.decoration, VoidCallback? onChanged)      : super(onChanged);  final RRecTabIndicator decoration;  @override  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {    final Rect rect = offset & configuration.size!;    final TextDirection textDirection = configuration.textDirection!;    final Rect indicator = decoration._indicatorRectFor(rect, textDirection);    final Paint paint = decoration.borderSide.toPaint()      ..strokeCap = StrokeCap.square      ..color = decoration.color;    final RRect rRect =    RRect.fromRectAndRadius(indicator, Radius.circular(decoration.radius));    canvas.drawRRect(rRect, paint);  }}
  • 调用非常简单,跟原来官方代码一模一样。

Scaffold(  appBar: AppBar(    // Here we take the value from the MyHomePage object that was created by    // the App.build method, and use it to set our appbar title.    title: Text(widget.title),    bottom: KuGouTabBar(      tabs: const [Tab(text: "音乐"), Tab(text: "动态"), Tab(text: "语文")],      // labelPadding: EdgeInsets.symmetric(horizontal: 8),      controller: _tabController,      // indicatorSize: TabBarIndicatorSize.label,      // isScrollable: true,      padding: EdgeInsets.zero,      indicator: const RRecTabIndicator(          radius: 4, insets: EdgeInsets.only(bottom: 5)),      indicatorMinWidth: 6,    ),  ),);

读到这里,这篇“怎么用Flutter实现酷狗流畅Tabbar效果”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么用Flutter实现酷狗流畅Tabbar效果

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么用Flutter实现酷狗流畅Tabbar效果

本文小编为大家详细介绍“怎么用Flutter实现酷狗流畅Tabbar效果”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么用Flutter实现酷狗流畅Tabbar效果”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧
2023-06-29

怎么使用Android Flutter实现弹幕效果

本篇内容介绍了“怎么使用Android Flutter实现弹幕效果”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言需求要点如下:弹幕行数为
2023-07-02

Flutter怎么使用NetworkImage实现图像显示效果

本篇内容主要讲解“Flutter怎么使用NetworkImage实现图像显示效果”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Flutter怎么使用NetworkImage实现图像显示效果”吧!
2023-06-30

Flutter怎么利用SizeTransition实现组件飞入效果

本篇内容主要讲解“Flutter怎么利用SizeTransition实现组件飞入效果”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Flutter怎么利用SizeTransition实现组件飞入效
2023-06-30

怎么用Python实现流星雨效果

这篇文章将为大家详细讲解有关怎么用Python实现流星雨效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。流星雨的前提是得先有一个流星,所谓流星,就是一个拖着尾巴的直线。所谓拖着尾巴,实际上是我们的浪漫想
2023-06-22

怎么用HTML5的canvas实现一个炫酷时钟效果

小编给大家分享一下怎么用HTML5的canvas实现一个炫酷时钟效果,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!对于H5来说,canvas可以说是它最有特色的一
2023-06-09

Flutter怎么实现不同缩放动画效果

本篇内容主要讲解“Flutter怎么实现不同缩放动画效果”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Flutter怎么实现不同缩放动画效果”吧!需求背景组件缩放可以向着一个方向进行缩放,放大列
2023-07-02

Android Flutter怎么实现仿闲鱼动画效果

这篇文章主要讲解了“Android Flutter怎么实现仿闲鱼动画效果”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android Flutter怎么实现仿闲鱼动画效果”吧!1、底部返回键
2023-07-05

Android中怎么实现一个炫酷进度条效果

这期内容当中小编将会给大家带来有关Android中怎么实现一个炫酷进度条效果,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。HorizontalProgressbarWithProgress的代码impor
2023-05-30

CSS3怎么实现流星雨效果

小编给大家分享一下CSS3怎么实现流星雨效果,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!说明:正文只讲述单个流星雨的实现方式,多个的效果只需要对单个的动画起始点
2023-06-14

Flutter怎么使用AnimatedBuilder实现动效复用

这篇文章主要介绍“Flutter怎么使用AnimatedBuilder实现动效复用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Flutter怎么使用AnimatedBuilder实现动效复用”文章
2023-06-29

Android中怎么利用ImageSlider实现一个炫酷轮播广告效果

这期内容当中小编将会给大家带来有关Android中怎么利用ImageSlider实现一个炫酷轮播广告效果,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. Gradle 依赖配置我们这里使用的是 andr
2023-05-30

使用canvas怎么实现一个流水灯效果

使用canvas怎么实现一个流水灯效果?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。具体内容如下 <
2023-06-15

怎么使用vue+gojs实现拖拽流程图效果

这篇文章主要介绍“怎么使用vue+gojs实现拖拽流程图效果”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么使用vue+gojs实现拖拽流程图效果”文章能帮助大家解决问题。一、流程图效果二、为什么
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录