├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── android
└── app
│ └── src
│ └── main
│ └── java
│ └── io
│ └── flutter
│ └── plugins
│ └── GeneratedPluginRegistrant.java
├── example
└── example.dart
├── images
├── icon_arrow.png
├── icon_cry.png
└── refresh.png
├── lib
└── pulltorefresh_flutter.dart
├── pubspec.lock
├── pubspec.yaml
├── pulltorefresh.iml
└── test
└── pulltorefresh_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 | ios/.generated/
9 | ios/Flutter/Generated.xcconfig
10 | ios/Runner/GeneratedPluginRegistrant.*
11 |
12 | build/
13 |
14 | .flutter-plugins
15 | .idea
16 | .metadata
17 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.1.9] - 2018.12.06
2 |
3 | * trigger pull to refresh by method call
4 |
5 | ## [0.1.8] - 2018.11.30
6 |
7 | * Fix some bug
8 |
9 | ## [0.1.7] - 2018.11.28
10 |
11 | * Fix some issues
12 |
13 | ## [0.1.6] - 2018.10.24
14 |
15 | * RefreshBox adds the status of data loading success
16 |
17 | ## [0.1.4] - 2018.10.23
18 |
19 | * Highly customizable
20 |
21 | ## [0.1.3] - 2018.10.18 PM
22 |
23 | * Pull and push are separately controllable
24 |
25 | ## [0.1.2] - 2018.10.18
26 |
27 | * Compatibility with ios
28 |
29 | ## [0.1.1] - 2018.09.16.
30 |
31 | * Upgrade version to above 0.1
32 | * Specification sample code
33 |
34 | ## [0.0.3] - 2018.09.11.
35 |
36 | * Initial Optimize code quality,And perfect documentation
37 |
38 | ## [0.0.1] - 2018.08.10.
39 |
40 | * Initial Open Source release
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pulltorefresh
2 | [](https://pub.dartlang.org/packages/pulltorefresh_flutter)
3 |
4 | 上下拉控件,理论上适配所有可滑动View, Android IOS 双平台通用. 上下拉分别可控, 可单独使用上拉或下拉; 支持上下拉头完全自定义,现已支持是否加载成功的状态通知,支持通过方法调用触发下拉刷新
5 |
6 | A control that make the ScrollView to be pull to refresh and push to load data. Theoretically compatible with all Scrollable Widgets. RefreshBox now supports full customization
7 | , Pull and push are separately controllable,can Compatible with Android and IOS, Support for notifications the status of load data whether succeed now,Support for triggering
8 | pull-down refresh by method call
9 |
10 | 如果使用当中有什么问题,请在github里提出个issues,thankYou
11 |
12 | If there is any problem with the use, please submit an issue in github,thankYou
13 |
14 | #### My organization's github:[https://github.com/OpenFlutter](https://github.com/OpenFlutter)
15 |
16 | #### Contact Me :OpenFlutter QQ群 892398530
17 |
18 |
19 |
20 | ## Usage
21 | Add this to your package's pubspec.yaml file:
22 |
23 | dependencies:
24 | pulltorefresh_flutter: "^0.1.9"
25 |
26 | 如果使用本项目默认图片,请下载 [https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/master/images/refresh.png](https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/master/images/refresh.png) 到你的images文件夹下,并在Pubspec.yaml添加如下配置
27 |
28 | If you want to use the default refresh image of this project (the rotated image), please download [https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/master/images/refresh.png](https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/master/images/refresh.png) to your images folder, and Pubspec.yaml is declared as follows.
29 |
30 | assets:
31 | - images/refresh.png
32 |
33 | 如果使用其他图片,请将图片放在Images文件夹下,在Pubspec.yaml文件中声明,并在PullAndPush对象中添加refreshIconPath属性,将图片路径设置给该属性
34 |
35 | If you want to use other images, put the image in the Images folder, declare it in the Pubspec.yaml file, add the property "refreshIconPath" in the PullAndPush object,and set the image path to this property
36 |
37 |
38 | Add it to your dart file:
39 |
40 | import 'package:pulltorefresh_flutter/pulltorefresh_flutter.dart';
41 |
42 | ## Example
43 | class PullAndPushTest extends StatefulWidget{
44 |
45 | @override
46 | State createState() {
47 | return new PullAndPushTestState();
48 | }
49 | }
50 |
51 | ///PullAndPush可以使用默认的样式,在此样式的基础上可以使用default**系列的属性改变显示效果,也可以自定义RefreshBox的样式(footerRefreshBox or headerRefreshBox),也就是说可以定义其中一个,另一个用默认的,也可以全部自定义
52 | ///isPullEnable;isPushEnable属性可以控制RefreshBox 是否可用,无论是自定义的还是默认的
53 | ///PullAndPush can use the default style,Based on this style, you can use the properties of the default** series to change the display,
54 | ///You can also customize the style of the RefreshBox (footerRefreshBox or headerRefreshBox), which means you can define one of them, and the other can be customized by default or all.
55 | class PullAndPushTestState extends State with TickerProviderStateMixin{
56 | List addStrs=["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"];
57 | List strs=["1","2","3","4","5","6","7","8","9","0"];
58 | ScrollController controller=new ScrollController();
59 | //For compatibility with ios ,must use RefreshAlwaysScrollPhysics ;为了兼容ios 必须使用RefreshAlwaysScrollPhysics
60 | ScrollPhysics scrollPhysics=new RefreshAlwaysScrollPhysics();
61 | //使用系统的请求
62 | var httpClient = new HttpClient();
63 | var url = "https://github.com/";
64 | var _result="";
65 | String customRefreshBoxIconPath="images/icon_arrow.png";
66 | AnimationController customBoxWaitAnimation;
67 | int rotationAngle=0;
68 | String customHeaderTipText="快尼玛给老子松手!";
69 | String defaultRefreshBoxTipText="快尼玛给老子松手!";
70 | ///button等其他方式,通过方法调用触发下拉刷新
71 | TriggerPullController triggerPullController=new TriggerPullController();
72 |
73 |
74 | @override
75 | void initState() {
76 | super.initState();
77 | //这个是刷新时控件旋转的动画,用来使刷新的Icon动起来
78 | customBoxWaitAnimation=new AnimationController(duration: const Duration(milliseconds: 1000*100), vsync: this);
79 | //第一次layout后会被调用
80 | //WidgetsBinding.instance.addPostFrameCallback((context){
81 | // triggerPullController.triggerPull();
82 | //});
83 | }
84 |
85 |
86 | @override
87 | Widget build(BuildContext context) {
88 | return new Scaffold(
89 | appBar: new AppBar(
90 | title: new Text("上下拉刷新"),
91 | ),
92 | body: new PullAndPush(
93 | //如果你headerRefreshBox和footerRefreshBox全都自定义了,则default**系列的属性均无效,假如有一个RefreshBox是用默认的(在该RefreshBox Enable的情况下)则default**系列的属性均有效
94 | //If your headerRefreshBox and footerRefreshBox are all customizable,then the default** attributes of the series are invalid,
95 | // If there is a RefreshBox is the default(In the case of the RefreshBox Enable)then the default** attributes of the series are valid
96 | defaultRefreshBoxTipText: defaultRefreshBoxTipText,
97 | headerRefreshBox: _getCustomHeaderBox(),
98 | triggerPullController:triggerPullController,
99 |
100 | //你也可以自定义底部的刷新栏;you can customize the bottom refresh box
101 | animationStateChangedCallback:(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
102 | _handleStateCallback( animationStates, refreshBoxDirectionStatus);
103 | },
104 | listView: new ListView.builder(
105 | //ListView的Item
106 | itemCount: strs.length,//+2,
107 | controller: controller,
108 | physics: scrollPhysics,
109 | itemBuilder: (BuildContext context,int index){
110 | return new Container(
111 | height: 35.0,
112 | child: new Center(
113 | child: new Text(strs[index],style: new TextStyle(fontSize: 18.0),),
114 | ),
115 | );
116 | }
117 | ),
118 | loadData: (isPullDown) async{
119 | await _loadData(isPullDown);
120 | },
121 | scrollPhysicsChanged: (ScrollPhysics physics) {
122 | //这个不用改,照抄即可;This does not need to change,only copy it
123 | setState(() {
124 | scrollPhysics=physics;
125 | });
126 | },
127 | )
128 | );
129 | }
130 |
131 |
132 |
133 | Widget _getCustomHeaderBox(){
134 | return new Container(
135 | color: Colors.grey,
136 | child: new Row(
137 | mainAxisAlignment: MainAxisAlignment.center,
138 | children: [
139 | new Align(
140 | alignment: Alignment.centerLeft,
141 | child: new RotatedBox(
142 | quarterTurns: rotationAngle,
143 | child: new RotationTransition( //布局中加载时动画的weight
144 | child: new Image.asset(
145 | customRefreshBoxIconPath,
146 | height: 45.0,
147 | width: 45.0,
148 | fit:BoxFit.cover,
149 | ),
150 | turns: new Tween(
151 | begin: 100.0,
152 | end: 0.0
153 | )
154 | .animate(customBoxWaitAnimation)
155 | ..addStatusListener((animationStatus) {
156 | if (animationStatus == AnimationStatus.completed) {
157 | customBoxWaitAnimation.repeat();
158 | }
159 | }
160 | ),
161 | ),
162 | ),
163 | ),
164 |
165 | new Align(
166 | alignment: Alignment.centerRight,
167 | child:new ClipRect(
168 | child:new Text(customHeaderTipText,style: new TextStyle(fontSize: 18.0,color: Color(0xffe6e6e6)),),
169 | ),
170 | ),
171 | ],
172 | )
173 | );
174 | }
175 |
176 | void _handleStateCallback(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
177 | switch (animationStates){
178 | //RefreshBox高度达到50,上下拉刷新可用;RefreshBox height reached 50,the function of load data is available
179 | case AnimationStates.DragAndRefreshEnabled:
180 | setState(() {
181 | //3.141592653589793是弧度,角度为180度,旋转180度;3.141592653589793 is radians,angle is 180⁰,Rotate 180⁰
182 | rotationAngle=2;
183 | });
184 | break;
185 |
186 | //开始加载数据时;When loading data starts
187 | case AnimationStates.StartLoadData:
188 | setState(() {
189 | customRefreshBoxIconPath="images/refresh.png";
190 | customHeaderTipText="正尼玛在拼命加载.....";
191 | });
192 | customBoxWaitAnimation.forward();
193 | break;
194 |
195 | //加载完数据时;RefreshBox会留在屏幕2秒,并不马上消失,这里可以提示用户加载成功或者失败
196 | // After loading the data,RefreshBox will stay on the screen for 2 seconds, not disappearing immediately,Here you can prompt the user to load successfully or fail.
197 | case AnimationStates.LoadDataEnd:
198 | customBoxWaitAnimation.reset();
199 | setState(() {
200 | rotationAngle = 0;
201 | if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PULL) {
202 | customRefreshBoxIconPath = "images/icon_cry.png";
203 | customHeaderTipText = "加载失败!请重试";
204 | }else if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PUSH){
205 | defaultRefreshBoxTipText="可提示用户加载成功Or失败";
206 | }
207 | });
208 | break;
209 |
210 | //RefreshBox已经消失,并且闲置;RefreshBox has disappeared and is idle
211 | case AnimationStates.RefreshBoxIdle:
212 | setState(() {
213 | rotationAngle=0;
214 | defaultRefreshBoxTipText=customHeaderTipText="快尼玛给老子松手!";
215 | customRefreshBoxIconPath="images/icon_arrow.png";
216 | });
217 | break;
218 | }
219 | }
220 |
221 | Future _loadData(bool isPullDown) async{
222 | try {
223 | var request = await httpClient.getUrl(Uri.parse(url));
224 | var response = await request.close();
225 | if (response.statusCode == HttpStatus.ok) {
226 | _result = await response.transform(utf8.decoder).join();
227 | setState(() {
228 | //拿到数据后,对数据进行梳理
229 | if(isPullDown){
230 | strs.clear();
231 | strs.addAll(addStrs);
232 | }else{
233 | strs.addAll(addStrs);
234 | }
235 | });
236 | } else {
237 | _result = 'error code : ${response.statusCode}';
238 | }
239 | } catch (exception) {
240 | _result = '网络异常';
241 | }
242 | print(_result);
243 | }
244 | }
245 |
246 | ## Notice
247 | 1. 有时ListView的Item太少而不能铺满屏幕,导致ListView 不可Scroll,PullToRefresh也不可使用,同时为了兼容IOS,所以初始化ScrollPhysics必须是RefreshAlwaysScrollPhysics。
248 | 2. 使用默认的样式可不用写headerRefreshBox和footerRefreshBox属性,若想自定义其中一个,则另一个为默认(默认状态下可用default**系列属性改变其外观:文字颜色等)
249 | 3. isPullEnable、isPushEnable属性可以控制RefreshBox 是否可用,无论是自定义的还是默认的
250 |
251 | 1. Sometimes the ListView has too few items to cover the screen, making the ListView not scrollable, PullToRefresh is not available , and for Compatible with IOS,so ScrollPhysics must be RefreshAlwaysScrollPhysics.
252 | 2. Use the default style without writing the headerRefreshBox and footerRefreshBox properties. If you want to customize one of them, the other is the default (the “ default** ” property can be changed by default in the default state: text color, etc.)
253 | 3. The isPullEnable and isPushEnable properties can control whether the RefreshBox is available, whether it is custom or default.
254 |
255 | ## Properties
256 |
257 | ##### triggerPullController
258 | 可通过此对象的方法调用触发下拉刷新,triggering pull-down refresh by method call
259 |
260 | ##### loadData
261 | 加载数据的回调;callback of data loading
262 |
263 | ##### scrollPhysicsChanged
264 | ListView的scrollPhysics发生改变时回调,通过SetState改变ListView的Physics, 这个回调直接抄demo里面的;
265 | the callback of when ListView’s scrollPhysics changed,please copy the callback from the demo
266 |
267 | ##### listView
268 | 用于上下拉的滑动控件;the widget for PullToRefresh
269 |
270 | ##### isShowLeadingGlow
271 | 在拉到顶部时,是否显示光晕效果,默认不显示;Whether to display the glow effect when pull,default is false
272 |
273 | ##### isShowTrailingGlow
274 | 在拉到底部时,是否显示光晕效果,默认不显示;Whether to display the glow effect when push,default is false
275 |
276 | ##### backgroundColor
277 | 自定义上下拉框的颜色; Customize the backgroundColor of the PullToRefresh box
278 |
279 | ##### refreshIconPath
280 | 自定义上下拉框中旋转的图片的路径; Customize the path of the rotated image in the PullToRefresh box
281 |
282 | ##### tipText
283 | 自定义上下拉框中的提示文字; Customize the text in the PullToRefresh box
284 |
285 | ##### textColor
286 | 自定义上下拉框中文字的颜色;Customize the color of the text in the PullToRefresh box
287 |
288 | ##### isPullEnable
289 | 是否使用下拉刷新,默认启用;Whether to use pull refresh, enabled by default;
290 |
291 | ##### isPushEnable
292 | 是否启用上拉加载,默认启用;Whether to enable push loading, enabled by default
293 |
294 | ##### glowColor
295 | 自定义光晕的颜色,默认为蓝色;Custom glow color, default is blue
296 |
297 | #### headerRefreshBox and footerRefreshBox
298 | 自定义头部Box和底部Box,Custom headerRefreshBox or footerRefreshBox
299 |
300 | #### animationStateChangedCallback
301 | 动画各种状态下的回调;Animation callbacks in various states
302 | DragAndRefreshEnabled,
303 | //RefreshBox高度达到50,上下拉刷新可用;RefreshBox height reached 50,the function of load data is available
304 |
305 | StartLoadData,
306 | //开始加载数据时;When loading data starts
307 |
308 | LoadDataEnd,
309 | //加载完数据时;RefreshBox会留在屏幕2秒,并不马上消失,After loading the data,RefreshBox will stay on the screen for 2 seconds, not disappearing immediately
310 |
311 | RefreshBoxIdle
312 | //RefreshBox已经消失,并且闲置;RefreshBox has disappeared and is idle
313 |
314 | ## LICENSE
315 | MIT License
316 |
317 | Copyright (c) 2018 baoolong
318 |
319 | Permission is hereby granted, free of charge, to any person obtaining a copy
320 | of this software and associated documentation files (the "Software"), to deal
321 | in the Software without restriction, including without limitation the rights
322 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
323 | copies of the Software, and to permit persons to whom the Software is
324 | furnished to do so, subject to the following conditions:
325 |
326 | The above copyright notice and this permission notice shall be included in all
327 | copies or substantial portions of the Software.
328 |
329 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
330 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
331 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
332 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
333 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
334 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
335 | SOFTWARE.
336 |
--------------------------------------------------------------------------------
/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java:
--------------------------------------------------------------------------------
1 | package io.flutter.plugins;
2 |
3 | import io.flutter.plugin.common.PluginRegistry;
4 |
5 | /**
6 | * Generated file. Do not edit.
7 | */
8 | public final class GeneratedPluginRegistrant {
9 | public static void registerWith(PluginRegistry registry) {
10 | if (alreadyRegisteredWith(registry)) {
11 | return;
12 | }
13 | }
14 |
15 | private static boolean alreadyRegisteredWith(PluginRegistry registry) {
16 | final String key = GeneratedPluginRegistrant.class.getCanonicalName();
17 | if (registry.hasPlugin(key)) {
18 | return true;
19 | }
20 | registry.registrarFor(key);
21 | return false;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/example.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 | import 'dart:ui';
4 |
5 | import 'package:flutter/material.dart';
6 | import 'package:flutter/rendering.dart';
7 | import 'package:pulltorefresh_flutter/pulltorefresh_flutter.dart';
8 |
9 |
10 | class PullAndPushTest extends StatefulWidget{
11 |
12 | @override
13 | State createState() {
14 | return new PullAndPushTestState();
15 | }
16 | }
17 |
18 | ///PullAndPush可以使用默认的样式,在此样式的基础上可以使用default**系列的属性改变显示效果,也可以自定义RefreshBox的样式(footerRefreshBox or headerRefreshBox),也就是说可以定义其中一个,另一个用默认的,也可以全部自定义
19 | ///isPullEnable;isPushEnable属性可以控制RefreshBox 是否可用,无论是自定义的还是默认的
20 | ///PullAndPush can use the default style,Based on this style, you can use the properties of the default** series to change the display,
21 | ///You can also customize the style of the RefreshBox (footerRefreshBox or headerRefreshBox), which means you can define one of them, and the other can be customized by default or all.
22 | class PullAndPushTestState extends State with TickerProviderStateMixin{
23 | List addStrs=["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"];
24 | List strs=["1","2","3","4","5","6","7","8","9","0"];
25 | ScrollController controller=new ScrollController();
26 | //For compatibility with ios ,must use RefreshAlwaysScrollPhysics ;为了兼容ios 必须使用RefreshAlwaysScrollPhysics
27 | ScrollPhysics scrollPhysics=new RefreshAlwaysScrollPhysics();
28 | //使用系统的请求
29 | var httpClient = new HttpClient();
30 | var url = "https://github.com/";
31 | var _result="";
32 | String customRefreshBoxIconPath="images/icon_arrow.png";
33 | AnimationController customBoxWaitAnimation;
34 | int rotationAngle=0;
35 | String customHeaderTipText="快尼玛给老子松手!";
36 | String defaultRefreshBoxTipText="快尼玛给老子松手!";
37 | ///button等其他方式,通过方法调用触发下拉刷新
38 | TriggerPullController triggerPullController=new TriggerPullController();
39 |
40 |
41 | @override
42 | void initState() {
43 | super.initState();
44 | //这个是刷新时控件旋转的动画,用来使刷新的Icon动起来
45 | customBoxWaitAnimation=new AnimationController(duration: const Duration(milliseconds: 1000*100), vsync: this);
46 | //第一次layout后会被调用
47 | //WidgetsBinding.instance.addPostFrameCallback((context){
48 | // triggerPullController.triggerPull();
49 | //});
50 | }
51 |
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 | return new Scaffold(
56 | appBar: new AppBar(
57 | title: new Text("上下拉刷新"),
58 | ),
59 | body: new PullAndPush(
60 | //如果你headerRefreshBox和footerRefreshBox全都自定义了,则default**系列的属性均无效,假如有一个RefreshBox是用默认的(在该RefreshBox Enable的情况下)则default**系列的属性均有效
61 | //If your headerRefreshBox and footerRefreshBox are all customizable,then the default** attributes of the series are invalid,
62 | // If there is a RefreshBox is the default(In the case of the RefreshBox Enable)then the default** attributes of the series are valid
63 | defaultRefreshBoxTipText: defaultRefreshBoxTipText,
64 | headerRefreshBox: _getCustomHeaderBox(),
65 | triggerPullController:triggerPullController,
66 |
67 | //你也可以自定义底部的刷新栏;you can customize the bottom refresh box
68 | animationStateChangedCallback:(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
69 | _handleStateCallback( animationStates, refreshBoxDirectionStatus);
70 | },
71 | listView: new ListView.builder(
72 | //ListView的Item
73 | itemCount: strs.length,//+2,
74 | controller: controller,
75 | physics: scrollPhysics,
76 | itemBuilder: (BuildContext context,int index){
77 | return new Container(
78 | height: 35.0,
79 | child: new Center(
80 | child: new Text(strs[index],style: new TextStyle(fontSize: 18.0),),
81 | ),
82 | );
83 | }
84 | ),
85 | loadData: (isPullDown) async{
86 | await _loadData(isPullDown);
87 | },
88 | scrollPhysicsChanged: (ScrollPhysics physics) {
89 | //这个不用改,照抄即可;This does not need to change,only copy it
90 | setState(() {
91 | scrollPhysics=physics;
92 | });
93 | },
94 | )
95 | );
96 | }
97 |
98 |
99 |
100 | Widget _getCustomHeaderBox(){
101 | return new Container(
102 | color: Colors.grey,
103 | child: new Row(
104 | mainAxisAlignment: MainAxisAlignment.center,
105 | children: [
106 | new Align(
107 | alignment: Alignment.centerLeft,
108 | child: new RotatedBox(
109 | quarterTurns: rotationAngle,
110 | child: new RotationTransition( //布局中加载时动画的weight
111 | child: new Image.asset(
112 | customRefreshBoxIconPath,
113 | height: 45.0,
114 | width: 45.0,
115 | fit:BoxFit.cover,
116 | ),
117 | turns: new Tween(
118 | begin: 100.0,
119 | end: 0.0
120 | )
121 | .animate(customBoxWaitAnimation)
122 | ..addStatusListener((animationStatus) {
123 | if (animationStatus == AnimationStatus.completed) {
124 | customBoxWaitAnimation.repeat();
125 | }
126 | }
127 | ),
128 | ),
129 | ),
130 | ),
131 |
132 | new Align(
133 | alignment: Alignment.centerRight,
134 | child:new ClipRect(
135 | child:new Text(customHeaderTipText,style: new TextStyle(fontSize: 18.0,color: Color(0xffe6e6e6)),),
136 | ),
137 | ),
138 | ],
139 | )
140 | );
141 | }
142 |
143 | void _handleStateCallback(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
144 | switch (animationStates){
145 | //RefreshBox高度达到50,上下拉刷新可用;RefreshBox height reached 50,the function of load data is available
146 | case AnimationStates.DragAndRefreshEnabled:
147 | setState(() {
148 | //3.141592653589793是弧度,角度为180度,旋转180度;3.141592653589793 is radians,angle is 180⁰,Rotate 180⁰
149 | rotationAngle=2;
150 | });
151 | break;
152 |
153 | //开始加载数据时;When loading data starts
154 | case AnimationStates.StartLoadData:
155 | setState(() {
156 | customRefreshBoxIconPath="images/refresh.png";
157 | customHeaderTipText="正尼玛在拼命加载.....";
158 | });
159 | customBoxWaitAnimation.forward();
160 | break;
161 |
162 | //加载完数据时;RefreshBox会留在屏幕2秒,并不马上消失,这里可以提示用户加载成功或者失败
163 | // After loading the data,RefreshBox will stay on the screen for 2 seconds, not disappearing immediately,Here you can prompt the user to load successfully or fail.
164 | case AnimationStates.LoadDataEnd:
165 | customBoxWaitAnimation.reset();
166 | setState(() {
167 | rotationAngle = 0;
168 | if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PULL) {
169 | customRefreshBoxIconPath = "images/icon_cry.png";
170 | customHeaderTipText = "加载失败!请重试";
171 | }else if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PUSH){
172 | defaultRefreshBoxTipText="可提示用户加载成功Or失败";
173 | }
174 | });
175 | break;
176 |
177 | //RefreshBox已经消失,并且闲置;RefreshBox has disappeared and is idle
178 | case AnimationStates.RefreshBoxIdle:
179 | setState(() {
180 | rotationAngle=0;
181 | defaultRefreshBoxTipText=customHeaderTipText="快尼玛给老子松手!";
182 | customRefreshBoxIconPath="images/icon_arrow.png";
183 | });
184 | break;
185 | }
186 | }
187 |
188 | Future _loadData(bool isPullDown) async{
189 | try {
190 | var request = await httpClient.getUrl(Uri.parse(url));
191 | var response = await request.close();
192 | if (response.statusCode == HttpStatus.ok) {
193 | _result = await utf8.decoder.bind(response).join();
194 | setState(() {
195 | //拿到数据后,对数据进行梳理
196 | if(isPullDown){
197 | strs.clear();
198 | strs.addAll(addStrs);
199 | }else{
200 | strs.addAll(addStrs);
201 | }
202 | });
203 | } else {
204 | _result = 'error code : ${response.statusCode}';
205 | }
206 | } catch (exception) {
207 | _result = '网络异常';
208 | }
209 | print(_result);
210 | }
211 | }
212 |
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/images/icon_arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/07493da924fdaed80f7f5e25a8989b001572a555/images/icon_arrow.png
--------------------------------------------------------------------------------
/images/icon_cry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/07493da924fdaed80f7f5e25a8989b001572a555/images/icon_cry.png
--------------------------------------------------------------------------------
/images/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baoolong/PullToRefresh_Flutter/07493da924fdaed80f7f5e25a8989b001572a555/images/refresh.png
--------------------------------------------------------------------------------
/lib/pulltorefresh_flutter.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:math' as math;
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/rendering.dart';
6 |
7 | typedef Future LoadData(bool isPullDown);
8 | typedef ScrollPhysicsChanged(ScrollPhysics physics);
9 | typedef AnimationStateChanged(AnimationStates animationStates,RefreshBoxDirectionStatus refreshBoxDirectionStatus);
10 |
11 |
12 | enum RefreshBoxDirectionStatus {
13 | // 上拉加载的状态 分别为 闲置 上拉 下拉
14 | IDLE, PUSH, PULL
15 | }
16 |
17 | enum RefreshBoxAnimationModel {
18 | // 加载数据前缩回,加载数据后缩回,主动触发弹出
19 | PUSH_BEFORE_LOAD_DATA, PULL_BEFORE_LOAD_DATA,PUSH_RECOVER_TO_NORMAL,PULL_RECOVER_TO_NORMAL,PULL_TRIGGER_BY_BUTTON
20 | }
21 |
22 |
23 | enum AnimationStates {
24 | ///RefreshBox高度达到50,上下拉刷新可用;RefreshBox height reached 50,the function of load data is available
25 | DragAndRefreshEnabled,
26 | ///开始加载数据时;When loading data starts
27 | StartLoadData,
28 | ///加载完数据时;RefreshBox会留在屏幕2秒,并不马上消失,After loading the data,RefreshBox will stay on the screen for 2 seconds, not disappearing immediately
29 | LoadDataEnd,
30 | ///空闲状态
31 | RefreshBoxIdle
32 | }
33 |
34 | class PullAndPush extends StatefulWidget{
35 |
36 | final LoadData loadData;
37 | final ScrollPhysicsChanged scrollPhysicsChanged;
38 | final ScrollView listView;
39 | //去掉过度滑动时ListView顶部的蓝色光晕效果
40 | final bool isShowLeadingGlow;
41 | final bool isShowTrailingGlow;
42 |
43 | final Color defaultRefreshBoxBackgroundColor;
44 | final String defaultRefreshBoxRefreshIconPath;
45 | final String defaultRefreshBoxTipText;
46 | final Color defaultRefreshBoxTextColor;//defaultRefreshBox
47 |
48 | final bool isPullEnable;
49 | final bool isPushEnable;
50 | final Color glowColor;
51 | final Widget headerRefreshBox;
52 | final Widget footerRefreshBox;
53 |
54 | final AnimationStateChanged animationStateChangedCallback;
55 | final TriggerPullController triggerPullController;
56 |
57 | PullAndPush({
58 | @required this.loadData,
59 | @required this.scrollPhysicsChanged,
60 | this.defaultRefreshBoxBackgroundColor:Colors.grey,
61 | this.defaultRefreshBoxTipText:"松手即可刷新",
62 | this.defaultRefreshBoxRefreshIconPath:"images/refresh.png",
63 | this.isPullEnable:true,
64 | this.isPushEnable:true,
65 | this.isShowLeadingGlow:false,
66 | this.isShowTrailingGlow:false,
67 | this.defaultRefreshBoxTextColor:Colors.white,
68 | this.glowColor:Colors.blue,
69 | this.headerRefreshBox,
70 | this.footerRefreshBox,
71 | this.animationStateChangedCallback,
72 | this.triggerPullController,
73 | @required this.listView}
74 | ):assert(
75 | listView!=null&&loadData!=null,
76 | );
77 |
78 | @override
79 | PullAndPushState createState() {
80 | return new PullAndPushState();
81 | }
82 | }
83 |
84 | class PullAndPushState extends State with TickerProviderStateMixin{
85 |
86 | ///记录当前头部HeaderBox的高度
87 | double topItemHeight=0.0;
88 | ///记录底部刷新boX的高度
89 | double bottomItemHeight=0.0;
90 |
91 | Animation animation;
92 | AnimationController animationController;
93 | ///RefreshBox,缩回去的距离,包括触发了上下拉刷新后缩回到刷新栏高度,和刷新完毕后box高度回缩到0的距离(恢复正常ListView)
94 | double _shrinkageDistance=0.0;
95 | ///刷新栏RefreshBox的高度
96 | final double _refreshHeight=50.0;
97 | ///头部刷新栏触发下拉刷新的高度
98 | final double _triggerRefreshTopHeight=80.0;
99 | RefreshBoxDirectionStatus states=RefreshBoxDirectionStatus.IDLE;
100 | AnimationStates animationStates=AnimationStates.RefreshBoxIdle;
101 |
102 | AnimationController animationControllerWait;
103 | ///记录RefreshBox在动画过程中是触发刷新(加载)的回缩还是刷新(加载)数据完后触发的刷新,
104 | RefreshBoxAnimationModel _boxAnimationStatus;
105 |
106 | bool isReset=false;
107 | bool isPulling=false;
108 |
109 | @override
110 | void initState() {
111 | super.initState();
112 | widget.triggerPullController.pullToPushState=this;
113 | //这个是刷新时控件旋转的动画,用来使刷新的Icon动起来
114 | animationControllerWait=new AnimationController(duration: const Duration(milliseconds: 1000*100), vsync: this);
115 |
116 | animationController = new AnimationController(duration: const Duration(milliseconds: 250), vsync: this);
117 |
118 | animation = new Tween(begin: 1.0, end: 0.0).animate(animationController)
119 | ..addListener(() {
120 | //因为animationController reset()后,addListener会收到监听,导致再执行一遍setState topItemHeight和bottomItemHeight会异常 所以用此标记
121 | //判断是reset的话就返回避免异常
122 | if(isReset){
123 | return;
124 | }
125 | //根据动画的值逐渐减小布局的高度,分为4种情况
126 | // 1.上拉高度超过_refreshHeight 2.上拉高度不够50 3.下拉拉高度超过_refreshHeight 4.下拉拉高度不够50
127 | setState(() {
128 | switch (_boxAnimationStatus){
129 | case RefreshBoxAnimationModel.PULL_TRIGGER_BY_BUTTON:
130 | topItemHeight=_refreshHeight-_refreshHeight*animation.value;
131 | break;
132 | case RefreshBoxAnimationModel.PULL_BEFORE_LOAD_DATA:
133 | //shrinkageDistance*animation.value是由大变小的,模拟出弹回的效果
134 | topItemHeight=_refreshHeight+_shrinkageDistance*animation.value;
135 | break;
136 | case RefreshBoxAnimationModel.PUSH_BEFORE_LOAD_DATA:
137 | bottomItemHeight=_refreshHeight+_shrinkageDistance*animation.value;
138 | //高度小于50时↓
139 | break;
140 | case RefreshBoxAnimationModel.PULL_RECOVER_TO_NORMAL:
141 | topItemHeight=_shrinkageDistance*animation.value;
142 | break;
143 | case RefreshBoxAnimationModel.PUSH_RECOVER_TO_NORMAL:
144 | bottomItemHeight=_shrinkageDistance*animation.value;
145 | break;
146 | }
147 | });
148 | });
149 |
150 |
151 | animation.addStatusListener((animationStatus){
152 | if(animationStatus==AnimationStatus.completed){
153 | //动画结束时首先将animationController重置
154 | isReset=true;
155 | animationController.reset();
156 | isReset=false;
157 | //动画完成后只有2种情况,高度是50和0,如果是高度》=50的,则开始刷新或者加载操作,如果是0则恢复ListView
158 | setState(() {
159 | switch (_boxAnimationStatus){
160 | case RefreshBoxAnimationModel.PULL_TRIGGER_BY_BUTTON:
161 | topItemHeight=_refreshHeight;
162 | _refreshStart(RefreshBoxDirectionStatus.PULL);
163 | break;
164 | case RefreshBoxAnimationModel.PULL_BEFORE_LOAD_DATA:
165 | topItemHeight=_refreshHeight;
166 | _refreshStart(RefreshBoxDirectionStatus.PULL);
167 | break;
168 | case RefreshBoxAnimationModel.PUSH_BEFORE_LOAD_DATA:
169 | bottomItemHeight=_refreshHeight;
170 | _refreshStart(RefreshBoxDirectionStatus.PUSH);
171 | //动画结束,高度回到0,上下拉刷新彻底结束,ListView恢复正常
172 | break;
173 | case RefreshBoxAnimationModel.PULL_RECOVER_TO_NORMAL:
174 | topItemHeight=0.0;
175 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
176 | states=RefreshBoxDirectionStatus.IDLE;
177 | _chenckStateAndCallback(AnimationStates.RefreshBoxIdle,RefreshBoxDirectionStatus.IDLE);
178 | break;
179 | case RefreshBoxAnimationModel.PUSH_RECOVER_TO_NORMAL:
180 | bottomItemHeight=0.0;
181 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
182 | states=RefreshBoxDirectionStatus.IDLE;
183 | isPulling=false;
184 | _chenckStateAndCallback(AnimationStates.RefreshBoxIdle,RefreshBoxDirectionStatus.IDLE);
185 | break;
186 | }
187 | });
188 | }else if(animationStatus==AnimationStatus.forward){
189 | //动画开始时根据情况计算要弹回去的距离
190 | if(topItemHeight>_triggerRefreshTopHeight){
191 | _shrinkageDistance=topItemHeight-_refreshHeight;
192 | _boxAnimationStatus=RefreshBoxAnimationModel.PULL_BEFORE_LOAD_DATA;
193 | }else if(bottomItemHeight>_refreshHeight){
194 | _shrinkageDistance=bottomItemHeight-_refreshHeight;
195 | _boxAnimationStatus=RefreshBoxAnimationModel.PUSH_BEFORE_LOAD_DATA;
196 | //这里必须有个动画,不然上拉加载时 ListView不会自动滑下去,导致ListView悬在半空
197 | widget.listView.controller.animateTo(widget.listView.controller.position.maxScrollExtent, duration: new Duration(milliseconds: 250), curve: Curves.linear);
198 | }else if(topItemHeight<=_triggerRefreshTopHeight&&topItemHeight>0.0){
199 | _shrinkageDistance=topItemHeight;
200 | states=RefreshBoxDirectionStatus.PULL;
201 | _boxAnimationStatus=RefreshBoxAnimationModel.PULL_RECOVER_TO_NORMAL;
202 | }else if(bottomItemHeight<=_refreshHeight&&bottomItemHeight>0.0){
203 | _shrinkageDistance=bottomItemHeight;
204 | states=RefreshBoxDirectionStatus.PUSH;
205 | _boxAnimationStatus=RefreshBoxAnimationModel.PUSH_RECOVER_TO_NORMAL;
206 | }else if(_boxAnimationStatus!=null&&_boxAnimationStatus==RefreshBoxAnimationModel.PULL_TRIGGER_BY_BUTTON){
207 | print("动过按键主动触发下拉刷新");
208 | }
209 | }
210 | });
211 | }
212 |
213 |
214 | ///按钮主动触发下拉刷新
215 | void triggerPullByButton(){
216 | if(states==RefreshBoxDirectionStatus.IDLE){
217 | _boxAnimationStatus=RefreshBoxAnimationModel.PULL_TRIGGER_BY_BUTTON;
218 | animationController.forward();
219 | }
220 | }
221 |
222 | Widget _getFooterBox(){
223 | if(widget.isPushEnable) {
224 | if(widget.footerRefreshBox==null){
225 | return _getDefaulttFooterBox();
226 | }else{
227 | return new Container(
228 | height: bottomItemHeight,
229 | child: widget.footerRefreshBox,
230 | );
231 | }
232 | }else{
233 | return new Container();
234 | }
235 | }
236 |
237 |
238 | Widget _getDefaulttFooterBox(){
239 | return new Container( //上拉加载布局
240 | color: widget.defaultRefreshBoxBackgroundColor,
241 | height: bottomItemHeight,
242 | child: new Row(
243 | mainAxisAlignment: MainAxisAlignment.center,
244 | children: [
245 | new Align(
246 | alignment: Alignment.centerLeft,
247 | child: new RotationTransition(
248 | child: new Image.asset(
249 | widget.defaultRefreshBoxRefreshIconPath, height: 45.0, width: 45.0,),
250 | turns: new Tween(begin: 100.0, end: 0.0).animate(
251 | animationControllerWait)
252 | ..addStatusListener((animationStatus) {
253 | if (animationStatus == AnimationStatus.completed) {
254 | animationControllerWait.repeat();
255 | }
256 | }),
257 | ),
258 | ),
259 | new Align(
260 | alignment: Alignment.centerRight,
261 | child: new Text(widget.defaultRefreshBoxTipText,
262 | style: new TextStyle(color: widget.defaultRefreshBoxTextColor),),
263 | ),
264 | ],
265 | ),
266 | );
267 | }
268 |
269 |
270 |
271 | Widget _getHeaderBox(){
272 | if(widget.isPullEnable) {
273 | if(widget.headerRefreshBox==null){
274 | return _getDefaultHeaderBox();
275 | }else{
276 | return new Container(
277 | height: topItemHeight,
278 | child: widget.headerRefreshBox,
279 | );
280 | }
281 | }else{
282 | return new Container();
283 | }
284 | }
285 |
286 | Widget _getDefaultHeaderBox(){
287 | return new Container( //下拉刷新的布局
288 | color: widget.defaultRefreshBoxBackgroundColor,
289 | height: topItemHeight,
290 | child: new Row(
291 | mainAxisAlignment: MainAxisAlignment.center,
292 | children: [
293 | new Align(
294 | alignment: Alignment.centerLeft,
295 | child: new RotationTransition( //布局中加载时动画的weight
296 | child: new Image.asset(
297 | widget.defaultRefreshBoxRefreshIconPath,
298 | height: 45.0,
299 | width: 45.0,
300 | //fit:BoxFit.cover
301 | ),
302 | turns: new Tween(begin: 100.0, end: 0.0).animate(
303 | animationControllerWait)
304 | ..addStatusListener((animationStatus) {
305 | if (animationStatus == AnimationStatus.completed) {
306 | animationControllerWait.repeat();
307 | }
308 | }),
309 | ),
310 | ),
311 |
312 | new Align( //这里是布局中的文字
313 | child: new ClipRect(
314 | child: new Text(widget.defaultRefreshBoxTipText,
315 | style: new TextStyle(color: widget.defaultRefreshBoxTextColor),),
316 | ),
317 | alignment: Alignment.centerRight,
318 | ),
319 | ],
320 | ),
321 | );
322 | }
323 |
324 |
325 | void _refreshStart (RefreshBoxDirectionStatus refreshBoxDirectionStatus) async{
326 | _chenckStateAndCallback(AnimationStates.StartLoadData,refreshBoxDirectionStatus);
327 | //只有默认的RefreshBox才用animationControllerWait启动动画,自定义的刷新栏,用户会自己操作动画
328 | if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PULL&&widget.headerRefreshBox==null&&widget.isPullEnable){
329 | //开始加载等待的动画
330 | animationControllerWait.forward();
331 | }else if(refreshBoxDirectionStatus==RefreshBoxDirectionStatus.PUSH&&widget.footerRefreshBox==null&&widget.isPushEnable){
332 | //开始加载等待的动画
333 | animationControllerWait.forward();
334 | }
335 |
336 | //这里我们开始加载数据 数据加载完成后,将新数据处理并开始加载完成后的处理
337 | await widget.loadData(topItemHeight>bottomItemHeight);
338 | if (!mounted) return;
339 |
340 | if(animationControllerWait.isAnimating){
341 | //结束加载等待的动画
342 | animationControllerWait.stop();
343 | }
344 | _chenckStateAndCallback(AnimationStates.LoadDataEnd,refreshBoxDirectionStatus);
345 |
346 | await Future.delayed(new Duration(seconds: 2));
347 |
348 | //开始将加载(刷新)布局缩回去的动画
349 | animationController.forward();
350 | //结束加载等待的动画
351 | animationControllerWait.reset();
352 | }
353 |
354 |
355 |
356 | @override
357 | void dispose() {
358 | animationController.dispose();
359 | animationControllerWait.dispose();
360 | super.dispose();
361 | }
362 |
363 |
364 |
365 | @override
366 | Widget build(BuildContext context) {
367 | return new Container(
368 | child:new Column(
369 | children: [
370 | _getHeaderBox(),
371 | new Expanded(
372 | flex:1,
373 | child: new NotificationListener(
374 | onNotification: (ScrollNotification notification){
375 | ScrollMetrics metrics=notification.metrics;
376 | if(notification is ScrollUpdateNotification){
377 |
378 | _handleScrollUpdateNotification(notification);
379 |
380 | }else if(notification is ScrollEndNotification){
381 |
382 | _handleScrollEndNotification();
383 |
384 | }else if(notification is UserScrollNotification){
385 |
386 | _handleUserScrollNotification(notification);
387 |
388 | }else if(metrics.atEdge&& notification is OverscrollNotification){
389 |
390 | _handleOverscrollNotification(notification);
391 |
392 | }
393 | return true;
394 | },
395 | child:ScrollConfiguration(
396 | behavior: MyBehavior(widget.isShowLeadingGlow,widget.isShowTrailingGlow,widget.glowColor),
397 | child: widget.listView,
398 | ),
399 | ),
400 | ),
401 | _getFooterBox(),
402 | ],
403 | ),
404 | );
405 | }
406 |
407 |
408 | void _handleScrollUpdateNotification(ScrollUpdateNotification notification){
409 | //当上拉加载时,不知道什么原因,dragDetails可能会为空,导致抛出异常,会发生很明显的卡顿,所以这里必须判空
410 | if(notification.dragDetails==null){
411 | return;
412 | }
413 | //Header刷新的布局可见时,且当手指反方向拖动(由下向上),notification 为 ScrollUpdateNotification,这个时候让头部刷新布局的高度+delta.dy(此时dy为负数)
414 | // 来缩小头部刷新布局的高度,当完全看不见时,将scrollPhysics设置为RefreshAlwaysScrollPhysics,来保持ListView的正常滑动
415 | if(topItemHeight>0.0){
416 | setState(() {
417 | // 如果头部的布局高度<0时,将topItemHeight=0;并恢复ListView的滑动
418 | if(topItemHeight+notification.dragDetails.delta.dy/2<=0.0){
419 | _chenckStateAndCallback(AnimationStates.RefreshBoxIdle,RefreshBoxDirectionStatus.IDLE);
420 | topItemHeight=0.0;
421 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
422 | }else {
423 | //当刷新布局可见时,让头部刷新布局的高度+delta.dy(此时dy为负数),来缩小头部刷新布局的高度
424 | topItemHeight = topItemHeight + notification.dragDetails.delta.dy / 2;
425 | }
426 | });
427 | }else if(bottomItemHeight>0.0){
428 | //底部的布局可见时 ,且手指反方向拖动(由上向下),这时notification 为 ScrollUpdateNotification,这个时候让底部加载布局的高度-delta.dy(此时dy为正数数)
429 | //来缩小底部加载布局的高度,当完全看不见时,将scrollPhysics设置为RefreshAlwaysScrollPhysics,来保持ListView的正常滑动
430 |
431 | setState(() {
432 | //如果底部的布局高度<0时,bottomItemHeight=0;并恢复ListView的滑动
433 | if(bottomItemHeight-notification.dragDetails.delta.dy/2<=0.0) {
434 | _chenckStateAndCallback(AnimationStates.RefreshBoxIdle,RefreshBoxDirectionStatus.IDLE);
435 | bottomItemHeight=0.0;
436 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
437 | }else{
438 | if(notification.dragDetails.delta.dy>0){
439 | //当加载的布局可见时,让上拉加载布局的高度-delta.dy(此时dy为正数数),来缩小底部的加载布局的高度
440 | bottomItemHeight = bottomItemHeight - notification.dragDetails.delta.dy / 2;
441 | }
442 | }
443 | });
444 | }
445 | }
446 |
447 |
448 | void _handleScrollEndNotification(){
449 | //如果滑动结束后(手指抬起来后),判断是否需要启动加载或者刷新的动画
450 | if(topItemHeight>0||bottomItemHeight>0){
451 | if(isPulling){
452 | return ;
453 | }
454 | //这里在动画开始时,做一个标记,表示上拉加载正在进行,因为在超出底部和头部刷新布局的高度后,高度会自动弹回,ListView也会跟着运动,
455 | //返回结束时,ListView的运动也跟着结束,会触发ScrollEndNotification,导致再次启动动画,而发生BUG
456 | if(bottomItemHeight>0){
457 | isPulling=true;
458 | }
459 | //启动动画后,ListView不可滑动
460 | widget.scrollPhysicsChanged(new NeverScrollableScrollPhysics());
461 | animationController.forward();
462 | }
463 | }
464 |
465 |
466 | void _handleUserScrollNotification(UserScrollNotification notification){
467 | if(bottomItemHeight>0.0&¬ification.direction==ScrollDirection.forward){
468 | //底部加载布局出现反向滑动时(由上向下),将scrollPhysics置为RefreshScrollPhysics,只要有2个原因。1 减缓滑回去的速度,2 防止手指快速滑动时出现惯性滑动
469 | widget.scrollPhysicsChanged(new RefreshScrollPhysics());
470 | }else if(topItemHeight>0.0&¬ification.direction==ScrollDirection.reverse){
471 | //头部刷新布局出现反向滑动时(由下向上)
472 | widget.scrollPhysicsChanged(new RefreshScrollPhysics());
473 | }else if(bottomItemHeight>0.0&¬ification.direction==ScrollDirection.reverse){
474 | //反向再反向(恢复正向拖动)
475 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
476 |
477 | }
478 | }
479 |
480 |
481 |
482 | void _handleOverscrollNotification(OverscrollNotification notification){
483 | //OverscrollNotification 和 metrics.atEdge 说明正在下拉或者 上拉
484 | //此处同上
485 | if(notification.dragDetails==null){
486 | return;
487 | }
488 |
489 | //如果notification.overscroll<0.0 说明是在下拉刷新,这里根据拉的距离设定高度的增加范围-->小于50时 是拖动速度的1/2,高度在50-90时 是
490 | //拖动速度的1/4 .........若果超过150,结束拖动,自动开始刷新,拖过刷新布局高度小于0,恢复ListView的正常拖动
491 | //当Item的数量不能铺满全屏时 上拉加载会引起下拉布局的出现,所以这里要判断下bottomItemHeight<0.5
492 | if(notification.overscroll<0.0&&bottomItemHeight<0.5&&widget.isPullEnable){
493 | setState(() {
494 | if(notification.dragDetails.delta.dy/2+topItemHeight<=0.0){
495 | //Refresh回弹完毕,恢复正常ListView的滑动状态
496 | _chenckStateAndCallback(AnimationStates.RefreshBoxIdle,RefreshBoxDirectionStatus.IDLE);
497 | topItemHeight=0.0;
498 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
499 | }else{
500 | if(topItemHeight>150.0){
501 | widget.scrollPhysicsChanged(new NeverScrollableScrollPhysics());
502 | animationController.forward();
503 | }else if(topItemHeight>120.0){
504 | topItemHeight=notification.dragDetails.delta.dy/6+topItemHeight;
505 | }else if(topItemHeight>_triggerRefreshTopHeight){
506 | _chenckStateAndCallback(AnimationStates.DragAndRefreshEnabled,RefreshBoxDirectionStatus.PULL);
507 | topItemHeight=notification.dragDetails.delta.dy/4+topItemHeight;
508 | }else {
509 | topItemHeight=notification.dragDetails.delta.dy/2+topItemHeight;
510 | }
511 | }
512 | });
513 | }else if(topItemHeight<0.5&&widget.isPushEnable){
514 |
515 | setState(() {
516 | if(-notification.dragDetails.delta.dy/2+bottomItemHeight<=0.0){
517 | //Refresh回弹完毕,恢复正常ListView的滑动状态
518 | _chenckStateAndCallback(AnimationStates.RefreshBoxIdle,RefreshBoxDirectionStatus.IDLE);
519 | bottomItemHeight=0.0;
520 | widget.scrollPhysicsChanged(new RefreshAlwaysScrollPhysics());
521 | }else{
522 | if(bottomItemHeight>75.0){
523 | if(isPulling){
524 | return;
525 | }
526 | isPulling=true;
527 | widget.scrollPhysicsChanged(new NeverScrollableScrollPhysics());
528 | animationController.forward();
529 | }else if(bottomItemHeight>60.0){
530 | bottomItemHeight=-notification.dragDetails.delta.dy/6+bottomItemHeight;
531 | }else if(bottomItemHeight>50.0){
532 | _chenckStateAndCallback(AnimationStates.DragAndRefreshEnabled,RefreshBoxDirectionStatus.PUSH);
533 | bottomItemHeight=-notification.dragDetails.delta.dy/4+bottomItemHeight;
534 | }else {
535 | bottomItemHeight=-notification.dragDetails.delta.dy/2+bottomItemHeight;
536 | }
537 | }
538 | });
539 | }
540 | }
541 |
542 | void _chenckStateAndCallback(AnimationStates currentState,RefreshBoxDirectionStatus refreshBoxDirectionStatus){
543 | if(animationStates!=currentState){
544 | animationStates=currentState;
545 | if(widget.animationStateChangedCallback!=null){
546 | widget.animationStateChangedCallback(animationStates,refreshBoxDirectionStatus);
547 | }
548 | }
549 | }
550 | }
551 |
552 |
553 |
554 |
555 | ///切记 继承ScrollPhysics 必须重写applyTo,,在NeverScrollableScrollPhysics类里面复制就可以
556 | ///出现反向滑动时用此ScrollPhysics
557 | class RefreshScrollPhysics extends ScrollPhysics {
558 | const RefreshScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
559 |
560 | @override
561 | RefreshScrollPhysics applyTo(ScrollPhysics ancestor) {
562 | return new RefreshScrollPhysics(parent: buildParent(ancestor));
563 | }
564 |
565 | @override
566 | bool shouldAcceptUserOffset(ScrollMetrics position) {
567 | return true;
568 | }
569 |
570 |
571 | ///防止ios设备上出现弹簧效果
572 | @override
573 | double applyBoundaryConditions(ScrollMetrics position, double value) {
574 | assert(() {
575 | if (value == position.pixels) {
576 | throw FlutterError(
577 | '$runtimeType.applyBoundaryConditions() was called redundantly.\n'
578 | 'The proposed new position, $value, is exactly equal to the current position of the '
579 | 'given ${position.runtimeType}, ${position.pixels}.\n'
580 | 'The applyBoundaryConditions method should only be called when the value is '
581 | 'going to actually change the pixels, otherwise it is redundant.\n'
582 | 'The physics object in question was:\n'
583 | ' $this\n'
584 | 'The position object in question was:\n'
585 | ' $position\n'
586 | );
587 | }
588 | return true;
589 | }());
590 | if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
591 | return value - position.pixels;
592 | if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
593 | return value - position.pixels;
594 | if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
595 | return value - position.minScrollExtent;
596 | if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
597 | return value - position.maxScrollExtent;
598 | return 0.0;
599 | }
600 |
601 |
602 |
603 | //重写这个方法为了减缓ListView滑动速度
604 | @override
605 | double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
606 | if(offset<0.0){
607 | return 0.00000000000001;
608 | }
609 | if(offset==0.0){
610 | return 0.0;
611 | }
612 | return offset/2;
613 | }
614 |
615 |
616 | //此处返回null时为了取消惯性滑动
617 | @override
618 | Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
619 | return null;
620 | }
621 | }
622 |
623 |
624 |
625 |
626 | ///可去掉过度滑动时ListView顶部的蓝色光晕效果
627 | class MyBehavior extends ScrollBehavior {
628 |
629 | final bool isShowLeadingGlow;
630 | final bool isShowTrailingGlow;
631 | final Color _kDefaultGlowColor;
632 |
633 | MyBehavior(this.isShowLeadingGlow,this.isShowTrailingGlow,this._kDefaultGlowColor);
634 |
635 | @override
636 | Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
637 |
638 | //如果头部或底部有一个 不需要 显示光晕时 返回GlowingOverscrollIndicator
639 | if(!isShowLeadingGlow||!isShowTrailingGlow){
640 | return new GlowingOverscrollIndicator(
641 | showLeading: isShowLeadingGlow,
642 | showTrailing: isShowTrailingGlow,
643 | child: child,
644 | axisDirection: axisDirection,
645 | color: _kDefaultGlowColor,
646 | );
647 | }else {
648 | //都需要光晕时 返回系统默认
649 | return super.buildViewportChrome(context, child, axisDirection);
650 | }
651 | }
652 | }
653 |
654 |
655 |
656 | ///切记 继承ScrollPhysics 必须重写applyTo,,在NeverScrollableScrollPhysics类里面复制就可以
657 | ///此类用来控制IOS过度滑动出现弹簧效果
658 | class RefreshAlwaysScrollPhysics extends AlwaysScrollableScrollPhysics {
659 | const RefreshAlwaysScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
660 |
661 | @override
662 | RefreshAlwaysScrollPhysics applyTo(ScrollPhysics ancestor) {
663 | return new RefreshAlwaysScrollPhysics(parent: buildParent(ancestor));
664 | }
665 |
666 | @override
667 | bool shouldAcceptUserOffset(ScrollMetrics position) {
668 | return true;
669 | }
670 |
671 | ///防止ios设备上出现弹簧效果
672 | @override
673 | double applyBoundaryConditions(ScrollMetrics position, double value) {
674 | assert(() {
675 | if (value == position.pixels) {
676 | throw FlutterError(
677 | '$runtimeType.applyBoundaryConditions() was called redundantly.\n'
678 | 'The proposed new position, $value, is exactly equal to the current position of the '
679 | 'given ${position.runtimeType}, ${position.pixels}.\n'
680 | 'The applyBoundaryConditions method should only be called when the value is '
681 | 'going to actually change the pixels, otherwise it is redundant.\n'
682 | 'The physics object in question was:\n'
683 | ' $this\n'
684 | 'The position object in question was:\n'
685 | ' $position\n'
686 | );
687 | }
688 | return true;
689 | }());
690 | if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
691 | return value - position.pixels;
692 | if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
693 | return value - position.pixels;
694 | if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
695 | return value - position.minScrollExtent;
696 | if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
697 | return value - position.maxScrollExtent;
698 | return 0.0;
699 | }
700 |
701 | ///防止ios设备出现卡顿
702 | @override
703 | Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
704 | final Tolerance tolerance = this.tolerance;
705 | if (position.outOfRange) {
706 | double end;
707 | if (position.pixels > position.maxScrollExtent)
708 | end = position.maxScrollExtent;
709 | if (position.pixels < position.minScrollExtent)
710 | end = position.minScrollExtent;
711 | assert(end != null);
712 | return ScrollSpringSimulation(
713 | spring,
714 | position.pixels,
715 | position.maxScrollExtent,
716 | math.min(0.0, velocity),
717 | tolerance: tolerance
718 | );
719 | }
720 | if (velocity.abs() < tolerance.velocity)
721 | return null;
722 | if (velocity > 0.0 && position.pixels >= position.maxScrollExtent)
723 | return null;
724 | if (velocity < 0.0 && position.pixels <= position.minScrollExtent)
725 | return null;
726 | return ClampingScrollSimulation(
727 | position: position.pixels,
728 | velocity: velocity,
729 | tolerance: tolerance,
730 | );
731 | }
732 | }
733 |
734 |
735 | class TriggerPullController{
736 | PullAndPushState pullAndPushState;
737 |
738 | set pullToPushState(PullAndPushState state){
739 | pullAndPushState=state;
740 | }
741 |
742 | void triggerPull(){
743 | if(pullAndPushState!=null){
744 | pullAndPushState.triggerPullByButton();
745 | }
746 | }
747 |
748 | }
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.0.8"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "1.0.4"
18 | charcode:
19 | dependency: transitive
20 | description:
21 | name: charcode
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.1.2"
25 | collection:
26 | dependency: transitive
27 | description:
28 | name: collection
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.14.11"
32 | flutter:
33 | dependency: "direct main"
34 | description: flutter
35 | source: sdk
36 | version: "0.0.0"
37 | flutter_test:
38 | dependency: "direct dev"
39 | description: flutter
40 | source: sdk
41 | version: "0.0.0"
42 | matcher:
43 | dependency: transitive
44 | description:
45 | name: matcher
46 | url: "https://pub.dartlang.org"
47 | source: hosted
48 | version: "0.12.3+1"
49 | meta:
50 | dependency: transitive
51 | description:
52 | name: meta
53 | url: "https://pub.dartlang.org"
54 | source: hosted
55 | version: "1.1.6"
56 | path:
57 | dependency: transitive
58 | description:
59 | name: path
60 | url: "https://pub.dartlang.org"
61 | source: hosted
62 | version: "1.6.2"
63 | quiver:
64 | dependency: transitive
65 | description:
66 | name: quiver
67 | url: "https://pub.dartlang.org"
68 | source: hosted
69 | version: "2.0.1"
70 | sky_engine:
71 | dependency: transitive
72 | description: flutter
73 | source: sdk
74 | version: "0.0.99"
75 | source_span:
76 | dependency: transitive
77 | description:
78 | name: source_span
79 | url: "https://pub.dartlang.org"
80 | source: hosted
81 | version: "1.4.1"
82 | stack_trace:
83 | dependency: transitive
84 | description:
85 | name: stack_trace
86 | url: "https://pub.dartlang.org"
87 | source: hosted
88 | version: "1.9.3"
89 | stream_channel:
90 | dependency: transitive
91 | description:
92 | name: stream_channel
93 | url: "https://pub.dartlang.org"
94 | source: hosted
95 | version: "1.6.8"
96 | string_scanner:
97 | dependency: transitive
98 | description:
99 | name: string_scanner
100 | url: "https://pub.dartlang.org"
101 | source: hosted
102 | version: "1.0.4"
103 | term_glyph:
104 | dependency: transitive
105 | description:
106 | name: term_glyph
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "1.0.1"
110 | test_api:
111 | dependency: transitive
112 | description:
113 | name: test_api
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "0.2.1"
117 | typed_data:
118 | dependency: transitive
119 | description:
120 | name: typed_data
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "1.1.6"
124 | vector_math:
125 | dependency: transitive
126 | description:
127 | name: vector_math
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "2.0.8"
131 | sdks:
132 | dart: ">=2.0.0 <3.0.0"
133 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: pulltorefresh_flutter
2 | description: A control that make the ScrollView to be pull to refresh and push to load data.Theoretically compatible with all Scrollable Widgets
3 | version: 0.1.9
4 | author: baoolong
5 | homepage: https://github.com/baoolong/PullToRefresh_Flutter
6 |
7 | environment:
8 | sdk: ">=2.0.0-dev.68.0 <3.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | flutter_test:
16 | sdk: flutter
17 |
18 | # For information on the generic Dart part of this file, see the
19 | # following page: https://www.dartlang.org/tools/pub/pubspec
20 |
21 | # The following section is specific to Flutter.
22 | flutter:
23 |
24 | # To add assets to your package, add an assets section, like this:
25 | assets:
26 | - images/refresh.png
27 | # - images/a_dot_ham.jpeg
28 | #
29 | # For details regarding assets in packages, see
30 | # https://flutter.io/assets-and-images/#from-packages
31 | #
32 | # An image asset can refer to one or more resolution-specific "variants", see
33 | # https://flutter.io/assets-and-images/#resolution-aware.
34 |
35 | # To add custom fonts to your package, add a fonts section here,
36 | # in this "flutter" section. Each entry in this list should have a
37 | # "family" key with the font family name, and a "fonts" key with a
38 | # list giving the asset and other descriptors for the font. For
39 | # example:
40 | # fonts:
41 | # - family: Schyler
42 | # fonts:
43 | # - asset: fonts/Schyler-Regular.ttf
44 | # - asset: fonts/Schyler-Italic.ttf
45 | # style: italic
46 | # - family: Trajan Pro
47 | # fonts:
48 | # - asset: fonts/TrajanPro.ttf
49 | # - asset: fonts/TrajanPro_Bold.ttf
50 | # weight: 700
51 | #
52 | # For details regarding fonts in packages, see
53 | # https://flutter.io/custom-fonts/#from-packages
54 |
--------------------------------------------------------------------------------
/pulltorefresh.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/pulltorefresh_test.dart:
--------------------------------------------------------------------------------
1 |
2 | void main() {
3 |
4 | }
5 |
--------------------------------------------------------------------------------