WebVR

主要面向Web前端工程师,需要一定Javascript及three.js基础;
本文主要分享内容为基于three.js开发WebVR思路及碰到的问题;
有兴趣的同学,欢迎跟帖讨论。

目录:
一、项目体验
1.1、项目简介
1.2、功能介绍
1.3、游戏体验
二、技术方案
2.1、为什么使用WebVR
2.2、常用的WebVR解决方案
2.2.1、Mozilla的A-Frame方案
2.2.2、three.js及webvr-polyfill方案
三、技术实现
3.1、知识储备
3.2、实现步骤
3.3、工作原理
四、技术难点
4.1、程序与用户共同控制摄像头
4.2、多重蒙板贴图
4.3、镜头移动
4.4、3d自适应长度文字提示
4.5、unity3d地形导出
4.6、3dmax动画导出问题
五、完整的源代码及相应组件

一、项目体验
1.1、项目简介:
1.1.1、名称:
“重历阿尔特里亚”——龙之谷手游手首发ChinaJoy2016预热VR小游戏

1.1.2、开发背景:
基于龙之谷手游具备的3D属性,全景视角体验,以及ChinaJoy首发的线下场景,我们和品牌讨论除了基于VR的线下体验项目。由于基于Web技术较好的兼容性、开发的高效性,我们采用了WebVR技术来实现整个体验。

1.1.3、使用WebVR优势:
1.1.3.1、普通web前端工程师可以参与VR应用开发,降低了开发门槛;
1.1.3.2、跨设备终端、跨操作系统、跨APP载体;
1.1.3.3、开发快速、维护方便、随时调整、传播便捷;
1.1.3.4、浏览器即可体验,无需安装。

1.2、功能介绍
基于游戏内3D场景、人物和道具模型,通过WebGL框架three.js开发的VR小游戏,在ChinaJoy龙之谷手游展台给玩家提供线下VR互动体验,并在后续应用于线上营销传播。不具备VR眼镜设备的用户可选择普通模式进行互动体验。

1.3、游戏体验
如果你身边正好有VR眼镜,请选择VR模式体验;如果没有,请选择普通模式。
需要说明的是,由于本次应用针对线下场景,而合作方三星提供了最新的S7手机和GearVR设备,所以项目只针对S7做了体验优化,所以可能部分手机会有卡顿或者3D模型错乱的情况。

你可以扫描如下二维码或打开http://dn.qq.com/act/vr/进行体验:

二、技术方案
2.1、为什么是时候尝试WebVR了?
2.1.1、时机慢慢成熟,我们通过几件事件即可感知:
2015年初,Mozilla在firefox nightly增加了对WebVR的支持;
2015年底,MozVR团队推出开源框架A-Frame,能过HTML标签,即可创建VR网页;
2015年底,Egret3D发布,开发团队称将在以后版本中实现WebVR的支持;
2016年初,Google与Mozilla联合创建WebVR标准;
2016年6月,Google计划将整个Chrome浏览器搬进VR世界中。
2.1.2、WebVR开发成本更低。
2015年VR硬件迅速发展,但时至今日,VR内容还是稍显单薄。原因在于,VR开发成本过高,而WebVR依托于WebGL及类似threeJS等框架,大大降低开发者进入VR领域的门槛。
2.1.3、Web自身的优势
上文中已有提及,依托也Web,具有不需安装、便于传播、便于快速迭代等特点。

2.2、目前阶段,常用的WebVR解决方案:
2.2.1、A-frame
介绍:Mozilla的开源框架,通过定制HTML元素即可构建WebVR方案的框架,适用于没有webGL与threeJS基础的初学者。
优点:基于threeJS的封装,通过特定的标签就能够快速创建VR网页;
缺点:所提供的组件有限,难以完成较复杂的项目。
实例:
2.2.1.1、创建一个简单的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Composite — A-Frame">
<script src="../aframe.js"></script>
</head>
<body>
    <a-scene>
        <!-- 环境光. -->
        <a-entity light="type: ambient; color: #888"></a-entity>
        <a-entity position="0 2.2 4">
        <!-- 添加相机 -->
        <a-entity camera look-controls wasd-controls>
            <!-- 添加圆环 -->
            <a-entity cursor
            geometry="primitive: ring; radiusOuter: 0.015; radiusInner: 0.01; segmentsTheta: 32" material="color: #283644; shader: flat" raycaster="far: 30" position="0 0 -0.75"></a-entity>
        </a-entity>
        </a-entity>
    </a-scene>
</body>
</html>

源码讲解:
如上简单的几个标签,即可构建一个包含灯光、相机、跟随相机的物体的场景,其余事情,都将由A-frame进行解析,具体标签与属性不多作讲解,可以参考 A-frame DOC

2.2.1.1、加载一个由软件(比如3dmax)导出的模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Composite — A-Frame">
<script src="../aframe.js"></script>
<script>
    AFRAME.registerComponent('json-model', {
        schema: {
            type: 'src'
        },
        init: function () {
            this.loader = new THREE.JSONLoader();
        },
        update: function () {
            var mesh = this.el.getOrCreateObject3D('mesh', THREE.Mesh);
            this.loader.load(this.data, function (geometry) {
            mesh.geometry = geometry;
            });
        }
    });
</script>
</head>
<body>
<a-scene>
    <a-assets>
        <a-asset-item id="sculpture" src="data/building-ground.js"></a-asset-item>
    </a-assets>
    <a-entity id="car" json-model="#sculpture"  position="0 0 0" scale="5 5 5" rotation="0 45 0" material="src: url(cross-domain/skin/xianxiasq_zhujianqiangmian_001.png)"></a-entity>
</a-scene>
</body>
</html>

源码讲解:
这个例子主要演示,A-Frame如何添加组件,对,因为A-Frame现阶段组件太少,加载自定义模式需要自己扩展组件。而组件添加需要three.js基础。
so,A-Frame出发点是非常美好的,学习几个简单的标签及属性,即可以搭建3d/webvr场景,但是现实却是目前它还并不成熟,并且伴随着A-Frame主设计师跳槽到Google,所以我很早就放弃这个方案了。

2、基于threeJS与webVR组件,事实上,A-frame就是基于这两者的封装。
优点:可以完成复杂项目,可以结合原生的webGL;
缺点:需要掌握threeJS,需要了解webGL,学习成本较高。

在本项目中,选用的就是这个方案,在下章节中,将会进行详细介绍。

三、技术实现
3.1、知识储备:
three.js(掌握)、webGL(了解)、javascript
对three.js没有基础的同学,可以移步至 Three.js实例教程

3.2、实现步骤:
简单来说,完成一个WebVR应用,需要以下三个步骤:
3.2.1、搭建场景

如上图与示:
首先我们需要载入我们的资源,这些资源包括地形、角色、动画、及辅助元素;
然后创建我们需要的元素,比如灯光、相机、天空等;
然后完成主业务逻辑。

3.2.2、交互
即用户的动作输入,这些动作包括:
位置移动、旋转、视线焦点、声音、甚至全身所有关节动作。
当然,当前我们可利用的硬件设备有限,手机自身可利用的如陀螺仪、罗盘、听筒。其余辅助设备常用如Leap Motion、Kinect等。
更多的额外设备意识着更高的使用成本,在本案例中使用的到的动作输入信息:
用户当前方向,由VRControls.js与webvr-polyfill.js实现完成;
用户视角焦点,完成按钮点击、攻击等动作,通过跟随相机的物体检测碰撞来完成。

3.2.3、分屏

如上图所示,为让用户更具沉侵感,通常会根据用户瞳距将屏幕分割成具有一定视差的两部分,勿需担心,这部分工作由VREffect.js来完成。

3.3、工作原理
上节中提到了webvr相关组件,本来我们可以简单利用它提供的接口就可以完成,但肯定还是有同学会好奇,它的工作原理是怎样的呢。

这得从Mozilla与Google 2016年初联手推出的WebVR API提案开始,WebVR Specification,该提案给VR硬件定义了专门定制的接口,让开发者能够构建出沉浸感强,舒适度高的VR体验。但由于该标准还处于草案阶段,所以我们开发需要WebVR Polyfill,这个组件不需要特定浏览器,就可以使用WebVR API中的接口。
所以我们只需要在项目中,引入webvr-polyfill.js及VRControls、VREffect两个类,并调用即可。

1
2
vrEffect = new THREE.VREffect(renderer);
vrControls = new THREE.VRControls(camera);

webvr-polyfill基于普通浏览器实现了WebVR API 1.0功能;
VRControls更新摄像头信息,让用户以第一人称置于场景中;
VREffect负责分屏。

四、技术难点

4.1、程序与用户共同控制摄像头
当程序在自动移动镜头的过程中,允许用户四处观察,这时候需要一个辅助容器共同控制镜头旋转与移动。

1
2
3
4
5
6
7
8
9
10
11
// 添加摄像机
camera = new THREE.PerspectiveCamera(60, size.w / size.h, 1, 10000);
camera.position.set(0, 0, 0);
camera.lookAt(new THREE.Vector3(0,0,0));
 
// 辅助镜头移动
dolly  = dolly = new THREE.Group();
dolly.position.set(10, 40, 40);
dolly.rotation.y = Math.PI/10;
dolly.add(camera);
scene.add(dolly);

4.2、多重蒙板贴图

如上图所示,该地形由三种贴图通过蒙板共同合成,这时候我们需要使用自定义Shader来实现,由rbg三个通道控制显示。
核心代码(片元着色器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fragmentShader: [
    'uniform sampler2D texture1;',
    'uniform sampler2D texture2;',
    'uniform sampler2D texture3;',
    'uniform sampler2D mask;',
    'void main() {',
        'vec4 colorTexture1 = texture2D(texture1, vUv* 40.0);',
        'vec4 colorTexture2 = texture2D(texture2, vUv* 60.0);',
        'vec4 colorTexture3 = texture2D(texture3, vUv* 20.0);',
        'vec4 colorMask = texture2D(mask, vUv);',
        'vec3  outgoingLight = vec3( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b ) * 0.6;',
        'gl_FragColor =  vec4(outgoingLight, 1.0);',
    '}'
].join("\n")

完整代码(添加three.js灯光,雾化):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// 合成材质
var map1 = texLoader.load('cross-domain/skins/foor_stone02.png' );
var map2 = texLoader.load('cross-domain/skins/green_wet09.png');
var map3 = texLoader.load('cross-domain/skins/stone_dry02.png');
 
// 自定义复合蒙板shader
THREE.FogShader = {
    uniforms: lib.extend( [
 
        THREE.UniformsLib[ "fog" ],
        THREE.UniformsLib[ "lights" ],
        THREE.UniformsLib[ "shadowmap" ],
        {
            'texture1': { type: "t", value: map1},
            'texture2': { type: "t", value: map2},
            'texture3': { type: "t", value: map3},
            'mask': { type: "t", value: texLoader.load('cross-domain/skins/mask.png')}
        }
    ] ),
    vertexShader: [
        "varying vec2 vUv;",
        "varying vec3 vNormal;",
        "varying vec3 vViewPosition;",
 
        THREE.ShaderChunk[ "skinning_pars_vertex" ],
        THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
        THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ],
 
        "void main() {",
 
            THREE.ShaderChunk[ "skinbase_vertex" ],
            THREE.ShaderChunk[ "skinnormal_vertex" ],
 
 
            "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
 
            "vUv = uv;",
            "vNormal = normalize( normalMatrix * normal );",
            "vViewPosition = -mvPosition.xyz;",
 
            "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
            THREE.ShaderChunk[ "logdepthbuf_vertex" ],
        "}"
    ].join('\n'),
 
    fragmentShader: [
        'uniform sampler2D texture1;',
        'uniform sampler2D texture2;',
        'uniform sampler2D texture3;',
        'uniform sampler2D mask;',
 
        'varying vec2 vUv;',
        'varying vec3 vNormal;',
        'varying vec3 vViewPosition;',
        // "vec3 outgoingLight = vec3( 0.0 );",
        THREE.ShaderChunk[ "common" ],
        THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
        THREE.ShaderChunk[ "fog_pars_fragment" ],
        THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ],
 
        'void main() {',
            THREE.ShaderChunk[ "logdepthbuf_fragment" ],
            THREE.ShaderChunk[ "alphatest_fragment" ],
             
            'vec4 colorTexture1 = texture2D(texture1, vUv* 40.0);',
            'vec4 colorTexture2 = texture2D(texture2, vUv* 60.0);',
            'vec4 colorTexture3 = texture2D(texture3, vUv* 20.0);',
            'vec4 colorMask = texture2D(mask, vUv);',
 
            'vec3 normal = normalize( vNormal );',
            'vec3 lightDir = normalize( vViewPosition );',
 
            'float dotProduct = max( dot( normal, lightDir ), 0.0 ) + 0.2;',
 
            'vec3  outgoingLight = vec3( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b ) * 0.6;',
 
 
            THREE.ShaderChunk[ "shadowmap_fragment" ],
            THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
            THREE.ShaderChunk[ "fog_fragment" ],
 
            // 'gl_FragColor = vec4( colorTexture1.rgb*colorMask.r + colorTexture2.rgb *colorMask.g + colorTexture3.rgb *colorMask.b, 1.0 )  + vec4(outgoingLight, 1.0);',
            // 'gl_FragColor = outgoingLight;',
            'gl_FragColor =  vec4(outgoingLight, 1.0);',
        '}'
    ].join("\n")
 
};
THREE.FogShader.uniforms.texture1.value.wrapS = THREE.FogShader.uniforms.texture1.value.wrapT = THREE.RepeatWrapping;
THREE.FogShader.uniforms.texture2.value.wrapS = THREE.FogShader.uniforms.texture2.value.wrapT = THREE.RepeatWrapping;
THREE.FogShader.uniforms.texture3.value.wrapS = THREE.FogShader.uniforms.texture3.value.wrapT = THREE.RepeatWrapping;
 
var material = new THREE.ShaderMaterial({
    uniforms        : THREE.FogShader.uniforms,
    vertexShader    : THREE.FogShader.vertexShader,
    fragmentShader  : THREE.FogShader.fragmentShader,
    fog: true
});

3、 镜头移动(依赖Tween类)
功能函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
cameraTracker: function(paths){
    var tweens = [];
    for(var i = 0; i < paths.length; i++) {
        (function(i){
            var tween = new TWEEN.Tween({pos: 0}).to({pos: 1}, paths[i].duration || 5000);
            tween.easing(paths[i].easing || TWEEN.Easing.Linear.None);
            tween.onStart(function(){
                var oriPos =  dolly.position;
                var oriRotation = dolly.rotation;
                this.oriPos = {x: oriPos.x, y: oriPos.y, z: oriPos.z};
                this.oriRotation = {x: oriRotation.x, y: oriRotation.y, z: oriRotation.z};
            });
            tween.onUpdate(paths[i].onupdate || function(){
                if(paths[i].pos) {
                    dolly.position.x = this.oriPos.x + this.pos * (paths[i].pos.x -  this.oriPos.x);
                    dolly.position.y = this.oriPos.y + this.pos * (paths[i].pos.y -  this.oriPos.y);
                    dolly.position.z = this.oriPos.z + this.pos * (paths[i].pos.z -  this.oriPos.z);       
                }
                if(paths[i].rotation) {
                    dolly.rotation.x = this.oriRotation.x + this.pos * (paths[i].rotation.x -  this.oriRotation.x);
                    dolly.rotation.y = this.oriRotation.y + this.pos * (paths[i].rotation.y -  this.oriRotation.y);
                    dolly.rotation.z = this.oriRotation.z + this.pos * (paths[i].rotation.z -  this.oriRotation.z);
                }
            });
            tween.onComplete(function(){
                paths[i].fn && paths[i].fn();
                var fn = tweens.shift();
                fn && fn.start();
            });
            tweens.push(tween);
        })(i);
    }
    tweens.shift().start();
}

调用:

1
2
3
lib.cameraTracker([
    {'pos': { x: -45,y: 5, z: -38},'rotation': {x: 0, y: -1.8, z: 0},  'easing': TWEEN.Easing.Cubic.Out,'duration':4000}
]);

4、自适应长度文字提示
根据文字长度生成canvas作为贴图到Sprite对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
hint = function(text, type, posY, fadeTime){
    var chinense = text.replace(/[u4E00-u9FA5]/g, '');
    var dbc = chinense.length;
    var sbc = text.length - dbc;
    var length = dbc * 2 + sbc;
    var fontsize = 40;
    var textWidth = fontsize* length / 2;
    posY = posY || 0.3;
    type = type || 1;
    fadeTime = fadeTime === window.undefined ? 500 : fadeTime;
 
    if(text == 'sucess' || text == 'fail') {
        text = ' ';
    }
 
    var canvas = document.createElement("canvas");     
    var width = 1024, height = 512;
    canvas.width = width;
    canvas.height = height;
    var context = canvas.getContext('2d');
 
    var imageObj = document.querySelector('#img-hint-' + type);
     
 
    context.drawImage(imageObj, width/2 - imageObj.width/2, height/2 - imageObj.height/2);
    context.font = 'Bold '+ fontsize +'px simhei';
    context.fillStyle = "rgba(255,255,255,1)";
    context.fillText(text, width/2-textWidth/2, height/2+15);          
 
 
    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;
 
    var mesh;
    var material = new THREE.SpriteMaterial({
        map: texture,
        transparent: true,
        opacity: 0
    });
    mesh = new THREE.Sprite(material);
    mesh.scale.set(width/400, height/400, 1);
    mesh.position.set(0, posY, -3);
    camera.add(mesh);  
 
    var tweenIn = new TWEEN.Tween({pos: 0}).to({pos: 1}, fadeTime);
    tweenIn.onUpdate(function(){
        material.opacity = this.pos;
    });
    if(fadeTime === 0) {
        material.opacity = 1;
    else {
        tweenIn.start();
    }
     
 
    var tweenOut = new TWEEN.Tween({pos: 1}).to({pos: 0}, fadeTime);
    tweenOut.onUpdate(function(){
        material.opacity = this.pos;
    });
    tweenOut.onComplete(function(){
        camera.remove(mesh);
    });
    tweenOut.fadeOut = tweenOut.start;
    tweenOut.remove = function(){
        camera.remove(mesh);
    }
 
    return tweenOut;
};

5、unity地形导出
5.1、首先将unity地形导出为obj

5.2、然后导入3dmax,使用ThreeJSExporter.ms导出为js格式。

6、3dmax动画导出问题
6.1、动画导出错误
通常是对象为可编辑多边形,需要转换成网格对象。

操作步骤:
6.1.1、选择对象,右键转换为可编辑网络;
6.1.2、选择蒙皮修改器,重新蒙皮;
6.1.3、点击蒙皮修改器下的骨骼 > 添加,添加原有的骨骼。
6.2、动画导出错乱
很容易让人以为是权重出问题了,但就我自己多个项目动画导出的经验来看,大部分出现在骨骼添加上。在3dmax及unity中,不添加根节点往往不影响动画执行,但导出到three.js,需要添加根节点。如果问题还存在,则仔细观察是哪个骨骼引起的,多余骨骼或缺少骨骼都可能引起动画错乱。

五、完整的源代码及相应组件
点击下载
main.js - 完整的源代码
tween.min.js - 动画类
OrbitControls.js - 视图控制器,旋转、移动、缩放场景,方便调试
audio.min.js - motion音频组件,解决自动播放音频问题
其余vr相关组件上文已有介绍

 
分类: three.js

WebVR的更多相关文章

  1. 【腾讯Bugly干货分享】WebVR如此近-three.js的WebVR示例解析

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57c7ff1689a6c9121b1adb16 作者:苏晏烨 关于WebVR 最 ...

  2. 龙之谷手游WebVR技术分享

    主要面向Web前端工程师,需要一定Javascript及three.js基础:本文主要分享内容为基于three.js开发WebVR思路及碰到的问题:有兴趣的同学,欢迎跟帖讨论. 目录:一.项目体验1. ...

  3. [译]WebVR技术方案草案

    注:基于官方的.bs规范专用格式进行了翻译,但结果发现无法编译成html格式,所幸基本兼容.markdown格式. 中文翻译项目地址:https://github.com/web3d/webvr-sp ...

  4. Mozilla公布WebVR API标准草案

    随着信息技术的迅速发展,虚拟现实(Virtual Reality,VR)技术在近些年不断完善,其应用范围也变得十分广泛.为了搭建逼真的虚拟场景,VR技术一般都需要用到大量精美的图像和复杂的动作.因此, ...

  5. 前端资讯周报 3.13 - 3.19: WebVR来了!以及如何优化scroll事件性能

    每周一我都会分享上一周我订阅的技术站点中,和解决问题的过程中阅读到的值得分享的文章.这是迫使我学习的一个动力 本周推荐 Minecraft in WebVR with HTML Using A-Fra ...

  6. Three.js与webVR

    WebVR如此近 - three.js的WebVR示例程序解析 关于WebVR 最近VR的发展十分吸引人们的眼球,很多同学应该也心痒痒的想体验VR设备,然而现在的专业硬件价格还比较高,入手一个估计就要 ...

  7. 翻译 | 使用A-Frame打造WebVR版《我的世界》

    原文地址:Minecraft in WebVR with HTML Using A-Frame 原文作者:Kevin Ngo 译者:Felix 校对:阿希 我是 Kevin Ngo,一名就职于 Moz ...

  8. WebVR认识

    什么是WebVR WebVR是一种开放标准,可以在浏览器中体验VR,目标是让每个人都能轻松进入虚拟现实体验.

  9. 分享12款令人瞠目结舌的WebVR演示和实验效果

    不管你信不信, WebVR绝对是浏览器下一个让你激动的技术方向, 也许很快你就可以使用VR头显或者相关设备直接访问web内容和资源啦! 在这篇资源分享帖中,我们将介绍很多基于浏览器的VR演示和游戏,帮 ...

随机推荐

  1. linux awk浅析(转)

    Awk 是一种非常好的语言,同时有一个非常奇怪的名称.在本系列(共三篇文章)的第一篇文章中,Daniel Robbins 将使您迅速掌握 awk 编程技巧.随着本系列的进展,将讨论更高级的主题,最后将 ...

  2. HDOJ 1194 Beat the Spread!(简单题)

    Problem Description Superbowl Sunday is nearly here. In order to pass the time waiting for the half- ...

  3. 二叉树后序遍历的非递归算法(C语言)

    首先非常感谢‘hicjiajia’的博文:二叉树后序遍历(非递归) 这篇随笔开启我的博客进程,成为万千程序员中的一员,坚持走到更远! 折磨了我一下午的后序遍历中午得到解决,关键在于标记右子树是否被访问 ...

  4. android设置组件所占的比例

    当我们使用linearlayout线性布局,放置三个textview空间,设置android:layout_width属性为wrap_content,并分别设置android:layout_weigh ...

  5. iOS--创建uiscrollview

    //创建uiscrollview self.PageHeight = self.view.bounds.size.height; self.PageWidth = self.view.bounds.s ...

  6. js函数收藏:获取cookie值

    //先设置一段子cookie var d = new Date(); d.setMonth(d.getMonth() + 1); d = d.toGMTString(); var a = " ...

  7. js中的循环语句

    js中的循环语句可分为三种:1.while:2.do……while:3.for. while的语法为 while (exp) {    //statements;} var a=1,b=0; whil ...

  8. MarkWord - 可发布博客的 Markdown编辑器 代码开源

    因为前一段时间看到 NetAnalyzer 在Windows10系统下UI表现惨不忍睹,所以利用一段时间为了学习一下WPF相关的内容,于是停停写写,用了WPF相关的技术,两个星期做了一个Markdow ...

  9. <有序数组>转化为<按二分法遍历顺序排列的数组>(C++实现)

    在进行参数试错时,通常将可能的参数由小到大排列一个个进行测试,这样的测试顺序很多时候不太合理,因此写了一个按二分法遍历顺序排列的算法,通常能更快的找到合适的参数.代码如下: /************ ...

  10. [转]RecyclerView初探

    原文地址:http://www.grokkingandroid.com/first-glance-androids-recyclerview/ RecyclerView是去年谷歌I/O大会上随Andr ...