Flutter Widget自定義總結

在Flutter實際開發中,你們可能會遇到flutter框架中提供的widget達不到咱們想要的效果,這時就須要咱們去自定義widget,從Flutter構建、佈局、繪製三部曲中咱們瞭解到,實際的測量、佈局、繪製操做都在RenderObject中,咱們是能夠進行繼承相關的RenderObject來實現自定義的。可是其實flutter框架在設計之初就給咱們預留出了自定義的入口,方便咱們進行自定義。git

CustomPaint自定義繪製

例:圓形進度條github

思路:使用CustomPaint繪製須要的效果canvas

class CircleProgress extends StatelessWidget {
  final Size size;
  final double progress;

  CircleProgress({@required this.size, @required this.progress});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: size,
      painter: CircleProgressPainter(endDegree: progress * 360),//在Painter中寫真正的繪畫邏輯
    );
  }
}

class CircleProgressPainter extends CustomPainter {
  ...省略

  @override
  void paint(Canvas canvas, Size size) {
   ...繪製的具體邏輯,size是畫布的大小
  }
}
複製代碼

CustomSingleChildLayout對單一child進行佈局

例:實現對child約束成正方形框架

思路:使用CustomSingleChildLayout對child進行佈局,並約束爲正方形less

class RectLayout extends StatelessWidget {
  final Widget child;

  RectLayout({@required this.child});

  @override
  Widget build(BuildContext context) {
    return CustomSingleChildLayout(
      delegate: RectLayoutDelegate(),//進行佈局的代理
      child: child,
    );
  }
}

class RectLayoutDelegate extends SingleChildLayoutDelegate {
  //肯定layout的size,constraints是parent傳過來的約束
  @override
  Size getSize(BoxConstraints constraints) => super.getSize(constraints);

  ///是否須要relayout
  @override
  bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) => false;

  ///肯定child的位置,返回一個相對於parent的偏移值,size是layout的大小,由getsize肯定,childSize由getConstraintsForChild得出的Constraints對child進行約束,獲得child自身的size
  @override
  Offset getPositionForChild(Size size, Size childSize) {
    double dx = (size.width - childSize.width) / 2;
    double dy = (size.height - childSize.height) / 2;
    return Offset(dx, dy);
  }

  ///肯定child的約束,用於肯定child的大小
  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {//
    double maxEdge = min(constraints.maxWidth, constraints.maxHeight);
    return BoxConstraints(maxWidth: maxEdge, maxHeight: maxEdge);
  }
}
複製代碼

CustomSingleChildLayout對多個child進行佈局

例:實現網格佈局ide

思路:使用CustomSingleChildLayout對child進行佈局、定位,使其成爲網格的佈局佈局

class GridLayout extends StatelessWidget {
  final List<Widget> children;
  final double horizontalSpace;
  final double verticalSpace;

  GridLayout(
      {@required this.children,
      @required this.horizontalSpace,
      @required this.verticalSpace});

  @override
  Widget build(BuildContext context) {
    List<Widget> layoutChildren = new List();
    for (int index = 0; index < children.length; index++) {
      layoutChildren.add(LayoutId(id: index, child: children[index]));
    }
    return CustomMultiChildLayout(
      delegate: GridLayoutDelegate(//真正的佈局實現
        horizontalSpace: horizontalSpace,
        verticalSpace: verticalSpace,
      ),
      children: layoutChildren,
    );
  }
}

class GridLayoutDelegate extends MultiChildLayoutDelegate {
  final double horizontalSpace;
  final double verticalSpace;
  List<Size> _itemSizes = List();

  GridLayoutDelegate(
      {@required this.horizontalSpace, @required this.verticalSpace});

  @override
  void performLayout(Size size) {
	//對每一個child進行逐一佈局
    int index = 0;
    double width = (size.width - horizontalSpace) / 2;
    var itemConstraints = BoxConstraints(
        minWidth: width, maxWidth: width, maxHeight: size.height);
    while (hasChild(index)) {
      _itemSizes.add(layoutChild(index, itemConstraints));
      index++;
    }
	//對每個child逐一進行定位
    index = 0;
    double dx = 0;
    double dy = 0;
    while (hasChild(index)) {
      positionChild(index, Offset(dx, dy));
      dx = index % 2 == 0 ? width + horizontalSpace : 0;
      if (index % 2 == 1) {
        double maxHeight =
            max(_itemSizes[index].height, _itemSizes[index - 1].height);
        dy += maxHeight + verticalSpace;
      }
      index++;
    }
  }

  @override
  bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
    return oldDelegate != this;
  }

  //肯定layout的size,constraints是parent傳過來的約束
  @override
  Size getSize(BoxConstraints constraints) => super.getSize(constraints);
}
複製代碼

組合自定義

通常狀況,組合自定義應該是咱們最常常用的方式,經過繼承自StatelessWidget或StatefulWidget,把多個Widget組合起來,從而達到咱們須要的效果。post

  1. 例:下拉刷新,上拉加載

實現一:經過自帶的RefreshIndictor和ScrollController組合實現學習

思路:經過對滾動進行監聽來觸發加載更多動畫

_scrollController.addListener(() {
  var maxScroll = _scrollController.position.maxScrollExtent;
  if (_scrollController.offset >= maxScroll) {
    if (widget.loadMoreStatus != LoadMoreStatus.noData) {
      widget.onLoadMore();
    }
  }
});
複製代碼

實現二:經過NotificationListener監聽scroll的總體狀態,讓後結合平移、動畫來實現

思路:經過監聽用戶overscroll的距離來平移內容區域,從而達到下拉刷新,上拉加載的效果

@override
Widget build(BuildContext context) {
  double topHeight =
      _pullDirection == PullDirection.DOWN ? _overScrollOffset.dy.abs() : 0;
  double bottomHeight =
      _pullDirection == PullDirection.UP ? _overScrollOffset.dy.abs() : 0;
  return Stack(
    children: <Widget>[
      widget.headerBuilder.buildTip(_state, topHeight),
      Align(
        alignment: Alignment.bottomCenter,
        child: widget.footerBuilder.buildTip(_state, bottomHeight),
      ),
      Transform.translate(
        offset: _overScrollOffset,
        child: NotificationListener<ScrollNotification>(
          onNotification: handleScrollNotification,
          child: DecoratedBox(
            decoration: BoxDecoration(color: Colors.grey[100]),
            child: ListView.builder(
              itemBuilder: buildItem,
              itemCount: 30,
            ),
          ),
        ),
      )
    ],
  );
}
複製代碼
  1. 例:上下左右滑動的layout

實現:經過GestureDetector監聽手勢滑動,而後經過平移來達到效果

思路:主要處理滑動邊界,以及開關的零界點

@override
Widget build(BuildContext context) {
  //debugPrint('_slideOffset:${_slideOffset.toString()}');
  return GestureDetector(
    onPanUpdate: handlePanUpdate,
    onPanEnd: handlePanEnd,
    child: Stack(
      children: <Widget>[
        widget.background,
        Transform.translate(
          child: widget.foreground,
          offset: _slideOffset,
        ),
      ],
    ),
  );
}
複製代碼

以上的完整代碼在這flutter知識點整理

Flutter學習總結

對Flutter的學習也有一段時間了,從最開始的Widget的使用,到後面的框架的一些研究,全部的心得與總結都會記錄下來,主要是對本身知識點的整理,一樣也爲了可以與廣大Flutter的學習者共同窗習,相互探討。

項目地址:flutter知識點整理

相關文章
相關標籤/搜索