angular 基于 Angular Material组件库 实现 的虚拟软键盘

117 阅读4分钟

一个软键盘组件,有两种形态,一种拼音键盘,一种纯数字键盘,这里贴出一张拼音键盘看看样式吧

1708937228919.jpg

image.png

接下来就是组件的三大件了 html

<div class="close-panel">
    <button mat-icon-button matTooltip="关闭" (click)="close()">
        <mat-icon>close</mat-icon>
    </button>
</div>
<div *ngIf="inputMode=='zh-cn'">
    <div class='simple-input-method' (click)="pinyinSelect($event)">
        <div class="pinyin">拼音:{{this.hanzi}}{{this.pinyin}}</div>
        <div class="result">
            <ol>
                <li *ngFor="let item of currentPageResult;let index=index" [attr.data-idx]='index+1'>{{item}}</li>
            </ol>
            <div class="page-up-down">
                <span class="page-up" [ngStyle]="{'opacity':pageCurrent>1?'1':'0.3'}"></span>
                <span class="page-down" [ngStyle]="{'opacity':pageCurrent<pageCount?'1':'0.3'}"></span>
            </div>
        </div>
    </div>
</div>
<div class="simple-keyboard"></div>

scss

.close-panel {
  display: flex;
  justify-content: flex-end;
}

// .zh-panel {
//   display: flex;

//   .zh-char {
//     padding: 10px;
//     cursor: pointer;

//     &:hover {
//       background-color: #ffe0b2;
//     }
//   }
// }

.input-value {
  margin-bottom: 5px;
}

::ng-deep .simple-keyboard .hg-button {
  height: 55px !important;
  font-size: 18px;
}


::ng-deep .mat-bottom-sheet-container-medium {
  min-width: 85vw !important;
  max-width: calc(100vw - 64px);
}

::ng-deep .mat-bottom-sheet-container-large {
  min-width: 70vw !important;
  max-width: calc(100vw - 128px);
}

// ::ng-deep .result-li {
//   float: left;
//   margin-left: 30px;
//   cursor: pointer;

//   &:first-child {
//     color: red;
//   }
// }


.simple-input-method {
  background: #FFF;
  border: solid 1px #ececec;
  border-radius: 5px;
  color: #0364CD;
  display: block;
  margin-bottom: 5px;
}

.simple-input-method .pinyin {
  border-bottom: solid 1px #ececec;
  padding: 4px 10px;
  font-weight: bold;
}

.simple-input-method .result {
  padding: 4px 10px 4px 0px;

  ol {
    margin: 0;
    padding: 0;
    display: inline-block;
    vertical-align: middle;

    li {
      float: left;
      margin-left: 30px;
      cursor: pointer;

      &:first-child {
        color: red;
      }
    }

    :after {
      content: '';
      display: block;
      clear: left;
    }
  }


}

// .simple-input-method .result ol:after {
//   content: '';
//   display: block;
//   clear: left;
// }


.simple-input-method .page-up-down {
  display: inline-block;
  border: solid 1px #BADBFF;
  font-size: 15px;
  color: #4C9AEF;
  border-radius: 1px;
  margin-left: 10px;
}

.simple-input-method .page-up-down .page-down {
  border-left: solid 1px #BADBFF;
}

.simple-input-method .page-up-down span {
  cursor: pointer;
}

.simple-input-method .page-up-down span.disable {
  opacity: .3;
}

ts

import { KeyboardService } from './keyBoard.service';
import { MatBottomSheetRef } from '@angular/material';
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import Keyboard from 'simple-keyboard';

@Component({
    selector: 'bottom-sheet',
    templateUrl: 'keyBoardBottomSheet.component.html',
    styleUrls: ['./keyBoardBottomSheet.component.scss']
})
export class KeyBoardBottomSheet implements OnInit {
    keyboard: Keyboard;
    inputValue;
    inputMode;


    hanzi = ''; // 候选汉字
    pinyin = ''; // 候选拼音
    result = []; // 当前匹配到的汉字集合
    currentPageResult = [];
    pageCurrent = 1; // 当前页
    pageSize = 9; // 每页大小
    pageCount = 0; // 总页数
    constructor(
        private keyboardSvr: KeyboardService,
        private bottomSheetRef: MatBottomSheetRef<KeyBoardBottomSheet>,
        private changeRef: ChangeDetectorRef) { }

    ngOnInit(): void {

    }
    ngAfterViewInit() {
        let layout;
        if (this.keyboardSvr.KeyBoardType == 'num') {
            layout = {
                default: ["1 2 3", "4 5 6", "7 8 9", ". 0 {bksp}", "{enter}"],
            };
        }
        else if (this.keyboardSvr.KeyBoardType == 'default') {
            layout = {
                default: [
                    "1 2 3 4 5 6 7 8 9 0 {bksp}",
                    "{tab} q w e r t y u i o p",
                    "{lock} a s d f g h j k l {enter}",
                    "{shift} z x c v b n m , .",
                    "{space}"]
            };
        }

        this.keyboard = new Keyboard({
            layout,
            display: {
                '{bksp}': '<i matListIcon style="font-size: 20px;" class="iconfont icon-delete" aria-hidden="true"></i>',
                '{enter}': '<i matListIcon style="font-size: 20px;" class="iconfont icon-huiche" aria-hidden="true"></i>',
                '{lock}': '中/英',
                '{tab}': 'tab',
                '{shift}': 'Shift',
                '{space}': '空格'
            },
            theme: "hg-theme-default hg-layout-numeric numeric-theme"
        });
        this.keyboardSvr.registKeyboard(this.keyboard);
        this.keyboardSvr.onZhCnChanged = (input) => {

            if (input == '{bksp}') {
                this.delChar()
            } else if (input == '{space}') {
                this.selectHanzi(1)
            } else {
                let keyCode = (input + '').toLocaleUpperCase().charCodeAt(0);
                if (keyCode >= 65 && keyCode <= 90) // A-Z
                {
                    this.addChar(String.fromCharCode(keyCode + 32));
                }
                else if (keyCode >= 48 && keyCode <= 57 && this.pinyin) // 1-9
                {
                    this.selectHanzi(keyCode - 48);
                }
            }
        }
        this.keyboardSvr.onInputModeChanged = (inputMode) => {
            this.inputMode = inputMode;
            this.changeRef.detectChanges();
            this.reset();
        }
    }
    close() {
        this.keyboardSvr.destroyKeyboard();
    }


    pinyinSelect(e) {
        var target = e.target;
        if (target.nodeName == 'LI') {
            this.selectHanzi(parseInt(target.getAttribute('data-idx')));
        }
        else if (target.nodeName == 'SPAN') {
            if (target.className == 'page-up' && this.pageCurrent > 1) {
                this.pageCurrent--;
                this.refreshPage();
            }
            else if (target.className == 'page-down' && this.pageCurrent < this.pageCount) {
                this.pageCurrent++;
                this.refreshPage();
            }
        }
    }

    selectHanzi(i) {
        var hz = this.result[(this.pageCurrent - 1) * this.pageSize + i - 1];
        if (!hz) return;
        this.hanzi += hz;
        var idx = this.pinyin.indexOf("'");
        if (idx > 0) {
            this.pinyin = this.pinyin.substr(idx + 1);
            this.refresh();
        }
        else // 如果没有单引号,表示已经没有候选词了
        {
            this.keyboardSvr.setInput(this.hanzi);
            this.reset();
        }
    }

    refresh() {
        var temp = this.getHanzi(this.pinyin.replace(/'/g, ''));
        this.result = temp[0];
        this.pinyin = temp[1];
        var count = this.result.length;
        this.pageCurrent = 1;
        this.pageCount = Math.ceil(count / this.pageSize);
        this.refreshPage();

    }

    refreshPage() {
        this.currentPageResult = this.result.slice((this.pageCurrent - 1) * this.pageSize, this.pageCurrent * this.pageSize);
        this.changeRef.detectChanges();
    }

    getSingleHanzi(pinyin) {
        return (<any>window).pinyinUtil.dict.py2hz2[pinyin] || (<any>window).pinyinUtil.dict.py2hz[pinyin] || '';
    }

    getHanzi(pinyin) {
        var result = this.getSingleHanzi(pinyin);
        if (result) return [result.split(''), pinyin];
        var temp = '';
        for (var i = 0, len = pinyin.length; i < len; i++) {
            temp += pinyin[i];
            result = this.getSingleHanzi(temp);
            if (!result) continue;
            // flag表示如果当前能匹配到结果、并且往后5个字母不能匹配结果,因为最长可能是5个字母,如 zhuang
            var flag = false;
            if ((i + 1) < pinyin.length) {
                for (var j = 1, len = pinyin.length; j <= 5 && (i + j) < len; j++) {
                    if (this.getSingleHanzi(pinyin.substr(0, i + j + 1))) {
                        flag = true;
                        break;
                    }
                }
            }
            if (!flag) return [result.split(''), pinyin.substr(0, i + 1) + "'" + pinyin.substr(i + 1)];
        }
        return [[], '']; // 理论上一般不会出现这种情况
    }
    addChar(ch) {
        this.pinyin += ch;
        this.refresh();
    }
    delChar() {
        if (this.pinyin.length == 0) {
            this.keyboardSvr.delInput();
        } else if (this.pinyin.length == 1) {
            this.reset();
            return;
        } else {
            this.pinyin = this.pinyin.substr(0, this.pinyin.length - 1);
            this.refresh();
        }
    }

    reset() {
        this.hanzi = '';
        this.pinyin = '';
        this.result = [];
        this.currentPageResult = [];
        this.pageCurrent = 1;
        this.pageCount = 0;
        this.changeRef.detectChanges();
    }
}

还有最重要的service.ts


import { Injectable } from '@angular/core';
import Keyboard from 'simple-keyboard';
import { MatBottomSheet, MatBottomSheetRef } from '@angular/material';

@Injectable()
export class KeyboardService  {
    private keyboard: Keyboard;
    private cmp: any;
    private svr: MatBottomSheet;
    isShow = false;
    onKeyboardOpened;
    onKeyboardClosed;
    onKeyboardTypeChanged;
    onZhCnChanged;
    onInputModeChanged;
    onInputChanged;
    private bRef: MatBottomSheetRef;
    private keyboardType: 'num' | 'default' = 'default';
    get KeyBoardType() {
        return this.keyboardType;
    }
    private value = '';
    private inputMode: 'zh-cn' | 'en' = 'en';
    constructor(
    ) {
      

    }

    registKeyboard(keyboard: Keyboard) {
        this.keyboard = keyboard;
        this.keyboard.options.onKeyPress = (input: string) => {
            // console.log('keypress', input);
            if (input == '{lock}') {
                this.changeInputMode();
            } else if (input == '{tab}') {

            } else {
                if (this.inputMode == 'en') {
                    if (input == '{bksp}') {
                        this.delInput()
                    }
                    else if (input == '{enter}') {
                        this.onInputChanged && this.onInputChanged(this.value, input);
                    }
                    else if (input == '{shift}') {
                        this.handleShift();
                    } else if (input == '{space}') {
                        this.setInput(' ')
                    } else {
                        this.setInput(input)
                    }
                } else if (this.inputMode == 'zh-cn') {
                    if (this.keyboard.options.layoutName == 'shift') {
                        this.handleShift();
                    }
                    this.onZhCnChanged && this.onZhCnChanged(input);
                }
            }

        }
    }

    private handleShift = () => {
        let currentLayout = this.keyboard.options.layoutName;
        let shiftToggle = currentLayout === "default" ? "shift" : "default";

        this.keyboard.setOptions({
            layoutName: shiftToggle
        });
    };

    setInput(input: string) {
        this.value += input;
        this.onInputChanged && this.onInputChanged(this.value);
    }
    delInput() {
        this.value = this.value.substr(0, this.value.length - 1);
        this.onInputChanged && this.onInputChanged(this.value);
    }

    changeInputMode() {
        this.keyboard && this.keyboard.clearInput();
        if (this.inputMode == 'zh-cn') {
            this.inputMode = 'en';
        }
        else if (this.inputMode == 'en') {
            if (!(<any>window).pinyinUtil) {
                throw ('拼音组件未成功加载');
            } else {
                this.inputMode = 'zh-cn';
            }
        }
        this.onInputModeChanged && this.onInputModeChanged(this.inputMode);
    }

    registContainer(svr, cmp) {
        this.svr = svr;
        this.cmp = cmp;
    }

    showKeyboard(type: 'num' | 'default' = 'default') {
        let setting = this.getFromLocalStorage<any>('TerminalSetting');
        console.log('showKeyboard:' + JSON.stringify(setting));
        let enableKeyboard = false;
        if (setting && setting.enableKeyboard) {
            enableKeyboard = true;
        }
        if (!enableKeyboard) {
            return;
        }

        if (this.keyboardType != type) {
            this.keyboardType = type;
        }

        if (this.bRef) {
        }
        else {
            this.bRef = this.svr.open(this.cmp, {
                panelClass: 'bottom-sheet-panel',
                hasBackdrop: false
            });
        }

        this.bRef.afterDismissed().subscribe(() => {
            this.destroyKeyboard();
            this.onKeyboardClosed && this.onKeyboardClosed();
        });
        this.bRef.afterOpened().subscribe(() => {
            this.isShow = true;
            this.onKeyboardOpened && this.onKeyboardOpened();
        });
    }

    changeKeyboardType(type: 'num' | 'default') {
        let layout;
        if (type == 'num') {
            if (this.inputMode = 'zh-cn') {
                this.changeInputMode();
            }
            layout = {
                default: ["1 2 3", "4 5 6", "7 8 9", ". 0 {bksp}", "{enter}"],
            }
        }
        else {
            layout = null;
        }

        this.keyboard.setOptions({
            layout
        });
        this.onKeyboardTypeChanged && this.onKeyboardTypeChanged();
    }

    clearKeyboardInput() {
        this.value = ''
        this.keyboard.clearInput();
    }

    keyboardHandle(onInputChanged) {
        if (this.keyboard) {
            this.keyboard.clearInput();
            this.value = '';
            this.onInputChanged = onInputChanged;
        }
    }

    destroyKeyboard() {
        if (!this.isShow) {
            return;
        }
        this.svr.dismiss();
        this.isShow = false;
        this.onInputChanged = null;
        this.onInputModeChanged = null;
        this.bRef = null;
        this.inputMode = 'en';

        this.onKeyboardClosed && this.onKeyboardClosed();
    }

    getFromLocalStorage<T>(key): T {
        let result;
        let r = localStorage.getItem(key);
        try {
            result = JSON.parse(r);
        }
        catch (error) {
            result = r;
        }
        return result;
    }

    changePosition(element: HTMLElement, container) {
        const positionY = element.offsetTop;
        const bodyHeight = document.documentElement.clientHeight;
        if (positionY + 400 > bodyHeight) {
            container.nativeElement.style.paddingBottom = '500px';
        }
        container.nativeElement.scrollTop = element.offsetTop;
    }

    resetPosition(container) {
        container.nativeElement.style.paddingBottom = '0px';
    }
}

现在贴上使用方法 在其他页面中找一个input,给个点击事件



<input matInput placeholder="LDC IP地址" formControlName="ip" (click)="showOrHiddeKeyboard()">



// 键盘的占位符,当然你也可以不要
<div style='height:400px' *ngIf="keyboardShow" #keyboardPadding></div>



ts中最重要的就是初始化时

keyboardShow: boolean;

conectLDCFormGroup = new FormGroup({
    "ip": new FormControl(this.value, Validators.required),
});


@ViewChild('keyboardPadding') keyboardPadding: ElementRef;

constructor(
    private bottomSheet: MatBottomSheet,
    public keyboardSvr: KeyboardService,
    private changeRef: ChangeDetectorRef,
) { }

ngOnInit(): void {
// 注册
this.keyboardSvr.registContainer(this.bottomSheet, KeyBoardBottomSheet);
// 订阅的键盘打开时
    this.keyboardSvr.onKeyboardOpened = () => {
        this.keyboardShow = true;
        this.changeRef.detectChanges();
        setTimeout(() => {
            this.keyboardPadding.nativeElement.scrollIntoView();
        }, 100);
    }
    // 键盘关闭时
    this.keyboardSvr.onKeyboardClosed = () => {
        this.keyboardShow = false;
    }
}

最后就是给出处理键盘的方法了,这里面获取键盘传过来的值

showOrHiddeKeyboard() {
    if (this.keyboardSvr.isShow) {
        this.keyboardSvr.destroyKeyboard();
    }
    else {
        this.keyboardSvr.showKeyboard('num');
        this.keyboardSvr.keyboardHandle(
            (input: string, key: string) => {
                if (key == '{enter}') {
                    this.btnOk();
                }
                else {
                // 就是在这里接收的值
                    this.conectLDCFormGroup.patchValue({ 'ip': input });
                }
            }
        );
    }
}