ionic 不支持级联选择器 ?
前言
笔者日常开发除了使用 Android 原生、Flutter 外,偶尔还会使用 ionic + cordova 的组合来完成部分跨平台应用开发。最近在使用过程中发现一个问题,ionic 组件 ion-datetime、ion-picker 不支持级联操作,但是可以通过一些取巧的方式间接实现,一起看看吧。
无级联常规使用
布局
<ion-header translucent><ion-toolbar><ion-title>Pickerion-title>ion-toolbar>
ion-header><ion-content fullscreen class="ion-padding"><ion-button expand="block" (click)="openPicker()">Show Single Column Pickerion-button><ion-button expand="block" (click)="openPicker(2, 5, multiColumnOptions)">Show Multi Column Pickerion-button>
ion-content>
单列、多列
import {Component} from '@angular/core';
import {PickerController, ToastController} from "@ionic/angular";@Component({selector: 'app-home',templateUrl: 'home.page.html',styleUrls: ['home.page.scss'],
})
export class HomePage {defaultColumnOptions = [['Dog','Cat','Bird','Lizard','Chinchilla']];multiColumnOptions = [['Minified','Responsive','Full Stack','Mobile First','Serverless'],['Tomato','Avocado','Onion','Potato','Artichoke']];constructor(private pickerController: PickerController,private toastController: ToastController) {}async openPicker(numColumns = 1, numOptions = 5, columnOptions = this.defaultColumnOptions) {const picker = await this.pickerController.create({columns: this.getColumns(numColumns, numOptions, columnOptions),mode: 'ios',buttons: [{text: 'Cancel',role: 'cancel'},{text: 'Confirm',handler: (value) => {console.log(value)if (numColumns == 1) {this.presentToast(value.col0.text)} else {this.presentToast(value.col0.text + ',' + value.col1.text)}}}]});await picker.present();}async presentToast(message: string) {const toast = await this.toastController.create({message: message,duration: 2000});await toast.present();}getColumns(numColumns, numOptions, columnOptions) {let columns = [];for (let i = 0; i < numColumns; i++) {columns.push({name: `col${i}`,options: this.getColumnOptions(i, numOptions, columnOptions)});}return columns;}getColumnOptions(columnIndex, numOptions, columnOptions) {let options = [];for (let i = 0; i < numOptions; i++) {options.push({text: columnOptions[columnIndex][i % numOptions],value: i})}return options;}
}
效果

尝试级联使用
ion-picker 组件支持的事件只有如下四种:

若要实现级联操作,开发者需要监听到每一列的取值变化,但是从 ionic 暴露的 API 来看,无法直接获取。但是通过查阅 ionic 的源码我们可以发现在ion-picker-column中存在ionPickerColChange事件。
文件位置:core/src/components/picker-column/picker-column.tsx

该事件返回的数据结构为:

借助这个事件,我们可以尝试自己实现级联选择。
布局
<ion-button expand="block" (click)="openCascadePicker()">Show Cascade Column Pickerion-button>
级联
// 级联数据,可以根据自己的需要调整
cascadeColumnOptions = [['贵州省','湖北省',], [['贵阳市','遵义市','凯里市','六盘水市',],['武汉市','黄石市','十堰市','宜昌市']]
];
// 级联数据单列选中值
cascadeColumnValue = [0, 0]
async openCascadePicker(numColumns = 2, columnOptions = this.cascadeColumnOptions) {const pickerOptions = {columns: this.getCascadeColumns(2, columnOptions),mode: 'ios',buttons: [{text: 'Cancel',role: 'cancel'},{text: 'Confirm',handler: (value) => {console.log(value)if (numColumns == 1) {this.presentToast(value.col0.text)} else {this.presentToast(value.col0.text + ',' + value.col1.text)}}}]}// @ts-ignorethis.cascadePicker = await this.pickerController.create(pickerOptions);await this.cascadePicker.present().then(() => {setTimeout(() => {try {// 通过元素选择器找到 Picker 的列const pickerCols = document.querySelectorAll('ion-picker-column');const provinceCol = pickerCols[0];const cityCol = pickerCols[1];// 监听 列值 变化事件provinceCol.addEventListener('ionPickerColChange', (event: CustomEvent) => {console.log(event)this.cascadeColumnValue[0] = event.detail.selectedIndexthis.cascadePicker.columns = this.getCascadeColumns(2, this.cascadeColumnOptions)})cityCol.addEventListener('ionPickerColChange', (event: CustomEvent) => {this.cascadeColumnValue[1] = event.detail.selectedIndexconsole.log(event)})} catch (e) {console.log(e);}}, 400); });
}
效果

已知问题
整体效果看上去还 OK ,但是这里存在一个 BUG ,官方暂时没有解决。示例中,”贵州省“和”湖北省“对应的二级选项数目均为四个,所以使用正常。但是当二级选项数据不一致时,会出现选项文字重叠的问题,虽然说稍稍滚动就可恢复正常,但是用户体验依然不失那么舒适。
Github Issues:https://github.com/ionic-team/ionic-framework/issues/17664

当然,针对这个问题,虽然官方没有给解决办法,但是 Github 大神在开源项目内提出了一种解决办法:https://github.com/zwlccc/ionic4-city-picker。

修改方式: https://github.com/zwlccc/ionic4-city-picker/tree/master/modify-datetime-entry-file。
- 打开
ion-datetime_3.entry.js文件,目录:node_modules/@ionic/core/dist/esm/ion-datetime_3.entry.js - 修改
update(y, duration, saveY)方法
update(y, duration, saveY) {if (!this.optsEl) {return;}// ensure we've got a good round number :)let translateY = 0;let translateZ = 0;const { col, rotateFactor } = this;const selectedIndex = col.selectedIndex = this.indexForY(-y);const durationStr = (duration === 0) ? '' : duration + 'ms';const scaleStr = `scale(${this.scaleFactor})`;const children = this.optsEl.children;const children_length = this.optsEl.children.length;const options_length = col.options.length;const length = children_length < options_length ? options_length : children_length;for (let i = 0; i < length; i++) {const button = children[i];const opt = col.options[i];const optOffset = (i * this.optHeight) + y;let transform = '';if (rotateFactor !== 0) {const rotateX = optOffset * rotateFactor;if (Math.abs(rotateX) <= 90) {translateY = 0;translateZ = 90;transform = `rotateX(${rotateX}deg) `;}else {translateY = -9999;}}else {translateZ = 0;translateY = optOffset;}const selected = selectedIndex === i;transform += `translate3d(0px,${translateY}px,${translateZ}px) `;if (this.scaleFactor !== 1 && !selected) {transform += scaleStr;}// Update transition durationif (this.noAnimate) {if (opt) {opt.duration = 0;}if (button) {button.style.transitionDuration = '';}}else if (opt) {if (duration !== opt.duration) {opt.duration = duration;if (button) {button.style.transitionDuration = durationStr;}}}// Update transformif (opt) {if (transform !== opt.transform) {opt.transform = transform;if (button) {button.style.transform = transform;}}}// Update selected itemif (opt) {if (selected !== opt.selected) {opt.selected = selected;if (selected && button) {button.classList.add(PICKER_OPT_SELECTED);}else if (button) {button.classList.remove(PICKER_OPT_SELECTED);}}}}this.col.prevSelected = selectedIndex;if (saveY) {this.y = y;}if (this.lastIndex !== selectedIndex) {// have not set a last index yethapticSelectionChanged();this.lastIndex = selectedIndex;}
}
- 修改
render()方法
render() {const col = this.col;const Button = 'button';const mode = getIonMode(this);return (h(Host, {class: {[mode]: true,'picker-col': true,'picker-opts-left': this.col.align === 'left','picker-opts-right': this.col.align === 'right'}, style: {'max-width': this.col.columnWidth}}, col.prefix && (h("div", {class: "picker-prefix",style: {width: col.prefixWidth}}, col.prefix)), h("div", {class: "picker-opts",style: {maxWidth: col.optionsWidth},ref: el => this.optsEl = el}, col.options.map((o, index) => h(Button, {type: "button",class: {'picker-opt': true, 'picker-opt-disabled': !!o.disabled, 'picker-opt-selected': o.selected},style: {transform: o.transform ? o.transform : 'translate3d(0px, -9999px, 90px)','transition-duration': o.duration ? o.duration : TRANSITION_DURATION + 'ms'},"opt-index": index}, o.text))), col.suffix && (h("div", {class: "picker-suffix", style: {width: col.suffixWidth}}, col.suffix))));}
虽然笔者的开发环境为 ionic V6.12.0,但是采用如上方式修改后可以解决问题,效果如下:

源码
https://github.com/onlyloveyd/CascaderPicker
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
