卡片堆栈

我们可以看到这个组件其实就是在一组卡片堆栈中进行滑动。这意味着我们需要一些控制器(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元素,其实很多时候都是因为这个组件会被广泛使用。