Flutter: 为图片添加说明信息

实现效果

如下:

See the Pen
图片上添加说明文字
by pruidong (@pruidong)
on CodePen.

image-3209

实现

在Container布局中,将图片放在BoxDecoration中.这样图片自然就在底层了.之后在Positioned中进行描述信息的处理,样式,位置这些都可以完全自定义.


import 'package:flutter/material.dart';



/// 在图片上添加说明文字.
/// 在图片上使用浅黑色背景,添加说明文字.
///
///
/// prd
/// 2020-07-26


void main() {
  runApp(
    MaterialApp(
      home: MyWidget(),
    ),
  );
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
            padding: EdgeInsets.all(2.0),
            decoration: BoxDecoration(
              image: DecorationImage(
                image: NetworkImage( 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',),
                fit: BoxFit.cover,
              ),
            ),
            child: Stack(
              children: [
                Positioned(
                  left: 2.0,
                  right: 2.0,
                  bottom: 2.0,
                  height: 70.0,
                  child: SingleChildScrollView(
                    child:Container(
                    padding: EdgeInsets.all(5.0),
                    decoration: BoxDecoration(
                        color: Colors.black45,
                        borderRadius: BorderRadius.all(Radius.circular(5.0))),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          "这是一段说明文字",
                          style: TextStyle(color: Colors.white,fontSize:22.0,),
                        ),
                        Text(
                          "可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字,可以滚动详细说明文字.",
                          style: TextStyle(color: Colors.white),
                        )
                      ],
                    ),
                  ),
                  )
                ),
              ],
            ),
          ),
    );
  }
}

Flutter: 轮播组件

实现一个轮播

在PC端实现轮播一般都是直接使用JavaScript库进行实现.但在Flutter上想要实现一个轮播,可能只有自己写一下了.

轮播的特点:

1. 可以自动播放;
2. 图片可以左右滑动(使用Flutter自带的PageView初始只能向右滑动,不能向左);
3. 可以定义自动播放的间隔时间.

大概就这么几个特点,可以实现个简单的例子.

image-3203

功能部分

目前仅实现简单功能:
1. 可以自定义网络图片或本地图片(可以混用本地图片和网络图片),
2. 自定义图片切换时间,
3. 自定义是否开启自动轮播.

上述几条可以满足一个简单轮播的需要,如果需要更多功能.可以在此基础上增加.

image-3204

源码

完整代码: Gitee仓库


/// 轮播组件
///
///  目前仅实现简单功能:
///   1. 可以自定义网络图片或本地图片(可以混用本地图片和网络图片),
///   2. 自定义图片切换时间,
///   3. 自定义是否开启自动轮播.
///
///  调用示例:
///
///  ```dart
///  static const List _images = [
///   'assets/images/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
///   'assets/images/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
///   'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
///   'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
///   'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
///   'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
///   'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',
///   'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
///   'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
///   'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg'
///  ];
///
/// ......
///
/// SizedBox(
//            width: double.infinity,
//            height: 200,
//            child: Carousel(
//              _images,
//              autoPlayer: false,
//            ),
//          ),
///
/// ```
///
///
///
///
class Carousel extends StatefulWidget {
  Carousel(
    this.imagesList, {
    Key key,
    this.autoPlayer = true,
    this.seconds = 5,
  })  : assert(imagesList.isNotEmpty),
        assert(seconds > 2),
        super(key: key);

  final int _maxPage = 2000000000;

  /// 轮播切换间隔秒数.
  final int seconds;

  /// 是否自动播放
  final bool autoPlayer;

  final List imagesList;

  @override
  _CarouselState createState() => new _CarouselState();
}

class _CarouselState extends State {

  PageController _pageController;
  Timer _timer;

  var _currIndex = 0;

  @override
  void initState() {
    super.initState();
    if (widget.autoPlayer) {
      startTimeout();
    }
    _pageController = new PageController(initialPage: widget._maxPage);
  }

  @override
  void dispose() {
    super.dispose();
    _pageController.dispose();
    if (_timer != null) {
      _timer.cancel();
    }

  }

  void startTimeout() {
    var timeout = Duration(seconds: widget.seconds);
    Timer.periodic(timeout, (Timer timer) {
      var tempIndex = _currIndex + 1;
      if (tempIndex >= widget.imagesList.length) {
        tempIndex = 0;
      }
      setState(() {
        _timer = timer;
        _currIndex = tempIndex;
      });
    });
  }

  List getBottomIcon() {
    var forLength = widget.imagesList.length;
    var res = List();
    for (var i = 0; i < forLength; i++) {
      if (_currIndex == i) {
        res.add(GestureDetector(
          /// 选中时的图标,可以更换为图片.
          child: Icon(Icons.brightness_1),
          onTap: () {
            setState(() {
              _currIndex = i;
            });
          },
        ));
      } else {
        res.add(GestureDetector(
          /// 未选中时的图标,可以更换为图片.
          child: Icon(Icons.panorama_fish_eye),
          onTap: () {
            setState(() {
              _currIndex = i;
            });
          },
        ));
      }
    }
    return res;
  }

  @override
  Widget build(BuildContext context) {
    var width = MediaQuery.of(context).size.width;
    return Scaffold(
      body: PageView.builder(
          controller: _pageController,
          onPageChanged: (int index) {
            var renderIndex = index - widget._maxPage;
            renderIndex = renderIndex % widget.imagesList.length;
            if (renderIndex < 0) {
              renderIndex += widget.imagesList.length;
            }
            setState(() {
              _currIndex = renderIndex;
            });
          },
          itemBuilder: (BuildContext context, int index) {
            var renderIndex = _currIndex - widget._maxPage;
            renderIndex = renderIndex % widget.imagesList.length;
            if (renderIndex < 0) {
              renderIndex += widget.imagesList.length;
            }
            return Container(
                child: Stack(children: [
              Center(
                child: widget.imagesList[_currIndex].startsWith("https://") ||
                        widget.imagesList[_currIndex].startsWith("http://")
                    ? CachedNetworkImage(
                        imageUrl: widget.imagesList[_currIndex],
                        fit: BoxFit.fill,
                        width: width,
                        height: double.infinity,
                        placeholder: (context, url) =>
                            CircularProgressIndicator(),
                        errorWidget: (context, url, error) => Icon(Icons.error),
                      )
                    : FadeInImage(
                        image: AssetImage(widget.imagesList[_currIndex]),
                        placeholder: AssetImage("assets/images/loading.gif"),
                        fit: BoxFit.fill,
                        width: width,
                        height: double.infinity,
                      ),
              ),
              Positioned(
                  left: 2.0,
                  right: 2.0,
                  bottom: 10.0,
                  child: Container(

                      /// padding: EdgeInsets.all(5.0),
                      child: widget.imagesList.length > 10
                          ? SingleChildScrollView(
                              scrollDirection: Axis.horizontal,
                              child: Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: getBottomIcon(),
                              ),
                            )
                          : Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: getBottomIcon(),
                            )))
            ]));
          }),
    );
  }
}

Flutter: 无限滚动

为什么需要无限滚动

之前做过一个横向图片展示的页面,发现就需要无限滚动(可以往左滑,也可以往右滑).第一时间首先想到的是用PageView,但是发现PageView不支持往左滑动,只能往右滑.这明显限制了功能的实用性.

于是在网上参考了一些教程之后,有了下面的代码.

image-3198

原理

原理其实很简单,就是初始化的时候给PageView设置一个特别大的值.这样无论往左或者往右都可以滑动.通过滑动的值,计算实际的下标,进行展示对应的数据即可.

在线运行效果

源码

源码如下:


import 'package:flutter/material.dart';

/// run to dartpad.dev or dartpad.cn
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: HomePage(),
      );
}

int maxPage = 2000000000;

class HomePage extends StatelessWidget {
  static const List<Color> _colors = [Colors.blue, Colors.green, Colors.red];

  final pageController = new PageController(initialPage: maxPage);

  @override
  Widget build(BuildContext context) => Scaffold(
        body: PageView.builder(
          controller: pageController,
          itemBuilder: (BuildContext context, int index) {
            var renderIndex = index - maxPage;
            renderIndex = renderIndex % _colors.length;
            if (renderIndex < 0) {
              renderIndex += _colors.length;
            }
            return Container(
              color:_colors[index % _colors.length],
                child: Center(
                    child: Text("text $renderIndex, default Index: $index",
                        style: TextStyle(
                            fontSize: 36.0,
                            color: Colors.white))));
          },
        ),
      );
}

Flutter: 动态修改应用主题

应用主题

现在很多应用都提供了手动修改应用主题的功能,一般也就两个主题: dark和light,简单说就是: 夜间和日间模式.部分没有提供手动修改的,则选择了跟随手机系统的主题.而在Flutter中,提供了这两种主题,可以很方便的切换.前提是在应用中,需要减少定制化的部分.同时需要针对两种主题进行测试.

实现说明

主题的修改是存在于: MaterialApp上面的theme属性,而这个属性的值为ThemeData,也就是除了系统自带的.也可以自定义主题.

原理分为以下几步:

  1. 父类: MaterialApp中的theme需要使用属性动态判断,以便进行主题切换;
  2. 父类: 需要定义一个类型方法,以便在其它页面调用并修改主题
  3. 子类: 定义一个类属性,用于接收父类定义的类型方法.并在需要修改主题时,传递参数调用该方法
  4. 父类: 定义具体方法,处理修改主题操作.

说起来好复杂,但是一看代码,一下就明白了.

image-3194

一个简单的”小栗子”

下面是一个简单的例子,只在首页实现了一个切换主题的功能.

例子用了CodePen.io的在线编辑,因为CodePen.io不支持导入第三方包,因此注释了SharedPreferences相关内容.如需使用,在本地导入即可

在线预览

源码


import 'package:flutter/material.dart';
/// import 'package:shared_preferences/shared_preferences.dart';



/// Flutter 动态修改应用主题
/// 
/// 
/// [特别说明]
/// 
/// 因为CodePen不支持引入第三方包,所以此处注释了SharedPreferences相关的内容(SharedPreferences可以使应用重启后设置依然生效).
/// 安装SharedPreferences: https://pub.dev/packages/shared_preferences
/// 
/// [原理]
/// 
/// 在MyApp中传递给SettingApp一个回调方法,如果SettingApp需要更新MyApp中的任意值,直接调用此方法即可(目前通过Map传递值,因为无法动态调用setState).
/// 
/// @version 2020--05-31
/// @author prd(pruidong@gmail.com)(bckf.cn)
/// 


/// Flutter dynamically modify the application theme
///
///
/// [Special Note]
///
/// Because CodePen does not support the introduction of third-party packages, the contents related to SharedPreferences are annotated here (SharedPreferences can make the settings still effective after the application restarts).
/// Install SharedPreferences: https://pub.dev/packages/shared_preferences
///
/// [Principle]
///
/// Pass a callback method to SettingApp in MyApp. If SettingApp needs to update any value in MyApp, just call this method (currently pass the value through Map  because setState cannot be called dynamically).
///
/// @version 2020--05-31
/// @author prd (pruidong@gmail.com)(bckf.cn)



void main() {
  runApp(MyApp()  );
}

typedef MainStateUpdateCall = void Function(Map map);


class MyApp extends StatefulWidget {
  const MyApp();
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {
  bool _darkTheme = false;
  /// final _ThemeKey = "THEME";


  /// 外部接口调用此方法修改本类的属性.
  ///
  /// 可以通过传递在map中传递值,来修改系统属性.
  /// 使用可以参考:
  /// 
  /// The external interface calls this method to modify the properties of this class.
  ///
  /// You can modify system properties by passing values in the map.
  /// Use can refer to:
  ///
  /// [_SettingAppState]的setAndReloadTheme方法.
  void mainStateUpdateMethod(Map map) {
    map.forEach((key, value) {
      setState((){
        switch(key){
          case "_darkTheme":
            _darkTheme=value;
            break;
        }
      });
    });
  }

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

  /// CodePen cannot introduce third-party packages, so comment the code below.
  /// 
  /// 
  void initTheme() async {
    // final prefs = await SharedPreferences.getInstance();
    // bool tempTheme = false;
    // if (prefs.containsKey(_ThemeKey)) {
    //   tempTheme = prefs.getBool(_ThemeKey);
    // }
    // setState(() {
    //   _darkTheme = tempTheme;
    // });
  }



  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme:_darkTheme ? ThemeData.dark() : ThemeData.light(), 
      home: SettingApp(mainStateUpdateCall: mainStateUpdateMethod,),
    );
  }
}



/// Setting.
///
///
class SettingApp extends StatefulWidget {
  const SettingApp({this.mainStateUpdateCall});

  final MainStateUpdateCall mainStateUpdateCall;

  @override
  _SettingAppState createState() => _SettingAppState();
}

class _SettingAppState extends State {
  bool _darkTheme = false;
  /// final _themeKey = "THEME";

  var resData = [];

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

  void initSetting() async {
    /// final prefs = await SharedPreferences.getInstance();
    /// bool tempTheme = false;
    ///if (prefs.containsKey(_ThemeKey)) {
    ///  tempTheme = prefs.getBool(_ThemeKey);
    ///  }
    // setState(() {
    //   _darkTheme = tempTheme;
    // });
    var tempResData = [
      Card(
        child: ListTile(
          title: Text('dark'),
          trailing: Switch(
              value: _darkTheme,
              onChanged: (value) {
                setAndReloadTheme(value);
                /// Call this method again to generate the list-so that the displayed value is correct.
                /// 重新调用此方法生成列表-以使显示的值正确.
                initSetting();
              }),
        ),
      ),
      Text("Support: bckf.cn / pruidong@gmail.com")
    ];
    setState(() {
      resData = tempResData;
    });
  }

  /// 保存设置和重新加载主题.
  /// Save the settings and reload the theme.
  ///
  void setAndReloadTheme(bool value) async {
    /// final prefs = await SharedPreferences.getInstance();
    /// prefs.setBool(_ThemeKey, value);
    /// 
    /// 
    /// 调用全局更换.
    /// Call global replacement.
    setState(() {
       _darkTheme = value;
     });
    print(value);
    print(_darkTheme);
    if (widget.mainStateUpdateCall != null) {
      var paramMap={"_darkTheme":value};
      widget.mainStateUpdateCall(paramMap);
    }
  }

  @override
  Widget build(BuildContext context) {
    var listView = ListView.separated(
      padding: const EdgeInsets.all(2),
      itemCount: resData.length,
      itemBuilder: (BuildContext itemBuildContext, int index) {
        return Padding(
          padding: EdgeInsets.all(5.0),
          child: resData[index],
        );
      },
      separatorBuilder: (BuildContext context, int index) => Divider(
        color: Theme.of(context).dividerColor,
      ),
    );
    return Scaffold(
      appBar: AppBar(title: Text("Flutter Dynamic Update App Theme")),
      body: listView,
    );
  }
}

代码比较简单,就不详细解释了.

JavaScript:Prism实现代码高亮

代码高亮解决方案

在之前用过Crayon Syntax Highlighter,Enlighter这两个代码高亮的插件.可能是js或其余部分存在冲突,总是导致奇奇怪怪的问题.也找了一些纯js的代码高亮框架.比如: Highlight.js.但最后发现一个高亮框架比较适合: Prism.js.

Prism.js

从我的角度介绍一下Prism.js:

  • 核心文件小(约2kb);
  • 可以按语言自动加载高亮文件(有条件)
  • 代码行号,复制代码等功能作为插件提供
  • 可按照插件架构自行扩展
  • 多种主题可选

最主要的不会存在升级Wordpress插件导致样式混乱的问题了.同时,在Prism.js的下载页面可以按照自己需要的部分进行下载,最大程度减少文件体积.

image-3180

综合引用

可以在Prism官网,直接下载一个打包好的JavaScript文件和CSS文件:

参考地址:

官方打包下载地址

打包之后的文件,只需要引入一个CSS和一个JavaScript文件即可.

示例

我目前使用的body下面的第一个div添加的class和data-toolbar-order:


		<div data-toolbar-order="show-language,copy-to-clipboard" class="line-numbers match-braces rainbow-braces">
		</div>

使用方式

  1. 先引入css
  2. 然后在pre,code中包含代码,同时为code设置class,class格式为: language-css,language-html,language-java或者lang-java,lang-css,lang-html.更多支持语言的简写,参考这里的Supported languages.
  3. 最后引入prism.js文件

下面的代码只会显示代码高亮(未引用语言文件的情况下,默认只包含了css,html,js语言的高亮文件):



<!DOCTYPE html>
<html>
<head>
	<title>test</title>
	<link href="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/themes/prism-dark.min.css" rel="stylesheet">
</head>
<body>
<pre><code class="language-css">p { color: red }</code></pre>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/prism/9000.0.1/prism.min.js"></script>
</html>


使用插件

目前我使用了如下插件:

  • Line Numbers(显示行号)
  • Remove initial line feed(删除初始换行)
  • Normalize Whitespace(空白规范)
  • Toolbar(工具栏)
  • Copy to Clipboard Button(复制按钮)
  • Show Language(显示语言)
  • Match braces(括号高亮[选中单个括号,另外一个对应的括号同时高亮])

显示行号

引入方式:

css:


	<link href="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet">

JavaScript:

	
		<script src="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
	

然后在body下面的第一个div设置:

	
		<div id="page" class="line-numbers">
			......内容
		</div>

之后直接使用即可:


<pre><code class="language-css">
p { color: red }

</code></pre>

括号高亮

引入:

css:


		<link href="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/match-braces/prism-match-braces.min.css" rel="stylesheet">

JavaScript:


<script src="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/match-braces/prism-match-braces.min.js"></script>

参考效果: 官方文档

实现效果:

添加class: match-braces 到body下面的第一个div即可.例如:

	
		<div id="page" class="line-numbers match-braces">
			......内容
		</div>

如果要显示五颜六色的括号,则继续添加: rainbow-braces,例如:

	
		<div id="page" class="line-numbers match-braces rainbow-braces">
			......内容
		</div>

工具栏

引入:

CSS:

	
	<link href="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/toolbar/prism-toolbar.min.css" rel="stylesheet">

JavaScript:

	
	<script src="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/toolbar/prism-toolbar.min.js"></script>

然后给body设置工具栏要显示的内容:

	
<div data-toolbar-order="show-language,copy-to-clipboard" class="line-numbers match-braces rainbow-braces">
	......
</div>

show-language,copy-to-clipboard

第一个是显示代码块中的语言,第二个是复制代码.

工具栏可以自定义.具体参考: 工具栏插件文档

显示语言

引入:

	
<script src="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/show-language/prism-show-language.min.js"></script>

复制代码

引入:

	
<script src="https://cdn.bootcdn.net/ajax/libs/prism/1.20.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>

即可完成工具栏的效果.