Contact Flutter has been for a while, during the recorded many development small problems, suffer from busy don’t have time to finish, the recent project progress step on track, to take this opportunity to spare some time to record these problems, and to share in the project development experience, and some matters needing attention of multiplatform packaging, hope to help everyone π.
UI Component usage
We’ve got a lot of native components here, from the Material Design family on Android to the Cupertino family on iOS. Judging from my use of the Flutter UI component over the past month or so, I have to say “smells good”. Since I was an Android developer before, I have a deep understanding of some of the “criticisms” of Android. For example, designers often ask us to restore the shadow effect in the design. We usually need to set the shadow color, X/Y offset, blur, etc. However, Android doesn’t have a component that supports all these attributes, so we have to use custom controls to do this. How many people are still using CardView? However, there is no need to worry about this with Flutter, which can be easily implemented with a box component like Container, commonly used in the front-end.
Of course, Flutter is powerful, but the UI components aren’t everything, and the path to cross-platform is bound to be long and fraught, with occasional hiccups.
TextField
-
Component overflow problem after soft keyboard spring up
Because the page does not support scrolling, once TextField is used, it is easy for the soft keyboard to pop up and overwrite some UI components. If not, the following problem will become “common” :
A RenderFlex overflowed by xx pixels on the bottom. Copy the code
A common solution is to get around this by nesting a layer of SingleChildScrollView so that when the soft keyboard pops up, the components underneath are automatically jacked up by the soft keyboard.
-
HintText not centered problem
Many people have encountered this problem. When we set hintText in the InputDecoration of TextField after setting Chinese Locale in the project, we will find that the text is offset by several pixels downwards, which should be a bug of Flutter. How to solve this problem? It’s as simple as setting the textBaseine property, as shown in the code below:
TextFormField( decoration: InputDecoration( prefixIcon: Icon( Icons.lock_outline ), hintText: S.of(context).loginPasswordHint, ), style: TextStyle( /// handle hint text offset problem. textBaseline: TextBaseline.alphabetic ), keyboardType: TextInputType.number, onSaved: (password) {}, ) Copy the code
For details, please refer to github.com/flutter/flu…
-
Focal point
The focus of the input box is mainly reflected in two points:
- When you go to another page and return, the soft keyboard automatically pops up.
- You cannot close the soft keyboard after switching to the numeric keypad on aN iOS phone
Both of these problems can be solved with FocusNode, starting with the following code:
FocusNode _writingFocusNode = FocusNode(); .void _clearTextFieldFocus() { if(_writingFocusNode.hasFocus) { _writingFocusNode.unfocus(); }}Copy the code
As you can easily tell, the above code creates a FocusNode object and declares a method to remove focus. In addition, we need to pass the _writingFocusNode we created to the TextField’s focusNode property. In question 1, we can remove the focus before the page jumps, so that the input box will not automatically pop up after returning from the secondary page. In question 2, we can automatically remove the focus (close the soft keyboard) after the user clicks on the blank area. The following code is for your reference:
Widget _buildInputArea() => Stack( children: <Widget>[ // Close the soft keyboard by clicking on the blank area GestureDetector( onTap: () { _clearTextFieldFocus(); }, child: Container( /// Be careful to set the background color here, otherwise the default transparent color may penetrate and fail to respond to click events color: AppTheme.surfaceColor, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, ), ), Column( children: <Widget>[ ScreenUtils.verticalSpace(32), // account input edit text Padding( padding: EdgeInsets.only(bottom: AutoSize.covert.dpToDp(12)), child: TextField( controller: _accountTextController, decoration: InputDecoration( prefixIcon: Padding( padding: EdgeInsets.all(AutoSize.covert.dpToDp(12)), child: ImageIcon(AssetImage(ImageAssets.ic_login_user_input)), ), hintText: S.of(context).loginAccountHint, ), keyboardType: TextInputType.number, ), ), // password input edit text Padding( padding: EdgeInsets.only(bottom: AutoSize.covert.dpToDp(12)), child: ValueListenableBuilder( valueListenable: obscureTextModel, builder: (context, value, child) => TextField( controller: _passwordTextController, obscureText: value, decoration: InputDecoration( prefixIcon: Padding( padding: EdgeInsets.all(AutoSize.covert.dpToDp(12)), child: ImageIcon(AssetImage(ImageAssets.ic_login_pwd_input)), ), suffixIcon: IconButton( icon: Icon(value ? Icons.visibility_off : Icons.visibility, size: AutoSize.covert.dpToDp(20)), onPressed: () { obscureTextModel.value = ! value; } ), hintText: S.of(context).loginPasswordHint, ), keyboardType: TextInputType.text, ), ), ), ], ), ], );Copy the code
Container
-
Box model features
For those of you who have worked with the front end, you have already experienced the power of the Box model. Therefore, I don’t need to explain the power of Container. It is an almost universal container for margin, padding, aligment, and background docoration properties such as shadows, gradients, rounded corners, etc.
-
Set the background color problem
Container is great, but there are a few things you need to be careful about when using Container. For example, the source code comment says: We can set the box background by color and decoration, but they cannot exist at the same time. If we want to keep the background color and use decoration, we can directly set the color property of the BoxDecoration.
SafeArea
Android has a status bar and a bottom navigation bar, but iOS also has a status bar and a “bottom navigation bar”, so if we need to display small components at the border of the page, we can nest a layer of SafeArea components on the outermost layer, that is, the UI components in the “safe zone”. It will not cause adaptation problems.
Material(
color: AppTheme.surfaceColor,
child: SafeArea(
child: Container(),
),
)
Copy the code
A list of components
The common list components of Flutter include ListView, GridView, PageView, and so on. A complete application must also include these components. We need to pay attention to the following points when using:
-
The Vertical Viewport was given unbounded height problem
As beginners, we’ve all had this problem in the beginning: The Vertical/Horizontal viewport was given unbounded height, which was caused by the fact that we did not specify the height or width for the list components. Generally, the scene was in the Column. List components can be boxed or Expanded to take up as much space as possible:
Column( children:[ ..., Expanded( child: GridView.builder( .... ) ) ) ] ) Copy the code
-
Physics properties
Those of you who have done native development know that in Android, scrolling components that slide to the top or bottom will have a water ripple effect of colorPrimary color, while in iOS, it will have a bouncback effect. If you take the default effect in Android and show it to the designer, the “fruit lovers” will not let you change it to the iOS rebound effect. Fortunately, Flutter has this in mind long ago. The sliding components of Flutter provide the physics property. Just set it to BouncingScrollPhysics to achieve the perfect rebound effect. At the same time, physics and other properties, no longer introduced here, and you can go to check the related documents and source code, mention NeverScrollableScrollPhysics here, namely slide is prohibited. What’s the use of this? Actually very useful, such as nested two sliding component can be one of the physics of the attribute is set to NeverScrollableScrollPhysics, such conflict can be solved simple fast sliding. In addition, this property is good for special situations where you don’t want the user to be able to swipe, but to control the list by button clicking.
Custom pop-ups
Flutter provides us with some built-in custom popovers that we won’t go into here. How to customize popovers? Actually very simple, just need to understand: popover is the page. Take the following effects for example:
The UI page above should not be too difficult to implement, so we are only one step away from Dialog effect: click blank area to close. We can use a Stack component to wrap a translucent mask (such as Container) and share functionality. We can add click events to the translucent mask:
Stack(
children: <Widget>[
// Close the popover by clicking the event in the blank area
GestureDetector(
onTap: () {
// Close the popover
Navigator.maybePop(context);
},
child: Container(
color: AppTheme.dialogBackgroundColor,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
),
),
Container(
child: ...
)
)
Copy the code
Ha ha, is there a sense of enlightenment, so that popover for us is not to write a page so simple π.
InkWell
InkWell, commonly known as the “water ripple” effect on Android, is a type of button that allows you to set the color of the ripple, rounded corners and other properties. Occasionally we might have a water ripple failure, usually because we had a background inside the Child in InkWell that obscured the water ripple effect. How to solve this problem? It’s easy to do, just put Material over InkWell and set color:
Material(
color: Colors.white,
child: InkWell(
borderRadius: AppTheme.buttonRadius, / / the rounded
splashColor: AppTheme.splashColor, // Ripple color
highlightColor: Colors.transparent, // Click state
onTap: () {}, // Click the eventchild: Container( ... ) ,),)Copy the code
Alternatively, we could use the same approach we used to implement custom dialogs, using Stack to wrap the area to be clicked and placing InkWell on top of it:
Stack(
children: <Widget>[
Image(),
Material(
color: Colors.transparent,
child: InkWell(
splashColor: AppTheme.splashColor,
onTap: () {},
),
)
)
],
)
Copy the code
IconButton
Like InkWell above, IconButton may also have a similar click effect problem, and the solution is much the same as above:
Material(
type: MaterialType.circle,
color: Colors.transparent,
clipBehavior: Clip.antiAlias,
child: IconButton(
onPressed: () => Share.share(S.of(context).commonWhoAppShareIconButtonDescription),
icon: Icon(
Icons.share,
size: 22,),),Copy the code
The Theme related
The Theme in Android is also familiar. It can customize the style of certain types of components, which can greatly reduce the amount of repetitive code and work. Theme is also provided in Flutter to allow us to configure and reuse global component styles. Normally, a class of components in the hi-fi blueprint we apply will have a similar style, such as:
The buttons on the two pages above should have the same style, so we can separate them into a Theme, as well as the other components. You can perform the following configuration (for reference only) :
class AppTheme {
AppTheme._();
/// common colors used in theme, text, background or borders
static const Color primaryColor = Color(0xFF92C9AA);
static const Color secondaryColor = Color(0x96A3E4C5);
static const Color colorAccent = Color(0xFFE5F378);
static const Color textPrimaryColor = Color(0xFF2A2A2A);
static const Color textSecondaryColor = Color(0xFF383838);
static const Color textHintColor = Color(0xFFB3B3B3);
static const Color borderColor = Color(0xFFE8E8E8);
static const Color surfaceColor = Color(0xFFF9F9F9);
static const double underlineBorderWidth = 0.6;
static TextTheme textTheme = TextTheme(
headline: headline,
title: title,
body1: body1,
body2: body2,
caption: small
);
// large title
static const headline = TextStyle(
fontWeight: FontWeight.bold,
fontSize: 30,
letterSpacing: 0.27,
color: textPrimaryColor
);
// normal title
static const title = TextStyle(
fontSize: 18,
letterSpacing: 0.18,
color: textPrimaryColor
);
// normal text body1
static TextStyle body1 = TextStyle(
fontSize: 16,
letterSpacing: 0.48,
color: textPrimaryColor
);
// normal text body2
static TextStyle body2 = TextStyle(
fontSize: 14,
letterSpacing: 0.25,
color: textSecondaryColor
);
static TextStyle small = TextStyle(
fontSize: 12,
color: textHintColor
);
// input field border decoration style
static const inputDecorationTheme = InputDecorationTheme(
hintStyle: TextStyle(fontSize: 14),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(width: underlineBorderWidth, color: primaryColor)
),
border: UnderlineInputBorder(
borderSide: BorderSide(width: underlineBorderWidth, color: borderColor)
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(width: underlineBorderWidth, color: borderColor)
),
disabledBorder: UnderlineInputBorder(
borderSide: BorderSide(width: underlineBorderWidth, color: borderColor)
),
);
}
Copy the code
So, we can apply this Theme rule globally in the entry application:
MaterialApp(
title: 'xxx'.// Global topic configuration
theme: ThemeData(
textTheme: AppTheme.textTheme,
primaryColor: AppTheme.primaryColor,
canvasColor: AppTheme.surfaceColor,
scaffoldBackgroundColor: AppTheme.surfaceColor,
inputDecorationTheme: AppTheme.inputDecorationTheme,
appBarTheme: AppTheme.appBarTheme,
/ /...
),
home: xxxPage()
)
Copy the code
This is just a list of some common UI component tips and questions. If you have any other questions, please leave a comment.
Realization of functional requirements
In addition to the use of some OF the UI components in Flutter, there are also many specific business functions required for the application. Common ones include third-party login, sharing, maps, Lottie animation access, third-party font download and loading, etc. At this time, we need to be flexible. We can choose to use some plug-ins and tools, or go to the Github Issue community of Flutter to find the answers while ensuring the smooth progress of the project. Here are some common requirements of Flutter.
The system language of the current device
Most of the time, we need to dynamically select the loaded content according to the language used by the current system. For example, we often need to load user privacy clauses in Chinese or English according to the current language. We can use Localizations to obtain the languageCode of the current language, so as to compare and process:
/// Determine the current language type
_navigateToUrl(Localizations.localeOf(context).languageCode == 'zh'
? Api.PRIVACY_POLICY_ZH_CN
: Api.PRIVACY_POLICY_EN);
Copy the code
Third party login/sharing
This part originally considered to write plug-ins to connect with the native sharing SDK, but in consideration of the time cost, it was temporarily shelved, and found several good plug-ins to realize this part of the function:
-
fluwx
This plugin should be highly integrated in wechat social sharing, payment and other aspects. It is maintained by the OpenFlutter community. No problems have been found so far. Specific configuration details are not explained, it is very detailed documentation, please refer to github.com/OpenFlutter…
In addition, there are many other excellent Flutter programs in their organization that you can also learn about.
-
flutter fake toolkit
This is a series of plug-ins, including wechat, Weibo, QQ, Alipay and many other platforms, let people admire the author’s productivity. At present, no major problems have been found in use. I also hope that the author can invite more partners to maintain and improve the frequency of updates. Here are a few of them:
QQ plug-in: github.com/v7lin/fake_…
Weibo plugin: github.com/v7lin/fake_…
Alipay: github.com/v7lin/fake_…
Lottie animation
I’m sure you’ve heard about The animation tool launched by Airbnb. Lottie supports multiple platforms, using the same JSON animation file to achieve the same animation effect on different platforms. Nowadays complex animation is often used by it, which can effectively reduce the development cost and maintain the high degree of animation reduction. There are also add-ons for Flutter that encapsulate Lottie animation so that we can feel its charm on Flutter as well.
Here, the plug-in I personally use is flutter_lottie, which is relatively stable and supports animation properties and progress operations. The only regret is that it has not been updated for some time: π. Considering the compatibility of iOS, I may write a plug-in myself. The dependency operations in pubspec.yaml are as follows:
# Use Lottie animation in Flutter.
# @link: https://pub.dev/packages/flutter_lottie
flutter_lottie: 0.2. 0
Copy the code
Specific use techniques can refer to its example: github.com/CameronStua…
Attached here is part of the code that controls the animation progress:
int _currentIndex = 0;
LottieController _lottieController;
PageController _pageController = PageController();
// the key frames of animation
final ANIMATION_PROGRESS = [
0.0.0.2083.0.594.0.8333.1
];
// the duration of each animation sections
final ANIMATION_TIMES = [
2300.4500.3500
];
// animation progress controller
Animation<double> animation;
AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = new AnimationController(
duration: Duration(milliseconds: ANIMATION_TIMES[_currentIndex]), vsync: this);
final Animation curve =
new CurvedAnimation(parent: _animationController, curve: Curves.linear);
animation = new Tween(begin: 0.0, end: 1.0).animate(curve);
animation.addListener(() {
_applyAnimation(animation.value);
});
}
// Layout code. Positioned( bottom:0,
child: Container(
width: MediaQuery.of(context).size.width,
// To place the animation component below
height: AutoSize.covert.dpToDp(667),
child: LottieView.fromFile(
filePath: 'assets/anims/user_guide_anim.json',
autoPlay: false,
loop: true,
reverse: true,
onViewCreated: (controller) {
_lottieController = controller;
Future.delayed(Duration(milliseconds: 1), () { _animationController.forward(); }); },),),),// description page view
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
margin: EdgeInsets.only(bottom: 60),
child: PageView(
physics: BouncingScrollPhysics(),
controller: _pageController,
onPageChanged: (index) {
setState(() {
_currentIndex = index;
_animationController.duration = Duration(milliseconds: ANIMATION_TIMES[index]);
});
Future.delayed(Duration(microseconds: 600), () {
_animationController.forward(from: 0);
});
},
children: _buildPageGroup(),
),
),
......
void _applyAnimation(double value) {
var startProgress = ANIMATION_PROGRESS[_currentIndex];
var endProgress = ANIMATION_PROGRESS[_currentIndex + 1];
var progress = startProgress + (endProgress - startProgress) * value;
_lottieController.setAnimationProgress(progress);
}
Copy the code
To briefly explain the above code logic, we mainly use Lottie to realize the switching animation of the user guide page. The guide page is divided into three pictures, so we need to record and save the key frames of the animation and the execution time of each picture. As for the animation control execution right is handed to the upper PageView to slide implementation, each slide through the AnimationController and setState((){}) to control and refresh the execution time and execution scale of each animation. The specific demo effect is as follows:
External font download and loading
If you have been exposed to the development of text editing function, you should know that we usually provide dozens of fonts for users to use, of course, we can not put so many font packages in the project packaging, which will significantly increase the size of the installation package. Our general approach is: when a user clicks to use a font for the first time, we will download it to the local storage of the mobile phone, and then load the font. Later, when the user selects the font again, it can be directly loaded from the local. The problem is that the current example of Flutter only provides us with a way to load fonts from the local Asset directory. Obviously, to achieve this, we need to find our own solution.
Fortunately, Flutter provides a FontLoader tool that converts ByteData into a font package and loads it into the application font library:
/// Registers a font asset to be loaded by this font loader.
///
/// The [bytes] argument specifies the actual font asset bytes. Currently,
/// only TrueType (TTF) fonts are supported.
void addFont(Future<ByteData> bytes) {
if (_loaded)
throw StateError('FontLoader is already loaded'); _fontFutures.add(bytes.then( (ByteData data) => Uint8List.view(data.buffer, data.offsetInBytes, data.lengthInBytes) )); }.../// Loads this font loader's font [family] and all of its associated assets
/// into the Flutter engine, making the font available to the current
/// application.
///
/// This method should only be called once per font loader. Attempts to
/// load fonts from the same loader more than once will cause a [StateError]
/// to be thrown.
///
/// The returned future will complete with an error if any of the font asset
/// futures yield an error.
Future<void> load() async {
if (_loaded)
throw StateError('FontLoader is already loaded');
_loaded = true;
final ε―θΏδ»£<Future<void>> loadFutures = _fontFutures.map(
(Future<Uint8List> f) => f.then<void>(
(Uint8List list) => loadFont(list, family)
)
);
return Future.wait(loadFutures.toList());
}
Copy the code
In this case, the solution is “easy” : just download the font locally and store it as a file, then convert the font file to ByteData for FontLoader to load. Here are some of the key code simplifications:
/// Load external fonts
Future loadFontFile(LetterFont font) async {
// load font file
var fontLoader = FontLoader(font.fontName);
fontLoader.addFont(await fetchFont(font));
await fontLoader.load();
}
/// Download font resources from the Internet
Future<ByteData> fetchFont(LetterFont font) async {
final response = await https.get(
font.fontUrl);
if (response.statusCode == 200) {
// You can also do the logic to save locally
return ByteData.view(response.bodyBytes.buffer);
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load font'); }}Copy the code
Package and shelf related
Packaging also has some details need to pay attention to, here is to talk about Android and iOS development environment configuration and packaging differences and list some common problems, other problems vary from person to person, also vary from version to version, will not be taken out separately.
In Android
-
The development tools
Android Studio3.6 Stable
-
Code compilation environment
Kotlin + AndroidX
Currently, the Flutter creation project has two options checked by default
-
Version Number Configuration
Configure flutterVersionCode and flutterVersionName in Android /app/build.gradle.
Note: If version is configured in pubspec.yaml, the specific packaged version of Flutter will actually be built according to the version of Pubspec. yaml.
-
The network configuration
Currently, Android officially does not recommend HTTP request format. HTTPS is recommended. Therefore, if HTTP request format is used in the project, you need to add network configuration. Create an XML file named network_security_config under android/app/ SRC /main/res, and copy the following code into it:
<network-security-config> <base-config cleartextTrafficPermitted="true"/> </network-security-config> Copy the code
Then set the networkSecurityConfig property in the Androidmanifest.xml file:
<application android:name="io.flutter.app.FlutterApplication" android:label="Timeory" android:icon="@mipmap/ic_launcher" tools:replace="android:name" android:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/network_security_config" tools:ignore="GoogleAppIndexingWarning">.</application> Copy the code
-
Access configuration
Permission application is usually used in our projects, and many flutter plug-ins require us to configure permissions ourselves. We may need to add the following common permissions to the androidmanifest. XML file (just an example) :
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> Copy the code
On Android6.0 and above, we also need to apply permissions dynamically in our code. There are many excellent permission application plug-ins for Flutter. This is not a problem on iOS, but Android may have strange problems on different models due to its fragmentation. Some models of redmi may fail to apply for location permission using the permission_hanlder plug-in.
-
Configuration of the Logo
You need to add and configure a Logo in the HDPI, MDPI, XHDPI, XXHDPI, or XXXHDPI format on android/app/ SRC /main/res. In addition, Android8.0 and above should have a rounded logo, otherwise the default Android robot logo will appear on some advanced models.
For details, please refer to the article: blog.csdn.net/guolin_blog…
-
packaging
We usually by flutter build apk to package, generated by the installation in the build/app/outputs/apk/release directory, such package of typing is compared commonly big, Because it includes arm64-V8A, ArmeabI-V7A and X86_64 CPU architecture packages. You can optionally package the CPU architecture for a specific model as required. Run the following command:
flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi Copy the code
After execution, apK packages in three formats are generated in the release directory.
In addition, you can choose some APK volume optimization scheme, please refer to:
My.oschina.net/u/1464083/b…
www.jianshu.com/p/555c948e5…
In the iOS
As I have been working on Android development before, I have not contacted iOS, so I still encounter many problems in packaging to iOS platform.
-
Development tools:
Xcode11.3.1 Stable (Packaging environment) + Visual Studio Code 1.42.1
-
Code compilation environment: Swift + Objective-C (Currently, Swift is selected by default to create a Flutter project. Because the configuration of Flutter has not been updated when the project is started, oc is used by some plug-ins in the project). We hope that the mainstream Swift will be gradually replaced in the future.
-
Version Number configuration:
Just configure Runner -> targets -> General -> Identity in Xcode.
-
The network configuration
In iOS, we are also officially restricted to use HTTPS requests. If we need to temporarily use HTTP requests to test, we can do the following configuration:
Runner -> targets -> General -> Info add App Transport Security Settings and Allow Arbitrary Loads And set the value to YES.
-
Configuration of the Logo
For logo configuration in iOS, you only need to find the following entry:
Click β‘οΈ to enter the logo resource directory. The default one is the official logo of Flutter. You only need to replace the resource according to the specific logo size.
-
Internationalization Language Configuration
If the project supports international languages, there is no need to configure it in Android, and add the attribute to info.plist to increase resource can be mixed in iOS and set the value to YES. Otherwise, the APP may actually display English after running.
-
Packaging related
Always use a stable version of Xcode when packaging, don’t use it
beta
Version, otherwise the following problems may occur:
This is a brief summary of my recent Flutter development process. It would be nice if I could help you π. I have just come into contact with Flutter. If you find this explanation, please point it out. Thank you for reading.