Adhering to the object, with the object; No object, look for an object; The ListWheelViewport component is finally running without the thought of creating an object. Previously due to cognitive limitations, has not been able to play ListWheelViewport, now, does grow up. This component has been included in FlutterUnit, now over 310+ components are included, congratulations, welcome star.

Take a look at the basics of ListWheelViewport:

Source location: flutter/lib/SRC/widgets/list_wheel_scroll_view dart parent class: RenderObjectWidget related components: ListWheelScrollView, CupertinoPicker, CupertinoDatePickerCopy the code

ListWheelViewport has the following scrolling viewport effect. If you’ve used a Cupertino style selector, you might think it’s similar. Yes, they all have ListWheelViewport participation at the bottom. After learning about ListWheelViewport, the others are all younger brothers.


A,ListWheelViewportThree required attributes

The property name type The default value introduce
itemExtent double required Item size in spindle direction
offset ViewportOffset required The viewport offset
childDelegate ListWheelChildDelegate required The child proxy constructor

Here is a simple demo to test the use of ListWheelViewport. The above three attributes must be given. ItemExtent is the simplest, representing the size of item along the main axis. If you scroll down the wheel, the main axis is the Y-axis, and itemExtent is the height of each item.

ChildDelegate attribute is ListWheelChildDelegate type, it is an abstract class, implementation class has the following three, including: ListWheelChildListDelegate accept List < widgets > to display. ListWheelChildBuilderDelegate by builder constructor to create the item. ListWheelChildLoopingListDelegate slide can be infinite List, and accept the List < widgets > to display.

The offset attribute needs to be passed into a ViewportOffset object, which is hard to build. However, based on previous experience, this object can be obtained from Scrollable. When the viewportBuilder property is assigned, the ViewportOffset object can be called back.

typedef ViewportBuilder = Widget Function(BuildContext context, ViewportOffset position);

class Scrollable extends StatefulWidget {
  const Scrollable({
    Key? key,
    this.axisDirection = AxisDirection.down,
    this.controller,
    this.physics,
    required this.viewportBuilder, //<---- viewportBuilder
    this.incrementCalculator,
    this.excludeFromSemantics = false.this.semanticChildCount,
    this.dragStartBehavior = DragStartBehavior.start,
    this.restorationId,
Copy the code

By putting these elements together, ListWheelViewport can be used. The code is as follows:

class ListWheelViewportDemo extends StatelessWidget {
  final List<Color> data = [
    Colors.blue[50], Colors.blue[100], Colors.blue[200],
    Colors.blue[300], Colors.blue[400], Colors.blue[500],
    Colors.blue[600], Colors.blue[700], Colors.blue[800],
    Colors.blue[900], Colors.blue[800], Colors.blue[700],
    Colors.blue[600], Colors.blue[500], Colors.blue[400],
    Colors.blue[300], Colors.blue[200], Colors.blue[100]];@override
  Widget build(BuildContext context) {
    return Container(
      height: 250,
      width: 320,
      child: Scrollable(
          axisDirection: AxisDirection.down,
          physics: BouncingScrollPhysics(),
          dragStartBehavior: DragStartBehavior.start,
          viewportBuilder: (ctx, position) => ListWheelViewport(
                itemExtent: 50,
                offset: position,
                childDelegate: ListWheelChildLoopingListDelegate(
                    children: data.map((e) => _buildItem(e)).toList()),
              )),
    );
  }

  Widget _buildItem(Color color) => Container(
        alignment: Alignment.center,
        color: color,
        child: Text(colorString(color),
            style: TextStyle(color: Colors.white, shadows: [
              Shadow(color: Colors.black, offset: Offset(. 5.. 5), blurRadius: 2))));String colorString(Color color) =>
      "#${color.value.toRadixString(16).padLeft(8.'0').toUpperCase()}";
}
Copy the code
  • Look at what the itemExtent attribute does
itemExtent = 80 itemExtent = 100

We have diametrics, sectors, etc. in the hospital

The property name type The default value introduce
perspective double 0.003 Fluoroscopy parameters 0~0.01
squeeze double 1.0 Squeeze the value
diameterRatio double 2.0 Diameter of the fraction

1. The perspective of attributes

Perspective means perspective. The default value is 0.003 and ranges from 0 to 0.01.

---->[RenderListWheelViewport]----
static const double defaultPerspective = 0.003;
Copy the code
  • This is aPerspective: 0.01The effect of

  • This is aPerspective: 0.001The effect is visibleperspectiveThe higher the value, the stronger the perspective.


2. Squeeze the properties

Squeeze means squeeze, default is 1.0.

  • This is aAdvertisement: 0.8The effect of

  • This is aAdvertisement: 1.5The effect is visiblesqueezeI can control itemLoose degree


3. DiameterRatio properties

Diameters, diameters, diameters, diameters, diameters, diameters, diameters, diameters

diameterRatio = 2 diameterRatio = pi/2

Other attributes

The property name type The default value introduce
magnification double 1.0 Zoom ratio
useMagnifier bool false Whether the zoom
clipBehavior Clip Clip.hardEdge Clipping behavior
renderChildrenOutsideViewport bool false Out of view whether render
offAxisFraction double 0.0 Axis center offset ratio
overAndUnderCenterOpacity double 1 Transparency outside the amplifier

1. Magnification effect

This component has the following magnification effects, which are controlled by Magnification and useMagnifier.

@override
Widget build(BuildContext context) {
  return Container(
    height: 250,
    width: 320,
    child: Scrollable(
        axisDirection: AxisDirection.down,
        physics: BouncingScrollPhysics(),
        dragStartBehavior: DragStartBehavior.start,
        viewportBuilder: (ctx, position) => ListWheelViewport(
          perspective: 0.008,
          squeeze: 1,
          diameterRatio: 2,
          itemExtent: 50,
          useMagnifier: true,
          magnification: 2,
          offset: position,
          childDelegate: ListWheelChildLoopingListDelegate(
              children: data.map((e) => _buildItem(e)).toList()),
        )),
  );
}
Copy the code

2. Out-of-bounds rendering and clipping

By default, the item is not in sight area will not apply colours to a drawing, can pass renderChildrenOutsideViewport: true to its display, pay attention to ClipBehavior at this time must be a Clip. None. The effect is as follows:

@override
Widget build(BuildContext context) {
  return Container(
    height: 250,
    width: 320,
    child: Scrollable(
        axisDirection: AxisDirection.down,
        physics: BouncingScrollPhysics(),
        dragStartBehavior: DragStartBehavior.start,
        viewportBuilder: (ctx, position) => ListWheelViewport(
          perspective: 0.008,
          squeeze: 1,
          diameterRatio: 2,
          renderChildrenOutsideViewport: true,
          clipBehavior: Clip.none,
          itemExtent: 50,
          offset: position,
          childDelegate: ListWheelChildLoopingListDelegate(
              children: data.map((e) => _buildItem(e)).toList()),
        )),
  );
}
Copy the code

3. offAxisFractionoverAndUnderCenterOpacityattribute

OffAxisFraction: 0.2 effect

OverAndUnderCenterOpacity: 0.4 effect


Four, based onListWheelViewportImplemented components

1. ListWheelScrollViewcomponent

The underlying implementation is based on _FixedExtentScrollable(subclass Scrollable) and ListWheelViewport, which has the ability to listen for sliding items in addition to the viewport. Below, the color of the small circle above is determined by selecting the color of the scroll wheel below. The related properties of ListWheelViewport are the same in ListWheelScrollView.

class CustomListWheelScrollView extends StatefulWidget {
  @override
  _CustomListWheelScrollViewState createState() =>
      _CustomListWheelScrollViewState();
}

class _CustomListWheelScrollViewState extends State<CustomListWheelScrollView> {
  var data = <Color>[
    Colors.orange[50],  Colors.orange[100], Colors.orange[200],
    Colors.orange[300], Colors.orange[400], Colors.orange[500],
    Colors.orange[600], Colors.orange[700], Colors.orange[800],
    Colors.orange[900]]. Color _color = Colors.blue;@override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        _buildCircle(),
        Container(
          height: 150,
          width: 300,
          child: ListWheelScrollView.useDelegate(
            childDelegate: ListWheelChildLoopingListDelegate(
                children: data.map((e) => _buildItem(e)).toList()),
            perspective: 0.006,
            itemExtent: 50, onSelectedItemChanged: (index) { setState(() => _color = data[index]); },),),],); } Widget _buildCircle() => Container( margin: EdgeInsets.only(bottom:5),
        width: 30,
        height: 30,
        decoration: BoxDecoration(color: _color, shape: BoxShape.circle),
      );

  Widget _buildItem(Color color) {
    return Container(
      key: ValueKey(color),
      alignment: Alignment.center,
      height: 50,
      color: color,
      child: Text(
        colorString(color),
        style: TextStyle(color: Colors.white, shadows: [
          Shadow(color: Colors.black, offset: Offset(. 5.. 5), blurRadius: 2)))); }String colorString(Color color) =>
      "#${color.value.toRadixString(16).padLeft(8.'0').toUpperCase()}";
}
Copy the code

2. CupertinoPicker component

CupertinoPicker’s internal source code implementation relies on ListWheelScrollView. So ListWheelViewport was responsible for the final effect.

class CustomCupertinoPicker extends StatelessWidget {
  final names = [
    'Java'.'Kotlin'.'Dart'.'Swift'.'C++'.'Python'."JavaScript"."PHP"."Go"."Object-c"
  ];

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 150,
      width: 300,
      child: CupertinoPicker(
          backgroundColor: CupertinoColors.systemGrey.withAlpha(33),
          diameterRatio: 1,
          offAxisFraction: 0.2,
          squeeze: 1.5,
          itemExtent: 40,
          onSelectedItemChanged: (position) {
            print('Current entry${names[position]}'); }, children: names.map((e) => Center(child: Text(e))).toList()), ); }}Copy the code

3. CupertinoDatePicker component

CupertinoDatePicker is internally based on the CupertinoPicker implementation.

class CustomCupertinoDatePicker extends StatefulWidget {
  @override
  _CustomCupertinoDatePickerState createState() =>
      _CustomCupertinoDatePickerState();
}

class _CustomCupertinoDatePickerState extends State<CustomCupertinoDatePicker> {
  DateTime _date = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 350,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Text(
            'Current date:${_date.toIso8601String()}',
            style: TextStyle(color: Colors.grey, fontSize: 16),
          ),
          _buildInfoTitle('CupertinoDatePickerMode.dateAndTime'),
          buildPicker(CupertinoDatePickerMode.dateAndTime),
        ],
      ),
    );
  }

  Container buildPicker(CupertinoDatePickerMode mode) {
    return Container(
      margin: EdgeInsets.all(10),
      height: 150,
      child: CupertinoDatePicker(
        mode: mode,
        initialDateTime: DateTime.now(),
        minimumYear: 2018,
        maximumYear: 2030,
        use24hFormat: false,
        minuteInterval: 1,
        backgroundColor: CupertinoColors.white,
        onDateTimeChanged: (date) {
          print(date); setState(() => _date = date); },),); } Widget _buildInfoTitle(info) {return Padding(
      padding: const EdgeInsets.only(left: 20, top: 20, bottom: 5),
      child: Text(
        info,
        style: TextStyle(
            color: Colors.blue, fontSize: 16, fontWeight: FontWeight.bold), ), ); }}Copy the code

4. Summary

In this way, the wheel-related components, traced back to their origins, are related to ListWheelViewport. So knowing the meaning of the properties of ListWheelViewport makes it easier to understand the other derived components. This is the same, should change. Maybe one day you’ll come across some kind of custom wheel effect, and ListWheelViewport will help you do that.

[1] ListWheelScrollView is implemented based on Scrollable + ListWheelViewport. [2] CupertinoPicker is implemented based on ListWheelScrollView. [3] CupertinoDatePicker is implemented based on CupertinoPicker.Copy the code