Laya 文本相关类(TextArea高度自适应)

一、位于laya.display包下的两个类

1.class Text extends Sprite
内部使用了graphics.fillBorderText绘制文本

2.class Input extends Text
内部封装了原生的文本输入框,并且是全局唯一的

private static function _createInputElement():void {
    _initInput(area = Browser.createElement("textarea"));
    _initInput(input = Browser.createElement("input"));
    
    inputContainer = Browser.createElement("div");
    inputContainer.style.position = "absolute";
    inputContainer.style.zIndex = 1E5;
    Browser.container.appendChild(inputContainer);
    //[IF-SCRIPT] inputContainer.setPos = function(x:int, y:int):void
    { inputContainer.style.left = x + 'px'; inputContainer.style.top = y + 'px'; };
}

在focusin时把原生的textarea或input添加到div中显示出来,在focusout时再移除。

//Input.as
public function set focus(value:Boolean):void {
    var input:* = nativeInput;
    
    if (_focus !== value) {
        if (value) {
            if (input.target) {
                input.target._focusOut();
            } else {
                _setInputMethod();
            }
            input.target = this;
            
            _focusIn();
        } else {
            input.target = null;
            _focusOut();
            Browser.document.body.scrollTop = 0;
            input.blur();
            
            if (Render.isConchApp) {
                input.setPos(-10000, -10000);
            } else if (inputContainer.contains(input))
                inputContainer.removeChild(input);
        }
    }
}

private function _setInputMethod():void {
    input.parentElement && (inputContainer.removeChild(input));
    area.parentElement && (inputContainer.removeChild(area));
    
    inputElement = (_multiline ? area : input);
    inputContainer.appendChild(inputElement);
    if (Text.RightToLeft)
    {
        inputElement.style.direction = "rtl";
    }
}

3.自动获取焦点,并弹出键盘
参考弹出一个面板 想让焦点自动在输入文本上且弹出手机键盘,官方答复是:“软键盘弹出必须有触发行为,不支持直接默认弹出虚拟键盘!”
其实在安卓上可以做到的,设置完input.focus=true之后,再调用_popupInputMethod即可。但IOS上无效。

public focusInInput() {
    this.chatInput.inputChat.focus = true;
    //移动平台在单击事件触发后弹出输入法
    var layaInput:any = Laya.Input;
    layaInput._popupInputMethod();
}

// 移动平台在单击事件触发后弹出输入法
private static function _popupInputMethod(e:*):void {
    //e.preventDefault();
    if (!Input.isInputting) return;
    
    var input:* = Input.inputElement;
    
    // 弹出输入法。
    input.focus();
}
二、位于laya.ui包下的两个类

1.class Component extends Sprite

2.class Label extends Component
//内部封装了一个Text,全是调用Text的方法,基本没有新代码。
protected var _tf:Text;

3.class TextInput extends Label

/**@inheritDoc */
override protected function createChildren():void {
    addChild(_tf = new Input());
    _tf.padding = Styles.inputLabelPadding;
    _tf.on(Event.INPUT, this, _onInput);
    _tf.on(Event.ENTER, this, _onEnter);
    _tf.on(Event.BLUR, this, _onBlur);
    _tf.on(Event.FOCUS, this, _onFocus);
}

在这段代码中,把_tf实例化为一个Input(Input是Text的一个子类,没有毛病)。所以可以看作TextInput是对Input的一个封装,比如很多方法是这样的:

public function get maxChars():int {
    return Input(_tf).maxChars;
}

public function set maxChars(value:int):void {
    Input(_tf).maxChars = value;
}

4.class TextArea extends TextInput
内部封装了两个滚动条,还有一些控制逻辑。

/**@private */
protected var _vScrollBar:VScrollBar;
/**@private */
protected var _hScrollBar:HScrollBar;
三、在处理高度自适应时,追踪了height属性

在class TextInput extends Label看到这段:

//TextInput.as
override protected function initialize():void {
    width = 128;
    height = 22;
}

override public function set height(value:Number):void {
    super.height = value;
    _bg && (_bg.height = value);
}

调用了super.height,所以去Label.as中看一看:

//Label.as
override public function set height(value:Number):void {
    super.height = value;
    _tf.height = value;
}

调用了_tf.height,所以去了Text.as中:

//Text.as
override public function set height(value:Number):void {
    if (value != _height) {
        super.height = value;
        isChanged = true;
    }
}

Text.as的super.height就是指向Sprite.as类了

//Sprite.as
public function set height(value:Number):void {
    if (this._height !== value) {
        this._height = value;
        conchModel && conchModel.size(this._width, value);
        repaint();
    }
}

再往回看,因为class Input extends Text,所以Input类继承了Text类的 set height,但是Input类的构造方法中,又指定了宽高值:

Input.as
/**创建一个新的 Input 类实例。*/
public function Input() {
    _width = 100;
    _height = 20;
    
    multiline = false;
    overflow = Text.SCROLL;
    
    on(Event.MOUSE_DOWN, this, _onMouseDown);
    on(Event.UNDISPLAY, this, _onUnDisplay);
}
四、进入正题,textarea高度自适应

在上述引擎源码分析中,可以看到class Input extends Text类中,是有一个原生的textarea的。作为静态属性,是全局唯一的,在Laya.init()中就会将其初始化。在HTML页面中也能看到基本属性:


所以要找到原生JS是如何处理textarea高度自适应,这里参考SegmentFault 如何创建一个高度自适应的textarea
第一种方式是把textarea替换成一个div,再把div的contentEditable设置为true。

第二种就是利用scrollHeight,设置到height上。这个可以参考http://www.jacklmoore.com/autosize/,我最终使用的就是这个库。当然也有手写的,这个网上帖子也比较多,可以参考textarea如何实现高度自适应(不出现滚动条)?

第三种是1楼高赞回复,在Textarea同级放一个pre 和span标签,然后把输入内容实时同步到span里。pre会随内容的高度变化而变化,expandingArea的高度又随pre变化,因为textarea的高度100% textarea的高度会随expandingArea变化,只要同步textarea的内容到pre中,就达到一个textarea随内容高度变化的目的了。


五、使用jackmoore autosize库遇到的一些问题

1.autosize库源码中的resize方法有这样两行:

ta.style.height = '';
ta.style.height = ta.scrollHeight + heightOffset + 'px';

将height置为空串这个操作,经过我的测试,如果注释掉的话,在删除一行时,textarea不会自动缩回去。但是这行代码是有副作用的,那就是在输入第一行时,会多出来一个空行。后来参考论坛回复,把rows设置为1解决了。在Laya中的代码可以在Laya.init执行后去操作Textarea的属性。

//Input.as
/**@private */
protected static var input:*;
/**@private */
protected static var area:*;
/**@private */
protected static var inputElement:*;
/**@private */
protected static var inputContainer:*;

private static function _createInputElement():void {
    _initInput(area = Browser.createElement("textarea"));
    _initInput(input = Browser.createElement("input"));
    ……

area无法直接访问,所以只能这样写了:

Laya.init(750, 1334, Laya.WebGL);
Laya.Input["area"].rows = 1;

2._syncInputTransform在手机上不执行
在Input.as的_focusIn方法中,最后有这么一段:

// 输入框重定位。
_syncInputTransform();
if (!Render.isConchApp && Browser.onPC)
    Laya.timer.frameLoop(1, this, _syncInputTransform);

为什么只有在Browser.onPC时,才执行这个重定位呢,原因不明。那么只能侦听Laya.Event.INPUT事件,自己再手动执行_syncInputTransform方法了。

/**
 * 在输入期间,如果 Input 实例的位置改变,调用_syncInputTransform同步输入框的位置。
 */
private function _syncInputTransform():void {
    var inputElement:Object = nativeInput;
    var transform:Object = 
    Utils.getTransformRelativeToWindow(this, padding[3], padding[0]);
    var inputWid:int = _width - padding[1] - padding[3];
    var inputHei:int = _height - padding[0] - padding[2];
    if (Render.isConchApp) {
        inputElement.setScale(transform.scaleX, transform.scaleY);
        inputElement.setSize(inputWid, inputHei);
        inputElement.setPos(transform.x, transform.y);
    } else {
        //[IF-SCRIPT]inputContainer.style.transform = 
        inputContainer.style.webkitTransform = 
        "scale(" + transform.scaleX + "," + 
        transform.scaleY + ") rotate(" + (Laya.stage.canvasDegree) + "deg)";
        //[IF-SCRIPT]inputElement.style.width = inputWid + 'px';
        //[IF-SCRIPT]inputElement.style.height = inputHei + 'px';
        //[IF-SCRIPT]inputContainer.style.left = transform.x + 'px';
        //[IF-SCRIPT]inputContainer.style.top  = transform.y + 'px';
    }
}

这里原始的_syncInputTransform会考虑padding后,去设置style.width和height。因为我们已经在autosize中计算了宽高,所以_syncInputTransform方法中就不用再计算了,只要设置inputContainer.style.left和top即可。

六、弹出键盘不遮挡输入框

参考H5移动端弹出键盘时遮挡输入框,使用了文中第一种方式

private initLayaInput():void{
    var ta:any = Laya.Input["area"];
    //避免textarea出现空行
    ta.rows = 1;
    ta.onfocus = this.taFocusIn;
    ta.onblur = this.taFocusOut;
}

private taFocusIn: any = this.delayScrollBody.bind(this);
private delayScrollBody(): void {
    Laya.timer.loop(500,this,this.scrollDocBody);
}

private scrollDocBody():void{
    Laya.Browser.document.body.scrollTop = 
    Laya.Browser.document.body.scrollHeight;
}

private taFocusOut: any = this.removeDelayScrollBody.bind(this);
private removeDelayScrollBody(): void {
    Laya.timer.clear(this,this.scrollDocBody);
}

另外,也可以参考移动端iOS第三方输入法遮挡底部input及android键盘回落后留白问题

七、焦点控制
//Input.as
/**创建一个新的 Input 类实例。*/
public function Input() {
    _width = 100;
    _height = 20;
    
    multiline = false;
    overflow = Text.SCROLL;
    
    on(Event.MOUSE_DOWN, this, _onMouseDown);
    on(Event.UNDISPLAY, this, _onUnDisplay);
}

private function _onUnDisplay(e:Event = null):void {
    focus = false;
}

private function _onMouseDown(e:Event):void {
    focus = true;
}

// 移动平台最后单击画布才会调用focus
// 因此 调用focus接口是无法都在移动平台立刻弹出键盘的
public function set focus(value:Boolean):void {
    var input:* = nativeInput;
    
    if (_focus !== value) {
        if (value) {
            if (input.target) {
                input.target._focusOut();
            } else {
                _setInputMethod();
            }
            input.target = this;
            
            _focusIn();
        } else {
            input.target = null;
            _focusOut();
            Browser.document.body.scrollTop = 0;
            input.blur();
            
            if (Render.isConchApp) {
                input.setPos(-10000, -10000);
            } else if (inputContainer.contains(input))
                inputContainer.removeChild(input);
        }
    }
}

这里看到,侦听Event.MOUSE_DOWN获得了焦点。那么点击INPUT之外的区域,是怎么失去焦点的呢。经过查找,在MouseManager.as中找到了:

private function onMouseDown(ele:*):void {
    if (Input.isInputting && Laya.stage.focus && 
    Laya.stage.focus["focus"] && !Laya.stage.focus.contains(_target)) {
        // 从UI Input组件中取得Input引用
        // _tf 是TextInput的属性
        var pre_input:* = Laya.stage.focus['_tf'] || Laya.stage.focus;
        var new_input:Input = ele['_tf'] || ele;
        
        // 新的焦点是Input的情况下,不需要blur;
        // 不过如果是Input和TextArea之间的切换,还是需要重新弹出输入法;
        if (new_input is Input && new_input.multiline == pre_input.multiline)
            pre_input['_focusOut']();
        else
            pre_input.focus = false;
    }
    TouchManager.I.onMouseDown(ele, _tTouchID, _isLeftMouse);
}

参考一下Input.focus代码,可以看出执行focus=false与执行_focusOut方法的区别。最重要一点是,focus=false会额外执行input.blur(),这将导致收起软键盘

// 移动平台最后单击画布才会调用focus
// 因此 调用focus接口是无法都在移动平台立刻弹出键盘的
public function set focus(value:Boolean):void {
    var input:* = nativeInput;
    
    if (_focus !== value) {
        if (value) {
            if (input.target) {
                input.target._focusOut();
            } else {
                _setInputMethod();
            }
            input.target = this;
            
            _focusIn();
        } else {
            input.target = null;
            _focusOut();
            Browser.document.body.scrollTop = 0;
            input.blur();
            
            if (Render.isConchApp) {
                input.setPos(-10000, -10000);
            } else if (inputContainer.contains(input))
                inputContainer.removeChild(input);
        }
    }
}

默认情况下,在点击聊天发送按钮后,输入框input会失去焦点,导致软键盘收起。这时继续输入,就要重新点击输入框input,是不是很烦。但是想更改这个默认设定,也只能改源码了……

if ((new_input instanceof laya.display.Input )&& new_input.multiline==pre_input.multiline)
    pre_input['_focusOut']();
else if((new_input instanceof laya.ui.Button )&& new_input.name == "sendBtn"){
    //聊天点击发送按钮(name==sendBtn),输入框不会失去焦点
}else{
    pre_input.focus=false;
}
八、没有MOUSE_UP事件

在输入框输入文本时,直接点击发送按钮,在部分浏览器上会出现只是缩回键盘,文本并未发出的情况。经过检查,是没有抛出MOUSE_UP事件,进而导致没有CLICK事件。将发送按钮改为侦听MOUSE_DOWN事件即可。

你可能感兴趣的:(Laya 文本相关类(TextArea高度自适应))