Skip to content

Commit db27fb1

Browse files
committed
feat(refresher): Improve refresher animation. Allow pulling icon rotation to be disabled.
1 parent 487e7a9 commit db27fb1

File tree

7 files changed

+111
-33
lines changed

7 files changed

+111
-33
lines changed

Diff for: js/angular/controller/scrollController.js

+4
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
224224
$timeout(function(){
225225
refresher.classList.remove('active');
226226
refresher.classList.remove('refreshing');
227+
refresher.classList.remove('refreshing-tail');
227228
refresher.classList.add('invisible');
228229
},300);
229230
}, function() {
@@ -236,6 +237,9 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
236237
},function(){
237238
// hideCallback
238239
refresher.classList.add('invisible');
240+
},function(){
241+
// tailCallback
242+
refresher.classList.add('refreshing-tail');
239243
});
240244
};
241245
}]);

Diff for: js/angular/directive/refresher.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
* refresher.
5656
* @param {string=} refreshing-text The text to display after the user lets go of
5757
* the refresher.
58+
* @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
59+
* icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
5860
*
5961
*/
6062
IonicModule
@@ -67,7 +69,7 @@ IonicModule
6769
'<div class="scroll-refresher" collection-repeat-ignore>' +
6870
'<div class="ionic-refresher-content" ' +
6971
'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
70-
'<div class="icon-pulling">' +
72+
'<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
7173
'<i class="icon {{pullingIcon}}"></i>' +
7274
'</div>' +
7375
'<div class="text-pulling" ng-bind-html="pullingText"></div>' +
@@ -77,7 +79,7 @@ IonicModule
7779
'</div>',
7880
compile: function($element, $attrs) {
7981
if (angular.isUndefined($attrs.pullingIcon)) {
80-
$attrs.$set('pullingIcon', 'ion-arrow-down-c');
82+
$attrs.$set('pullingIcon', 'ion-ios7-arrow-down');
8183
}
8284
if (angular.isUndefined($attrs.refreshingIcon)) {
8385
$attrs.$set('refreshingIcon', 'ion-loading-d');
@@ -88,6 +90,7 @@ IonicModule
8890
pullingText: '@',
8991
refreshingIcon: '@',
9092
refreshingText: '@',
93+
disablePullingRotation: '@',
9194
$onRefresh: '&onRefresh',
9295
$onPulling: '&onPulling'
9396
});

Diff for: js/views/scrollView.js

+43-25
Original file line numberDiff line numberDiff line change
@@ -687,13 +687,11 @@ ionic.views.Scroll = ionic.views.View.inherit({
687687

688688
self.resetScrollView = function(e) {
689689
//return scrollview to original height once keyboard has hidden
690-
if(self.isScrolledIntoView) {
691-
self.isScrolledIntoView = false;
692-
container.style.height = "";
693-
container.style.overflow = "";
694-
self.resize();
695-
ionic.scroll.isScrolling = false;
696-
}
690+
self.isScrolledIntoView = false;
691+
container.style.height = "";
692+
container.style.overflow = "";
693+
self.resize();
694+
ionic.scroll.isScrolling = false;
697695
};
698696

699697
//Broadcasted when keyboard is shown on some platforms.
@@ -1116,8 +1114,6 @@ ionic.views.Scroll = ionic.views.View.inherit({
11161114
},
11171115

11181116
resize: function() {
1119-
if(!this.__container || !this.options) return;
1120-
11211117
// Update Scroller dimensions for changed content
11221118
// Add padding to bottom of content
11231119
this.setDimensions(
@@ -1286,17 +1282,21 @@ ionic.views.Scroll = ionic.views.View.inherit({
12861282
* @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
12871283
* @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
12881284
* @param hideCallback {Function} Callback to execute when the refresher should be hidden. This is for hiding the refresher when it's behind the nav bar.
1285+
* @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
12891286
*/
1290-
activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback, showCallback, hideCallback) {
1287+
activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback, showCallback, hideCallback, tailCallback) {
12911288

12921289
var self = this;
12931290

12941291
self.__refreshHeight = height;
1295-
self.__refreshActivate = activateCallback;
1296-
self.__refreshDeactivate = deactivateCallback;
1297-
self.__refreshStart = startCallback;
1298-
self.__refreshShow = showCallback;
1299-
self.__refreshHide = hideCallback;
1292+
self.__refreshActivate = function(){ionic.requestAnimationFrame(activateCallback);};
1293+
self.__refreshDeactivate = function(){ionic.requestAnimationFrame(deactivateCallback);};
1294+
self.__refreshStart = function(){ionic.requestAnimationFrame(startCallback);};
1295+
self.__refreshShow = function(){ionic.requestAnimationFrame(showCallback);};
1296+
self.__refreshHide = function(){ionic.requestAnimationFrame(hideCallback);};
1297+
self.__refreshTail = function(){ionic.requestAnimationFrame(tailCallback);};
1298+
self.__refreshTailTime = 100;
1299+
self.__minSpinTime = 600;
13001300
},
13011301

13021302

@@ -1308,6 +1308,9 @@ ionic.views.Scroll = ionic.views.View.inherit({
13081308
// We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
13091309
this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
13101310

1311+
var d = new Date();
1312+
self.refreshStartTime = d.getTime();
1313+
13111314
if (this.__refreshStart) {
13121315
this.__refreshStart();
13131316
}
@@ -1320,14 +1323,25 @@ ionic.views.Scroll = ionic.views.View.inherit({
13201323
finishPullToRefresh: function() {
13211324

13221325
var self = this;
1323-
1324-
self.__refreshActive = false;
1325-
if (self.__refreshDeactivate) {
1326-
self.__refreshDeactivate();
1326+
// delay to make sure the spinner has a chance to spin for a split second before it's dismissed
1327+
var d = new Date();
1328+
var delay = 0;
1329+
if(self.refreshStartTime + self.__minSpinTime > d.getTime()){
1330+
delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
13271331
}
1332+
setTimeout(function(){
1333+
if(self.__refreshTail){
1334+
self.__refreshTail();
1335+
}
1336+
setTimeout(function(){
1337+
self.__refreshActive = false;
1338+
if (self.__refreshDeactivate) {
1339+
self.__refreshDeactivate();
1340+
}
13281341

1329-
self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
1330-
1342+
self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
1343+
},self.__refreshTailTime);
1344+
},delay);
13311345
},
13321346

13331347

@@ -1939,10 +1953,14 @@ ionic.views.Scroll = ionic.views.View.inherit({
19391953
// We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
19401954
self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
19411955

1956+
var d = new Date();
1957+
self.refreshStartTime = d.getTime();
1958+
19421959
if (self.__refreshStart) {
19431960
self.__refreshStart();
19441961
}
1945-
1962+
// for iOS-ey style scrolling
1963+
if(!ionic.Platform.isAndroid())self.__startDeceleration();
19461964
} else {
19471965

19481966
if (self.__interruptedAnimation || self.__isDragging) {
@@ -2139,7 +2157,7 @@ ionic.views.Scroll = ionic.views.View.inherit({
21392157
self.__minDecelerationScrollTop = 0;
21402158
self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
21412159
self.__maxDecelerationScrollTop = self.__maxScrollTop;
2142-
2160+
if(self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight *-1;
21432161
}
21442162

21452163
// Wrap class method
@@ -2160,11 +2178,11 @@ ionic.views.Scroll = ionic.views.View.inherit({
21602178

21612179
//Make sure the scroll values are within the boundaries after a bounce,
21622180
//not below 0 or above maximum
2163-
if (self.options.bouncing) {
2181+
if (self.options.bouncing && !self.__refreshActive) {
21642182
self.scrollTo(
21652183
Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
21662184
Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
2167-
false
2185+
self.__refreshActive
21682186
);
21692187
}
21702188
}

Diff for: scss/_scaffolding.scss

+13-5
Original file line numberDiff line numberDiff line change
@@ -157,21 +157,21 @@ body.grade-c {
157157

158158
@keyframes refresh-spin {
159159
0% { transform: translate3d(0,0,0) rotate(0); }
160-
100% { transform: translate3d(0,0,0) rotate(-180deg); }
160+
100% { transform: translate3d(0,0,0) rotate(180deg); }
161161
}
162162

163163
@-webkit-keyframes refresh-spin {
164164
0% {-webkit-transform: translate3d(0,0,0) rotate(0); }
165-
100% {-webkit-transform: translate3d(0,0,0) rotate(-180deg); }
165+
100% {-webkit-transform: translate3d(0,0,0) rotate(180deg); }
166166
}
167167

168168
@keyframes refresh-spin-back {
169-
0% { transform: translate3d(0,0,0) rotate(-180deg); }
169+
0% { transform: translate3d(0,0,0) rotate(180deg); }
170170
100% { transform: translate3d(0,0,0) rotate(0); }
171171
}
172172

173173
@-webkit-keyframes refresh-spin-back {
174-
0% {-webkit-transform: translate3d(0,0,0) rotate(-180deg); }
174+
0% {-webkit-transform: translate3d(0,0,0) rotate(180deg); }
175175
100% {-webkit-transform: translate3d(0,0,0) rotate(0); }
176176
}
177177

@@ -230,12 +230,16 @@ body.grade-c {
230230
}
231231

232232
&.active {
233-
.icon-pulling {
233+
.icon-pulling:not(.pulling-rotation-disabled) {
234234
@include animation-name(refresh-spin);
235235
-webkit-transform: translate3d(0,0,0) rotate(-180deg);
236236
transform: translate3d(0,0,0) rotate(-180deg);
237237
}
238238
&.refreshing {
239+
@include transition(transform .2s);
240+
@include transition(-webkit-transform .2s);
241+
-webkit-transform: scale(1,1);
242+
transform: scale(1,1);
239243
.icon-pulling,
240244
.text-pulling {
241245
display: none;
@@ -244,6 +248,10 @@ body.grade-c {
244248
.text-refreshing {
245249
display: block;
246250
}
251+
&.refreshing-tail{
252+
-webkit-transform: scale(0,0);
253+
transform: scale(0,0);
254+
}
247255
}
248256
}
249257
}

Diff for: test/html/list-simple.html

Whitespace-only changes.

Diff for: test/html/pull-to-refresh.html

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<html ng-app="ionicApp">
2+
<head>
3+
<meta charset="utf-8">
4+
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
5+
6+
<title>Ionic Pull to Refresh</title>
7+
8+
<link rel="stylesheet" href="../../dist/css/ionic.css">
9+
</head>
10+
<body ng-controller="MyCtrl">
11+
12+
<ion-header-bar class="bar-positive">
13+
<h1 class="title">Pull To Refresh</h1>
14+
</ion-header-bar>
15+
16+
<ion-content>
17+
<ion-refresher on-refresh="doRefresh()">
18+
19+
</ion-refresher>
20+
<ion-list>
21+
<ion-item ng-repeat="item in items">{{item}}</ion-item>
22+
</ion-list>
23+
</ion-content>
24+
<script src="../../dist/js/ionic.bundle.js"></script>
25+
<script>
26+
angular.module('ionicApp', ['ionic'])
27+
28+
.controller('MyCtrl', function($scope, $timeout) {
29+
$scope.items = ['Item 1', 'Item 2', 'Item 3'];
30+
$scope.doRefresh = function() {
31+
$timeout( function() {
32+
//simulate async response
33+
$scope.items.push('New Item ' + Math.floor(Math.random() * 1000) + 4);
34+
//Stop the ion-refresher from spinning
35+
$scope.$broadcast('scroll.refreshComplete');
36+
}, 30);
37+
};
38+
});
39+
</script>
40+
</body>
41+
</html>

Diff for: test/unit/angular/directive/refresher.unit.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe('ionRefresher directive', function() {
7070

7171
it('should have default pullingIcon', function() {
7272
var el = setup();
73-
expect(el[0].querySelector('.icon-pulling .ion-arrow-down-c')).toBeTruthy();
73+
expect(el[0].querySelector('.icon-pulling .ion-ios7-arrow-down')).toBeTruthy();
7474
});
7575
it('should allow custom pullingIcon', function() {
7676
var el = setup('pulling-icon="super-icon"');
@@ -97,4 +97,8 @@ describe('ionRefresher directive', function() {
9797
expect(el[0].querySelector('.text-refreshing').innerHTML).toBe('5 <b>text</b>');
9898
});
9999

100+
it('should allow pulling rotation animation to be disabled', function() {
101+
var el = setup('disable-pulling-rotation="true"');
102+
expect(el[0].querySelector('.pulling-rotation-disabled').innerHTML).toBeTruthy();
103+
});
100104
});

0 commit comments

Comments
 (0)