From db27fb116cd3139b271cf9d20c462fb3746f25df Mon Sep 17 00:00:00 2001 From: Perry Govier Date: Tue, 16 Sep 2014 16:22:02 -0500 Subject: [PATCH] feat(refresher): Improve refresher animation. Allow pulling icon rotation to be disabled. --- js/angular/controller/scrollController.js | 4 ++ js/angular/directive/refresher.js | 7 +- js/views/scrollView.js | 68 ++++++++++++------- scss/_scaffolding.scss | 18 +++-- test/html/list-simple.html | 0 test/html/pull-to-refresh.html | 41 +++++++++++ test/unit/angular/directive/refresher.unit.js | 6 +- 7 files changed, 111 insertions(+), 33 deletions(-) delete mode 100644 test/html/list-simple.html create mode 100644 test/html/pull-to-refresh.html diff --git a/js/angular/controller/scrollController.js b/js/angular/controller/scrollController.js index e3635029ce6..64d6e1225c1 100644 --- a/js/angular/controller/scrollController.js +++ b/js/angular/controller/scrollController.js @@ -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() { @@ -236,6 +237,9 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca },function(){ // hideCallback refresher.classList.add('invisible'); + },function(){ + // tailCallback + refresher.classList.add('refreshing-tail'); }); }; }]); diff --git a/js/angular/directive/refresher.js b/js/angular/directive/refresher.js index b11c57da609..0fef8a8a258 100644 --- a/js/angular/directive/refresher.js +++ b/js/angular/directive/refresher.js @@ -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 @@ -67,7 +69,7 @@ IonicModule '
' + '
' + - '
' + + '
' + '' + '
' + '
' + @@ -77,7 +79,7 @@ IonicModule '
', 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'); @@ -88,6 +90,7 @@ IonicModule pullingText: '@', refreshingIcon: '@', refreshingText: '@', + disablePullingRotation: '@', $onRefresh: '&onRefresh', $onPulling: '&onPulling' }); diff --git a/js/views/scrollView.js b/js/views/scrollView.js index b0ab3124623..b30333a5224 100644 --- a/js/views/scrollView.js +++ b/js/views/scrollView.js @@ -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. @@ -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( @@ -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; }, @@ -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(); } @@ -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); }, @@ -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) { @@ -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 @@ -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 ); } } diff --git a/scss/_scaffolding.scss b/scss/_scaffolding.scss index d490aa5b8fe..6a0a35ac650 100644 --- a/scss/_scaffolding.scss +++ b/scss/_scaffolding.scss @@ -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); } } @@ -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; @@ -244,6 +248,10 @@ body.grade-c { .text-refreshing { display: block; } + &.refreshing-tail{ + -webkit-transform: scale(0,0); + transform: scale(0,0); + } } } } diff --git a/test/html/list-simple.html b/test/html/list-simple.html deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/html/pull-to-refresh.html b/test/html/pull-to-refresh.html new file mode 100644 index 00000000000..daac5e4374a --- /dev/null +++ b/test/html/pull-to-refresh.html @@ -0,0 +1,41 @@ + + + + + + Ionic Pull to Refresh + + + + + + +

Pull To Refresh

+
+ + + + + + + {{item}} + + + + + + diff --git a/test/unit/angular/directive/refresher.unit.js b/test/unit/angular/directive/refresher.unit.js index 84e64f7801b..975bb3909ba 100644 --- a/test/unit/angular/directive/refresher.unit.js +++ b/test/unit/angular/directive/refresher.unit.js @@ -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"'); @@ -97,4 +97,8 @@ describe('ionRefresher directive', function() { expect(el[0].querySelector('.text-refreshing').innerHTML).toBe('5 text'); }); + 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(); + }); });