簡單實現(xiàn)一下Flutter的Stepper做一個側(cè)邊進度條

因為 flutter 提供的 Stepper 無法滿足業(yè)務(wù)需求,于是只好自己實現(xiàn)一個了

flutter Stepper 的樣式

原生 Stepper

我實現(xiàn)的 Stepper

我實現(xiàn)的 Stepper

這個或許根本不叫 Stepper 吧,也沒有什么步驟褐着,只是當前的配送進度艺谆,不需要數(shù)字步驟唱逢,希望所有內(nèi)容都能顯示出來爷速,原生的則是有數(shù)字表示第幾步陵刹,把當前步驟外的其他的內(nèi)容都隱藏了迂求。

那么開始進行分析碾盐,整個需求中,有點難度的也就是這個左邊的進度線了揩局。我們把進度看做一個 ListView 毫玖,每條進度都是一個 Item

item

先來看怎么布局這個Item,一開始我是想在最外層做成一個 Row 布局,像這樣

image.png

左邊是圓和線付枫,右邊是內(nèi)容烹玉,然而我太天真了,左邊的 線 高度沒法跟隨右邊的高度阐滩,即右邊有多高春霍,左邊就有多高。也就是我必須給左邊的View設(shè)置一個高度叶眉,否則就沒法顯示出來址儒。。衅疙。絕望ing莲趣,如果我左邊寫死了高度,右邊的內(nèi)容因為用戶字體過大而高度超過左邊的線饱溢,那么兩個 Item 之間的線就沒法連在一起了喧伞。

然后我看到了 Flutter 的 Stepper ,雖然不符合需求绩郎,但是人家左邊的線是 Item 和 Item 相連的潘鲫,我就看了下他的源碼,豁然開朗肋杖,人家的布局是個 Colum 溉仑。整體看起來是這樣的。

image.png

這樣的話状植,就好理解了浊竟,Colum 的第一個 child 我們稱為 Head , 第二個 child 我們稱為 Body 。

Head 的布局如圖是個 Row津畸,左邊是圓和線振定,右邊是個 Text。
Body 的布局是個 Container 肉拓, 包含了一個 Column 后频,Column 里面就是兩個Text。相信小伙伴們已經(jīng)想到了暖途,Body左邊的那條線就是 Container 的 border

圓和線我選擇自己繪制卑惜,練習一下,下面是線和圓的自定義View代碼


class LeftLineWidget extends StatelessWidget {
  final bool showTop;
  final bool showBottom;
  final bool isLight;

  const LeftLineWidget(this.showTop, this.showBottom, this.isLight);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(horizontal: 16),//圓和線的左右外邊距
      width: 16,
      child: CustomPaint(
        painter: LeftLinePainter(showTop, showBottom, isLight),
      ),
    );
  }
}

class LeftLinePainter extends CustomPainter {
  static const double _topHeight = 16; //圓上的線高度
  static const Color _lightColor = XColors.mainColor;//圓點亮的顏色
  static const Color _normalColor = Colors.grey;//圓沒點亮的顏色

  final bool showTop; //是否顯示圓上面的線
  final bool showBottom;//是否顯示圓下面的線
  final bool isLight;//圓形是否點亮

  const LeftLinePainter(this.showTop, this.showBottom, this.isLight);

  @override
  void paint(Canvas canvas, Size size) {
    double lineWidth = 2; // 豎線的寬度
    double centerX = size.width / 2; //容器X軸的中心點
    Paint linePain = Paint();// 創(chuàng)建一個畫線的畫筆
    linePain.color = showTop ? Colors.grey : Colors.transparent;
    linePain.strokeWidth = lineWidth;
    linePain.strokeCap = StrokeCap.square;//畫線的頭是方形的
    //畫圓上面的線
    canvas.drawLine(Offset(centerX, 0), Offset(centerX, _topHeight), linePain);
    //依據(jù)下面的線是否顯示來設(shè)置是否透明
    linePain.color = showBottom ? Colors.grey : Colors.transparent;
    // 畫圓下面的線
    canvas.drawLine(
        Offset(centerX, _topHeight), Offset(centerX, size.height), linePain);
    // 創(chuàng)建畫圓的畫筆
    Paint circlePaint = Paint();
    circlePaint.color = isLight ? _lightColor : _normalColor;
    circlePaint.style = PaintingStyle.fill;
    // 畫中間的圓
    canvas.drawCircle(Offset(centerX, _topHeight), centerX, circlePaint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    if(oldDelegate is LeftLinePainter){
      LeftLinePainter old = oldDelegate;
      if(old.showBottom!=showBottom){
        return true;
      }
      if(old.showTop!=showTop){
        return true;
      }
      if(old.isLight!=isLight){
        return true;
      }
      return false;
    }
    return true;
  }
}

左側(cè)的圓和線是3個部分丧肴,分別是圓的上面那條線残揉,和圓胧后,以及圓下面的那條線芋浮,
通過 showTopshowBottom 來控制上面那條線和下面那條線是否顯示。

圓和線解決了,我就把Head組裝起來

Row(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    // 圓和線
    Container( 
      height: 32,
      child: LeftLineWidget(false, true, true),
    ),
    Expanded(child: Container(
      padding: EdgeInsets.only(top: 4),
      child: Text(
        '天天樂超市(限時降價)已取貨',
        style: TextStyle(fontSize: 18),
        overflow: TextOverflow.ellipsis,
      ),
    ))
  ],
)

編譯運行后截圖

image.png

(這里截圖跟之前不一樣是因為我又單獨建立了一個demo)

接下來寫下面的 Body

Container(
  //這里寫左邊的那條線
  decoration: BoxDecoration(
    border:Border(left: BorderSide(
      width: 2,// 寬度跟 Head 部分的線寬度一致纸巷,下面顏色也是
      color: Colors.grey
    ))
  ),
  margin: EdgeInsets.only(left: 23), //這里的 left 的計算在代碼塊下面解釋怎么來的
  padding: EdgeInsets.fromLTRB(22,0,16,16),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      Text('配送員:吳立亮 18888888888'),
      Text('時間:2018-12-17 09:55:22')
    ],
  ),
)

這里說一下 margin 的 left 參數(shù)值是怎么計算的镇草。
設(shè)置這個是為了 Body 的左邊框跟上面 Head 的線能對齊連上,不能錯開瘤旨。
首先我們的 LeftLineWidget 是有個 margin 的梯啤,他的左右外邊距是16,自身的寬度是16存哲。因為線在中間因宇,所以寬度要除以2。那就是:左外邊距+寬度除以2 left = 16 + 16/2 算出來是24祟偷。

可是我們這里寫的23察滑,是因為邊框的線的寬度是從容器的邊界往里面走的。我們算出來的邊距會讓 Body 的容器邊界在上面的線中間修肠『爻剑看起來像這樣。

image.png

所以還要減去線寬的一半嵌施,線寬是2饲化,除以2等于1, 最后left = 16+(16/2)-(2/2)=23,翻譯成中文 left = LeftLineWidget左邊距+(LeftLineWidget寬度?2)-(LeftLineWidget線寬?2)

最后看起來像這樣:


多復(fù)制幾個


image

最后一item要隱藏邊框吗伤,把邊框線顏色設(shè)置為透明即可吃靠。

渲染樹是這樣的

渲染樹

最后奉上完整代碼:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stepper',
      home: Scaffold(
        appBar: AppBar(
          elevation: 0,
          title: Text('自定義View'),
        ),
        body: ListView(
          shrinkWrap: true,
          children: <Widget>[
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Container(// 圓和線
                      height: 32,
                      child: LeftLineWidget(false, true, true),
                    ),
                    Expanded(child: Container(
                      padding: EdgeInsets.only(top: 4),
                      child: Text(
                        '天天樂超市(限時降價)已取貨',
                        style: TextStyle(fontSize: 18),
                        overflow: TextOverflow.ellipsis,
                      ),
                    ))
                  ],
                ),
                Container(
                  decoration: BoxDecoration(
                    border:Border(left: BorderSide(
                      width: 2,
                      color: Colors.grey
                    ))
                  ),
                  margin: EdgeInsets.only(left: 23),
                  padding: EdgeInsets.fromLTRB(22,0,16,16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text('配送員:吳立亮 18888888888'),
                      Text('時間:2018-12-17 09:55:22')
                    ],
                  ),
                )
              ],
            ),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Container(// 圓和線
                      height: 32,
                      child: LeftLineWidget(true, true, false),
                    ),
                    Expanded(child: Container(
                      padding: EdgeInsets.only(top: 4),
                      child: Text(
                        '天天樂超市(限時降價)已取貨',
                        style: TextStyle(fontSize: 18),
                        overflow: TextOverflow.ellipsis,
                      ),
                    ))
                  ],
                ),
                Container(
                  decoration: BoxDecoration(
                      border:Border(left: BorderSide(
                          width: 2,
                          color: Colors.grey
                      ))
                  ),
                  margin: EdgeInsets.only(left: 23),
                  padding: EdgeInsets.fromLTRB(22,0,16,16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text('配送員:吳立亮 18888888888'),
                      Text('時間:2018-12-17 09:55:22')
                    ],
                  ),
                )
              ],
            ),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Container(// 圓和線
                      height: 32,
                      child: LeftLineWidget(true, false, false),
                    ),
                    Expanded(child: Container(
                      padding: EdgeInsets.only(top: 4),
                      child: Text(
                        '天天樂超市(限時降價)已取貨',
                        style: TextStyle(fontSize: 18),
                        overflow: TextOverflow.ellipsis,
                      ),
                    ))
                  ],
                ),
                Container(
                  decoration: BoxDecoration(
                      border:Border(left: BorderSide(
                          width: 2,
                          color: Colors.transparent
                      ))
                  ),
                  margin: EdgeInsets.only(left: 23),
                  padding: EdgeInsets.fromLTRB(22,0,16,16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text('配送員:吳立亮 18888888888'),
                      Text('時間:2018-12-17 09:55:22')
                    ],
                  ),
                )
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class LeftLineWidget extends StatelessWidget {
  final bool showTop;
  final bool showBottom;
  final bool isLight;

  const LeftLineWidget(this.showTop, this.showBottom, this.isLight);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(horizontal: 16),
      width: 16,
      child: CustomPaint(
        painter: LeftLinePainter(showTop, showBottom, isLight),
      ),
    );
  }
}

class LeftLinePainter extends CustomPainter {
  static const double _topHeight = 16;
  static const Color _lightColor = Colors.deepPurpleAccent;
  static const Color _normalColor = Colors.grey;

  final bool showTop;
  final bool showBottom;
  final bool isLight;

  const LeftLinePainter(this.showTop, this.showBottom, this.isLight);

  @override
  void paint(Canvas canvas, Size size) {
    double lineWidth = 2;
    double centerX = size.width / 2;
    Paint linePain = Paint();
    linePain.color = showTop ? Colors.grey : Colors.transparent;
    linePain.strokeWidth = lineWidth;
    linePain.strokeCap = StrokeCap.square;
    canvas.drawLine(Offset(centerX, 0), Offset(centerX, _topHeight), linePain);
    Paint circlePaint = Paint();
    circlePaint.color = isLight ? _lightColor : _normalColor;
    circlePaint.style = PaintingStyle.fill;
    linePain.color = showBottom ? Colors.grey : Colors.transparent;
    canvas.drawLine(
        Offset(centerX, _topHeight), Offset(centerX, size.height), linePain);
    canvas.drawCircle(Offset(centerX, _topHeight), centerX, circlePaint);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市足淆,隨后出現(xiàn)的幾起案子撩笆,更是在濱河造成了極大的恐慌,老刑警劉巖缸浦,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夕冲,死亡現(xiàn)場離奇詭異,居然都是意外死亡裂逐,警方通過查閱死者的電腦和手機歹鱼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卜高,“玉大人弥姻,你說我怎么就攤上這事〔籼危” “怎么了庭敦?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長薪缆。 經(jīng)常有香客問我秧廉,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任疼电,我火速辦了婚禮嚼锄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蔽豺。我一直安慰自己区丑,他們只是感情好,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布修陡。 她就那樣靜靜地躺著沧侥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魄鸦。 梳的紋絲不亂的頭發(fā)上正什,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天,我揣著相機與錄音号杏,去河邊找鬼婴氮。 笑死,一個胖子當著我的面吹牛盾致,可吹牛的內(nèi)容都是我干的主经。 我是一名探鬼主播,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼庭惜,長吁一口氣:“原來是場噩夢啊……” “哼罩驻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起护赊,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤惠遏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骏啰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體节吮,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年判耕,在試婚紗的時候發(fā)現(xiàn)自己被綠了透绩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡壁熄,死狀恐怖帚豪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情草丧,我是刑警寧澤狸臣,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站昌执,受9級特大地震影響烛亦,放射性物質(zhì)發(fā)生泄漏诈泼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一此洲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧委粉,春花似錦呜师、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至栗涂,卻和暖如春知牌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背斤程。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工角寸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忿墅。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓扁藕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疚脐。 傳聞我的和親對象是個殘疾皇子亿柑,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 概述 在網(wǎng)易云課堂學習李南江老師的《從零玩轉(zhuǎn)HTML5前端+跨平臺開發(fā)》時,所整理的筆記棍弄。筆記內(nèi)容為根據(jù)個人需求所...
    墨荀閱讀 2,344評論 0 7
  • 一望薄、CSS入門 1、css選擇器 選擇器的作用是“用于確定(選定)要進行樣式設(shè)定的標簽(元素)”呼畸。 有若干種形式的...
    寵辱不驚丶歲月靜好閱讀 1,602評論 0 6
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5痕支? 答:HTML5是最新的HTML標準。 注意:講述HT...
    kismetajun閱讀 27,519評論 1 45
  • 瀏覽器與服務(wù)器的基本概念 瀏覽器(安裝在電腦里面的一個軟件) 作用: ①將網(wǎng)頁內(nèi)容渲染呈現(xiàn)給用戶查看蛮原。 ②讓用戶通...
    云還灬閱讀 1,128評論 0 0
  • HTML 5 HTML5概述 因特網(wǎng)上的信息是以網(wǎng)頁的形式展示給用戶的采转,因此網(wǎng)頁是網(wǎng)絡(luò)信息傳遞的載體。網(wǎng)頁文件是用...
    阿啊阿吖丁閱讀 3,906評論 0 0