本文制作一个加密货币钱包,设计来源于Pinterest。文章分为两部分,第一部分集中讲设计,后一部分写如何从真实后台的数据流获取数据。
开始
创建项目并切换目录
flutter create crypto_wallet
cd create crypto_wallet
打开main.dart文件,清空全部内容后增加以下代码:
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Crypto Wallet',
home: CryptoWallet(),
);
}
}
创建CryptoWallet组件。设计包含有四个区域:上部使用gradient渐变背景、文本和图标;底部采用灰色背景,一组产品(portfolio)项和四个按钮stack栈。CryptoWallet返回Scaffold组件,代码如下:
import 'package:flutter/material.dart';
void main()=>runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Crypto Wallet',
home: CryptoWallet(),
);
}
}
class CryptoWallet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
使用Stack组件修改代码如下:
return Scaffold(
body: SingleChildScrollView(
child: Stack(
children: [],
),
),
);
SingleChildScrollView组件确保子组件Stack在界面变化(如变小)时可以滚动。
上部区域
仔细看设计图,上部分和下部分放入Column组件中的两个指定高度的container容器中。
上部分有graident渐变色。Flutter中,container容器组件使用decoration属性来定义gradient渐变、shape形状、color颜色、border边框、blend渲染等。
对于上面部分的container容器,我们使用linear gradient线性渐变,指定中左开始到中右结束点的颜色。然后增加必要的外部间距padding:EdgeInsets.only(top:55,left:20,right:20),设置Container容器高度MediaQuery.of(context).size.height*0.40(屏幕高度的40%)。
底部区域
底部区域不能直接设置合理的高度和颜色,代码修改如下:
class CryptoWallet extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Stack(
children: [
Column(
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF81269D),
const Color(0xFFEE112D)
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
height: MediaQuery.of(context).size.height * .40,
padding: EdgeInsets.only(top: 55, left: 20, right: 20),
),
Container(
height: MediaQuery.of(context).size.height * .75,
color: Colors.grey,
),
],
),
],
),
));
}
}
代码运行如下
在上部代码中增加新的Column子组件。在此之前导入font awesome用来引入需要的图标。打开pubsec.yaml导入以下依赖:
font_awesome_flutter: ^8.2.0
在main.dart里导入
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
Column的最上面组件为Row组件,Row的mainAxisAlignment设置为spaceBetween,children的三个子组件:两个图标和一个文本。实现用两个SizedBox组件间隔两个文件组件,代码修改如下:
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: Icon(
Icons.menu,
color: Colors.white,
),
onPressed: () {},
),
Text("投资组合 (24H)",
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w500)),
IconButton(
icon: Icon(
FontAwesomeIcons.bell,
color: Colors.white,
),
onPressed: () {},
),
],
),
SizedBox(height: 40),
Text("43,729.00",
style: TextStyle(
color: Colors.white,
fontSize: 45.0,
fontWeight: FontWeight.bold)),
SizedBox(height: 20),
Text(
r"+ $3,157.67 (23%)",
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
fontWeight: FontWeight.w300),
),
])
保存并刷新应用
产品(portfolio)项区域
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF81269D),
const Color(0xFFEE112D)
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
height: MediaQuery.of(context).size.height * .40,
padding: EdgeInsets.only(top: 55, left: 20, right: 20),
),
制作方法cryptoPortfolioItem()进行封装
card的子组件使用InkWell利用onTap属性进行产品的触发事件。InkWell子组件使用container容器设置padding外间距,color颜色和border边框的radius弧度后加入child子组件。
class cryptoPortfolioItem extends StatelessWidget {
IconData icon;
String name;
double amount;
double rate;
String percentage;
cryptoPortfolioItem(
this.icon, this.name, this.amount, this.rate, this.percentage);
@override
Widget build(BuildContext context) {
return Card(
elevation: 1.0,
child: InkWell(
onTap: () => print("tapped"),
child: Container(
padding: EdgeInsets.only(top: 15.0, bottom: 15.0, right: 15.0),
height: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22.0)),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 10.0, right: 15.0),
child: Icon(icon, color: Colors.grey, size:40)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(name, style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),),
Text("\$$amount", style: TextStyle(fontSize: 18.0,fontWeight: FontWeight.bold))
],
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("$rate BTC", style: TextStyle(fontSize: 15.0,
fontWeight: FontWeight.normal)),
Text("+ $percentage", style: TextStyle(
fontSize: 16.0,
color: Colors.red[500],
))
],
)
],
),
flex: 3,
)
],
),
)));
}
}
调用该方法
cryptoPortfolioItem(FontAwesomeIcons.btc, "BTC", 410.80,0.0036, "82.19(92%)"),
在container容器中增加ListView组件显示cryptoPortfolioItem()。
Container(
alignment: Alignment.topCenter,
padding: EdgeInsets.only(
top: MediaQuery.of(context).size.height * .30,
right: 10.0,
left: 10.0),
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: ListView(
children: [
cryptoPortfolioItem(FontAwesomeIcons.btc, "BTC", 410.80,
0.0036, "82.19(92%)"),
cryptoPortfolioItem(FontAwesomeIcons.ethereum, "ETH", 1089.86,
126.0, "13.10(2.3%)"),
cryptoPortfolioItem(FontAwesomeIcons.xRay, "XRP", 22998.13,
23000, "120(3.6%)"),
cryptoPortfolioItem(FontAwesomeIcons.btc, "BTC", 410.80,
0.0036, "82.19(92%)"),
cryptoPortfolioItem(FontAwesomeIcons.ethereum, "ETH", 1089.86,
126.0, "13.10(2.3%)"),
cryptoPortfolioItem(FontAwesomeIcons.xRay, "XRP", 22998.13,
23000, "120(3.6%)"),
cryptoPortfolioItem(FontAwesomeIcons.btc, "BTC", 410.80,
0.0036, "82.19(92%)"),
cryptoPortfolioItem(FontAwesomeIcons.ethereum, "ETH", 1089.86,
126.0, "13.10(2.3%)"),
cryptoPortfolioItem(FontAwesomeIcons.xRay, "XRP", 22998.13,
23000, "120(3.6%)"),
],
),
),
),
保存并刷新应用
四个按钮区域
使用Positioned组件制作四个按钮,代码如下:
Positioned(
bottom: MediaQuery.of(context).size.height * .37,
left: MediaQuery.of(context).size.width * .05,
child: RaisedButton(
padding: EdgeInsets.symmetric(
vertical: 18.0,
horizontal: 38.0,
),
color: Color(0xFFEE112D),
onPressed: () {},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25.0)
),
child: Text(
"发送",
style: TextStyle(
fontSize: 18.0,
color: Colors.white,
fontWeight: FontWeight.bold,
)
)
),
),
Positioned(
bottom: MediaQuery.of(context).size.height * .37,
right: MediaQuery.of(context).size.width * .05,
child: RaisedButton(
padding: EdgeInsets.symmetric(
vertical: 18.0,
horizontal: 30.0,
),
color: Color(0xFFEE112D),
onPressed: () {},
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(25.0)),
child: Text(
"接收",
style: TextStyle(
fontSize: 18.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
)),
),
Positioned(
bottom: MediaQuery.of(context).size.height * .43,
left: MediaQuery.of(context).size.width * .30,
child: RaisedButton(
padding: EdgeInsets.symmetric(
vertical: 18.0,
horizontal: 38.0,
),
color: Color(0xFFEE112D),
onPressed: () {},
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(25.0)),
child: Text(
"交易",
style: TextStyle(
fontSize: 18.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
)),
),
Positioned(
bottom: MediaQuery.of(context).size.height * .33,
left: MediaQuery.of(context).size.width * .40,
child: FloatingActionButton(
backgroundColor: Colors.white,
onPressed: null,
child: Icon(
Icons.close,
color: Colors.black,
),
),
),
保存并刷新应用