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

最终效果:

自定义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.jsPickerSlideIn

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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT飞牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值