Preface:

To prepare for the August challenge of the nuggets, I’m going to pick 31 components this month that haven’t been introduced before and do a full analysis and attribute introduction. These articles will be used as important material in the collection of the Flutter components. I hope I can stick to it, your support will be my biggest motivation ~

  • 1. [Flutter component collection] NotificationListener | August more text challenge
  • 2.【Flutter Components 】Dismissible | August more challenges
  • 3.【Flutter Component Collection 】 How the Switch was made | August More text Challenge[this article]

1. Detailed explanation of Switch component usage

Some people may think that the Switch component is very simple, what is there to say? In fact, the Switch component source code is full of nearly a thousand lines, which is worth learning about the theme processing, platform adaptation, event processing, animation processing, drawing processing. So without further ado, let’s see how the Switch is made.


1. Switch simplest use:valueonChanged

Note when using the Switch component: This component is a StatelessWidget, meaning that it does not maintain the Switch state itself. This means that I have to rebuild the Switch component to Switch the state. When you build the Switch, you must pass value and onChanged, where value represents the status of the Switch and onChanged is the callback function for the status change.

Below, define the state _value in _SwitchDemoState to represent the state of the Switch Switch, change the state value in the _onChanged callback, and rebuild the Switch component so that the Switch can be clicked.

class SwitchDemo extends StatefulWidget {
  const SwitchDemo({Key? key}) : super(key: key);

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

class _SwitchDemoState extends State<SwitchDemo> {
  bool _value = false;

  @override
  Widget build(BuildContext context) {
    return Switch(
      value: _value,
      onChanged: _onChanged,
    );
  }

  void _onChanged(boolvalue) { setState(() { _value = value; }); }}Copy the code

Why does the Switch not maintain the state of the Switch itself instead of leaving it to the outside world? If the Switch is a StatelessWidget, why is it possible to perform sliding animations? And when did the onChanged method trigger? With these questions in mind, let’s gradually get to know the properties of the unfamiliar Switch component.


2. Four main colors of the Switch

As you can see from the constructor of the Switch, there are quite a few color-related properties defined.

Let’s look at the first four color attributes:

  • inactiveThumbColorOn behalf of closeThe circleThe color.
  • inactiveTrackColorOn behalf of closechuteThe color.

  • activeColorIt stands for when openedThe circleThe color.
  • inactiveTrackColorIt stands for when openedchuteThe color.

Switch(
  activeColor: Colors.blue,
  activeTrackColor: Colors.green,
  inactiveThumbColor: Colors.orange,
  inactiveTrackColor: Colors.pinkAccent,
  value: _value,
  onChanged: _onChanged,
);
Copy the code

3. HoverColor, mouseCursor and splashRadius

The first two properties generally only work on the desktop or web. HoverColor is the color of the outer circle of hover, and splashRadius is the radius of the circle. If you don’t want the hover effect of the outer circle, set the radius to 0. In addition, mouseCursor represent the style of the mouse, such as the following little fist SystemMouseCursors. Grabbing.

Switch(
  activeColor: Colors.blue,
  activeTrackColor: Colors.green,
  inactiveThumbColor: Colors.orange,
  inactiveTrackColor: Colors.pinkAccent,
  hoverColor: Colors.blue.withOpacity(0.2),
  mouseCursor: SystemMouseCursors.grabbing,
  value: _value,
  onChanged: _onChanged,
);
Copy the code

The mouseCursor attribute is of type mouseCursor, where a number of mouse pointer types are defined in SystemMouseCursors for use. Here are some effects:

contextMenu copy forbidden text

5. Specify pictures

With activeThumbImage and inactiveThumbImage you can specify the image in the small circle when it is on/off. Another onActiveThumbImageError and onInactiveThumbImageError two callback used for monitoring images load error.

When the circle specifies both the image and the color properties, the image is displayed.

Switch(
  activeColor: Colors.blue,
  activeThumbImage: AssetImage('assets/images/icon_head.png'),
  inactiveThumbImage: AssetImage('assets/images/icon_8.jpg'),
  activeTrackColor: Colors.green,
  inactiveThumbColor: Colors.orange,
  inactiveTrackColor: Colors.pinkAccent,
  hoverColor: Colors.blue.withOpacity(0.2),
  mouseCursor: SystemMouseCursors.move,
  splashRadius: 15,
  value: _value,
  onChanged: _onChanged,
);
Copy the code

Theme related properties: thumbColor and trackColor

Some Material components that are interactive define the interaction behavior by having the MaterialState enumeration, which has the following seven elements.

enum MaterialState {
  hovered,
  focused,
  pressed,
  dragged,
  selected,
  disabled,
  error,
}
Copy the code

We can see that both of these members are of type MaterialStateProperty. How and what are the characteristics of objects of this type?

---->[Switch member Statement]----finalMaterialStateProperty<Color? >? thumbColor;finalMaterialStateProperty<Color? >? trackColor;Copy the code

Simply by MaterialStateProperty resolveWith method, pass in a function returns the corresponding generic data. The following callback function is getThumbColor with the callback parameter Set

. It simply says that generic data is returned from the MaterialState set. As you can see from the annotation of the thumbColor property source code, the Switch has the following four materialstates.

In getThumbColor, return a different color for each state based on the states, so that the Switch automatically uses the corresponding color for each state. For example, onChanged: null means the Switch is not available, and in getThumbColor disabled returns red.

ThumbColor for small circle color, trackColor for chute color, use the same way. Now, one might ask: There are three properties that can set the circle, so they all exist at the same time, what about the priority? As a result, the inactiveThumbImage is shown first with the following priority:

InactiveThumbImage > thumbColor > inactiveThumbColor > Default Switch themeCopy the code

SwitchTheme is an InheritedWidget that maintains type SwitchThemeData. The default Switch theme is an InheritedWidget that maintains type SwitchThemeData.

We can specify the default style for the Switch in the subtree by nesting the SwitchTheme at the upper level. Since the SwitchTheme component is inherited internally from the MaterialApp, we can specify the theme style for the Switch in the Theme. This will use the default theme style when specifying the associated color properties of the Switch:


7. Switch focus: focusColor and AutoFocus

The Switch component has focus, and focus-related processing is encapsulated inside the component. FocusColor indicates the focusColor. One feature of a component that can be focused is that you can switch focus by pressing the Tab key on the desktop or web platform. Here’s how the six switches Switch focus with the Tab key:

@override
Widget build(BuildContext context) {
  return
    Wrap(
      children: List.generate(6, (index) => Switch(
        value: _value,
        focusColor: Colors.blue.withOpacity(0.1),
        onChanged: _onChanged,
      ))
    );
}
Copy the code

8. Switch size related: materialTapTargetSize

MaterialTapTargetSize is an enumeration type with two elements. This property can affect the size of the Switch, as shown in the following padded and shrinkWrap distributions. The default is padded, as shown in debugging. What this attribute does is described in detail in the source analysis section below.

enum MaterialTapTargetSize {
  padded,
  shrinkWrap,
}
Copy the code


Dig some details in the Switch source code

Type 1._SwitchType

The Switch class has a member of type _SwitchType, which is completely wrapped inside the Switch and cannot be operated on directly. _SwitchType is an enumeration class with only two elements.

enum_SwitchType {material, adaptive} ---->[Switch member declaration]----final _SwitchType _switchType;
Copy the code

Since they are member variables, they must be initialized inside the class. In general, the place where member variables are initialized is in the constructor. As follows, in the normal construction of the Switch, _switchType is set to _switchType.material.


In general, enumerations are used for sorting purposes. In the Switch#build method, the build logic varies according to the value of _switchType. If it is Material, all platforms use a Material style Switch. Adaptive will use different styles of switches depending on the platform. Material style will be used in Android, Fuchsia, Linux and Windows. We use the Cupertino style in iOS and macOS.

At this point, one might ask, the _SwitchType member is completely encapsulated inside the Switch, so how do you set the adaptive type? A close look at the source code shows that the Switch also has an adaptive construct, where _switchType is set to _switchType.adaptive.


2. Two styles of Switch builds

_buildCupertinoSwitch is used to build Switch components on iOS and macOS platforms when the adaptive mode is set up. It can be seen that it is built internally by CupertinoSwitch. The effect is as follows:


The _buildMaterialSwitch component is used to build a Material style Switch component. As you can see, the _MaterialSwitch component is used to build its interior. At this point we can answer the question: If the Switch is a StatelessWidget, why is it possible to perform sliding animations? Because the _MaterialSwitch component is a StatefulWidget, it can change the component state internally.


3.Switch size determination

As you can see from the above, both styles of Switch get Size from _getSwitchSize. In the following code, you can see that the size is controlled by the MaterialTapTargetSize object. If not specified, the materialTapTargetSize is present by topic. As you can see from debugging, the materialTapTargetSize is present by default in the topic.

Size _getSwitchSize(ThemeData theme) {
  final MaterialTapTargetSize effectiveMaterialTapTargetSize = materialTapTargetSize
    ?? theme.switchTheme.materialTapTargetSize
    ?? theme.materialTapTargetSize;
  switch (effectiveMaterialTapTargetSize) {
    case MaterialTapTargetSize.padded:
      return const Size(_kSwitchWidth, _kSwitchHeight);
    case MaterialTapTargetSize.shrinkWrap:
      return constSize(_kSwitchWidth, _kSwitchHeightCollapsed); }}Copy the code

The following section provides debugging information for both the Padded and shrinkWrap sections, respectively, to clarify the size situation.


At this point, the source code for the Switch component is exhaustive. As you can see, it doesn’t do much as a StatelessWidget, except define a lot of properties and build from other components. That is to say, it plays a role of overall planning and encapsulation of platform differences, and the purpose is to facilitate user use.


4. Timing when the onChanged method is triggered

The onChanged method is triggered by ToggleableStateMixin#_handleTap. Here is the source code for buildToggleable, which listens for click events through GestureDetector.

In _MaterialSwitchState. The build method, can see them through the GestureDetector to monitor the horizontal drag event, which is why the Switch can support the cause of the drag, And the child property is buildToggleable, which is the component above, which supports click events. This is a good example of multi-event listening.


5. Creation and triggering of animation

If you look closely at the slide process, you can see that there is a shift animation and a transparency gradient animation. First, the origin of the animation:


These animators are defined in ToggleableStateMixin. The _MaterialSwitchState is mixed with ToggleableStateMixin.


As with implicit animations, the animation trigger in _MaterialSwitchState executes the didUpdateWidget through the refactoring component. If you know implicit animation, it’s not hard to understand the Switch’s animation-triggering mechanism.

Finally, the drawing is drawn by _SwitchPainter. This drawing board is relatively complex, so I will not expand it here. Those who are interested can study it by themselves.

This is the end of this article. Thanks for watching. See you tomorrow