对于这个模拟,本人内心其实是拒绝的,但是没办法,公司的UI不安常理出牌,经常整出一些神级交互,同时拥有着我不理解的审美,最关键的问题是,即使在千钧一发之际,也会和我纠结几个像素的大小和颜色点的区别,也没有半点商量的余地,对于此,我内心没有一点波澜(才怪),甚至想吃一顿肯德基。吐槽归吐槽,那能怎么办,问题总得解决。
技术点
- 功能抽象
- 元素定位
- 动态交互
- 事件回调
- 细节处理
- 注册物理返回键
- 路由改变时消失
- 基本按钮:取消
功能抽象
抽象成一个指令的话,在项目中用的经常用到,但用过ActionSheet的都知道,他是用过一个服务的方式调用的,而不是在HTML使用指令,那这是怎么做到的呢?观察源码才发现,我们可以搭配指令和$compile服务的方式来使用。核心代码如下:
// $rootScope.$new()的作用就是为$rootScope创建一个新的scope。
var scope = $rootScope.$new(true);
// 通过$compile的方式得到元素
var element = scope.element = $compile('<action-sheet-up ng-class="cssClass" buttons="buttons"></action-sheet-up>')(scope);
// 达到显示的目的
$ionicBody.append(element);
// 达到消失的目的
element.remove();
元素定位
首先我们需要一个大背景置为灰色,给人一个幕布的感觉,使用户的焦点放在你要显示的菜单上,同时菜单需要定位在底部。具体结构如下:
<div class="ActionSheet-dt my-action-sheet-backdrop">
<div class="my-action-sheet-wrapper">
</div>
</div>
具体CSS如下:
.ActionSheet-dt.my-action-sheet-backdrop{
transition: background-color 150ms ease-in-out;
position: fixed;
top: 0;
left: 0;
right:0;
bottom: 0;
z-index: 11;
background-color: transparent;
}
.ActionSheet-dt.my-action-sheet-backdrop.active{
background-color: rgba(0, 0, 0, 0.4);
}
.ActionSheet-dt .my-action-sheet-wrapper {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
max-width: 500px;
margin: auto;
background: #F2F5F8;
transform: translate3d(0, 100%, 0);
transition: all cubic-bezier(0.36, 0.66, 0.04, 1) 500ms;
}
/*还有这种操作,这个my-action-sheet-up样式必须在my-action-sheet-wrapper下,否则样式被覆盖,不生效*/
.ActionSheet-dt .my-action-sheet-up{
transform: translate3d(0, 0, 0);
}
动态交互
如果背景和菜单瞬间出来的话,就显得太突兀,可不可以背景逐渐加深,而菜单从底部慢慢出来呢,消失相反!答案是可以的,ionic源码中为我们提供了两种方式添加动态效果,分别是$animate服务和css3的动画属性。下面分别讲解。
$animate服务,代码如下:
$animate.addClass(element, 'active').then(function () {
})
其实实现渐变效果的还是CSS3动画起的效果:
/*在150ms渐变*/
.ActionSheet-dt.my-action-sheet-backdrop{
transition: background-color 150ms ease-in-out;
}
CSS3动画主要通过transition和transform,主要掌握translate3d的妙用。具体如下:
/*默认不可见*/
.ActionSheet-dt .my-action-sheet-wrapper {
transform: translate3d(0, 100%, 0);
transition: all cubic-bezier(0.36, 0.66, 0.04, 1) 500ms;
}
/*还有这种操作,这个my-action-sheet-up样式必须在my-action-sheet-wrapper下,否则样式被覆盖,不生效*/
.ActionSheet-dt .my-action-sheet-up{
transform: translate3d(0, 0, 0);
}
事件回调
ionic ActionSheet的事件回调处理比较简单,直接返回被点击按钮的下标。需要开发人员根据下标判断哪个按钮被点击,从而采取处理。
细节处理
注册物理返回键
在上拉菜单出现时,在安卓端,用户点击物理返回键采取的措施应该是上拉菜单收起,而不是返回上一页,因此需要注册物理按键的事件回调,具体代码如下:
// 物理返回按钮注册函数返回值用来取消注册,如果上拉菜单出现,点击返回则关闭上拉菜单,而不是返回页面
scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
function() {
$timeout(scope.cancel);
},
IONIC_BACK_PRIORITY.actionSheet
);
需要注意的是,在上拉菜单收起时,需要取消注册
路由改变时消失
在路由发生改变时,应该收起菜单,同样的道理,监听路由改变事件即可。
var stateChangeListenDone = scope.cancelOnStateChange ? $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) : noop;
需要注意的是,在上拉菜单收起时,需要取消监听
具体使用
使用是传递配置对象,如果要使用代码收起菜单,直接调用显示菜单的返回值方法即可。
var actionSheetUp = ActionSheetUp.show({
buttons: [
{
text: '确定',
styleObject: {
color:'#1fb5ff'
}
}
],
titleText: '确定要更新此次签到记录吗?',
cancelText: '取消',
cancel: function () {
// 取消操作
console.log('close');
},
buttonClicked: function (index) {
console.log(index);
return true;
}
});
// 消失
actionSheetUp()
完整代码
js部分
/**
* Created by Administrator on 2017/9/12.
*/
angular.module('starter.directive')
.directive('actionSheetUp', ['$document', function ($document) {
return {
restrict: 'E',
replace: true,
scope: true,
// 不能使用templateUrl,否则查找不到元素
template: '<div class="ActionSheet-dt my-action-sheet-backdrop">' +
'<div class="my-action-sheet-wrapper">' +
'<div class="ActionSheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
'<div class="ActionSheet-items">' +
'<div class="ActionSheet-item" ng-click="buttonClicked($index)" ng-class="b.className" ng-style="b.styleObject" ng-repeat="b in buttons" ng-bind-html="b.text"></div>' +
'<div class="ActionSheet-item" ng-if="cancelText" ng-click="cancel()" ng-bind-html="cancelText"></div>' +
'</div></div></div>',
link: function ($scope, $element) {
// 点击ESC键消失
var keyUp = function (e) {
if (e.which == 27) {
$scope.cancel();
$scope.$apply();
}
};
// 点击阴影消失
var backdropClick = function (e) {
if (e.target == $element[0]) {
$scope.cancel();
$scope.$apply();
}
};
// 销毁与事件解绑
$scope.$on('$destroy', function () {
$element.remove();
$document.unbind('keyup', keyUp);
});
$document.bind('keyup', keyUp);
$element.bind('click', backdropClick);
}
}
}]);
angular.module('starter.services')
.factory('ActionSheetUp', ['$rootScope', '$ionicBody', '$compile', '$animate', '$timeout','IONIC_BACK_PRIORITY','$ionicPlatform',
function ($rootScope, $ionicBody, $compile, $animate, $timeout,IONIC_BACK_PRIORITY,$ionicPlatform) {
var extend = angular.extend;
var noop = angular.noop;
/*
* opts:
* titleText标题文本
* cancelText取消按钮文本
* cancel点击取消按钮回调事件
* buttons:数组,对象主要属性text,className,style
* buttonClicked点击按钮回调事件,参数为数组下标
* */
function actionSheetUp(opts) {
// $rootScope.$new()的作用就是为$rootScope创建一个新的scope。
var scope = $rootScope.$new(true);
extend(scope, {
cancel: noop,
buttonClicked: noop,
$deregisterBackButton: noop,
buttons: [],
cancelOnStateChange: true
}, opts || {});
// 使支持传入cssClass来自定义样式
var element = scope.element = $compile('<action-sheet-up ng-class="cssClass" buttons="buttons"></action-sheet-up>')(scope);
// 得到内容元素,用来实现动画效果
var sheetEl = angular.element(element[0].querySelector('.my-action-sheet-wrapper'));
var stateChangeListenDone = scope.cancelOnStateChange ? $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) : noop;
// action-sheet-open 干嘛的呢
scope.showSheet = function (done) {
// 防止出现多个
if (scope.removed) return;
$ionicBody.append(element);
// 背景渐变出现
$animate.addClass(element, 'active').then(function () {
if (scope.removed) return;
(done || noop)();
})
// 内容区域慢慢出现
$timeout(function () {
if (scope.removed) return;
sheetEl.addClass('my-action-sheet-up');
}, 20, false);
}
scope.removeSheet = function (done) {
if (scope.removed) return;
scope.removed = true;
// 慢慢消失
sheetEl.removeClass('my-action-sheet-up');
scope.$deregisterBackButton();//取消监听物理返回
stateChangeListenDone();//取消监听
$animate.removeClass(element, 'active').then(function() {
scope.$destroy();
element.remove();
sheetEl = null;
(done || noop)(opts &&opts.buttons);
});
}
// 物理返回按钮注册函数返回值用来取消注册,如果上拉菜单出现,点击返回则关闭上拉菜单,而不是返回页面
scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
function() {
$timeout(scope.cancel);
},
IONIC_BACK_PRIORITY.actionSheet
);
scope.cancel = function () {
scope.removeSheet(opts && opts.cancel);
}
scope.buttonClicked = function(index) {
// 如果方法返回true,意味着我们在执行完操作后可以直接关闭
if (opts.buttonClicked(index, opts.buttons[index]) === true) {
scope.removeSheet();
}
};
// 默认打开功能
scope.showSheet();
// 满足调用返回值即可关闭的需求
return scope.cancel;
}
return {
show: actionSheetUp
}
}])
;
CSS部分
.ActionSheet-dt.my-action-sheet-backdrop{
transition: background-color 150ms ease-in-out;
position: fixed;
top: 0;
left: 0;
right:0;
bottom: 0;
z-index: 11;
background-color: transparent;
}
.ActionSheet-dt.my-action-sheet-backdrop.active{
background-color: rgba(0, 0, 0, 0.4);
}
.ActionSheet-dt .my-action-sheet-wrapper {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
max-width: 500px;
margin: auto;
background: #F2F5F8;
transform: translate3d(0, 100%, 0);
transition: all cubic-bezier(0.36, 0.66, 0.04, 1) 500ms;
}
/*还有这种操作,这个my-action-sheet-up样式必须在my-action-sheet-wrapper下,否则样式被覆盖,不生效*/
.ActionSheet-dt .my-action-sheet-up{
transform: translate3d(0, 0, 0);
}
.ActionSheet-dt .ActionSheet-title{
padding:8px;
color: #555;
text-align: center;
}
.ActionSheet-dt .ActionSheet-items .ActionSheet-item{
padding:14px;
color: #222;
margin-bottom: 10px;
text-align: center;
background: #fff;
}
.ActionSheet-dt .ActionSheet-items .ActionSheet-item:last-child{
margin-bottom: 0;
}