Flutter仿寫一個iOS風格的通訊錄

此文章主要介紹怎么使用Flutter的Cupertino風格控件往扔,寫一個iOS風格的通訊錄,還有在此過程中遇到的問題及解決辦法窑滞。

大家在用Flutter寫App的時候定血,一般都會使用material風格的控件转砖,因為material風格的控件比較豐富须鼎,但是,他在iOS上就會顯得Android氣息比較重府蔗,不太適合晋控,所以本文章將通過用仿寫iOS通訊錄,系統(tǒng)地介紹Cupertino控件礁竞,及系統(tǒng)的一些底層控件和怎么自己定義優(yōu)美的適合自己的控件糖荒。

由于使用的聯(lián)系人三方包的限制,有些功能未能實現(xiàn)模捂,我會持續(xù)關注這個聯(lián)系人插件的更新捶朵,及時加上新功能。

Github地址

首頁

首頁截圖

主要用到的控件及問題

CupertinoPageScaffold

一個iOS風格Scaffold狂男,可以添加NavigationBar综看。

NestedScrollView

實現(xiàn)浮動的NavigationBar和SearchBar。

NestedScrollView我用的自己重寫過的岖食,主要是因為源碼中的有兩個問題红碑。

1、當列表滑動到底部泡垃,然后繼續(xù)滑動析珊,然后停止,松手蔑穴,這時候可列表會重新滾動到底部忠寻,但是源碼沒有處理當速度等于0的時候的情況,所以當松手的時候存和,列表會回彈回去奕剃,回彈距離小于maxScrollExtent

源碼如下:

@protected
ScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) {
  return position.createBallisticScrollActivity(
    position.physics.createBallisticSimulation(
      velocity == 0 ? position as ScrollMetrics : _getMetrics(position, velocity),
      velocity,
    ),
    mode: _NestedBallisticScrollActivityMode.inner,
  );
}

這里當velocity == 0的時候捐腿,直接把innerPosition賦值給了createBallisticSimulation方法的position參數(shù)纵朋,我們繼續(xù)往下看。

ScrollActivity createBallisticScrollActivity(
  Simulation simulation, {
  @required _NestedBallisticScrollActivityMode mode,
  _NestedScrollMetrics metrics,
}) {
  if (simulation == null) return IdleScrollActivity(this);
    assert(mode != null);
    switch (mode) {
      case _NestedBallisticScrollActivityMode.outer:
        assert(metrics != null);
        if (metrics.minRange == metrics.maxRange) return IdleScrollActivity(this);
        return _NestedOuterBallisticScrollActivity(
          coordinator,
          this,
          metrics,
          simulation,
          context.vsync,
        );
      case _NestedBallisticScrollActivityMode.inner:
        return _NestedInnerBallisticScrollActivity(
          coordinator,
          this,
          simulation,
          context.vsync,
        );
      case _NestedBallisticScrollActivityMode.independent:
        return BallisticScrollActivity(this, simulation, context.vsync);
  }
  return null;
}

這里velocity == 0的時候茄袖,執(zhí)行的是

case _NestedBallisticScrollActivityMode.inner:
  return _NestedInnerBallisticScrollActivity(
    coordinator,
    this,
    simulation,
     context.vsync,
  );

這時候的simulation就是上面通過innerPosition得到的操软,然后傳給了_NestedInnerBallisticScrollActivity,我們在繼續(xù)往下看宪祥,

class _NestedInnerBallisticScrollActivity extends BallisticScrollActivity {
  _NestedInnerBallisticScrollActivity(
    this.coordinator,
    _NestedScrollPosition position,
    Simulation simulation,
    TickerProvider vsync,
  ) : super(position, simulation, vsync);

  final _NestedScrollCoordinator coordinator;

  @override
  _NestedScrollPosition get delegate => super.delegate as _NestedScrollPosition;

  @override
  void resetActivity() {
    delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(
      delegate,
      velocity,
    ));
  }

  @override
  void applyNewDimensions() {
    delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(
      delegate,
      velocity,
    ));
  }

  @override
  bool applyMoveTo(double value) {
    return super.applyMoveTo(coordinator.nestOffset(value, delegate));
  }
}

我們發(fā)現(xiàn)這里執(zhí)行的操作并不是我們想要的寺鸥,當velocity == 0猪钮,滑動距離大于maxScrollExtent的時候品山,我們只想滾動到列表的最底部胆建,所以我們改一下這里的實現(xiàn)。此處有兩種實現(xiàn)方式:

第一種方式:改_getMetrics方法
// This handles going forward (fling up) and inner list is
// underscrolled, OR, going backward (fling down) and inner list is
// scrolled past zero. We want to skip the pixels we don't need to grow
// or shrink over.
if (velocity > 0.0) {
  // shrinking
  extra = _outerPosition.minScrollExtent - _outerPosition.pixels;
} else if (velocity < 0.0) {
  // growing
  extra = _outerPosition.pixels - (_outerPosition.maxScrollExtent - _outerPosition.minScrollExtent);
} else {
  extra = 0.0;
}
assert(extra <= 0.0);
minRange = _outerPosition.minScrollExtent;
maxRange = _outerPosition.maxScrollExtent + extra;
assert(minRange <= maxRange);
correctionOffset = 0.0;

這里加上velocity == 0的判斷肘交。

第二種方式:修改createInnerBallisticScrollActivity方法笆载,加上velocity == 0的判斷。
@protected
ScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) {
  return position.createBallisticScrollActivity(
    position.physics.createBallisticSimulation(
      velocity == 0 ? position as ScrollMetrics : _getMetrics(position, velocity),
      velocity,
    ),
    mode: velocity == 0 ? _NestedBallisticScrollActivityMode.independent : _NestedBallisticScrollActivityMode.inner,
  );
}

2涯呻、當我們手動調用position.moveTo方法滾動到最底部的時候凉驻,獲取到的maxScrollExtent并不是實際innerPositionmaxScrollExtent,而應該是maxScrollExtent - outerPosition.maxScrollExtent + outerPosition.pixels复罐。

接下來我們分析源碼看看哪里出了問題涝登。
首先,我們看看與之有直接關聯(lián)的maxScrollExtent方法效诅。

@override
double get maxScrollExtent => _maxScrollExtent;

我們看到只是單純的返_maxScrollExtent胀滚,那我們看看_maxScrollExtent是在哪里賦值的,經(jīng)過查看源碼得知乱投,_maxScrollExtent賦值的地方主要在下面這個方法里:

@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
  assert(minScrollExtent != null);
  assert(maxScrollExtent != null);
  if (!nearEqual(_minScrollExtent, minScrollExtent, Tolerance.defaultTolerance.distance) ||
    !nearEqual(_maxScrollExtent, maxScrollExtent, Tolerance.defaultTolerance.distance) ||
    _didChangeViewportDimensionOrReceiveCorrection) {
    assert(minScrollExtent != null);
    assert(maxScrollExtent != null);
    assert(minScrollExtent <= maxScrollExtent);
    _minScrollExtent = minScrollExtent;
    _maxScrollExtent = maxScrollExtent;
    _haveDimensions = true;
    applyNewDimensions();
    _didChangeViewportDimensionOrReceiveCorrection = false;
  }
  return true;
}

所以我們重寫這個方法咽笼,修改如下:

@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
  assert(minScrollExtent != null);
  assert(maxScrollExtent != null);
  var outerPosition = coordinator._outerPosition;
  var outerMaxScrollExtent = outerPosition.maxScrollExtent;
  var outerPixels = outerPosition.pixels;
  if (outerMaxScrollExtent != null && outerPixels != null) {
    maxScrollExtent -= outerMaxScrollExtent - outerPixels;
    maxScrollExtent = math.max(minScrollExtent, maxScrollExtent);
  }
  return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
}

這樣我們成功解決了上面提到的兩個問題。

CustomScrollView

實現(xiàn)浮動的Index戚炫。

SliverPersistentHeader

實現(xiàn)Index固定在頭部剑刑。

CupertinoSliverRefreshIndicator

實現(xiàn)下拉刷新。

群組

群組

新建聯(lián)系人頁面

新建聯(lián)系人
點擊取消時

編輯頭像

編輯頭像
移動和縮放
選擇濾鏡
選擇后双肤,再次編輯

聯(lián)系人詳情

聯(lián)系人詳情
長按復制

選擇標簽

選擇標簽

至此施掏,基本完成。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末茅糜,一起剝皮案震驚了整個濱河市七芭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌限匣,老刑警劉巖抖苦,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異米死,居然都是意外死亡锌历,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門峦筒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來究西,“玉大人,你說我怎么就攤上這事物喷÷辈模” “怎么了遮斥?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扇丛。 經(jīng)常有香客問我术吗,道長,這世上最難降的妖魔是什么帆精? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任较屿,我火速辦了婚禮,結果婚禮上卓练,老公的妹妹穿的比我還像新娘隘蝎。我一直安慰自己,他們只是感情好襟企,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布嘱么。 她就那樣靜靜地躺著,像睡著了一般顽悼。 火紅的嫁衣襯著肌膚如雪曼振。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天表蝙,我揣著相機與錄音拴测,去河邊找鬼。 笑死府蛇,一個胖子當著我的面吹牛集索,可吹牛的內容都是我干的。 我是一名探鬼主播汇跨,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼务荆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了穷遂?” 一聲冷哼從身側響起函匕,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚪黑,沒想到半個月后盅惜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡忌穿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年抒寂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掠剑。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡屈芜,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情井佑,我是刑警寧澤属铁,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站躬翁,受9級特大地震影響焦蘑,放射性物質發(fā)生泄漏。R本人自食惡果不足惜姆另,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一喇肋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧迹辐,春花似錦、人聲如沸甚侣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殷费。三九已至印荔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間详羡,已是汗流浹背仍律。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留实柠,地道東北人水泉。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像窒盐,于是被迫代替她去往敵國和親草则。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345