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.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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,093评论 4 62
  • 从巴黎到巴塞罗那,很顺利。巴塞罗那回到巴黎,车坏了。 车是高铁,6个小时。走到半路,车停了,过了一会,空调关了,在...
    夏爱东西阅读 325评论 0 3
  • 阳光明媚, 母女俩在散步,我匆匆经过她们的身旁。 小女孩儿正蹦蹦跳跳的往前走。她左蹦一下,右跳一下,到了离她妈妈四...
    如一居阅读 342评论 0 3
  • 他从不提读过什么书、去过什么地方玩、见过什么人物、拥有多少财富。他无需以此证明什么。
    晚霞消失之时阅读 174评论 0 1
  • 人们总是以为自己可以知晓一切。 所以不必向瞎眼的人辩白,眼瞳里光华折射回忆和未来之间的现在,真实。还是镜子的另一面...
    2c297ebd0054阅读 216评论 0 1