Flutter中由于滑動沖突導(dǎo)致BottomSheet中含有Listview等滾動組件時下拉效果失效問題

場景

在社交類軟件中,經(jīng)常都會有類似抖音這種顯示彈出框的場景,用來顯示一段評論列表夺艰∮罂蓿↓

底部評論彈框.png

在flutter里面,有一個交互效果非常不錯的原生組件:BottomSheet,自帶入場出場動畫以及下拉退出動畫效果郁副,所以毫無疑問使用這個控件是非常好的選擇减牺,可是當(dāng)使用起來會發(fā)現(xiàn),如果BoottomSheet中的子組件是可滾動類型的child時存谎,會因為滑動沖突問題導(dǎo)致下拉退出功能失效拔疚,一旦無法實現(xiàn)下拉退出效果,在用戶體驗上是非常不好的既荚,這個組件也顯得比較雞肋稚失,那么該如何在它的基礎(chǔ)上實現(xiàn)下拉效果呢?

思路

我們從BottomSheet的源碼看恰聘,內(nèi)部其實是通過獲取用戶的滑動距離配合AnimationBuilder實現(xiàn)的動態(tài)改變彈框高度句各,從而實現(xiàn)的上拉下拉的動畫效果吸占。

image.png

而當(dāng)包含Listview時,由于Listview的本身滑動機(jī)制使得BottomSheet內(nèi)部的觸摸事件無法傳遞所致凿宾,所以從這里入手我們直接自己來處理整個用戶的觸摸手勢矾屯,當(dāng)listview在頂部時手勢響應(yīng)下拉動畫,當(dāng)listview不在頂部時則繼續(xù)響應(yīng)listview本身的滑動效果即可初厚。
所以這里會涉及到如下組件:

  • Listener:用于監(jiān)聽用戶的手勢件蚕,獲取手指按下的位置、滑動的距離产禾、離開屏幕的時機(jī)排作。
  • AnimationContainer:動態(tài)的改變彈框高度,從而達(dá)到高度隨著手勢變化的下拉動畫效果亚情。
  • StreamBuilder:通過不斷的接收手勢事件來改變彈框的高度纽绍。

實現(xiàn)

部分代碼如下,完整代碼在文末


/// 用來發(fā)送事件 改變彈框高度的stream
StreamController<double> _streamController = StreamController<double>.broadcast();

/// 列表彈起的正常高度
double _totalHeight = 400;

/// 記錄手指按下的位置
double _pointerDy = 0;

AnimatedContainer(
  duration: Duration(milliseconds: 30),
  height: currentHeight,
  child: Listener(
    onPointerMove: (event){
      // 觸摸事件過程 手指一直在屏幕上且發(fā)生距離滑動
      if(_scrollController.offset != 0){
        // 只有列表滾動到頂部時才觸發(fā)下拉動畫效果
        print("onPointerMove:${_scrollController.offset}");
        return;
      }
      double distance = event.position.dy - _pointerDy;
      if (distance.abs() > 0) {
        // 獲取手指滑動的距離势似,計算彈框?qū)崟r高度拌夏,并發(fā)送事件
        double _currentHeight = _totalHeight - distance;
        if(_currentHeight > _totalHeight){
          return;
        }
        _streamController.sink.add(_currentHeight);
      }
    },
    onPointerUp: (event){
      // 觸摸事件結(jié)束 手指離開屏幕
      // 這里認(rèn)為滑動超過一半就認(rèn)為用戶要退出了,值可以根據(jù)實際體驗修改
      if(currentHeight < (_totalHeight * 0.5)){
        Navigator.pop(context);
      }else{
        _streamController.sink.add(_totalHeight);
      }
    },
    onPointerDown: (event){
      // 觸摸事件開始 手指開始接觸屏幕
      _pointerDy = event.position.dy + _scrollController.offset;
    },
    child: ListView(
      controller: _scrollController,
      physics: currentHeight != _totalHeight ? NeverScrollableScrollPhysics() : ClampingScrollPhysics(),
      children: [

      ],
    ),
  ),
)

關(guān)鍵點(diǎn)

  1. 當(dāng)listview本身沒有滑動到頂部時還要繼續(xù)響應(yīng)listview的滑動效果履因,是否在頂部的判斷條件是_scrollController.offset != 0
// 觸摸事件過程 手指一直在屏幕上且發(fā)生距離滑動 
if(_scrollController.offset != 0){ 
    // 只有列表滾動到頂部時才觸發(fā)下拉動畫效果 
    print("onPointerMove:${_scrollController.offset}"); 
    return; 
}
  1. 獲取手指滑動的縱向距離障簿,彈框的動態(tài)高度=彈框的最大高度-手指滑動的距離
double distance = event.position.dy - _pointerDy;
if (distance.abs() > 0) {
  // 獲取手指滑動的距離,計算彈框?qū)崟r高度栅迄,并發(fā)送事件
  double _currentHeight = _totalHeight - distance;
  if(_currentHeight > _totalHeight){
    return;
  }
  _streamController.sink.add(_currentHeight);
}
  1. 在獲取手指離開屏幕的方法中站故,有兩種情況,一種是滑動距離不夠毅舆,彈框要回到最大高度西篓,一種是滑動距離足夠,這時將彈框pop掉即可憋活,注意這里pop掉是會響應(yīng)BottomSheet的退出動畫岂津,所以無需在做特殊處理。
onPointerUp: (event){
  // 觸摸事件結(jié)束 手指離開屏幕
  // 這里認(rèn)為滑動超過一半就認(rèn)為用戶要退出了悦即,值可以根據(jù)實際體驗修改
  if(currentHeight < (_totalHeight * 0.5)){
    Navigator.pop(context);
  }else{
    _streamController.sink.add(_totalHeight);
  }
},
  1. 當(dāng)動畫效果發(fā)生時吮成,也就是彈框的高度不是最大高度時,這里要將listview的滑動效果禁止掉辜梳,否則在手指上下滑動彈框整體時會有l(wèi)istview的鬼畜響應(yīng)效果粱甫,彈框高度恢復(fù)時也同步恢復(fù)listview的滑動響應(yīng)。
physics: currentHeight != _totalHeight ? NeverScrollableScrollPhysics() : ClampingScrollPhysics(),

效果

bottom_sheet.gif

總結(jié)

  • 通過這個問題還是意識到一定要多看源碼作瞄、多看源碼茶宵、多看源碼,往往看似比較困難的問題通常在懂得原理后也就迎刃而解宗挥。
  • 頻繁的通過stream發(fā)送事件不是此方法的最優(yōu)解乌庶,只要思路正確叶摄,配合上Flutter本身的多種多樣的組件其實是有很多解決方案的“材猓或許你有更好的思路也不妨交流下~

完整代碼

代碼地址

掘金主頁

主頁

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宵喂,隨后出現(xiàn)的幾起案子糠赦,更是在濱河造成了極大的恐慌,老刑警劉巖锅棕,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拙泽,死亡現(xiàn)場離奇詭異,居然都是意外死亡裸燎,警方通過查閱死者的電腦和手機(jī)顾瞻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來德绿,“玉大人荷荤,你說我怎么就攤上這事∫莆龋” “怎么了蕴纳?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長个粱。 經(jīng)常有香客問我古毛,道長,這世上最難降的妖魔是什么都许? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任稻薇,我火速辦了婚禮,結(jié)果婚禮上胶征,老公的妹妹穿的比我還像新娘塞椎。我一直安慰自己,他們只是感情好睛低,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布忱屑。 她就那樣靜靜地躺著,像睡著了一般暇昂。 火紅的嫁衣襯著肌膚如雪莺戒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天急波,我揣著相機(jī)與錄音从铲,去河邊找鬼。 笑死澄暮,一個胖子當(dāng)著我的面吹牛名段,可吹牛的內(nèi)容都是我干的阱扬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼伸辟,長吁一口氣:“原來是場噩夢啊……” “哼麻惶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起信夫,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤窃蹋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后静稻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體警没,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年振湾,在試婚紗的時候發(fā)現(xiàn)自己被綠了杀迹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡押搪,死狀恐怖树酪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情大州,我是刑警寧澤嗅回,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站摧茴,受9級特大地震影響绵载,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苛白,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一娃豹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧购裙,春花似錦懂版、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至薇芝,卻和暖如春蓬抄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背夯到。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工嚷缭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓阅爽,卻偏偏與公主長得像路幸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子付翁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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