Skip to content

Commit 2894958

Browse files
committed
feat: add CustomMaterialIndicator widget
1 parent 7937a30 commit 2894958

File tree

8 files changed

+321
-44
lines changed

8 files changed

+321
-44
lines changed

example/lib/indicators/check_mark_indicator.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,11 @@ class _CheckMarkIndicatorState extends State<CheckMarkIndicator> with SingleTick
5555
onStateChanged: (change) {
5656
/// set [_renderCompleteState] to true when controller.state become completed
5757
if (change.didChange(to: IndicatorState.complete)) {
58-
setState(() {
59-
_renderCompleteState = true;
60-
});
58+
_renderCompleteState = true;
6159

6260
/// set [_renderCompleteState] to false when controller.state become idle
6361
} else if (change.didChange(to: IndicatorState.idle)) {
64-
setState(() {
65-
_renderCompleteState = false;
66-
});
62+
_renderCompleteState = false;
6763
}
6864
},
6965
builder: MaterialIndicatorDelegate(

example/lib/screens/example_indicator_screen.dart

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,21 @@ class ExampleIndicatorScreen extends StatelessWidget {
1212
backgroundColor: appBackgroundColor,
1313
appBar: const ExampleAppBar(),
1414
body: SafeArea(
15-
child: CustomRefreshIndicator(
16-
leadingScrollIndicatorVisible: false,
17-
trailingScrollIndicatorVisible: false,
18-
builder: MaterialIndicatorDelegate(
19-
builder: (context, controller) {
20-
return Icon(
21-
Icons.ac_unit,
22-
color: Theme.of(context).colorScheme.primary,
23-
size: 30,
24-
);
25-
},
26-
scrollableBuilder: (context, child, controller) {
27-
return Opacity(
28-
opacity: 1.0 - controller.value.clamp(0.0, 1.0),
29-
child: child,
30-
);
31-
},
32-
).call,
15+
child: CustomMaterialIndicator(
3316
onRefresh: () => Future.delayed(const Duration(seconds: 2)),
17+
indicatorBuilder: (context, controller) {
18+
return Icon(
19+
Icons.ac_unit,
20+
color: Theme.of(context).colorScheme.primary,
21+
size: 30,
22+
);
23+
},
24+
scrollableBuilder: (context, child, controller) {
25+
return FadeTransition(
26+
opacity: Tween(begin: 1.0, end: 0.0).animate(controller.clamp(0.0, 1.0)),
27+
child: child,
28+
);
29+
},
3430
child: const ExampleList(itemCount: 6),
3531
),
3632
),

lib/custom_refresh_indicator.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export 'src/data/data.dart';
22
export 'src/custom_refresh_indicator.dart';
33
export 'src/delegates/delegates.dart';
4+
export 'src/widgets/widgets.dart';
45
export 'src/utils/utils.dart';

lib/src/custom_refresh_indicator.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class CustomRefreshIndicator extends StatefulWidget {
107107
final AsyncCallback onRefresh;
108108

109109
/// Called on every indicator state change.
110+
///
111+
/// There is no need to use setState in this function, as the indicator will be rebuilt automatically.
110112
final OnStateChanged? onStateChanged;
111113

112114
/// The indicator controller stores all the data related
@@ -232,11 +234,11 @@ class CustomRefreshIndicatorState extends State<CustomRefreshIndicator> with Tic
232234
final onStateChanged = widget.onStateChanged;
233235
if (onStateChanged != null && controller.state != newState) {
234236
onStateChanged(IndicatorStateChange(controller.state, newState));
235-
// Triggers a rebuild of the widget to ensure that the new state
236-
// will be handled correctly by the indicator widget.
237-
_update();
238237
}
239238
controller.setIndicatorState(newState);
239+
// Triggers a rebuild of the widget to ensure that the new state
240+
// will be handled correctly by the indicator widget.
241+
_update();
240242
}
241243

242244
/// Notifies the listeners of the controller

lib/src/delegates/indicator_builder_delegate.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
22
import 'package:flutter/widgets.dart';
33

44
/// An abstract class for defining indicator delegates.
5+
@Deprecated(
6+
'Indicator delegates are deprecated and will be removed in the upcoming release. '
7+
'If you want to exapose some pre-built functionality wrap the CustomRefreshIndicator widget '
8+
'with your custom implementation.',
9+
)
510
abstract class IndicatorBuilderDelegate {
611
const IndicatorBuilderDelegate();
712

lib/src/delegates/material_indicator_delegate.dart

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
22
import 'package:flutter/material.dart';
3-
import 'dart:math' as math;
4-
5-
typedef MaterialIndicatorBuilder = Widget Function(
6-
BuildContext context,
7-
IndicatorController controller,
8-
);
93

104
/// Builds a container that behaves similarly to the material refresh indicator
5+
@Deprecated('In favor of CustomMaterialIndicator widget.')
116
class MaterialIndicatorDelegate extends IndicatorBuilderDelegate {
127
/// The distance from the child's top or bottom [edgeOffset] where
138
/// the refresh indicator will settle. During the drag that exposes the refresh
@@ -84,6 +79,7 @@ class MaterialIndicatorDelegate extends IndicatorBuilderDelegate {
8479
Theme.of(context).canvasColor;
8580

8681
return Stack(
82+
clipBehavior: Clip.hardEdge,
8783
children: <Widget>[
8884
scrollableBuilder(context, child, controller),
8985
_PositionedIndicatorContainer(
@@ -165,7 +161,7 @@ class _PositionedIndicatorContainer extends StatelessWidget {
165161

166162
@override
167163
Widget build(BuildContext context) {
168-
if (controller.side.isNone) return const SizedBox();
164+
if (controller.side.isNone) return const SizedBox.shrink();
169165

170166
final isVerticalAxis = controller.side.isTop || controller.side.isBottom;
171167
final isHorizontalAxis = controller.side.isLeft || controller.side.isRight;
@@ -174,8 +170,6 @@ class _PositionedIndicatorContainer extends StatelessWidget {
174170
? AlignmentDirectional(-1.0, controller.side.isTop ? 1.0 : -1.0)
175171
: AlignmentDirectional(controller.side.isLeft ? 1.0 : -1.0, -1.0);
176172

177-
final double value = controller.isFinalizing ? 1.0 : controller.value;
178-
179173
return Positioned(
180174
top: isHorizontalAxis
181175
? 0
@@ -197,15 +191,20 @@ class _PositionedIndicatorContainer extends StatelessWidget {
197191
: controller.side.isRight
198192
? edgeOffset
199193
: null,
200-
child: ClipRRect(
201-
child: Align(
202-
alignment: alignment,
203-
heightFactor: isVerticalAxis ? math.max(value, 0.0) : null,
204-
widthFactor: isHorizontalAxis ? math.max(value, 0.0) : null,
205-
child: Container(
194+
child: Align(
195+
alignment: alignment,
196+
heightFactor: isVerticalAxis ? 0.0 : null,
197+
widthFactor: isHorizontalAxis ? 0.0 : null,
198+
child: SlideTransition(
199+
position: controller.isFinalizing
200+
? const AlwaysStoppedAnimation(Offset(0.0, 1.0))
201+
: Tween(begin: const Offset(0.0, 0.0), end: const Offset(0.0, 1.0)).animate(controller),
202+
child: Padding(
206203
padding: _getEdgeInsets(controller.side),
207-
alignment: _getAlignement(controller.side),
208-
child: child,
204+
child: Align(
205+
alignment: _getAlignement(controller.side),
206+
child: child,
207+
),
209208
),
210209
),
211210
),

0 commit comments

Comments
 (0)