最終效果:
代碼書(shū)寫(xiě)過(guò)程中難點(diǎn)在于:
1、根據(jù)年月,動(dòng)態(tài)計(jì)算日,并且切換到當(dāng)前日的顯示。
2、組件export輸出數(shù)據(jù)的值時(shí),picker.js系統(tǒng)組件中默認(rèn)輸出內(nèi)部selectindex指向的value,但是實(shí)際可能是不正確的,需要輸出顯示的日的值。
3、MutationObserver 觀察者模式的使用:【學(xué)習(xí)地址】
4、ion-multi-picker的使用:【官方說(shuō)明】
1、下載ion-multi-picker
npm i ion-multi-picker --save
2、創(chuàng)建組件
ionic g component datetime
執(zhí)行以上命令后,在component目錄中新建了datetime組件目錄,如圖:
3、代碼詳細(xì)
datetime.html代碼
<ion-multi-picker item-content [multiPickerColumns]="dataColumns" (ionOpen)="listenDo()" (ionChange)="ionChange($event)" [separator]="'/'" [(ngModel)]="option.Value" cancelText="取消" doneText="確定"></ion-multi-picker>
datetime.ts代碼
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { PubFunction } from '../../pubfunction';
import 'rxjs/Rx';
declare var window: {
MutationObserver: any,
WebKitMutationObserver: any,
MozMutationObserver: any
}
@Component({
selector: 'datetime',
templateUrl: 'datetime.html'
})
export class DatetimeComponent {
public dataColumns: Array<any> = [];
private _def = new importModel(2015, 2039);//min:1999 max:2039
public option: importModel;
@Input("Options")
importValue: any; //獲取從父組件傳遞過(guò)來(lái)的數(shù)據(jù)
@Output('Export')
EmitData: EventEmitter<string> = new EventEmitter();
constructor() {
this.init();
}
listenDo() {
setTimeout(() => {
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var ColumnYear = document.getElementsByClassName("picker-opts")[0];
var ColumnMonth = document.getElementsByClassName("picker-opts")[1];
var observerY = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type == "attributes") {
this.setDateItem();
}
});
});
observerY.observe(ColumnYear, {
// childList:子節(jié)點(diǎn)的變動(dòng)(指新增,刪除或者更改)。
// attributes:屬性的變動(dòng)。
// characterData:節(jié)點(diǎn)內(nèi)容或節(jié)點(diǎn)文本的變動(dòng)。
// subtree:布爾值,表示是否將該觀察器應(yīng)用于該節(jié)點(diǎn)的所有后代節(jié)點(diǎn)。
// attributeOldValue:布爾值,表示觀察attributes變動(dòng)時(shí),是否需要記錄變動(dòng)前的屬性值。
// characterDataOldValue:布爾值,表示觀察characterData變動(dòng)時(shí),是否需要記錄變動(dòng)前的值。
// attributeFilter:數(shù)組,表示需要觀察的特定屬性(比如['class','src'])。
attributeFilter: ['moved'],
// subtree: true
});
var observerM = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type == "attributes") {
this.setDateItem();
}
});
});
observerM.observe(ColumnMonth, {
attributeFilter: ['moved'],
// subtree: true
});
this.setDateItem();
}, 500);
}
Bu10(num: Number) {
return num < 10 ? '0' + num : num.toString();
}
setDateItem() {
let y = document.getElementsByClassName("picker-opt picker-opt-selected")[0];
let m = document.getElementsByClassName("picker-opt picker-opt-selected")[1];
let d = document.getElementsByClassName("picker-opt picker-opt-selected")[2];
let ds = document.getElementsByClassName("picker-opts")[2].children;
let year = Number(y.innerHTML);
let month = Number(m.innerHTML);
let date = Number(d.innerHTML);
let currentDate = new Date(year.toString() + "-" + month.toString());
currentDate.setMonth(currentDate.getMonth() + 1);
currentDate.setDate(0);
//29以上日期控制顯示/隱藏
for (let i = 29; i <= 31; i++) {
if (i <= currentDate.getDate()) {
ds[i - 1].classList.remove("picker-opt-disabled");
} else if (i > currentDate.getDate()) {
ds[i - 1].classList.add("picker-opt-disabled");
}
}
//日期大于上限,重置最大日期
if (date > currentDate.getDate()) {
document.getElementsByClassName("picker-opt picker-opt-selected")[2].classList.remove("picker-opt-selected");
ds[currentDate.getDate() - 1].classList.add("picker-opt-selected");
}
//刷新3D顯示效果
date = Number(document.getElementsByClassName("picker-opt picker-opt-selected")[2].innerHTML);
for (let i = 1; i <= 31; i++) {
(ds[i - 1] as HTMLElement).style.transform = " translate3d(-9999px, 0px, 0px)";//清空全部
}
let nestestItem = (y.previousElementSibling || y.nextElementSibling) as HTMLElement;
let rotateX = Number(/rotateX\((.+)deg\)/.exec(nestestItem.style.transform)[1]);
for (let i = date - 3; i <= date + 3; i++) {
if (i >= 1 && i <= 31) {
(ds[i - 1] as HTMLElement).style.transform = "rotateX(" + rotateX * (date - i) + "deg) translate3d(0px, 0px, 90px)";
}
}
}
init() {
this.option = Object.assign(this._def, this.importValue);
//年
this.dataColumns.push({
name: "year",
columnWidth: "18%",
options: this.generateYear()
});
//月
this.dataColumns.push({
name: "month",
// parentCol: 'year',
columnWidth: "10%",
options: this.generateMonth()
});
//日
this.dataColumns.push({
name: "day",
// parentCol: 'month',
columnWidth: "10%",
options: this.generateDay()
});
//時(shí)間
this.dataColumns.push({
name: "time",
columnWidth: "25%",
options: this.generateTime()
});
setTimeout(() => {
this.EmitData.emit(this.option.Value);
}, 300);
}
ionChange(data) {
let d = document.getElementsByClassName("picker-opt picker-opt-selected")[2];
let values = this.option.Value.split("/");
values[2] = d.innerHTML;
this.option.Value = values.join("/");
this.EmitData.emit(this.option.Value);
}
//生成年
generateYear(): Array<optionModel> {
let items: Array<optionModel> = [];
for (let i = this.option.minYear; i <= this.option.maxYear; i++) {
let item = new optionModel(i.toString(), i.toString());
items.push(item);
}
return items;
}
//生成月
generateMonth(): Array<optionModel> {
let items: Array<optionModel> = [];
for (let i = 1; i <= 12; i++) {
let item = new optionModel((i < 10 ? '0' + i : i).toString(), (i < 10 ? '0' + i : i).toString());
items.push(item);
}
return items;
}
//生成日
generateDay(): Array<optionModel> {
let items: Array<optionModel> = [];
for (let i = 1; i <= 31; i++) {
let item = new optionModel((i < 10 ? '0' + i : i).toString(), (i < 10 ? '0' + i : i).toString());
items.push(item);
}
return items;
}
//生成時(shí)間
generateTime(): Array<optionModel> {
let items: Array<optionModel> = [];
for (let i = 0; i <= 23; i++) {
for (let k = 0; k <= 3; k++) {
let hour = (i < 10 ? '0' + i : i).toString();
let minite = (15 * k < 10 ? '0' + 15 * k : 15 * k).toString();
let item = new optionModel(hour + ":" + minite, hour + ":" + minite);
items.push(item);
}
}
return items;
}
}
class importModel {
constructor(
public minYear: number = 1999,
public maxYear: number = 2039,
public Value: string = PubFunction.Format(new Date(), 'yyyy/MM/dd/hh') + ":00"
) { }
}
class optionModel {
constructor(
public text: string = "",
public value: string = "",
public disabled: Boolean = false,
public parentVal: string = ""
) {
}
}
1、滑動(dòng)停止后,修改當(dāng)前列上moved屬性值
文件路徑:node_modules\ionic-angular\components\picker\picker-column.js
修改pointerStart、pointerEnd、decelerate方法中代碼,
//pointerStart
PickerColumnCmp.prototype.pointerStart = function (ev) {
......
for (var i = 0; i < options.length; i++) {
if (!options[i].disabled) {
minY = Math.min(minY, i);
maxY = Math.max(maxY, i);
}
}
var currentText = document.getElementsByClassName("picker-opt-selected")[2].innerHTML;
if (this.col.name == "day" && this.col.selectedIndex != Number(currentText) - 1) {
// this.lastIndex = this.col.prevSelected = this.col.selectedIndex = Number(currentText) - 1
this.y = (Number(currentText) - 1) * this.optHeight * -1;
}
// console.log(Number(currentText) - 1, this.col.selectedIndex, this.col.prevSelected, this.lastIndex, this.lastTempIndex);
this.minY = (minY * this.optHeight * -1);
this.maxY = ((maxY - this.colEle.nativeElement.getElementsByClassName("picker-opt-disabled").length) * this.optHeight * -1);
// console.log("有效:" + (maxY - this.colEle.nativeElement.getElementsByClassName("picker-opt-disabled").length).toString());
// this.maxY = (maxY * this.optHeight * -1);
return true;
};
//pointerEnd
PickerColumnCmp.prototype.pointerEnd = function (ev) {
......
if (this.bounceFrom > 0) {
// bounce back up
this.update(this.minY, 100, true, true);
//頂部拖拽結(jié)束,標(biāo)記時(shí)間戳,用于監(jiān)聽(tīng)dom attribute變化。
this.colEle.nativeElement.setAttribute("moved", +new Date());
return;
}
else if (this.bounceFrom < 0) {
// bounce back down
this.update(this.maxY, 100, true, true);
//底部拖拽結(jié)束,標(biāo)記時(shí)間戳,用于監(jiān)聽(tīng)dom attribute變化。
this.colEle.nativeElement.setAttribute("moved", +new Date());
return;
}
......
};
//decelerate
PickerColumnCmp.prototype.decelerate = function () {
......
var notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1);
this.update(y, 0, true, !notLockedIn);
if (notLockedIn) {
// isn't locked in yet, keep decelerating until it is
this.rafId = this._plt.raf(this.decelerateFunc);
} else {
//滑動(dòng)結(jié)束,標(biāo)記時(shí)間戳,用于監(jiān)聽(tīng)dom attribute變化。
this.colEle.nativeElement.setAttribute("moved", +new Date());
}
......
};
修改maxY的計(jì)算方式,每次點(diǎn)擊滑動(dòng)時(shí),重新計(jì)算有多少有效的button。
maxY:有效button個(gè)數(shù)
this.maxY:有效button垂直方向坐標(biāo)
this.optHeight:每個(gè)button的高度
var currentText = document...
注意務(wù)必使用var定義變量,如使用let定義,在android7+中打包后,會(huì)卡死在啟動(dòng)頁(yè)面,一直處于加載狀態(tài)。
2、添加ionOpen事件
multi-picker.js中添加ionOpen事件,打開(kāi)時(shí)觸發(fā),需要修改3處(aot編譯后真機(jī)運(yùn)行時(shí),ionOpen事件不觸發(fā),原因不詳。暫且用click代替)
文件路徑:node_modules\ion-multi-picker\dist\components\multi-picker\multi-picker.js
3、禁止點(diǎn)擊背景后關(guān)閉
MultiPicker.prototype.open = function () {
var _this = this;
if (this._disabled) {
return;
}
var pickerOptions = { enableBackdropDismiss: false};//add
var picker = this._pickerCtrl.create(pickerOptions);
picker.setCssClass("exx-datetime");//add,添加外圍的css類(lèi)
var cancel = { text: this.cancelText, role: 'multi-picker-cancel', handler: function () { _this.ionCancel.emit(null); } };
4、css樣式exx-datetime代碼
//選擇日期組件
.exx-datetime{}
.exx-datetime .picker-wrapper{ height: 300px; padding: 20px 0; bottom: 50%; margin-bottom:-150px;}
.exx-datetime .picker-wrapper .picker-toolbar{ position: absolute;bottom: 20px; border-bottom: none;}
.exx-datetime .picker-wrapper .picker-toolbar .picker-toolbar-button{text-align: center}
.exx-datetime .picker-wrapper .picker-toolbar .picker-toolbar-button .picker-button{background-color: #387ef7; color: #fff; min-width: 50%}
.exx-datetime .picker-wrapper .picker-toolbar .picker-toolbar-button.picker-toolbar-multi-picker-cancel .picker-button{ background-color: #f8f8f8; color: #000; border:1px solid #eee;}
.exx-datetime .picker-wrapper .picker-columns{ background-color: #e2e6e9;}
.exx-datetime .picker-wrapper .picker-columns .picker-above-highlight{}
.exx-datetime .picker-wrapper .picker-columns .picker-below-highlight{}
5、在彈出頁(yè)面頭部添加藍(lán)色標(biāo)題欄
node_modules\ionic-angular\components\picker\picker-component.js
代碼中搜索ion-backdrop,在后面插入如下代碼:
<div class=\"picker-header\"><span>時(shí)間選擇</span></div>
在aot編譯后,這個(gè)標(biāo)題不生效。還需要把picker-component.metadata.json中對(duì)應(yīng)的html也加上這一段。
6、修改PickerSlideIn、PickerSlideOut動(dòng)畫(huà)效果
node_modules\ionic-angular\components\picker\picker-transitions.js
PickerSlideIn
var ele = this.enteringView.pageRef().nativeElement;
var backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop'));
var wrapper = new Animation(this.plt, ele.querySelector('.picker-wrapper'));
backdrop.fromTo('opacity', 0.01, 1);
wrapper.fromTo('translateY', '100%', '0%');
this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper);
PickerSlideOut
var ele = this.leavingView.pageRef().nativeElement;
var pickerFrame = new Animation(this.plt, ele);
pickerFrame.fromTo('opacity', 1, 0);
this.easing('cubic-bezier(.36,.66,.04,1)').duration(300).add(pickerFrame);
調(diào)用
<datetime item-content (Export)="qjtime=$event"></datetime>