ionic3自定義日期滑動(dòng)選擇組件(兼容性有問(wèn)題,僅作為思路參考)

最終效果:

自定義datetime組件.gif

代碼書(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組件目錄,如圖:


image.png

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

image.png

image.png

image.png

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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,200評(píng)論 4 61
  • 從巴黎到巴塞羅那,很順利。巴塞羅那回到巴黎,車(chē)壞了。 車(chē)是高鐵,6個(gè)小時(shí)。走到半路,車(chē)停了,過(guò)了一會(huì),空調(diào)關(guān)了,在...
    夏愛(ài)東西閱讀 329評(píng)論 0 3
  • 陽(yáng)光明媚, 母女倆在散步,我匆匆經(jīng)過(guò)她們的身旁。 小女孩兒正蹦蹦跳跳的往前走。她左蹦一下,右跳一下,到了離她媽媽四...
    如一居閱讀 358評(píng)論 0 3
  • 他從不提讀過(guò)什么書(shū)、去過(guò)什么地方玩、見(jiàn)過(guò)什么人物、擁有多少財(cái)富。他無(wú)需以此證明什么。
    晚霞消失之時(shí)閱讀 179評(píng)論 0 1
  • 人們總是以為自己可以知曉一切。 所以不必向瞎眼的人辯白,眼瞳里光華折射回憶和未來(lái)之間的現(xiàn)在,真實(shí)。還是鏡子的另一面...
    2c297ebd0054閱讀 230評(píng)論 0 1