一、位于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事件即可。