Skip to content

Commit

Permalink
feat(refresher): Improve refresher animation. Allow pulling icon rota…
Browse files Browse the repository at this point in the history
…tion to be disabled.
  • Loading branch information
perrygovier committed Sep 16, 2014
1 parent 487e7a9 commit db27fb1
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 33 deletions.
4 changes: 4 additions & 0 deletions js/angular/controller/scrollController.js
Expand Up @@ -224,6 +224,7 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
$timeout(function(){
refresher.classList.remove('active');
refresher.classList.remove('refreshing');
refresher.classList.remove('refreshing-tail');
refresher.classList.add('invisible');
},300);
}, function() {
Expand All @@ -236,6 +237,9 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca
},function(){
// hideCallback
refresher.classList.add('invisible');
},function(){
// tailCallback
refresher.classList.add('refreshing-tail');
});
};
}]);
Expand Down
7 changes: 5 additions & 2 deletions js/angular/directive/refresher.js
Expand Up @@ -55,6 +55,8 @@
* refresher.
* @param {string=} refreshing-text The text to display after the user lets go of
* the refresher.
* @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
* icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
*
*/
IonicModule
Expand All @@ -67,7 +69,7 @@ IonicModule
'<div class="scroll-refresher" collection-repeat-ignore>' +
'<div class="ionic-refresher-content" ' +
'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
'<div class="icon-pulling">' +
'<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
'<i class="icon {{pullingIcon}}"></i>' +
'</div>' +
'<div class="text-pulling" ng-bind-html="pullingText"></div>' +
Expand All @@ -77,7 +79,7 @@ IonicModule
'</div>',
compile: function($element, $attrs) {
if (angular.isUndefined($attrs.pullingIcon)) {
$attrs.$set('pullingIcon', 'ion-arrow-down-c');
$attrs.$set('pullingIcon', 'ion-ios7-arrow-down');
}
if (angular.isUndefined($attrs.refreshingIcon)) {
$attrs.$set('refreshingIcon', 'ion-loading-d');
Expand All @@ -88,6 +90,7 @@ IonicModule
pullingText: '@',
refreshingIcon: '@',
refreshingText: '@',
disablePullingRotation: '@',
$onRefresh: '&onRefresh',
$onPulling: '&onPulling'
});
Expand Down
68 changes: 43 additions & 25 deletions js/views/scrollView.js
Expand Up @@ -687,13 +687,11 @@ ionic.views.Scroll = ionic.views.View.inherit({

self.resetScrollView = function(e) {
//return scrollview to original height once keyboard has hidden
if(self.isScrolledIntoView) {
self.isScrolledIntoView = false;
container.style.height = "";
container.style.overflow = "";
self.resize();
ionic.scroll.isScrolling = false;
}
self.isScrolledIntoView = false;
container.style.height = "";
container.style.overflow = "";
self.resize();
ionic.scroll.isScrolling = false;
};

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

resize: function() {
if(!this.__container || !this.options) return;

// Update Scroller dimensions for changed content
// Add padding to bottom of content
this.setDimensions(
Expand Down Expand Up @@ -1286,17 +1282,21 @@ ionic.views.Scroll = ionic.views.View.inherit({
* @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
* @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
* @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.
* @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
*/
activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback, showCallback, hideCallback) {
activatePullToRefresh: function(height, activateCallback, deactivateCallback, startCallback, showCallback, hideCallback, tailCallback) {

var self = this;

self.__refreshHeight = height;
self.__refreshActivate = activateCallback;
self.__refreshDeactivate = deactivateCallback;
self.__refreshStart = startCallback;
self.__refreshShow = showCallback;
self.__refreshHide = hideCallback;
self.__refreshActivate = function(){ionic.requestAnimationFrame(activateCallback);};
self.__refreshDeactivate = function(){ionic.requestAnimationFrame(deactivateCallback);};
self.__refreshStart = function(){ionic.requestAnimationFrame(startCallback);};
self.__refreshShow = function(){ionic.requestAnimationFrame(showCallback);};
self.__refreshHide = function(){ionic.requestAnimationFrame(hideCallback);};
self.__refreshTail = function(){ionic.requestAnimationFrame(tailCallback);};
self.__refreshTailTime = 100;
self.__minSpinTime = 600;
},


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

var d = new Date();
self.refreshStartTime = d.getTime();

if (this.__refreshStart) {
this.__refreshStart();
}
Expand All @@ -1320,14 +1323,25 @@ ionic.views.Scroll = ionic.views.View.inherit({
finishPullToRefresh: function() {

var self = this;

self.__refreshActive = false;
if (self.__refreshDeactivate) {
self.__refreshDeactivate();
// delay to make sure the spinner has a chance to spin for a split second before it's dismissed
var d = new Date();
var delay = 0;
if(self.refreshStartTime + self.__minSpinTime > d.getTime()){
delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
}
setTimeout(function(){
if(self.__refreshTail){
self.__refreshTail();
}
setTimeout(function(){
self.__refreshActive = false;
if (self.__refreshDeactivate) {
self.__refreshDeactivate();
}

self.scrollTo(self.__scrollLeft, self.__scrollTop, true);

self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
},self.__refreshTailTime);
},delay);
},


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

var d = new Date();
self.refreshStartTime = d.getTime();

if (self.__refreshStart) {
self.__refreshStart();
}

// for iOS-ey style scrolling
if(!ionic.Platform.isAndroid())self.__startDeceleration();
} else {

if (self.__interruptedAnimation || self.__isDragging) {
Expand Down Expand Up @@ -2139,7 +2157,7 @@ ionic.views.Scroll = ionic.views.View.inherit({
self.__minDecelerationScrollTop = 0;
self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
self.__maxDecelerationScrollTop = self.__maxScrollTop;

if(self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight *-1;
}

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

//Make sure the scroll values are within the boundaries after a bounce,
//not below 0 or above maximum
if (self.options.bouncing) {
if (self.options.bouncing && !self.__refreshActive) {
self.scrollTo(
Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
false
self.__refreshActive
);
}
}
Expand Down
18 changes: 13 additions & 5 deletions scss/_scaffolding.scss
Expand Up @@ -157,21 +157,21 @@ body.grade-c {

@keyframes refresh-spin {
0% { transform: translate3d(0,0,0) rotate(0); }
100% { transform: translate3d(0,0,0) rotate(-180deg); }
100% { transform: translate3d(0,0,0) rotate(180deg); }
}

@-webkit-keyframes refresh-spin {
0% {-webkit-transform: translate3d(0,0,0) rotate(0); }
100% {-webkit-transform: translate3d(0,0,0) rotate(-180deg); }
100% {-webkit-transform: translate3d(0,0,0) rotate(180deg); }
}

@keyframes refresh-spin-back {
0% { transform: translate3d(0,0,0) rotate(-180deg); }
0% { transform: translate3d(0,0,0) rotate(180deg); }
100% { transform: translate3d(0,0,0) rotate(0); }
}

@-webkit-keyframes refresh-spin-back {
0% {-webkit-transform: translate3d(0,0,0) rotate(-180deg); }
0% {-webkit-transform: translate3d(0,0,0) rotate(180deg); }
100% {-webkit-transform: translate3d(0,0,0) rotate(0); }
}

Expand Down Expand Up @@ -230,12 +230,16 @@ body.grade-c {
}

&.active {
.icon-pulling {
.icon-pulling:not(.pulling-rotation-disabled) {
@include animation-name(refresh-spin);
-webkit-transform: translate3d(0,0,0) rotate(-180deg);
transform: translate3d(0,0,0) rotate(-180deg);
}
&.refreshing {
@include transition(transform .2s);
@include transition(-webkit-transform .2s);
-webkit-transform: scale(1,1);
transform: scale(1,1);
.icon-pulling,
.text-pulling {
display: none;
Expand All @@ -244,6 +248,10 @@ body.grade-c {
.text-refreshing {
display: block;
}
&.refreshing-tail{
-webkit-transform: scale(0,0);
transform: scale(0,0);
}
}
}
}
Expand Down
Empty file removed test/html/list-simple.html
Empty file.
41 changes: 41 additions & 0 deletions test/html/pull-to-refresh.html
@@ -0,0 +1,41 @@
<html ng-app="ionicApp">
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">

<title>Ionic Pull to Refresh</title>

<link rel="stylesheet" href="../../dist/css/ionic.css">
</head>
<body ng-controller="MyCtrl">

<ion-header-bar class="bar-positive">
<h1 class="title">Pull To Refresh</h1>
</ion-header-bar>

<ion-content>
<ion-refresher on-refresh="doRefresh()">

</ion-refresher>
<ion-list>
<ion-item ng-repeat="item in items">{{item}}</ion-item>
</ion-list>
</ion-content>
<script src="../../dist/js/ionic.bundle.js"></script>
<script>
angular.module('ionicApp', ['ionic'])

.controller('MyCtrl', function($scope, $timeout) {
$scope.items = ['Item 1', 'Item 2', 'Item 3'];
$scope.doRefresh = function() {
$timeout( function() {
//simulate async response
$scope.items.push('New Item ' + Math.floor(Math.random() * 1000) + 4);
//Stop the ion-refresher from spinning
$scope.$broadcast('scroll.refreshComplete');
}, 30);
};
});
</script>
</body>
</html>
6 changes: 5 additions & 1 deletion test/unit/angular/directive/refresher.unit.js
Expand Up @@ -70,7 +70,7 @@ describe('ionRefresher directive', function() {

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

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

0 comments on commit db27fb1

Please sign in to comment.