当前位置:网站首页>Flutter tips: various fancy nesting of listview and pageview

Flutter tips: various fancy nesting of listview and pageview

2022-07-05 04:37:00 Love cat de Xiaoguo

This time Flutter The trick is ListView and PageView Fancy nesting , Different Scrollable I believe you will not be unfamiliar with the nested conflict problem of , Passed today ListView and PageView The three nesting modes of take you to gain some different tips .

Normal nesting

The most common nesting should be horizontal PageView Plus longitudinal ListView The combination of , In general, there will be no problem with this combination , Unless you insist on sliding sideways .

Recently, I happened to meet several people asking at the same time :“ Oblique slip ListView Easy to switch to PageView slide ” The problem of , as follows GIF Shown , When the user is sliding ListView when , After the sliding angle belt is tilted , What may cause sliding is PageView instead of ListView .

xiehuadong

Although I don't think this is a problem from my personal experience , But if the product insists on your modification , Do you want to rewrite it yourself PageView Are your gestures responsive ?

Let's look at it briefly , Whether it's PageView still ListView Their sliding effect comes from Scrollable , and Scrollable Internal response to different directions , It's through RawGestureDetector complete :

  • VerticalDragGestureRecognizer Handle vertical gestures
  • HorizontalDragGestureRecognizer Handle horizontal gestures

So simply look at the judgment logic of their response , You can see a very interesting method computeHitSlop according to pointer The type of determines, of course, the smallest pixel needed to hit , Touch the default is kTouchSlop (18.0).

image-20220613103745974

Do you have a flash of inspiration when you see this : If we put PageView Of touchSlop Revised , Is it possible to adjust the sensitivity of its response ? It happens to be computeHitSlop In the method , It can go through DeviceGestureSettings To configure the , and DeviceGestureSettings From MediaQuery , So the following code shows :

body: MediaQuery(
  /// Raise height  touchSlop  To  50 , such  pageview  Sliding may have a little effect ,
  /// But the problem of oblique sliding trigger is probably handled 
  data: MediaQuery.of(context).copyWith(
      gestureSettings: DeviceGestureSettings(
    touchSlop: 50,
  )),
  child: PageView(
    scrollDirection: Axis.horizontal,
    pageSnapping: true,
    children: [
      HandlerListView(),
      HandlerListView(),
    ],
  ),
),

Tip 1 : By nesting a MediaQuery , Then adjust gestureSettings Of touchSlop To modify PageView The lightness of , And don't forget , You need to put ListView Of touchSlop Switching will default Of kTouchSlop

class HandlerListView extends StatefulWidget {
  @override
  _MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<HandlerListView> {
  @override
  Widget build(BuildContext context) {
    return MediaQuery(
      /// here  touchSlop   You need to return to the default 
      data: MediaQuery.of(context).copyWith(
          gestureSettings: DeviceGestureSettings(
        touchSlop: kTouchSlop,
      )),
      child: ListView.separated(
        itemCount: 15,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('Item $index'),
          );
        },
        separatorBuilder: (context, index) {
          return const Divider(
            thickness: 3,
          );
        },
      ),
    );
  }
}

Finally, let's see the effect , as follows GIF Shown , Now even if you slide sideways , It also triggers PageView Horizontal sliding of , Triggered only when moving laterally PageView The gesture , Of course , If there is any problem with this rough writing , It's probably reduced PageView Sensitivity of response .

xiehuabudong

In the same direction PageView nesting ListView

After introducing the general use , Then something different , Switch vertically PageView Nested vertically scrolling ListView , Is your first feeling unreliable , Why is there such a scene ?

For products , They won't think about how you achieve it , They just slap their heads and say Taobao can , Why can't you , So if it's you , What would you do ?

And about this demand , The result of the current discussion in the community is : hold PageView and ListView Sliding of is disabled , And then through RawGestureDetector Manage yourself .

If you are not interested in implementing logical analysis , You can see directly at the end of this section Source link .

Don't panic when you see your own management , Although we should realize it by ourselves PageView and ListView Gesture distribution , But there is no need to rewrite PageView and ListView , We can reuse their Darg Response logic , As shown in the following code :

  • adopt NeverScrollableScrollPhysics It's forbidden PageView and ListView The rolling effect of
  • Through the top RawGestureDetector Of VerticalDragGestureRecognizer Manage gesture events by yourself
  • To configure PageController and ScrollController Used to get status
body: RawGestureDetector(
  gestures: <Type, GestureRecognizerFactory>{
    VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
            VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(),
        (VerticalDragGestureRecognizer instance) {
      instance
        ..onStart = _handleDragStart
        ..onUpdate = _handleDragUpdate
        ..onEnd = _handleDragEnd
        ..onCancel = _handleDragCancel;
    })
  },
  behavior: HitTestBehavior.opaque,
  child: PageView(
    controller: _pageController,
    scrollDirection: Axis.vertical,
    /// Mask the default sliding response 
    physics: const NeverScrollableScrollPhysics(),
    children: [
      ListView.builder(
        controller: _listScrollController,
        /// Mask the default sliding response 
        physics: const NeverScrollableScrollPhysics(),
        itemBuilder: (context, index) {
          return ListTile(title: Text('List Item $index'));
        },
        itemCount: 30,
      ),
      Container(
        color: Colors.green,
        child: Center(
          child: Text(
            'Page View',
            style: TextStyle(fontSize: 50),
          ),
        ),
      )
    ],
  ),
),

And then let's see _handleDragStart Realization , As shown in the following code , Making gestures details when , We mainly judge :

  • adopt ScrollController Judge ListView Whether or not visible
  • Judge whether the touch position is ListIView Within the scope of
  • Judge which one to pass according to the status Controller To produce Drag object , Used to respond to subsequent sliding events

  void _handleDragStart(DragStartDetails details) {
    /// First judge  Listview  Whether it is visible or can be called 
    /// Generally invisible  hasClients false , because  PageView  either  keepAlive
    if (_listScrollController?.hasClients == true &&
        _listScrollController?.position.context.storageContext != null) {
      /// obtain  ListView  Of   renderBox
      final RenderBox? renderBox = _listScrollController
          ?.position.context.storageContext
          .findRenderObject() as RenderBox;

      /// Judge whether the touch position is  ListView  Inside 
      /// Generally, it is not within the scope because  ListView  It has slid up , The coordinate position is inconsistent with the touch position 
      if (renderBox?.paintBounds
              .shift(renderBox.localToGlobal(Offset.zero))
              .contains(details.globalPosition) ==
          true) {
        _activeScrollController = _listScrollController;
        _drag = _activeScrollController?.position.drag(details, _disposeDrag);
        return;
      }
    }

    /// At this time, it can be regarded as  PageView  Need to slide 
    _activeScrollController = _pageController;
    _drag = _pageController?.position.drag(details, _disposeDrag);
  }

We are mainly at the beginning of touch , When judging the object that needs response ListView still PageView , And then through _activeScrollController Save the response object of course , And through Controller Generate Drag object .

In short : When the sliding event occurs , By default, a Drag Used to handle subsequent sliding events ,Drag The original event will be processed and then given ScrollPosition To trigger the subsequent sliding effect .

And then _handleDragUpdate In the method , It mainly determines whether the response needs to be switched to PageView :

  • If you don't need it, continue to use what you got before _drag?.update(details) Respond to ListView rolling
  • If necessary, pass _pageController Switch to new _drag Object is used to respond to
void _handleDragUpdate(DragUpdateDetails details) {
  if (_activeScrollController == _listScrollController &&

      /// Move your fingers up , That is, it is about to show the bottom  PageView
      details.primaryDelta! < 0 &&

      /// At the bottom , Switch to  PageView
      _activeScrollController?.position.pixels ==
          _activeScrollController?.position.maxScrollExtent) {
    /// Switch the corresponding controller 
    _activeScrollController = _pageController;
    _drag?.cancel();

    /// Reference resources   Scrollable  in 
    /// Because it is switching controller , That is to update  Drag
    /// Drag and drop the process to switch to  PageView  in , So we need to   DragStartDetails
    /// So we need to  DragUpdateDetails  become  DragStartDetails
    /// Extract  PageView  Inside  Drag  The corresponding  details
    _drag = _pageController?.position.drag(
        DragStartDetails(
            globalPosition: details.globalPosition,
            localPosition: details.localPosition),
        _disposeDrag);
  }
  _drag?.update(details);
}

Here is a little knowledge : As shown in the above code , We can simply go through details.primaryDelta Judge whether the sliding direction and moving is the spindle

Finally, as follows GIF Shown , You can see PageView nesting ListView Sliding in the same direction can work normally , But there are still two small problems , As can be seen from the illustration :

  • After switching ListView The location of has not been saved
  • The product requires removal ListView Edge overflow effect

7777777777777

So we need to deal with ListView Make one KeepAlive , Then use a simple method to remove Android Edge sliding Material effect :

  • adopt with AutomaticKeepAliveClientMixin Give Way ListView Keep the sliding position after switching
  • adopt ScrollConfiguration.of(context).copyWith(overscroll: false) Rapid removal Scrollable The edge of Material effect
child: PageView(
  controller: _pageController,
  scrollDirection: Axis.vertical,
  /// Get rid of  Android  On the default edge drag effect 
  scrollBehavior:
      ScrollConfiguration.of(context).copyWith(overscroll: false),


/// Yes  PageView  Inside  ListView  do  KeepAlive  Remember the location 
class KeepAliveListView extends StatefulWidget {
  final ScrollController? listScrollController;
  final int itemCount;

  KeepAliveListView({
    required this.listScrollController,
    required this.itemCount,
  });

  @override
  KeepAliveListViewState createState() => KeepAliveListViewState();
}

class KeepAliveListViewState extends State<KeepAliveListView>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView.builder(
      controller: widget.listScrollController,

      /// Mask the default sliding response 
      physics: const NeverScrollableScrollPhysics(),
      itemBuilder: (context, index) {
        return ListTile(title: Text('List Item $index'));
      },
      itemCount: widget.itemCount,
    );
  }

  @override
  bool get wantKeepAlive => true;
}

So here we have another trick to unlock : adopt ScrollConfiguration.of(context).copyWith(overscroll: false) Rapid removal Android Sliding to the edge Material 2 effect , Why do you say Material2, because Material3 Change up , concrete : Flutter 3 Under the ThemeExtensions and Material3 .

000000000

The source code of this section can be seen : https://github.com/CarGuo/gsy_flutter_demo/blob/7838971cefbf19bb53a71041cd100c4c15eb6443/lib/widget/vp_list_demo_page.dart#L75

In the same direction ListView nesting PageView

Is there anything more unconventional ? The answer is yes , After all, the small head of the product , How can I not think of Sliding vertically ListView Nested vertically switched PageView This demand .

With the front idea , In fact, the realization of this logic is similar : hold PageView and ListView Sliding of is disabled , And then through RawGestureDetector Manage yourself , The difference is the difference in gesture distribution .

RawGestureDetector(
          gestures: <Type, GestureRecognizerFactory>{
            VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
                    VerticalDragGestureRecognizer>(
                () => VerticalDragGestureRecognizer(),
                (VerticalDragGestureRecognizer instance) {
              instance
                ..onStart = _handleDragStart
                ..onUpdate = _handleDragUpdate
                ..onEnd = _handleDragEnd
                ..onCancel = _handleDragCancel;
            })
          },
          behavior: HitTestBehavior.opaque,
          child: ListView.builder(
                /// Mask the default sliding response 
                physics: NeverScrollableScrollPhysics(),
                controller: _listScrollController,
                itemCount: 5,
                itemBuilder: (context, index) {
                  if (index == 0) {
                    return Container(
                      height: 300,
                      child: KeepAlivePageView(
                        pageController: _pageController,
                        itemCount: itemCount,
                      ),
                    );
                  }
                  return Container(
                      height: 300,
                      color: Colors.greenAccent,
                      child: Center(
                        child: Text(
                          "Item $index",
                          style: TextStyle(fontSize: 40, color: Colors.blue),
                        ),
                      ));
                }),
        )

It's also in _handleDragStart In the method , First of all, we need to judge :

  • ListView If it has slipped , Will not respond to the top PageView Events
  • If at this time ListView Not sliding at the top , Judge whether the gesture position is PageView in , If it's a response PageView Events
  void _handleDragStart(DragStartDetails details) {
    /// As long as it's not the top , No response  PageView  The slide of 
    /// So this judgment only supports vertical  PageView  stay   ListView  At the top of the 
    if (_listScrollController.offset > 0) {
      _activeScrollController = _listScrollController;
      _drag = _listScrollController.position.drag(details, _disposeDrag);
      return;
    }

    /// At this time   ListView  At the top of the 
    if (_pageController.hasClients) {
      /// obtain  PageView
      final RenderBox renderBox =
          _pageController.position.context.storageContext.findRenderObject()
              as RenderBox;

      /// Judge whether the touch range is  PageView
      final isDragPageView = renderBox.paintBounds
          .shift(renderBox.localToGlobal(Offset.zero))
          .contains(details.globalPosition);

      /// If in  PageView  Switch to  PageView
      if (isDragPageView) {
        _activeScrollController = _pageController;
        _drag = _activeScrollController.position.drag(details, _disposeDrag);
        return;
      }
    }

    /// be not in  PageView  I will continue to respond  ListView
    _activeScrollController = _listScrollController;
    _drag = _listScrollController.position.drag(details, _disposeDrag);
  }

And then _handleDragUpdate In the method , Determine if the PageView Has slid to the last page , Also switch the sliding event to ListView

void _handleDragUpdate(DragUpdateDetails details) {
  var scrollDirection = _activeScrollController.position.userScrollDirection;

  /// Judge if the response at this time is still  _pageController, Is it the last page 
  if (_activeScrollController == _pageController &&
      scrollDirection == ScrollDirection.reverse &&

      /// Is it the last page , Switch back to... On the last page  pageController
      (_pageController.page != null &&
          _pageController.page! >= (itemCount - 1))) {
    /// Switch back to the  ListView
    _activeScrollController = _listScrollController;
    _drag?.cancel();
    _drag = _listScrollController.position.drag(
        DragStartDetails(
            globalPosition: details.globalPosition,
            localPosition: details.localPosition),
        _disposeDrag);
  }
  _drag?.update(details);
}

Of course , As well as KeepAlive And remove the list Material Edge effect , The results are as follows GIF Shown .

22222222222

The source code of this section can be seen :https://github.com/CarGuo/gsy_flutter_demo/blob/7838971cefbf19bb53a71041cd100c4c15eb6443/lib/widget/vp_list_demo_page.dart#L262

Finally, add a little trick : If you need Flutter Print the process of gesture competition , You can configure the debugPrintGestureArenaDiagnostics = true; To make the Flutter The processing process of outputting gesture competition .

import 'package:flutter/gestures.dart';
void main() {
  debugPrintGestureArenaDiagnostics = true;
  runApp(MyApp());
}

image-20220613115808538

Last

So to conclude , This chapter introduces how to pass Darg Solve various gesture conflicts caused by nesting , I believe you also know how to use Controller and Darg To quickly customize some sliding requirements , for example ListView linkage ListView Differential sliding effect :

///listView  linkage  listView
class ListViewLinkListView extends StatefulWidget {
  @override
  _ListViewLinkListViewState createState() => _ListViewLinkListViewState();
}

class _ListViewLinkListViewState extends State<ListViewLinkListView> {
  ScrollController _primaryScrollController = ScrollController();
  ScrollController _subScrollController = ScrollController();

  Drag? _primaryDrag;
  Drag? _subDrag;

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    _primaryScrollController.dispose();
    _subScrollController.dispose();
    super.dispose();
  }

  void _handleDragStart(DragStartDetails details) {
    _primaryDrag =
        _primaryScrollController.position.drag(details, _disposePrimaryDrag);
    _subDrag = _subScrollController.position.drag(details, _disposeSubDrag);
  }

  void _handleDragUpdate(DragUpdateDetails details) {
    _primaryDrag?.update(details);

    /// Divide 10 Achieve the difference effect 
    _subDrag?.update(DragUpdateDetails(
        sourceTimeStamp: details.sourceTimeStamp,
        delta: details.delta / 30,
        primaryDelta: (details.primaryDelta ?? 0) / 30,
        globalPosition: details.globalPosition,
        localPosition: details.localPosition));
  }

  void _handleDragEnd(DragEndDetails details) {
    _primaryDrag?.end(details);
    _subDrag?.end(details);
  }

  void _handleDragCancel() {
    _primaryDrag?.cancel();
    _subDrag?.cancel();
  }

  void _disposePrimaryDrag() {
    _primaryDrag = null;
  }

  void _disposeSubDrag() {
    _subDrag = null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("ListViewLinkListView"),
        ),
        body: RawGestureDetector(
          gestures: <Type, GestureRecognizerFactory>{
            VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<
                    VerticalDragGestureRecognizer>(
                () => VerticalDragGestureRecognizer(),
                (VerticalDragGestureRecognizer instance) {
              instance
                ..onStart = _handleDragStart
                ..onUpdate = _handleDragUpdate
                ..onEnd = _handleDragEnd
                ..onCancel = _handleDragCancel;
            })
          },
          behavior: HitTestBehavior.opaque,
          child: ScrollConfiguration(
            /// Get rid of  Android  On the default edge drag effect 
            behavior:
                ScrollConfiguration.of(context).copyWith(overscroll: false),
            child: Row(
              children: [
                new Expanded(
                    child: ListView.builder(

                        /// Mask the default sliding response 
                        physics: NeverScrollableScrollPhysics(),
                        controller: _primaryScrollController,
                        itemCount: 55,
                        itemBuilder: (context, index) {
                          return Container(
                              height: 300,
                              color: Colors.greenAccent,
                              child: Center(
                                child: Text(
                                  "Item $index",
                                  style: TextStyle(
                                      fontSize: 40, color: Colors.blue),
                                ),
                              ));
                        })),
                new SizedBox(
                  width: 5,
                ),
                new Expanded(
                  child: ListView.builder(

                      /// Mask the default sliding response 
                      physics: NeverScrollableScrollPhysics(),
                      controller: _subScrollController,
                      itemCount: 55,
                      itemBuilder: (context, index) {
                        return Container(
                          height: 300,
                          color: Colors.deepOrange,
                          child: Center(
                            child: Text(
                              "Item $index",
                              style:
                                  TextStyle(fontSize: 40, color: Colors.white),
                            ),
                          ),
                        );
                      }),
                ),
              ],
            ),
          ),
        ));
  }
}

44444444444444

原网站

版权声明
本文为[Love cat de Xiaoguo]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207050435278985.html