diff --git a/douban_app/lib/components/clipper/custom_rect_clipper.dart b/douban_app/lib/components/clipper/custom_rect_clipper.dart new file mode 100644 index 0000000000000000000000000000000000000000..d8f235c8c8c9f5dad6e7b58cf4984ea39e519962 --- /dev/null +++ b/douban_app/lib/components/clipper/custom_rect_clipper.dart @@ -0,0 +1,16 @@ +import 'package:flutter/cupertino.dart'; + +/// 自定义矩形裁剪框,通过传入Rect来进行裁剪 +class CustomRectClipper extends CustomClipper { + Rect rect; + CustomRectClipper({this.rect}); + @override + Rect getClip(Size size) { + return rect ?? Rect.fromLTWH(0, 0, size.width, size.height); + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) { + return false; + } +} diff --git a/douban_app/lib/components/star_rate/index.dart b/douban_app/lib/components/star_rate/index.dart index b625ad1a3c3558bcd22e0e8f135faa73d567e4a1..9f5d2f08ece8a50701e1593d32e8c66c24792ff9 100644 --- a/douban_app/lib/components/star_rate/index.dart +++ b/douban_app/lib/components/star_rate/index.dart @@ -1,19 +1,81 @@ +import 'package:douban_app/components/clipper/custom_rect_clipper.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; /// 五角星评级组件 +/// 可指定五角星宽度,间距,以及填充色,背景色 +/// 可指定 星星数 +/// class StartRate extends StatefulWidget { final double rate; - StartRate({Key key, this.rate}) : super(key: key); + final double starWidth; + final double starSpacing; + final Color starFrontColor; + final Color starBgColor; + final int starCount; + StartRate( + {Key key, + this.rate, + this.starWidth = 11, + this.starSpacing = -1.5, + this.starCount = 5, + this.starFrontColor = const Color.fromRGBO(254, 153, 1, 1), + this.starBgColor = const Color.fromRGBO(224, 224, 224, 1)}) + : super(key: key ?? UniqueKey()); // 务必需要保证有key,防止不更新 @override _StartRateState createState() => _StartRateState(); } class _StartRateState extends State { + _buildStarList({bool needCliped = false, Color color}) { + double rate = widget.rate; + int starCount = widget.starCount; + double starSpacing = widget.starSpacing; + double starWidth = widget.starWidth; + double width = starCount * starWidth + starSpacing * (starCount - 1); + double clipWidth = (rate / starCount) * width; + + List iconList = []; + for (int i = 0; i < starCount; i++) { + iconList.add(Positioned( + left: i > 0 ? i * (starWidth.w + starSpacing.w) : 0.0, + child: Icon( + Icons.star, + color: color, + size: starWidth.w, + ), + )); + } + var starWidget = Container( + width: width + 5.0.w, + height: starWidth, + child: Stack( + children: iconList, + )); + if (!needCliped) { + return starWidget; + } + + return Container( + child: ClipRect( + child: starWidget, + clipper: CustomRectClipper( + rect: Rect.fromLTWH(0, 0, clipWidth.w, starWidth)), + ), + ); + } + @override Widget build(BuildContext context) { return Container( - child: Text("TODO:评分:${widget.rate}"), + child: Stack( + children: [ + _buildStarList(color: widget.starBgColor), + _buildStarList(needCliped: true, color: widget.starFrontColor) + ], + ), ); } } diff --git a/douban_app/lib/components/top_search/index.dart b/douban_app/lib/components/top_search/index.dart index 3aaa4a778fd38a8cd0b6bce206446d407f698d1e..7d958b110b93b5dfb9b78e976f84e2f9b906b567 100644 --- a/douban_app/lib/components/top_search/index.dart +++ b/douban_app/lib/components/top_search/index.dart @@ -19,12 +19,12 @@ class TopSearch extends StatelessWidget { flex: 1, child: Container( width: 280, - height: 40, - padding: EdgeInsets.fromLTRB(14, 0, 14, 0), + height: 32, margin: EdgeInsets.only(left: 12, right: 12), + padding: EdgeInsets.only(top: 14), decoration: BoxDecoration( color: Color.fromARGB(255, 238, 244, 240), - borderRadius: BorderRadius.circular(14), + borderRadius: BorderRadius.circular(12), ), child: TextField( keyboardType: TextInputType.text, @@ -33,7 +33,8 @@ class TopSearch extends StatelessWidget { decoration: InputDecoration( border: InputBorder.none, hintText: '国产剧小组', - hintStyle: TextStyle(fontSize: 14.0)), + hintStyle: TextStyle( + fontSize: 14.0, color: Color.fromRGBO(152, 164, 154, 1))), ), ), ), diff --git a/douban_app/lib/utils/launch_url.dart b/douban_app/lib/utils/launch_url.dart new file mode 100644 index 0000000000000000000000000000000000000000..7d472f5f5cb925963210b7bde11914222189a87c --- /dev/null +++ b/douban_app/lib/utils/launch_url.dart @@ -0,0 +1,10 @@ +import 'package:url_launcher/url_launcher.dart'; + +/// 尝试用自带浏览器打开URL +launchUrl(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } +} diff --git a/douban_app/lib/views/book/tab_pages/movie/ad_area.dart b/douban_app/lib/views/book/tab_pages/movie/ad_area.dart index 6baf88c226c217f95a101080b164cd4d906f0be8..6ce237db447eb05a83c81fce996f58af4fb68164 100644 --- a/douban_app/lib/views/book/tab_pages/movie/ad_area.dart +++ b/douban_app/lib/views/book/tab_pages/movie/ad_area.dart @@ -1,20 +1,101 @@ +import 'dart:async'; + +import 'package:douban_app/utils/launch_url.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class ADArea extends StatefulWidget { + ADArea({Key key}) : super(key: key); + + @override + _ADAreaState createState() => _ADAreaState(); +} -class ADArea extends StatelessWidget { - const ADArea({Key key}) : super(key: key); +class _ADAreaState extends State { + String imgUrl = ""; + String jumpUrl = ""; + + @override + void initState() { + super.initState(); + + imgUrl = + "http://5b0988e595225.cdn.sohucs.com/images/20180221/f81bcc41e8d245c1a8ef5c44426af2fb.jpeg"; + jumpUrl = "http://flutter.dev"; + } @override Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.all(20.w), - decoration: BoxDecoration( - color: Colors.green.shade400, - borderRadius: BorderRadius.all(Radius.circular(6.w))), - width: 1.sw, - height: 80.w, - child: Text("广告区域"), + // 显示弹窗 + Future _showMyDialog() async { + return showDialog( + context: context, + // barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: const Text('广告弹窗'), + content: SingleChildScrollView( + child: ListBody( + children: const [ + Text('这是一条广告.'), + Text('是否跳转广告商品主页?'), + ], + ), + ), + actions: [ + TextButton( + child: const Text('取消'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('确定'), + onPressed: () { + // 关闭弹窗,并跳转浏览器 + Navigator.of(context).pop(); + launchUrl(jumpUrl); + }, + ), + ], + ); + }, + ); + } + + return InkWell( + onTap: _showMyDialog, + child: Container( + margin: + EdgeInsets.only(left: 20.w, right: 20.w, top: 32.w, bottom: 20.w), + decoration: BoxDecoration( + color: Colors.green.shade400, + image: DecorationImage( + fit: BoxFit.cover, image: NetworkImage(imgUrl)), + borderRadius: BorderRadius.all(Radius.circular(6.w))), + width: 1.sw, + height: 98.w, + child: Stack( + children: [ + Positioned( + bottom: 8.0.w, + right: 8.0.w, + child: Container( + decoration: BoxDecoration( + color: Color.fromRGBO(0, 0, 0, 0.4), + borderRadius: BorderRadius.circular(4.w)), + padding: EdgeInsets.only( + left: 6.w, top: 1.w, right: 6.w, bottom: 3.w), + child: Text( + "广告", + style: TextStyle(color: Colors.white54, fontSize: 11.sp), + ), + ), + ), + ], + )), ); } } diff --git a/douban_app/lib/views/book/tab_pages/movie/billboard/index.dart b/douban_app/lib/views/book/tab_pages/movie/billboard/index.dart new file mode 100644 index 0000000000000000000000000000000000000000..bc8fb051685d528737f64e13dc260827ce828155 --- /dev/null +++ b/douban_app/lib/views/book/tab_pages/movie/billboard/index.dart @@ -0,0 +1,81 @@ +import 'package:douban_app/utils/launch_url.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +/// 用于电影页的宣传栏 + +class Billboard extends StatefulWidget { + Billboard({Key key}) : super(key: key); + + @override + _BillboardState createState() => _BillboardState(); +} + +class _BillboardState extends State { + String imgUrl = ""; + String jumpUrl = ""; + + @override + void initState() { + super.initState(); + + imgUrl = + "http://img2.baidu.com/it/u=2063801547,2313500636&fm=253&app=138&f=JPG?w=1920&h=500"; + jumpUrl = "http://flutter.dev"; + } + + // 显示弹窗 + Future _showMyDialog() async { + return showDialog( + context: context, + // barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: const Text('测试推广'), + content: SingleChildScrollView( + child: ListBody( + children: const [ + Text('这是一条简单的推广.'), + Text('是否跳转至推广链接?'), + ], + ), + ), + actions: [ + TextButton( + child: const Text('取消'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('确定'), + onPressed: () { + // 关闭弹窗,并跳转浏览器 + Navigator.of(context).pop(); + launchUrl(jumpUrl); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: _showMyDialog, + child: Container( + margin: + EdgeInsets.only(left: 20.w, right: 20.w, top: 30.w, bottom: 24.w), + decoration: BoxDecoration( + color: Colors.green.shade400, + image: DecorationImage( + image: NetworkImage(imgUrl), fit: BoxFit.cover), + borderRadius: BorderRadius.all(Radius.circular(6.w))), + width: 1.sw, + height: 98.w, + )); + } +} diff --git a/douban_app/lib/views/book/tab_pages/movie/coming_soon/coming_soon_card.dart b/douban_app/lib/views/book/tab_pages/movie/coming_soon/coming_soon_card.dart index 29cf706a1cce83a7f7f82bf597a37a73ac7f33ca..9448eb764f638086200862ba3b37881a1de1ad3e 100644 --- a/douban_app/lib/views/book/tab_pages/movie/coming_soon/coming_soon_card.dart +++ b/douban_app/lib/views/book/tab_pages/movie/coming_soon/coming_soon_card.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -5,26 +7,59 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; typedef OnTapFunc = void Function(); class ComingSoonCard extends StatelessWidget { - final String cartTitle; + final String cardTitle; final String cardSubTitle; final List imgUrlList; final OnTapFunc onTapFunc; const ComingSoonCard( {Key key, - this.cartTitle, + this.cardTitle, this.cardSubTitle, this.imgUrlList = const [], this.onTapFunc}) : super(key: key); + Widget _stackImage(List imgUrls, + {double containerWidth = 56.6, double imageWidth, double imageHeight}) { + double _imageWidth = imageWidth ?? 0.1.sw; + double _imageHeight = imageHeight ?? 40.w; + int len = imgUrlList.length; + List children = []; + int realLen = min(len - 1, 2); + for (int i = realLen; i >= 0; i--) { + children.add(Positioned( + right: (realLen - i) * (containerWidth - _imageWidth) / 2, + child: Container( + width: _imageWidth, + height: _imageHeight, + decoration: BoxDecoration( + boxShadow: [BoxShadow(color: Colors.grey[200], blurRadius: 5)], + borderRadius: BorderRadius.circular(6.w), + image: DecorationImage( + image: NetworkImage(imgUrlList[i]), fit: BoxFit.cover)), + ))); + } + + return Stack(children: children); + } + @override Widget build(BuildContext context) { + var cardWidth = 0.42.sw; + var paddingWidth = 10.w; + var halfContainerWidth = cardWidth / 2 - 16.w - paddingWidth; + var imageWidth = 0.1.sw; + double imageHeight = halfContainerWidth - 4.w; return InkWell( onTap: () { this.onTapFunc(); }, child: Container( + margin: EdgeInsets.only(top: 16.w), + width: cardWidth, + height: 98.w, + padding: EdgeInsets.symmetric(vertical: paddingWidth), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6.w), @@ -36,7 +71,52 @@ class ComingSoonCard extends StatelessWidget { blurRadius: 8.w, ) ]), - child: Text(cardSubTitle), + child: Column( + children: [ + Container( + padding: EdgeInsets.only(left: paddingWidth, right: 4.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(cardTitle), Icon(Icons.chevron_right_outlined)], + ), + ), + Container( + margin: EdgeInsets.only(top: 4.w), + padding: EdgeInsets.symmetric(horizontal: paddingWidth), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: halfContainerWidth + 6.w, + height: imageHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(cardSubTitle, + softWrap: true, style: TextStyle(fontSize: 10.sp)) + ], + ), + ), + Container( + width: halfContainerWidth, + height: imageHeight, + decoration: BoxDecoration(boxShadow: [ + BoxShadow( + color: Colors.grey[200], + offset: Offset.zero, + blurRadius: 8.w, + ) + ]), + child: _stackImage(imgUrlList, + containerWidth: halfContainerWidth, + imageWidth: imageWidth, + imageHeight: imageHeight), + ), + ], + ), + ) + ], + ), ), ); } diff --git a/douban_app/lib/views/book/tab_pages/movie/coming_soon/index.dart b/douban_app/lib/views/book/tab_pages/movie/coming_soon/index.dart index 99a40727afaaa2c9251a374153f5b4f18520dc7c..2274cb377b5dacd2477f2bd1d2d1c0588945ef18 100644 --- a/douban_app/lib/views/book/tab_pages/movie/coming_soon/index.dart +++ b/douban_app/lib/views/book/tab_pages/movie/coming_soon/index.dart @@ -21,7 +21,7 @@ class _ComingSoonListState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ComingSoonCard( - cartTitle: "国内即将上映", + cardTitle: "国内即将上映", cardSubTitle: "近期有${internalCount}部热门电影", imgUrlList: [ "https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2692433481.webp", @@ -33,7 +33,7 @@ class _ComingSoonListState extends State { }, ), ComingSoonCard( - cartTitle: "全球值得期待", + cardTitle: "全球值得期待", cardSubTitle: "近期有${internationalCount}部热门电影", imgUrlList: [ "https://img2.doubanio.com/view/photo/s_ratio_poster/public/p2692433481.webp", diff --git a/douban_app/lib/views/book/tab_pages/movie/index.dart b/douban_app/lib/views/book/tab_pages/movie/index.dart index d08fbc688527a7fefc3f7afe83a9466ef4e59996..7bfcd7e6fcd16b8253a9e97298bfb3e72e76d5eb 100644 --- a/douban_app/lib/views/book/tab_pages/movie/index.dart +++ b/douban_app/lib/views/book/tab_pages/movie/index.dart @@ -1,4 +1,7 @@ +import 'dart:math'; + import 'package:douban_app/views/book/tab_pages/movie/ad_area.dart'; +import 'package:douban_app/views/book/tab_pages/movie/billboard/index.dart'; import 'package:douban_app/views/book/tab_pages/movie/coming_soon/index.dart'; import 'package:douban_app/views/book/tab_pages/movie/popular/index.dart'; import 'package:douban_app/views/book/tab_pages/movie/top_banner.dart'; @@ -15,7 +18,13 @@ class _TabMovieState extends State { @override Widget build(BuildContext context) { return ListView( - children: [TopBanner(), PopularMovie(), ComingSoonList(), ADArea()], + children: [ + TopBanner(), + PopularMovie(), + Random().nextBool() ? Billboard() : Container(), + ComingSoonList(), + Random().nextBool() ? ADArea() : Container() + ], ); } } diff --git a/douban_app/lib/views/book/tab_pages/movie/popular/index.dart b/douban_app/lib/views/book/tab_pages/movie/popular/index.dart index 358644cb1aa996dc934fb216a3289c9687c70258..2cdfe8f78c649b9a0c157bc9472b774b20553f6a 100644 --- a/douban_app/lib/views/book/tab_pages/movie/popular/index.dart +++ b/douban_app/lib/views/book/tab_pages/movie/popular/index.dart @@ -15,15 +15,15 @@ class _PopularMovieState extends State { int activeIndex = 0; var hotShowData = [ - {"name": '很长的电影名称,要多长有多长'}, - {"name": '热映名称1'}, + {"name": '很长的电影名称,要多长有多长', "rate": 3.7}, + {"name": '热映名称1', "rate": 4.9}, {"name": '热映名称2'}, {"name": '热映名称3'} ]; var popularData = [ - {"name": '热门很长的电影名称,要多长有多长'}, - {"name": '热门电影名称1'}, + {"name": '热门很长的电影名称,要多长有多长', "rate": 5.0}, + {"name": '热门电影名称1', "rate": 4.7}, {"name": '热门电影名称2'}, {"name": '热门电影名称3'} ]; @@ -73,7 +73,10 @@ class _PopularMovieState extends State { ], ), ), - VideoList(activeList) + Container( + margin: EdgeInsets.only(top: 10.w), + child: VideoList(activeList), + ) ], )); } diff --git a/douban_app/lib/views/book/tab_pages/movie/popular/video_card.dart b/douban_app/lib/views/book/tab_pages/movie/popular/video_card.dart index ef000451de74fe747af85058db219b6ea39b8534..a3b6b065b6401d5f2f944b75eb07789a735f2315 100644 --- a/douban_app/lib/views/book/tab_pages/movie/popular/video_card.dart +++ b/douban_app/lib/views/book/tab_pages/movie/popular/video_card.dart @@ -20,6 +20,7 @@ class _VideoCardState extends State { Widget build(BuildContext context) { var videoCardModel = widget.videoCardModel; var width = widget.containerWidth; + var starColor = Color.fromRGBO(254, 153, 1, 1); return Container( margin: EdgeInsets.only(right: 10.w), width: width.sw, @@ -35,14 +36,32 @@ class _VideoCardState extends State { fit: BoxFit.cover, ), ), - Text( - videoCardModel.name, - softWrap: true, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontWeight: FontWeight.bold), + Container( + margin: EdgeInsets.only(top: 3.w), + child: Text( + videoCardModel.name, + softWrap: true, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11.sp), + ), ), - StartRate( - rate: videoCardModel.rate ?? 0.0, + Container( + margin: EdgeInsets.only(top: 3.w), + child: Row( + children: [ + StartRate(rate: videoCardModel.rate ?? 0.0), + Padding( + padding: EdgeInsets.symmetric(horizontal: 2.w), + child: Text( + (videoCardModel.rate ?? 0.0).toString(), + style: TextStyle( + color: starColor, + fontSize: 10.6.sp, + fontWeight: FontWeight.w500), + ), + ) + ], + ), ) ], ), diff --git a/douban_app/lib/views/book/tab_pages/movie/popular/video_list.dart b/douban_app/lib/views/book/tab_pages/movie/popular/video_list.dart index 3086ec996c12d25ea013e12d32edbc7b4b687764..9ab111f4b2935243309ef11fd714ff863952be89 100644 --- a/douban_app/lib/views/book/tab_pages/movie/popular/video_list.dart +++ b/douban_app/lib/views/book/tab_pages/movie/popular/video_list.dart @@ -4,7 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; class VideoList extends StatefulWidget { - final List> listMap; + final List> listMap; VideoList(this.listMap, {Key key}) : super(key: key); @override @@ -13,7 +13,7 @@ class VideoList extends StatefulWidget { class _VideoListState extends State { /// 将map转化为VideoCardModel list - getMovieList(List> listMap, {int limit = 10}) { + getMovieList(List> listMap, {int limit = 10}) { return listMap.sublist(0, listMap.length > limit ? limit : null).map((e) { return VideoCard(videoCardModel: VideoCardModel.fromMap(e)); }).toList(); diff --git a/douban_app/pubspec.yaml b/douban_app/pubspec.yaml index cff64cede3fda5af1fabb2cd5e18e972a51df9cd..fd72d6ecd16b9b733d6b5570a96b7071167041ec 100644 --- a/douban_app/pubspec.yaml +++ b/douban_app/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 + url_launcher: ^5.2.7 dev_dependencies: flutter_test: