使用Flutter來完成Uplabs上炫酷的互動

ditclear發表於2019-05-12

What is Flutter?

Flutter 是 Google 用以幫助開發者在 iOS 和 Android 兩個平臺(現在是全平臺)開發高質量原生 UI 的移動 SDK。Flutter 相容現有的程式碼,免費並且開源,在全球開發者中廣泛被使用.

What is Uplabs?

Uplabs是設計師和開發人員尋找,分享和購買靈感和資源以構建應用和網站的地方。

在這裡,每個人都可以:

1. 瀏覽並在我們的Material Design(Android),iOS和macOS以及Site的日常展示中找到靈感。

2. 搜尋特定的UI元素和解決方案;

3. 分享她/他的作品(設計,圖書館,片段,應用程式,網站)作為靈感或免費贈品;

4. 出售她/他的作品(主題,模板,圖示等)。

使用Flutter來完成Uplabs上炫酷的互動

Uplabs上有很多免費的設計圖,開發人員可以選取其中一個,比如說Google maps。

使用Flutter來完成Uplabs上炫酷的互動

可以看到右邊有xd的圖示,表示該專案下載下來之後需要用Adobe XD 開啟,當然也支援sketch、PS、Figma等等。

使用Flutter來完成Uplabs上炫酷的互動

開啟之後,點選右上角的預覽按鈕能夠預覽原型互動,對於實現互動細節非常重要。

使用Flutter來完成Uplabs上炫酷的互動

你還可以將設計稿通過外掛(比如藍湖XD)匯出到藍湖平臺,相當於一個免費的UI大師就位了。

使用Flutter來完成Uplabs上炫酷的互動

總的來說,對於Flutter開發者而言,這裡就是一座寶庫。

許多用原生技術都難以實現或者較難實現的互動,運用Flutter,在鍛鍊你的Flutter技能同時還能有一個滿意?的結果。

How to implement ?

我們可以來實現一個簡單的過渡效果

使用Flutter來完成Uplabs上炫酷的互動

問題:現在通過UI圖可以得知正方形的初始大小為100,起始位置為居中、距離底部100px,經過過渡後的位置為居中、距離底部500px,同時大小改為300,設定圓角為30.

知道了起點和終點,我們可以結合Stack和Positioned來完成位置的變化。

 Stack(
        children: <Widget>[
          Positioned(
            bottom: 100,
            left: (screenWidth - 100) / 2, //center
            width: 100,
            height: 100,
            child: DecoratedBox(
              decoration: BoxDecoration(
                color: Colors.red,
                border: Border.all(),
                borderRadius: BorderRadius.all(Radius.circular(0)),
              ),
            ),
          ),
        ],
      )
複製程式碼

接著我們來完成動畫,你可以選擇組合多個動畫,但這樣會稍顯麻煩,其實我們只需要確定一個動畫,其它的動畫只是附帶引起的變化而已。

這裡選用bottom的偏移進行動畫,開始的時候距離底部為100,結束之後距離底部為500,時間我們挑選為500毫秒。

AnimationController animationController;
Animation animation;
//offset bottom
double offset = 0;
@override
void initState() {
  super.initState();
  animationController = AnimationController(duration: Duration(milliseconds: 500), vsync: this);
  animation = Tween<double>(begin: 0.0, end: 500.0-100.0).animate(animationController)
    ..addListener(() {
        // notify ui update
      setState(() {
        offset = animation.value;
      });
    });
}
複製程式碼

當動畫進行時,offset就可以更新為動畫這時候的值,然後通過setState通知UI更新。

這時,就需要更改bottom的表示式為:

 bottom: 100   ->   bottom:100+offset
複製程式碼

但是為了引起正方形其它引數的變化,因此,我們最好是得到一個offset佔總偏移量的比重。

get currentPercent => offset / (500.0-100.0);
複製程式碼

接著我們的表示式也可以用另一種形式來寫:

 bottom: 100   ->   bottom:100+offset  -> bottom:100+(500.0-100.0)*currentPercent
複製程式碼

用這樣的邏輯,我們便可以完成上述的過渡效果。

Stack(
  children: <Widget>[
    Positioned(
      // start 100, center end 500 center
      bottom: 100 + (500 - 100) * currentPercent,
      left: (screenWidth - (100 + (300 - 100) * currentPercent)) / 2,
      width: 100 + (300 - 100) * currentPercent,
      height: 100 + (300 - 100) * currentPercent,
      child: GestureDetector(
          onTap: () {
            if (animationController.status == AnimationStatus.completed) {
              animationController.reverse();
            } else {
              animationController.forward();
            }
          },
          child: DecoratedBox(
              decoration: BoxDecoration(
                  color: Colors.red,
                  border: Border.all(),
                  borderRadius: BorderRadius.all(Radius.circular(30 * currentPercent))))),
    )
  ],
)
複製程式碼

處理手勢

在上面的程式碼中我們已經套上了一層GestureDetector,然後通過onTap回撥來處理點選事件,這裡再進一步,再加上拖動效果。

使用Flutter來完成Uplabs上炫酷的互動

垂直方向的手勢監聽可以通過onVerticalDragUpdate來處理,根據返回的DragUpdateDetails引數,可以獲取的滑動距離,我們可以根據它來改變offset。

onVerticalDragUpdate: (details) {
  // scrollUp means -=
  offset -= details.delta.dy;
  if (offset > 400) {
    offset = 400;
  } else if (offset < 0) {
    offset = 0;
  }
  setState(() {});
},
複製程式碼

當手指離開螢幕的時候,我們再根據offset的大小和狀態通過動畫移動到合適的位置。

需要注意的是動畫開始的值也就是begin是變化的,因此我們的動畫也需要動態建立。

onVerticalDragEnd: (_) {
  if (isEnd) {
    if (currentPercent >= 0.7) {
      animate(true);
    } else {
      animate(false);
    }
  } else {
    if (currentPercent >= 0.3) {
      animate(true);
    } else {
      animate(false);
    }
  }
},
複製程式碼

isEnd代表處於結束位置,再來看看動畫。

/// 滑動到開始或結束位置,Swipe to the start or end position
///
/// [end] true is the end position, otherwise the start position
/// [end] 為true是結束位置 反之是開始位置
void animate(bool end) {
  animationController = AnimationController(
      duration: Duration(milliseconds: (1 + 500 * (isEnd ? currentPercent : (1.0 - currentPercent))).toInt()),
      vsync: this);
  animation = Tween<double>(begin: offset, end: end ? 400.0 : 0.0).animate(animationController)
    ..addListener(() {
      setState(() {
        offset = animation.value;
      });
    })
    ..addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        isEnd = !isEnd;
      }
    });
  animationController.forward();
}
複製程式碼

begin的值都是offset,只是end的值需要通過是滑動到開始或結束位置而改變,需要注意的就是動畫時間也需要根據偏移量offset有所變化。

其它更為複雜的互動也不過是同一個套路,你可以檢視flutter_challenge_googlemaps來了解它,效果圖如下:

使用Flutter來完成Uplabs上炫酷的互動

Join in Flutter-UI-Challenges

為了讓更多的開發者嘗試Flutter技術,在體會到Flutter魅力的同時完成精美的互動,我在GitHub上建立了Flutter-UI-Challenges這個組織,開發者可以通過實現Uplabs中一個UI挑戰來加入我們。

如果你完成了其中一個挑戰,恭喜你,如果你想提交併加入我們,那麼可以在 JoinUs中提Issue,格式如下:

使用Flutter來完成Uplabs上炫酷的互動

Issue名稱的格式為flutter_chanllenge_xx,比如flutter_challenge_googlemaps.

內容請附上 Uplabs 上UI挑戰的網址GitHub相應實現的網址

注意: 請給Issue打上joinus標籤。

我們會對其進行評審以決定是否可以通過,評審內容包括:

  • 效果是否相符?

完成度至少在80%以上,

  • 質量

我們不僅要求能實現精美的互動效果,同時也追求更高的程式碼的質量,完善且符合dart規範的註釋和精簡有力的程式碼是我們的追求。

  • 符合規範

專案名請以**flutter_chanllenge_**開頭

Readme的格式請參考flutter_challenge_googlemaps

要求有符合Dart文件規範的註釋和合理的程式碼拆分。

最後

期待您的加入。

==================== 分割線 ======================

如果你想了解更多關於MVVM、Flutter、響應式程式設計方面的知識,歡迎關注我。

你可以在以下地方找到我:

簡書:www.jianshu.com/u/117f1cf0c…

掘金:juejin.im/user/582d60…

Github: github.com/ditclear

使用Flutter來完成Uplabs上炫酷的互動

相關文章