前言
上一篇文章模仿掘金将文章列表渲染了出来,接下来是展示文章详情,在此我引用了flutter_markdown来实现markdown的渲染,在此遇到了一些问题,并将解决办法记录在文章里面,该项目已经放到github中,欢迎查看
主要实现步骤如下:
- 引入flutter_markdown
- 构造页面,渲染数据
-
- 头像
- 作者、等级以及创建时间
- 文章详情渲染
引入flutter_markdown
pubspec.yaml文件引入
flutter_markdown: 0.6.6
执行命令
flutter packages get
构造页面,渲染数据
头像
头像可以使用 CircleAvatar 处理头像
代码如下:
// 头像
Expanded(
flex: 1,
child: CircleAvatar(
backgroundImage: NetworkImage(_articleDtlModel.avatarUrl),
radius: 20,
)),
作者、等级以及创建时间
这里主要是一些布局问题,还有引入插件dateFormat
date_format: 2.0.4
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(_articleDtlModel.author,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w300)),
// TODO level
],
),
Text(
formatDate(
_articleDtlModel.ctime, [yy,'-',mm,'-',dd,' ',HH,':',nn,':',ss]),
style: TextStyle(
fontSize: 12.0, fontWeight: FontWeight.w300))
],
)),
文章详情渲染
这里遇到了两个问题:
- Column 下面 不能直接用ListView
- ListView与Markdown的嵌套使用
首先第一个问题github中提供了相关的解决办法链接,文中提到了,可以用Container或者Expanded包裹ListView处理
解决完这步的时候,我发现了代码依然报错,百度过后发现滚动类型的widget嵌套使用的时候会有以下问题,
A listview嵌套B listview,这里面有两个问题:
a. B listview不显示
b. A listview滑动监听被B listview窃取
解决问题1:要在B listview里面添加属性 shrinkWrap: true 即可
解决问题2:在B listview里面添加属性 physics: NeverScrollableScrollPhysics() 即可
参考文档:blog.csdn.net/qq_27981847…,
这时候我看了下Markdown组件,果然它也是滚动类型组件。
Markdown组件源码:
/// A scrolling widget that parses and displays Markdown.
///
/// Supports all GitHub Flavored Markdown from the
/// [specification](https://github.github.com/gfm/).
///
/// See also:
///
/// * [MarkdownBody], which is a non-scrolling container of Markdown.
/// * <https://github.github.com/gfm/>
class Markdown extends MarkdownWidget {
/// Creates a scrolling widget that parses and displays Markdown.
const Markdown({
Key? key,
required String data,
bool selectable = false,
MarkdownStyleSheet? styleSheet,
MarkdownStyleSheetBaseTheme? styleSheetTheme,
SyntaxHighlighter? syntaxHighlighter,
MarkdownTapLinkCallback? onTapLink,
VoidCallback? onTapText,
String? imageDirectory,
List<md.BlockSyntax>? blockSyntaxes,
List<md.InlineSyntax>? inlineSyntaxes,
md.ExtensionSet? extensionSet,
MarkdownImageBuilder? imageBuilder,
MarkdownCheckboxBuilder? checkboxBuilder,
MarkdownBulletBuilder? bulletBuilder,
Map<String, MarkdownElementBuilder> builders =
const <String, MarkdownElementBuilder>{},
MarkdownListItemCrossAxisAlignment listItemCrossAxisAlignment =
MarkdownListItemCrossAxisAlignment.baseline,
this.padding = const EdgeInsets.all(16.0),
this.controller,
this.physics,
this.shrinkWrap = false,
bool softLineBreak = false,
}) : super(
key: key,
data: data,
selectable: selectable,
styleSheet: styleSheet,
styleSheetTheme: styleSheetTheme,
syntaxHighlighter: syntaxHighlighter,
onTapLink: onTapLink,
onTapText: onTapText,
imageDirectory: imageDirectory,
blockSyntaxes: blockSyntaxes,
inlineSyntaxes: inlineSyntaxes,
extensionSet: extensionSet,
imageBuilder: imageBuilder,
checkboxBuilder: checkboxBuilder,
builders: builders,
listItemCrossAxisAlignment: listItemCrossAxisAlignment,
bulletBuilder: bulletBuilder,
softLineBreak: softLineBreak,
);
最后文章详情渲染组件如下
// 文章详情渲染
Expanded(
child: ListView(
// physics: new NeverScrollableScrollPhysics(),
// shrinkWrap: true,
children: <Widget>[
Image.network(
_articleDtlModel.imgUrl,
width: 750,
),
Markdown(
data: _articleDtlModel.content,
physics: new NeverScrollableScrollPhysics(), // 滚动失效
shrinkWrap: true // markdown不显示
)
],
)),
整体代码如下
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_application/config/theme.dart';
import 'package:flutter_application/pages/common/bar/common_bar.dart';
import 'package:flutter_application/utils/common_utils.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class ArticleDetail extends StatefulWidget {
// final ArticleDtlModel articleDtlModel;
// ArticleDetail(this.articleDtlModel);
_ArticleDetailState createState() => _ArticleDetailState();
}
class _ArticleDetailState extends State<ArticleDetail> {
ArticleDtlModel _articleDtlModel = new ArticleDtlModel(ctime: DateTime.now());
var tempArticleModel;
var tempTags = [];
Future<void> _loadData() async {
await rootBundle
.loadString("assets/json/article_detail.json")
.then((value) => {tempArticleModel = json.decode(value)});
_articleDtlModel = new ArticleDtlModel(
avatarUrl: tempArticleModel['author_user_info']['avatar_large'],
ctime: DateTime.parse(tempArticleModel['article_info']['ctime']),
imgUrl: tempArticleModel['article_info']['cover_image'],
author: tempArticleModel['author_user_info']['user_name'],
content: tempArticleModel['article_info']['mark_content'],
title: tempArticleModel['article_info']['title'],
level: tempArticleModel['author_user_info']['level'],
viewCount: tempArticleModel['article_info']['view_count'],
commentCount: tempArticleModel['article_info']['comment_count'],
followCount: tempArticleModel['author_user_info']['followee_count'],
collectCount: tempArticleModel['article_info']['collect_count'],
diggCount: tempArticleModel['article_info']['digg_count'],
// tags: curTags
);
// 重构页面
setState(() {
_articleDtlModel = _articleDtlModel;
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CommonBar(title: _articleDtlModel.title),
body: Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.fromLTRB(12, 6, 12, 12),
color: secondBgColor,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flex(
direction: Axis.horizontal,
children: [
// 头像
Expanded(
flex: 1,
child: CircleAvatar(
backgroundImage: NetworkImage(_articleDtlModel.avatarUrl),
radius: 20,
)),
// 作者、等级以及创建时间
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(_articleDtlModel.author,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w300)),
// TODO level
],
),
Text(
dateFormat(
_articleDtlModel.ctime, 'yyyy-mm-dd HH:mm:ss'),
style: TextStyle(
fontSize: 12.0, fontWeight: FontWeight.w300))
],
)),
],
),
// 文章详情渲染
Expanded(
child: ListView(
// physics: new NeverScrollableScrollPhysics(),
// shrinkWrap: true,
children: <Widget>[
Image.network(
_articleDtlModel.imgUrl,
width: 750,
),
Markdown(
data: _articleDtlModel.content,
physics: new NeverScrollableScrollPhysics(),
shrinkWrap: true
)
],
)),
],
),
),
);
}
}
class ArticleDtlModel {
final String avatarUrl;
final String author;
final String title;
final String imgUrl;
final int level;
final DateTime ctime;
final int viewCount;
final int commentCount;
final int followCount;
final int collectCount;
final int diggCount;
final String content;
final List<Map> tags;
ArticleDtlModel(
{this.avatarUrl = '',
this.author = '',
this.title = '',
this.imgUrl = '',
this.level = 1,
required this.ctime,
this.viewCount = 0,
this.commentCount = 0,
this.followCount = 0,
this.collectCount = 0,
this.diggCount = 0,
this.content = '',
this.tags = const []});
}
最后文章详情渲染组件如下
// 文章详情渲染
Expanded(
child: ListView(
// physics: new NeverScrollableScrollPhysics(),
// shrinkWrap: true,
children: <Widget>[
Image.network(
_articleDtlModel.imgUrl,
width: 750,
),
Markdown(
data: _articleDtlModel.content,
physics: new NeverScrollableScrollPhysics(), // 滚动失效
shrinkWrap: true // markdown不显示
)
],
)),
整体代码如下
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_application/config/theme.dart';
import 'package:flutter_application/pages/common/bar/common_bar.dart';
import 'package:flutter_application/utils/common_utils.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
class ArticleDetail extends StatefulWidget {
// final ArticleDtlModel articleDtlModel;
// ArticleDetail(this.articleDtlModel);
_ArticleDetailState createState() => _ArticleDetailState();
}
class _ArticleDetailState extends State<ArticleDetail> {
ArticleDtlModel _articleDtlModel = new ArticleDtlModel(ctime: DateTime.now());
var tempArticleModel;
var tempTags = [];
Future<void> _loadData() async {
await rootBundle
.loadString("assets/json/article_detail.json")
.then((value) => {tempArticleModel = json.decode(value)});
_articleDtlModel = new ArticleDtlModel(
avatarUrl: tempArticleModel['author_user_info']['avatar_large'],
ctime: DateTime.parse(tempArticleModel['article_info']['ctime']),
imgUrl: tempArticleModel['article_info']['cover_image'],
author: tempArticleModel['author_user_info']['user_name'],
content: tempArticleModel['article_info']['mark_content'],
title: tempArticleModel['article_info']['title'],
level: tempArticleModel['author_user_info']['level'],
viewCount: tempArticleModel['article_info']['view_count'],
commentCount: tempArticleModel['article_info']['comment_count'],
followCount: tempArticleModel['author_user_info']['followee_count'],
collectCount: tempArticleModel['article_info']['collect_count'],
diggCount: tempArticleModel['article_info']['digg_count'],
// tags: curTags
);
// 重构页面
setState(() {
_articleDtlModel = _articleDtlModel;
});
}
@override
void initState() {
// TODO: implement initState
super.initState();
_loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CommonBar(title: _articleDtlModel.title),
body: Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.fromLTRB(12, 6, 12, 12),
color: secondBgColor,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flex(
direction: Axis.horizontal,
children: [
// 头像
Expanded(
flex: 1,
child: CircleAvatar(
backgroundImage: NetworkImage(_articleDtlModel.avatarUrl),
radius: 20,
)),
// 作者、等级以及创建时间
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(_articleDtlModel.author,
style: TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.w300)),
// TODO level
],
),
Text(
dateFormat(
_articleDtlModel.ctime, 'yyyy-mm-dd HH:mm:ss'),
style: TextStyle(
fontSize: 12.0, fontWeight: FontWeight.w300))
],
)),
],
),
// 文章详情渲染
Expanded(
child: ListView(
// physics: new NeverScrollableScrollPhysics(),
// shrinkWrap: true,
children: <Widget>[
Image.network(
_articleDtlModel.imgUrl,
width: 750,
),
Markdown(
data: _articleDtlModel.content,
physics: new NeverScrollableScrollPhysics(),
shrinkWrap: true
)
],
)),
],
),
),
);
}
}
class ArticleDtlModel {
final String avatarUrl;
final String author;
final String title;
final String imgUrl;
final int level;
final DateTime ctime;
final int viewCount;
final int commentCount;
final int followCount;
final int collectCount;
final int diggCount;
final String content;
final List<Map> tags;
ArticleDtlModel(
{this.avatarUrl = '',
this.author = '',
this.title = '',
this.imgUrl = '',
this.level = 1,
required this.ctime,
this.viewCount = 0,
this.commentCount = 0,
this.followCount = 0,
this.collectCount = 0,
this.diggCount = 0,
this.content = '',
this.tags = const []});
}
效果图
该项目已经放到github中,欢迎查看