1. Introduction
Flutter, as one of the most popular technologies, has attracted the attention of many technology enthusiasts due to its excellent performance and advantages of smoothing multiple differences. Some big companies, such as Xianyu, Meituan and Tencent, have already put Flutter into production. Although the ecosystem of Flutter is not yet fully mature, thanks to the support of Google, it is growing fast enough that we can expect the need for Flutter developers to grow in the future.
Whether it’s for technology experimentation or future trends, 9102 years on, as a front-end developer, there seems to be no reason not to try it. It is with this mentality that the author also began to learn Flutter and built a warehouse for practice. All subsequent codes will be hosted on it. Welcome Star to learn Flutter together. This is my series on Flutter:
- Build a beautiful UI with Flutter – Basic Components
- Flutter rolling container component – ListView chapter
- Flutter grid layout – GridView article
- Use custom ICONS in Flutter
In the last article, we looked at some of the most frequently used basic components of Flutter. But in some scenarios, a Flutter can give overflow warnings when the width or height of a component exceeds the edge of the screen. To solve this problem, today we’ll look at one of the most commonly used scrollable components, the ListView component.
2. ListView usage method
The ListView component in Flutter is very similar in functionality to the ScrollView/FlatList component in RN, but there are some differences in how it is used. Let’s take a look at some common uses of the ListView component.
2.1 ListView ()
The first way to use it is to call its default constructor directly to create the list, which is equivalent to the ScrollView component in RN. There is a problem with lists created this way: Long lists or subcomponents that require expensive rendering overhead will still be created by the ListView even if they do not appear on screen, which can be an overhead that can cause performance problems and even lag if used improperly.
However, while this approach may have performance issues, it depends on its different application scenarios. Let’s take a look at its constructors (unusual attributes have been omitted) :
ListView({
Axis scrollDirection = Axis.vertical,
ScrollController controller,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
double cacheExtent,
List<Widget> children = const <Widget>[],
})
Copy the code
scrollDirection
: Scroll direction of the listAxis
thehorizontal
andvertical
, you can see that the default is vertical scrolling;controller
: controller, related to list scrolling, such as listening for list scrolling events;physics
: The physics of scrolling the list to the edge and continuing to drag,Android
withiOS
The effect is different.Android
Will appear a ripple (corresponding toClampingScrollPhysics
), andiOS
There is a rebound effect on elasticity (corresponding toBouncingScrollPhysics
). You can use it if you want to render each effect on different platformsAlwaysScrollableScrollPhysics
, it automatically selects the physics for each platform. If you want to disable the drag effect on the edge, you can use itNeverScrollableScrollPhysics
;shrinkWrap
: This property determines whether the length of the list wraps only the length of its contents. whenListView
Embedded in an infinitely long container component,shrinkWrap
Must be true, otherwiseFlutter
Will give a warning;padding
: List margins;itemExtent
: Child element length. Specifying this value when the length of each item in the list is fixed helps improve the performance of the list (because it helpsListView
Calculates the position of each element before actually rendering the child elements);cacheExtent
: Pre-rendered area length,ListView
It leaves one on either side of its viewable areacacheExtent
Length of area as pre-rendered area (forListView.build
orListView.separated
Constructor to create a list, child elements that are not in the viewable and prerendered areas are not created or destroyed);children
: An array of components that hold child elements.
There are a lot of attributes above, none of which is as solid as an actual example. We can use a ListView component to wrap the bank card, pet card, and circle of friends examples implemented in the previous article:
Code (Address of the file)
class NormalList extends StatelessWidget {
const NormalList({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
returnListView( children: <Widget>[ CreditCard(data: creditCardData), PetCard(data: petCardData), FriendCircle(data: friendCircleData), ], ); }}Copy the code
preview
As you can see, the use of the default constructor is very simple, simply placing the child component in the children array. But the potential problem, as explained earlier, is that the listView. build constructor is better for long lists.
2.2 ListView. The build ()
The ListView default constructor, while simple to use, does not work with long lists. To do this, let’s look at the listView. build constructor:
ListView.builder({
...
int itemCount,
@required IndexedWidgetBuilder itemBuilder,
})
Copy the code
Here we omit some parameters that are not commonly used and duplicate the default ListView constructor. In contrast, we can see that listView. builder has two new parameters:
itemCount
: The number of elements in the list;itemBuilder
: render method for child elements, allowing custom child element components (equivalent torn
In theFlatList
The component’srenderItem
Properties).
Unlike the way the Default ListView constructor specifies child elements with the children argument, ListView.build returns control of rendering child elements to the caller by exposing the unified itemBuilder method. Here we use an example of wechat public account to illustrate the use of listview. build method (public card layout can be seen here, but also a consolidation and review of the basic components) :
Code (Address of the file)
class SubscribeAccountList extends StatelessWidget {
const SubscribeAccountList({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Color(0xFFEFEFEF),
child: ListView.builder(
itemCount: subscribeAccountList.length,
itemBuilder: (context, index) {
returnSubscribeAccountCard(data: subscribeAccountList[index]); },),); }}Copy the code
preview
As you can see from the above code, the two most important parameters for listView. build to create a list are itemCount and itemBuilder. For the example of a list of public numbers, since the layout of each public number message card is regular and the list can be very large, listView.build is a good way to create it.
2.3 ListView. Separated ()
The listView. build constructor can be used to solve most list class requirements, but some list items need to be separated. In this case, another constructor provided by Flutter, ListView.separated list, can be used to create lists. Let’s see how the constructor is different:
ListView.separated({
...
@required IndexedWidgetBuilder separatorBuilder
})
Copy the code
Compared to the ListView.build constructor, you can see that ListView.separated is just one more mandatory parameter. As the name suggests, this is the callback method exposed to the caller’s custom splitter component. Using the alipay friends list as an example (see the layout of friend cards here), let’s see how listview. separated can be used:
Code (Address of the file)
class FriendList extends StatelessWidget {
const FriendList({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: friendListData.length,
itemBuilder: (context, index) {
return FriendCard(data: friendListData[index]);
},
separatorBuilder: (context, index) {
return Divider(
height: . 5,
indent: 75,
color: Color(0xFFDDDDDD)); }); }}Copy the code
preview
The difference is that we implement separatorBuilder, which allows us to define the separators between each child element.
2.4 summary
So far, we have learned a total of ListView, ListView.build and ListView.separated list creation methods, each of which has its own applicable scenarios, so we still need to analyze the requirements on a case-by-case basis.
However, ListView also has a constructor: listView.custom. And listView. build and ListView.separated are ultimately implemented by ListView.custom. This article does not cover this approach, however, because the three constructors mentioned above are generally sufficient to solve the problem (we’ll look at this later when we encounter real problems).
3. ListView advanced method
We’ve covered the basics of ListView usage above, but in a real product, we’ll also encounter the need for drop-down list refreshes and pull-up loads. Next, let’s learn how to implement this kind of interaction in Flutter.
3.1 Drop-down Refresh
It is very easy to implement drop-down refresh of lists in Flutter because Flutter encapsulates a RefreshIndicator component and is very convenient to use. Take a look at the sample code:
class PullDownRefreshList extends StatefulWidget {
const PullDownRefreshList({Key key}) : super(key: key);
@override
_PullDownRefreshListState createState() => _PullDownRefreshListState();
}
class _PullDownRefreshListState extends State<PullDownRefreshList> {
Future onRefresh() {
return Future.delayed(Duration(seconds: 1), () {
Toast.show('Current data is up to date', context);
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: this.onRefresh,
child: ListView.separated(
itemCount: friendListData.length,
itemBuilder: (context, index) {
return FriendCard(data: friendListData[index]);
},
separatorBuilder: (context, index) {
return Divider(
height: . 5,
indent: 75,
color: Color(0xFFDDDDDD)); },),); }}Copy the code
Because the data source for the list is mutable, we chose to inherit the StatefulWidget for this component.
As you can see, the use of refreshIndicators is quite simple, just take our original ListView as its child and implement its onRefresh method. The onRefresh method is actually a callback that notifies the RefreshIndicator when the refresh is complete. In the code above, we simulate a 1s wait as a network request and then pop up a Toast prompt “the data is up to date” (here Toast is installed with the package Toast: ^0.1.3, which is not provided by Flutter native).
Here is a copy of Toutiao’s list UI as an example (the layout of news cards can be seen here). Let’s take a look at the effect:
You can see that everything is working as expected and the RefreshIndicators are very easy to use. However, because the RefreshIndicator component packaged with Flutter is a bit weak in customizability, it is not able to meet the requirements of custom styles found in most apps. Fortunately, RefreshIndicator source code is not very much. After learning animation, we will come back to study how to customize a custom drop-down refresh component.
3.2 Pull-up Loading
In addition to pull-down refreshing, pull-up loading is another list operation that is often encountered. This time, however, Flutter does not provide a ready-made component that can be invoked directly like a drop-down refresh. The pull-up load interaction needs to be done by ourselves. To this end, let’s first make a simple analysis:
- One is required within the component
list
Variable stores the data source for the current list; - One is required within the component
bool
Type ofisLoading
Flag bit to indicate whether the current isLoading
State; - You need to be able to tell if the current list has been scrolled to the bottom, and this takes advantage of what we mentioned earlier
controller
The attributes (ScrollController
The scrolling position of the current list and the maximum scrolling area of the list can be obtained by comparison. - When you start loading data, you need to set the
isLoading
Set totrue
; When the data is loaded, the new data needs to be merged intolist
Variable, and re-willisLoading
Set tofalse
.
Based on the above idea, we can get the following code:
class PullUpLoadMoreList extends StatefulWidget {
const PullUpLoadMoreList({Key key}) : super(key: key);
@override
_PullUpLoadMoreListState createState() => _PullUpLoadMoreListState();
}
class _PullUpLoadMoreListState extends State<PullUpLoadMoreList> {
bool isLoading = false;
ScrollController scrollController = ScrollController();
List<NewsViewModel> list = List.from(newsList);
@override
void initState() {
super.initState();
// Add a listener to the list scroll
this.scrollController.addListener(() {
// Slide bottom key judgment
if(!this.isLoading &&
this.scrollController.position.pixels >= this.scrollController.position.maxScrollExtent
) {
// Start loading data
setState(() {
this.isLoading = true;
this.loadMoreData(); }); }}); }@override
void dispose() {
// Release resources when the component is destroyed (do not forget, otherwise it may cause memory leaks)
super.dispose();
this.scrollController.dispose();
}
Future loadMoreData() {
return Future.delayed(Duration(seconds: 1), () {
setState(() {
this.isLoading = false;
this.list.addAll(newsList);
});
});
}
Widget renderBottom() {
// TODO
}
@override
Widget build(BuildContext context) {
return ListView.separated(
controller: this.scrollController,
itemCount: this.list.length + 1,
separatorBuilder: (context, index) {
return Divider(height: . 5, color: Color(0xFFDDDDDD));
},
itemBuilder: (context, index) {
if (index < this.list.length) {
return NewsCard(data: this.list[index]);
} else {
return this.renderBottom(); }}); }}Copy the code
One thing to notice is that the itemCount value of the list has changed to list.length + 1 because we rendered one more bottom component. When not loading, we can show a pull-up load for more suggestive components; When loading data, we can show an effort to load… The placeholder component of. RenderBottom is implemented as follows:
Widget renderBottom() {
if(this.isLoading) {
return Container(
padding: EdgeInsets.symmetric(vertical: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Trying to load... ',
style: TextStyle(
fontSize: 15,
color: Color(0xFF333333),
),
),
Padding(padding: EdgeInsets.only(left: 10)),
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 3() [() [() [() }else {
return Container(
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
child: Text(
'Pull up to load more',
style: TextStyle(
fontSize: 15,
color: Color(0xFF333333),),),); }}Copy the code
Finally, let’s look at the final implementation:
4. To summarize
First of all, this article introduces the commonly used ListView, ListView.build and ListView.separated three constructors to create lists, and combined with practical examples to explain its different use scenarios. This is followed by an introduction to how Flutter should implement two common interactions, pull-down refresh and pull-up load of list components.
Now that you have a basic understanding of how to use ListView in Flutter, all you need to do is practice using it
All the code of this article is hosted here, you can also follow my Blog, welcome to exchange learning ~