FlutterCalendarWidget
A calendar control on Flutter can be customized to look as you want.
Calendar implementation is not difficult, mainly the previous implementation of the performance is a little poor, under the optimization. This article mainly records the process of optimizing the calendar project.
screenshots
The project address
Calendar supports Web preview: Click here for preview
The project structure
Below is the overall structure of the project, nothing special, just some data needed to write a calendar.
- Constants: Stores some constants
- Model: Entity class DateModel for custom calendars
- Style: Customize some styles
- Utils: utility class
- Widgets: Widgets that display monthly and weekly views.
- Calendar_provider: share status classes created using the provider
- Configuration: Defines configuration information
- Controller: A calendar controller that can operate or configure a calendar
- Flutter_custom_calendar: calendar Widget
The Widget hierarchy
- The whole is a Column with a custom weekbar fixed at the top.
- Below is an AnimatedContainer that animates height changes between weekly and monthly views.
- IndexedStack holds the widgets for the weekly and monthly views. The same thing with the Stack is that the layout is stacked, and the difference with the Stack is that the children will be drawn when the Stack is displayed. The IndexedStack draws only the child specified as index. So here we use index toggle to show the week view and the month view. Specific articles:Exploring Stack and IndexedStack in Flutter
Optimize the records
Optimized performance
Before optimization, true TM is a piece of red.
After various optimizations, the final performance is shown in the figures below. Overall performance is good, switching between pages is not so hard. Most of the time consuming of each frame is less than 16ms, which meets the expectation and can be further optimized.
- Figure 1: AndroidStudio’s toolbar, Flutter Performance, allows you to view Performance data
- Figure 2: Turn on the Show Performance Overlay switch to display a floating window of Performance statistics on the app.
- Figure 3: Devtools provided by Flutter allows you to view performance data in a browser. (Please use Baidu for details)
Code optimization
Before, all the configuration information of the calendar was put in the controller, and there would be a lot of configurable information of the calendar, which felt a bit chaotic. This time, pull all the configuration information into a CalendarConfiguration object. And the CalendarConfiguration object is stored in the top-level Provider status class, allowing the child widgets to get the configuration information directly.
Introduce the Provider status management framework
The introduction of the provider state management framework, on the one hand, can avoid various nested data transfer, on the other hand, to achieve local refresh to improve performance.
On the use of the provider: Flutter | state management guide – the provider
- Code before introducing the provider
All sorts of data and state have to be passed layer by layer down to the child widgets, which is kind of gross.
- Code after introducing the provider
Create a status class CalendarProvider to share the data and status of the calendar so that child widgets can retrieve the data directly from the CalendarProvider. The code looks a little better than before, but it still needs to be optimized.
Use compute to load the data
Related articles: Flutter asynchronous Programming: Future, Isolate, and event loops
-
Dart is a single-threaded language, and Isolate is the thread in Dart.
-
The default Flutter code runs within the same ISOLATE. Each Isolate has its own Event loop, EventLoop, and two queues (MicroTask and Event). As shown in the figure below, the MicroTask queue takes precedence over the Event queue. When there is no MicroTask Event, the first item in the Event queue will be executed.
-
Note: Future operations are also handled through the Event queue. Future and Async are not executed in parallel, but follow the order in which events are processed in an event loop. Consider using Isolate if heavy processing may take some time to complete and may affect application performance. So, you can leverage multiple ISOLates to achieve true parallel processing.
-
Flutter provides a method of compute that we can use to create an ISOLATE directly. The Compute function encapsulates the creation of the ISOLATE and the underlying messaging so that we don’t have to worry about the underlying implementation, just the functional implementation. Use Compute to write computers
-
Use scenarios of Isolate
So some time-consuming operations can be performed in parallel on another ISOLATE.
- JSON decoding: Decoding JSON (HttpRequest response) may take some time => use compute
- Encryption: Encryption may take a long time. => Isolate
- Image processing: Processing images (e.g. clipping) does take some time to complete => Isolate
-
Application in calendar items
Let’s say I’m going to display the month view, and I need to load the data and get 42 items. It is necessary to calculate 42 items corresponding to this month, the DateModel corresponding to each item, and all kinds of required information. This process is time-consuming, so I use Compute to put this operation on another ISOLATE.
Lazy loading of data using getters
A variable in Dart has a setter and getter method by default, and the getter method is used to get the value of the variable.
If the value of the variable is required a series of calculations (which may be time-consuming) to get the result. If the result had been evaluated and assigned to the variable when the object was created, it would have taken some extra time. It might be computed, but it’s not going to be used later, so it’s not necessary.
So you can use something like the following to implement lazy loading. When the getter is called, the value is checked to see if it is empty, and then the data is computed.
Each DateModel contains a bunch of attributes that I need to calculate, such as lunar calendar, traditional festivals, 24 solar terms. These calculations are complicated and time consuming, so I put some of the properties in the corresponding getter method so that I don’t have to calculate all the properties when I first load the data.
Click item to refresh the calendar
After clicking item, call setState to refresh the state of the calendar. As you can imagine, the performance will be worse.
One way to improve Build efficiency is to reduce the starting point for traversal. If you call setState directly from the calendar Widget, you will need to refresh the entire calendar Widget tree during rebuild.
So the idea here is to split the calendar item into a StatefulWidget so that if you call the setState method on a calendar item, only that item will be refreshed, reducing the refresh scope to the item level.
- There is a function called refreshItem, which can be called either by itself or externally.
Note: The setState method is invoked only after checking mounted. Because it is possible that the node has already been removed from the element tree, setState will return an error. In normal development, pay attention to this problem as well. For example, when acquiring network data, if the current page is disposed, an error will be reported if setState is called directly after the interface data is returned.
Exception caught by gesture
The following assertion was thrown while handling a gesture:
setState() called after dispose()
Copy the code
- Multi-select mode: Refresh only the current item.
For each item, the GestureDetector listens for the click event of the calendar item, and then calls the setState method to refresh itself.
- Radio mode: Refresh only two items, the current item and the previous item.
Radio mode, for example, if we select an item, we need to refresh that item and refresh the Widget of the last selected item. So we define a lastClickItemState variable to hold the State object of the last clicked item, and call the refreshItem method of lastClickItemState each time the item is clicked.
ItemContainerState lastClickItemState; // Last clicked itemCopy the code
Using AutomaticKeepAliveClientMixin, make the PageView save the internal state of the item
This believes that engaged in PageView friends, want to switch to other pages, need to achieve the page to maintain the state. AutomaticKeepAliveClientMixin the Mixin is Flutter to keep the page setup. Which page needs to keep the page state, in this page to mix.
Only two components can hold the page state: PageView and IndexedStack.
So here using AutomaticKeepAliveClientMixin switch in time, will maintain a month, or next month on the page.
Join AutomaticKeepAliveClientMixin mix, and rewrite wantKeepAlive method.
Use IndexStack to switch functionality:
AnimatedContainer+IndexStack to switch between weekly and monthly views. Do not know if there is any other better implementation plan, but also ask big guys to give advice.
- One is animation. The switch between the two views, the height change, is animated and can be implemented using a ready-made AnimatedContainer. Much more convenient than using AnimationController yourself.
- What widgets are used to hold the weekly view and monthly view widgets, now using IndexStack?
Starting with the Stack for the weekly view and monthly view widgets, this is also possible. IndexStack = IndexStack = IndexStack = IndexStack = IndexStack
The same thing with the Stack is that the layout is stacked, and the difference with the Stack is that the children will be drawn when the Stack is displayed. The IndexedStack draws only the child specified as index. So here we use index toggle to show the week view and the month view.
The RenderStack corresponds to the RenderStack, and as you can see, the paint method ends up drawing all of the children.
The IndexedStack inherits the Stack, and the corresponding renderObject is the RenderIndexedStack, which inherits the RenderStack. Overridden the paintStack method to draw only the child of the specified index.
conclusion
Write an open source library, although write very spicy chicken, but still have quite a lot of harvest. Through this project, the method of Flutter performance optimization was put into practice and some principles of Flutter were further understood.