美文网首页Ionic 3技术汇总
ionic3自定义日期滑动选择组件(兼容性有问题,仅作为思路参考

ionic3自定义日期滑动选择组件(兼容性有问题,仅作为思路参考

作者: IT飞牛 | 来源:发表于2019-02-13 16:01 被阅读0次

    最终效果:

    自定义datetime组件.gif

    代码书写过程中难点在于:
    1、根据年月,动态计算日,并且切换到当前日的显示。
    2、组件export输出数据的值时,picker.js系统组件中默认输出内部selectindex指向的value,但是实际可能是不正确的,需要输出显示的日的值。
    3、MutationObserver 观察者模式的使用:【学习地址
    4、ion-multi-picker的使用:【官方说明

    1、下载ion-multi-picker

    npm i ion-multi-picker --save
    

    2、创建组件

    ionic g component datetime
    

    执行以上命令后,在component目录中新建了datetime组件目录,如图:


    image.png

    3、代码详细

    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; //获取从父组件传递过来的数据
    
      @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:子节点的变动(指新增,删除或者更改)。
            // attributes:属性的变动。
            // characterData:节点内容或节点文本的变动。
            // subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。
            // attributeOldValue:布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。
            // characterDataOldValue:布尔值,表示观察characterData变动时,是否需要记录变动前的值。
            // attributeFilter:数组,表示需要观察的特定属性(比如['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()
        });
        //时间
        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;
      }
      //生成时间
      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、滑动停止后,修改当前列上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);
                //顶部拖拽结束,标记时间戳,用于监听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);
                //底部拖拽结束,标记时间戳,用于监听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 {
                    //滑动结束,标记时间戳,用于监听dom attribute变化。
                    this.colEle.nativeElement.setAttribute("moved", +new Date());
                }
    ......
        };
    

    修改maxY的计算方式,每次点击滑动时,重新计算有多少有效的button。
    maxY:有效button个数
    this.maxY:有效button垂直方向坐标
    this.optHeight:每个button的高度

    var currentText = document...
    注意务必使用var定义变量,如使用let定义,在android7+中打包后,会卡死在启动页面,一直处于加载状态。

    2、添加ionOpen事件

    multi-picker.js中添加ionOpen事件,打开时触发,需要修改3处(aot编译后真机运行时,ionOpen事件不触发,原因不详。暂且用click代替)

    文件路径:node_modules\ion-multi-picker\dist\components\multi-picker\multi-picker.js

    image.png
    image.png
    image.png

    3、禁止点击背景后关闭

    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类
            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、在弹出页面头部添加蓝色标题栏

    node_modules\ionic-angular\components\picker\picker-component.js
    代码中搜索ion-backdrop,在后面插入如下代码:

    <div class=\"picker-header\"><span>时间选择</span></div>
    

    在aot编译后,这个标题不生效。还需要把picker-component.metadata.json中对应的html也加上这一段。

    6、修改PickerSlideIn、PickerSlideOut动画效果

    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);
    

    调用

    <datetime item-content (Export)="qjtime=$event"></datetime>
    

    相关文章

      网友评论

        本文标题:ionic3自定义日期滑动选择组件(兼容性有问题,仅作为思路参考

        本文链接:https://www.haomeiwen.com/subject/hoddeqtx.html