卡片堆栈
我们可以看到这个组件其实就是在一组卡片堆栈中进行滑动。这意味着我们需要一些控制器(View Controller)来管理这些卡片,当滑动时推出一个卡片并且让另一个新的卡片进入堆栈,让我们看一下是怎么写的。
var SwipeableCardController = ionic.controllers.ViewController.inherit({
initialize: function(opts) {
this.cards = [];
// 根据opts进行初始化
},
pushCard: function(card) {
this.cards.push(card);
// 显示卡片
},
popCard: function() {
var card = this.cards.pop();
// 卡片消失的动画
}
})
这就是控制器的核心啦!除了管理堆栈,还需要初始化每一张卡片并且告诉它们消失时的动画,仅此而已!
相对控制器,视图就复杂一些,它需要对卡片界面进行渲染(控制器只需要管理逻辑就可以),它还要对DOM元素进行跟踪并且为其附上监听的事件。
在下面的例子中,我已经列出了一小段代码,里面讲了拖动时会发生什么,所有的代码在代码库里:
var SwipeableCardView = ionic.views.View.inherit({
initialize: function(opts) {
// 保存卡片元素
this.el = opts.el;
this.bindEvents();
},
bindEvents: function() {
var self = this;
ionic.onGesture('dragstart', function(e) {
// 拖动过程开始
}, this.el);
ionic.onGesture('drag', function(e) {
// 拖动的过程
window.rAF(function() {
self._doDrag(e);
});
}, this.el);
ionic.onGesture('dragend', function(e) {
// 拖动过程结束
}, this.el);
},
_doDrag: function(e) {
// 在拖动的过程中计算出我们已经拖多远
var o = e.gesture.deltaY / 3;
// Get the angle of rotation based on the
// drag distance and our distance from the
// center of the card (computed in dragstart),
// and the side of the card we are dragging from
this.rotationAngle = Math.atan(o/this.touchDistance) * this.rotationDirection;
// Don't rotate if dragging up
if(e.gesture.deltaY < 0) {
this.rotationAngle = 0;
}
// Update the y position of this view
this.y = this.startY + (e.gesture.deltaY * 0.4);
// Apply the CSS transformation to the card,
// translating it up or down, and rotating
// it based on the computed angle
this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + this.x + 'px, ' + this.y + 'px, 0) rotate(' + (this.rotationAngle || 0) + 'rad)';
}
});
这就是ionic的强大之处,我们可以监听各种复杂的手势例如拖拽,向一个特定的方向拖动,轻扫,双指缩放和其他很酷的手势(使用Hammer.js)
手势系统会告诉我们已经拖了多远(如上面那个例子中的gesture.deltaY)和拖动的速度和角度。
AngluarJS的帮助
如果我们只有上面的视图和控制器的话,我们就不得不自己写一些js来管理它们。我们希望的是创建一些DOM节点来显示卡片,为这些节点实例化一些SwipeableCardView,并且把他们放在一个控制器上就行了。如果没有AngluarJS来集成这个卡片堆栈的话,我们必须手写这些烦人的代码,为此我们必须创建一些自定义指令,现在,我们假设指令是这样的:
<swipe-cards>
<swipe-card ng-repeat="card in cards" on-destroy="cardDestroyed($index)" on-swipe="cardSwiped($index)">
<!-- Card content here -->
</swipe-card>
</swipe-cards>
我们使用上面的这个自定义元素来指定,这整个元素又SwipeableCardController来管理,每一个孩子<swipe-card>就是一个SwipeableCardView视图实例。
让我们从<swipe-cards>开始把,它比较简单:
// 定义模块名称,加载ionic代码
angular.module('ionic.contrib.ui.cards', ['ionic'])
.directive('swipeCards', ['$rootScope', function($rootScope) {
return {
restrict: 'E',
template: '<div class="swipe-cards" ng-transclude></div>',
replace: true,
transclude: true,
scope: {},
controller: function($scope, $element) {
// Instantiate the controller
var swipeController = new SwipeableCardController({
});
// We add a root scope event listener to facilitate interacting with the
// directive incase of no direct scope access
$rootScope.$on('swipeCard.pop', function(isAnimated) {
swipeController.popCard(isAnimated);
});
// return the object so it is accessible to child
// directives that 'require' this directive as a parent.
return swipeController;
}
}
}])
接下来看看<swipe-card>:
.directive('swipeCard', ['$timeout', function($timeout) {
return {
restrict: 'E',
template: '<div class="swipe-card" ng-transclude></div>',
// Requiring the swipeCards directive makes the controller available
// in the linking function
require: '^swipeCards',
replace: true,
transclude: true,
scope: {
onSwipe: '&',
onDestroy: '&'
},
compile: function(element, attr) {
return function($scope, $element, $attr, swipeCards) {
var el = $element[0];
// Instantiate our card view
var swipeableCard = new SwipeableCardView({
el: el,
onSwipe: function() {
$timeout(function() {
$scope.onSwipe();
});
},
onDestroy: function() {
$timeout(function() {
$scope.onDestroy();
});
},
});
// Make the card available to the parent scope, not necessary
// but makes it easier to interact with (similar to iOS exposing
// parent controllers and views dynamically to children)
$scope.$parent.swipeCard = swipeableCard;
// We can push a new card onto the controller card stack, animating it in
swipeCards.pushCard(swipeableCard);
}
}
}
}])
我喜欢使用一个很小的AngularJS来处理像滑动卡片这么复杂的组件,事实上,大多数控制器和视图已经暴露在ionic的代码里了。
我们被问及最多的问题是什么时候使用directives指令来替换DOM元素,其实很多时候都是因为这个组件会被广泛使用。