📜  Flutter – 自定义小部件

📅  最后修改于: 2021-09-23 06:25:49             🧑  作者: Mango

当我们希望应用程序具有自定义外观和感觉时,我们会创建自定义小部件,并且我们知道特定小部件会重复。我们可以在一个新的dart文件中创建自定义小部件,其中包含所有代码并在构造函数中定义我们需要的参数。

有关如何拆分小部件的更多信息,您可以访问Flutter上的文章 – 将应用拆分为小部件

在这里,我们将讨论如何通过将自定义属性应用于小部件并使它们与自己的属性分开来构建简单应用程序的示例。我们将制作一个 BMI 计算器应用程序,它可以根据身高和体重来计算一个人的 BMI。为了展示如何使用自定义小部件,我们还在屏幕上定义了更多的东西。

让我们从清理main 开始。 dart文件为:

Dart
import 'package:custom_widget_demo/home.dart';
import 'package:flutter/material.dart';
  
void main() {
  runApp(MyApp());
}
  
class MyApp extends StatelessWidget {
    
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'GFG Custom Widget Demo',
      theme: ThemeData.dark(),
      home: Home(),
    );
  }
}


Dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
  
class CustomContainer extends StatelessWidget {
  CustomContainer(
      {@required this.child, this.height, this.width, this.onTap, this.color});
  final Function onTap;
  final Widget child;
  final double height;
  final double width;
  final Color color;
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        height: height,
        width: width,
        padding: EdgeInsets.all(12),
        decoration: BoxDecoration(
            color: color, borderRadius: BorderRadius.all(Radius.circular(8))),
        child: child,
      ),
    );
  }
}


Dart
import 'package:flutter/material.dart';
  
class CustomButton extends StatelessWidget {
  CustomButton({this.onTap, this.color = Colors.white30, this.icon});
  final Function onTap;
  final Color color;
  final IconData icon;
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        height: 50,
        width: 50,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(30), color: color),
        child: Icon(icon),
      ),
    );
  }
}


Dart
import 'package:flutter/material.dart';
  
class CustomColumn extends StatelessWidget {
  CustomColumn({this.text, this.child});
  final String text;
  final Widget child;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          text,
          style: TextStyle(fontSize: 18),
        ),
        child
      ],
    );
  }
}


Dart
import 'package:custom_widget_demo/widgets/custom_button.dart';
import 'package:custom_widget_demo/widgets/custom_column.dart';
import 'package:custom_widget_demo/widgets/custom_container.dart';
import 'package:flutter/material.dart';
import 'dart:math';
  
enum g { m, f }


Dart
class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}
  
class _HomeState extends State {
  final activeColor = Colors.white30;
  final inactiveColor = Colors.white12;
  g isSelected;
  int height = 160;
  int weight = 60;
  int age = 25;
  String bmi = '';


Dart
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('GFG Custom Widget'),
    ),
    body: SafeArea(
      child: Container(
        padding: EdgeInsets.all(12),
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  child: CustomContainer(
                    color: isSelected == g.m ? activeColor : inactiveColor,
                    onTap: () {
                      setState(() {
                        isSelected = g.m;
                      });
                    },
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 20.0),
                      child: Text(
                        'FEMALE',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: 18),
                      ),
                    ),
                  ),
                ),
                SizedBox(
                  width: 10,
                ),
                Expanded(
                  child: CustomContainer(
                    color: isSelected == g.f ? activeColor : inactiveColor,
                    onTap: () {
                      setState(() {
                        isSelected = g.f;
                      });
                    },
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 20.0),
                      child: Text(
                        'MALE',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: 18),
                      ),
                    ),
                  ),
                ),
              ],
            ),


Dart
SizedBox(
        height: 10,
      ),
      CustomContainer(
        color: inactiveColor,
        height: 100,
        child: CustomColumn(
          text: 'HEIGHT $height cm',
          child: SliderTheme(
            data: SliderTheme.of(context).copyWith(
              activeTrackColor: Colors.white,
              thumbColor: Colors.green,
              overlayColor: Color(0x2900ff00),
              thumbShape:
                  RoundSliderThumbShape(enabledThumbRadius: 15.0),
              overlayShape:
                  RoundSliderOverlayShape(overlayRadius: 25.0),
            ),
            child: Slider(
              value: height.toDouble(),
              min: 120.0,
              max: 220.0,
              onChanged: (double newValue) {
                setState(() {
                  height = newValue.floor();
                });
              },
            ),
          ),
        ),
      ),


Dart
SizedBox(
        height: 10,
      ),
      Row(
        children: [
          Expanded(
            child: CustomContainer(
              color: inactiveColor,
              child: CustomColumn(
                text: 'WEIGHT $weight',
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CustomButton(
                        onTap: () {
                          setState(() {
                            weight = weight - 1;
                          });
                        },
                        icon: Icons.arrow_downward,
                      ),
                      SizedBox(
                        width: 10,
                      ),
                      CustomButton(
                        onTap: () {
                          setState(() {
                            weight = weight + 1;
                          });
                        },
                        icon: Icons.arrow_upward,
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
          SizedBox(
            width: 10,
          ),
          Expanded(
            child: CustomContainer(
              color: inactiveColor,
              child: CustomColumn(
                text: 'AGE $age',
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CustomButton(
                        onTap: () {
                          setState(() {
                            age = age - 1;
                          });
                        },
                        icon: Icons.arrow_downward,
                      ),
                      SizedBox(
                        width: 10,
                      ),
                      CustomButton(
                        onTap: () {
                          setState(() {
                            age = age + 1;
                          });
                        },
                        icon: Icons.arrow_upward,
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),


Dart
SizedBox(
        height: 10,
      ),
      Row(
        children: [
          Expanded(
            child: CustomContainer(
              onTap: () {
                setState(() {
                  bmi = '';
                });
              },
              width: double.infinity,
              child: Text(
                'CLEAR',
                style: TextStyle(
                  fontSize: 18,
                ),
                textAlign: TextAlign.center,
              ),
              color: activeColor,
            ),
          ),
          SizedBox(
            width: 10,
          ),
          Expanded(
            child: CustomContainer(
              onTap: () {
                double _bmi = weight / pow(height / 100, 2);
 
                setState(() {
                  bmi = _bmi.toStringAsFixed(1);
                });
              },
              width: double.infinity,
              child: Text(
                'GET BMI',
                style: TextStyle(
                  fontSize: 18,
                ),
                textAlign: TextAlign.center,
              ),
              color: Colors.green,
            ),
          ),
        ],
      ),


Dart
SizedBox(
                height: 10,
              ),
              Expanded(
                child: CustomContainer(
                  width: double.infinity,
                  child: Column(
                    children: [
                      SizedBox(
                        height: 20,
                      ),
                      Text(
                        'YOUR BMI IS',
                        style: TextStyle(
                            fontSize: 18, fontWeight: FontWeight.bold),
                      ),
                      SizedBox(
                        height: 20,
                      ),
                      Text(
                        bmi,
                        style: TextStyle(
                            fontSize: 100, fontWeight: FontWeight.bold),
                      )
                    ],
                  ),
                  color: inactiveColor,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}


如您所见,我们定义了 Home Screen 来显示屏幕上的所有组件。现在我们已经清理了主文件,我们将创建一个小部件目录并添加三个文件,即 custom_button。dart,custom_column。 dart和 custom_container。 dart文件到这个目录。我们将在这些文件中为我们的自定义小部件编写代码。

custom_container开始。在 dart文件中,我们将编写以下代码:

Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
  
class CustomContainer extends StatelessWidget {
  CustomContainer(
      {@required this.child, this.height, this.width, this.onTap, this.color});
  final Function onTap;
  final Widget child;
  final double height;
  final double width;
  final Color color;
  
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        height: height,
        width: width,
        padding: EdgeInsets.all(12),
        decoration: BoxDecoration(
            color: color, borderRadius: BorderRadius.all(Radius.circular(8))),
        child: child,
      ),
    );
  }
}

在这个自定义小部件中,我们创建了一个 Stateless CustomContainer小部件,它基本上是一个带有圆角和onTap属性的容器,我们使用GestureDetector小部件实现了。我们已经将小部件的属性定义为接受函数的onTap 、接受小部件的子属性、高度、宽度,最后是颜色属性。我们将在稍后定义的 Home Widget 中看到 this 的用法。

继续我们在 custom_button 中定义的下一个自定义小部件。 dart文件,它只是一个CustomButton小部件,我们定义如下 –

Dart

import 'package:flutter/material.dart';
  
class CustomButton extends StatelessWidget {
  CustomButton({this.onTap, this.color = Colors.white30, this.icon});
  final Function onTap;
  final Color color;
  final IconData icon;
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        height: 50,
        width: 50,
        decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(30), color: color),
        child: Icon(icon),
      ),
    );
  }
}

我们简单地定义了一个无状态小部件,它充当一个简单的按钮,它使用GestureDetector来检测功能,并接受一个图标作为要显示在按钮内的子项。它是一个圆形按钮,具有固定的高度和宽度。如果需要,我们可以更改颜色,但它已经具有半透明白色的自定义颜色。

来到我们的最后一个小部件,它只是我们在 custom_column 中定义的CustomColumn 。 dart文件为:

Dart

import 'package:flutter/material.dart';
  
class CustomColumn extends StatelessWidget {
  CustomColumn({this.text, this.child});
  final String text;
  final Widget child;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          text,
          style: TextStyle(fontSize: 18),
        ),
        child
      ],
    );
  }
}

此小部件接受文本和子项作为其在列中显示的属性。

现在所有组件都已准备就绪,我们将编写将在屏幕上显示所有内容的代码。从现在开始所有的代码都是家的一部分我们在lib 中定义的dart文件

Dart

import 'package:custom_widget_demo/widgets/custom_button.dart';
import 'package:custom_widget_demo/widgets/custom_column.dart';
import 'package:custom_widget_demo/widgets/custom_container.dart';
import 'package:flutter/material.dart';
import 'dart:math';
  
enum g { m, f }

我们导入所有自定义小部件以及材料和数学。dart文件。除此之外,我们还为性别g创建了一个枚举

Dart

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}
  
class _HomeState extends State {
  final activeColor = Colors.white30;
  final inactiveColor = Colors.white12;
  g isSelected;
  int height = 160;
  int weight = 60;
  int age = 25;
  String bmi = '';

Home 小部件是一个有状态的小部件,我们拥有所有属性,如activeColorinactiveColor 、用于性别选择的isSelected 、用于 BMI 计算的身高、体重和儿子。

Dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('GFG Custom Widget'),
    ),
    body: SafeArea(
      child: Container(
        padding: EdgeInsets.all(12),
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  child: CustomContainer(
                    color: isSelected == g.m ? activeColor : inactiveColor,
                    onTap: () {
                      setState(() {
                        isSelected = g.m;
                      });
                    },
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 20.0),
                      child: Text(
                        'FEMALE',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: 18),
                      ),
                    ),
                  ),
                ),
                SizedBox(
                  width: 10,
                ),
                Expanded(
                  child: CustomContainer(
                    color: isSelected == g.f ? activeColor : inactiveColor,
                    onTap: () {
                      setState(() {
                        isSelected = g.f;
                      });
                    },
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 20.0),
                      child: Text(
                        'MALE',
                        textAlign: TextAlign.center,
                        style: TextStyle(fontSize: 18),
                      ),
                    ),
                  ),
                ),
              ],
            ),

对于第一部分,我们定义了一个 Scaffold,在其下定义了一个AppBar ,然后作为子项,我们定义了SafeArea 。现在来到组件,我们定义了一个包含屏幕所有组件的列。该列的第一个子项是一个行小部件,其中包含两个带有onTap函数的CustomContainer小部件,用于选择性别并在我们这样做时更改容器的颜色。

Dart

SizedBox(
        height: 10,
      ),
      CustomContainer(
        color: inactiveColor,
        height: 100,
        child: CustomColumn(
          text: 'HEIGHT $height cm',
          child: SliderTheme(
            data: SliderTheme.of(context).copyWith(
              activeTrackColor: Colors.white,
              thumbColor: Colors.green,
              overlayColor: Color(0x2900ff00),
              thumbShape:
                  RoundSliderThumbShape(enabledThumbRadius: 15.0),
              overlayShape:
                  RoundSliderOverlayShape(overlayRadius: 25.0),
            ),
            child: Slider(
              value: height.toDouble(),
              min: 120.0,
              max: 220.0,
              onChanged: (double newValue) {
                setState(() {
                  height = newValue.floor();
                });
              },
            ),
          ),
        ),
      ),

给一些空间后,我们再次定义与激活的色与接受的文本作为具有高度动态变化的高度与滑块的帮助,我们定义了一个CustomColumn CustomContainer。我们为滑块提供了自定义属性,以根据我们的应用程序进行外观和感觉。

Dart

SizedBox(
        height: 10,
      ),
      Row(
        children: [
          Expanded(
            child: CustomContainer(
              color: inactiveColor,
              child: CustomColumn(
                text: 'WEIGHT $weight',
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CustomButton(
                        onTap: () {
                          setState(() {
                            weight = weight - 1;
                          });
                        },
                        icon: Icons.arrow_downward,
                      ),
                      SizedBox(
                        width: 10,
                      ),
                      CustomButton(
                        onTap: () {
                          setState(() {
                            weight = weight + 1;
                          });
                        },
                        icon: Icons.arrow_upward,
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
          SizedBox(
            width: 10,
          ),
          Expanded(
            child: CustomContainer(
              color: inactiveColor,
              child: CustomColumn(
                text: 'AGE $age',
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      CustomButton(
                        onTap: () {
                          setState(() {
                            age = age - 1;
                          });
                        },
                        icon: Icons.arrow_downward,
                      ),
                      SizedBox(
                        width: 10,
                      ),
                      CustomButton(
                        onTap: () {
                          setState(() {
                            age = age + 1;
                          });
                        },
                        icon: Icons.arrow_upward,
                      )
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),

再次在给出一些空间后,我们定义了一个带有两个CustomContainer的行,它们都接受一个CustomColumn ,文本为 Weight 和 Age。这两个容器在一行中都有两个按钮作为我们定义的CustomColumn的子项。这些按钮的功能是增加或减少体重和年龄的值。

Dart

SizedBox(
        height: 10,
      ),
      Row(
        children: [
          Expanded(
            child: CustomContainer(
              onTap: () {
                setState(() {
                  bmi = '';
                });
              },
              width: double.infinity,
              child: Text(
                'CLEAR',
                style: TextStyle(
                  fontSize: 18,
                ),
                textAlign: TextAlign.center,
              ),
              color: activeColor,
            ),
          ),
          SizedBox(
            width: 10,
          ),
          Expanded(
            child: CustomContainer(
              onTap: () {
                double _bmi = weight / pow(height / 100, 2);
 
                setState(() {
                  bmi = _bmi.toStringAsFixed(1);
                });
              },
              width: double.infinity,
              child: Text(
                'GET BMI',
                style: TextStyle(
                  fontSize: 18,
                ),
                textAlign: TextAlign.center,
              ),
              color: Colors.green,
            ),
          ),
        ],
      ),

在这里,我们在CustomContainer的帮助下定义了两个按钮。第一个用于清除显示的输出,另一个用于在屏幕上显示输出。

Dart

SizedBox(
                height: 10,
              ),
              Expanded(
                child: CustomContainer(
                  width: double.infinity,
                  child: Column(
                    children: [
                      SizedBox(
                        height: 20,
                      ),
                      Text(
                        'YOUR BMI IS',
                        style: TextStyle(
                            fontSize: 18, fontWeight: FontWeight.bold),
                      ),
                      SizedBox(
                        height: 20,
                      ),
                      Text(
                        bmi,
                        style: TextStyle(
                            fontSize: 100, fontWeight: FontWeight.bold),
                      )
                    ],
                  ),
                  color: inactiveColor,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

应用程序的最后一个组件也是一个CustomContainer ,用于在屏幕上显示计算出的 BMI。这完成了我们的应用程序。现在您可以在您的设备上运行该应用程序。

输出: