前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >yoga 初探

yoga 初探

原创
作者头像
QQ音乐技术团队
修改2017-10-18 09:45:09
7.5K2
修改2017-10-18 09:45:09
举报

1. yoga简介

yoga本是Facebook在React Native里引入的一种跨平台的基于CSS的布局系统,它实现了Flexbox规范,随着该系统不断完善,Facebook对其进行重启发布,并取名为yoga。详情可以参考https://facebook.github.io/

yoga有如下特性:

  • 完全兼容Flexbox布局,遵守W3C的规范。
  • 支持Java、C#、Objective-C、C四种语言。
  • 底层代码使用C语言编写,性能不是问题,并且可以更容易跟其他平台集成。
  • 支持流行框架如React Native。

2. flexbox简介

2009年,W3C提出了一种新的方案——Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

目前的前端主要是使用html/css/js实现,其中css用于前端的布局。任何一个html的容器可以通过css指定为flex布局,一旦一个容器被指定为flex布局,其子元素就可以按照flexbox的语法进行布局,但是设为Flex布局后,子元素的float、clear和vertical-align会失效。

指定flex布局的方式如下

.box{    display: flex;}

flexbox的基本语法

想要使用yoga,首先必须要了解flexbox的基本语法,因为yoga布局中的属性均是使用flexbox的语法进行设置的,只是具体的api不一样而已。flexbox的基本语法不是本文想要讲解的内容,但是在网上有很多学习资料,在yoga官网的( https://facebook.github.io/yoga/docs/flex-direction/ ) 中也有部分对flexbox的基本介绍,可以通过其进行一些了解。

3. yoga应用

3.1. yoga新增内容

3.1.1. Aspect ratio(宽高比)

Aspect ratio(宽高比)是yoga较之于flexbox规范新增的属性,主要适用于已知一个方向的长度以及宽高比的布局情况,比如说video,images或其他多媒体类型,它接受大于0的float类型的值。 Aspect ratio的特性

  • Aspect ratio依赖于item的最小和最大尺寸。
  • Aspect ratio比flexGrow有更高的优先级。
  • 如果item的Aspect ratio,width和height属性均被定义,则cross轴的尺寸被覆盖(flexbox规范有main轴和cross轴的概念,由flex容器的flex-direction属性指定main轴的方向)。

3.1.2. RTL(自右向左布局)

web前端不支持RTL,而yoga支持。

yoga对于Margin,Padding和Border新增了start和end的值,当当前布局方向为RTL时,start表示右边而end表示左边,这与LTR布局恰好相反。熟悉android xml布局的可能对start和end属性比较熟悉,因为android在4.2之后也引入了start和end,用于适应某些国家或某些人的书写方向的习惯。

3.2. yoga搭配android开发的环境配置

方法一:自己安装使用buck编译git项目进行环境配置

  1. 由于yoga是使用buck构建的,所以首先需要在你的电脑上安装buck,通过buck 官网(https://buckbuild.com/setup/getting_started.html) 可以了解如何安装buck和使用buck进行编译,网上也有许多关于buck构建的资料可以自行查找,这里就不进行赘述了。
  2. 从github上获取yoga(https://github.com/facebook/yoga), 在yoga目录下使用buck进行编译,由于这里只是进行android开发环境的配置,所以目前只需要编译java目录下的代码即可。编译命令为buck build //java:java 和 buck build //java:jni。编译过程中出现的一些问题可以在文章最后的FAQ章节看到。
  3. 编译完成后,在yoga目录下的buck-out下找到了许多jar包,但是并没有编译出so库,尝试各种方法都没有办法输出so库,根据http://www.jianshu.com/p/d4289b16a133 中所说的,使用buck build //java:jni#android-armv7,shared命令编译,则出现了build failed:Unrecognized flavor in target //java:jni#android-armv7,shared错误。错误信息说是指定的编译属性无法识别,查看buck文档(https://buckbuild.com/command/build.html), 文档中没有这样指定编译属性的说明,网上这方面的资源也比较少,找不到相关的错误和解决办法,摆弄好久都没有解决掉,所以这块只能暂时放在一边了。

方法二:从示例工程获取已存在的yoga模块导入使用

  1. 获取并编译https://github.com/koudle/AndYogaSample , 运行成功,其项目结构只是一个普通的android工程+导入的外部工程模块yoga。
  2. 新建一个android工程,通过file -> new -> new module -> Import Gradle Project导入AndYogaSample下的yoga模块,并切换到project视图,右键点击自己工程的app->open modules setting,打开工程结构,选择modules下的app后选择Dependencies的tab,添加新的模块依赖yoga后重新执行一遍gradle即可。(也可以再gradle文件中直接加入compile project(':yoga'))
  3. 接着便可以在新建的工程中使用Yoga的java api编写相关代码,并成功运行。

可以看到通过方法二便可以使用yoga特性和javaApi编写代码了,其关键在于这个yoga模块,现在来看看这个yoga模块到底是什么吧。

yoga模块中libs目录下主要有两个jar包,jsr305.jar和soloader-0.1.0.jar,可以在方法一的第3点中所说的buck-out目录中找到。src目录下为java代码和一些so库,java代码为https://github.com/facebook/yoga 中java目录下的java代码,so库则是http://www.jianshu.com/p/d4289b16a133 中所说的生成的一些so库,但是通过方法一无法成功生成这些so库,只好借用一下别人生成的。

这个yoga模块,可能是以下三种来源中的一个。一是facebook对外发布的,二是github上yoga项目生成的,三是该示例的作者自己写的,具体情况还不确定,但是有一点可以确定,就是在新建项目或者已存在的项目通过导入该模块便可以直接使用yoga的特性。

3.3. yoga在android上的使用

如何在android上使用yoga布局呢?这里首先要复习一下http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html , 因为在yoga的javaApi与css中设置flex属性有极大的类似。这里对其分别简述一下方便对比。

3.3.1. Flexbox在html/css上的使用

  • 使用html语言指定容器和子项目<div class="second-face"> <span class="pip"></span> <span class="pip"></span> </div>
  • 使用css语言指定容器为flex布局.second-face { display: flex; }
  • 设置容器属性以及子项目布局属性.second-face { justify-content: space-between; } .second-face .pip:nth-of-type(2) { align-self: flex-end; }
  • 设置容器外观属性和子项目外观属性 [class$="face"] { margin: 16px; padding: 4px; background-color: #e7e7e7; width: 104px; height: 104px; object-fit: contain; box-shadow: inset 0 5px white, inset 0 -5px #bbb, inset 5px 0 #d7d7d7, inset -5px 0 #d7d7d7; border-radius: 10%; } .pip { display: block; width: 24px; height: 24px; border-radius: 50%; margin: 4px; background-color: #333; box-shadow: inset 0 3px #111, inset 0 -3px #555; }
  • 执行结果

3.3.2. Yoga在android上的使用

  • 创建YogaNode YogaNode root = new YogaNode(); YogaNode image1 = new YogaNode(); YogaNode image2 = new YogaNode();
  • 属性设置
root.setWidth(getWindowManager().getDefaultDisplay().getWidth() / 2);
root.setHeight(getWindowManager().getDefaultDisplay().getWidth() / 2);
root.setJustifyContent(YogaJustify.SPACE_BETWEEN);

image1.setHeight(60 * getResources().getDisplayMetrics().density);
image1.setWidth(60 * getResources().getDisplayMetrics().density);
image1.setMargin(YogaEdge.ALL, 15 * getResources().getDisplayMetrics().density);

image2.setHeight(60 * getResources().getDisplayMetrics().density);
image2.setWidth(60 * getResources().getDisplayMetrics().density);
image2.setMargin(YogaEdge.ALL, 15 * getResources().getDisplayMetrics().density);
image2.setAlignSelf(YogaAlign.FLEX_END);
  • 设置容器和子项目,与flexbox不同的是,这里不需要进行指定布局方式,而是只要有child的就是容器,没有child的就是子项目。root.addChildAt(image1, 0); root.addChildAt(image2, 1);
  • 计算布局root.calculateLayout();
  • 获取布局结果 text.getLayoutX(); text.getLayoutY();
  • 创建android自带控件并根据布局结果设置控件位置属性,因为yoga和android的适配目前还不完善,在android上使用yoga布局只能获取布局计算结果并且自己设置。
ImageView imageView1 = new ImageView(this);
imageView1.setImageResource(R.drawable.little_point);
imageView1.setX(image1.getLayoutX());
imageView1.setY(image1.getLayoutY());
layout.addView(imageView1);

ImageView imageView2 = new ImageView(this);
imageView2.setImageResource(R.drawable.little_point);
imageView2.setX(image2.getLayoutX());
imageView2.setY(image2.getLayoutY());
layout.addView(imageView2);
  • 设置容器外观属性以及子项目外观属性。由于yoga无法设置外观属性,实际上设置的外观属性还是针对android控件来设置,使用xml简单设置一下外观,以下分别是背景和小圆点。
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <stroke android:color="@color/colorGrey"
        android:width="3dp"/>
</shape>

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/colorGrey"/>
    <size android:height="60dp" android:width="60dp"/>
    <stroke android:width="3dp"
        android:color="@color/colorBlack"/>
</shape>
  • 执行结果

可以看出,这两种布局方式大同小异,都是指定容器和子项目,并设置其属性。其属性设置也均符合flex规范,只是一个直接指定,一个调用方法指定。不同的是使用yoga需要调用root.calculateLayout();获取布局结果,而布局结果也只是每个YogaNode中保存了一些位置和大小等信息,并不是向html/css中直接有ui显示布局结果。

按照上述所说,使用YogaNode的calculateLayout()方法后,每个YogaNode内就保存了它在屏幕上应该存在的位置与大小,但是这些属性如何使用在android的布局系统上呢?或者说如何与android的布局联系起来呢?

之前有一个想法是,通过这些属性信息,再对android的控件的属性信息设置,或者说,Yoga本身就进行了这样的处理。

于是看了看官网,按照官网上所说,Data用来将yoga和其他布局系统联系起来的参数,使用Data能够使一个object和一个YogaNode联系起来。

于是编写如下代码:

YogaNode text = new YogaNode();
Button button = new Button(this);
button.setText("test");
text.setData(button);

再次运行程序,屏幕上并没有显示button。所以可能确实是要自己根据yoga计算的布局结果来进行相应的设置,这样一来就特别麻烦,需要自己写一个库来进行相应的处理以便进行这些设置。

这里简要写一个可以显示布局效果的demo,通过仿照http://codepen.io/LandonSchropp/pen/KpzzGo 中的骰子5写的,其中的控件的位置是通过计算结果然后自己设置的,所以代码比较丑。

首先使用yoga进行布局

YogaNode root = new YogaNode();
root.setWidth(getWindowManager().getDefaultDisplay().getWidth() / 2);
root.setHeight(getWindowManager().getDefaultDisplay().getWidth() / 2);
root.setJustifyContent(YogaJustify.SPACE_BETWEEN);

layout.getLayoutParams().width = getWindowManager().getDefaultDisplay().getWidth() / 2;
layout.getLayoutParams().height = getWindowManager().getDefaultDisplay().getWidth() / 2;
layout.requestLayout();

YogaNode row1 = new YogaNode();
row1.setJustifyContent(YogaJustify.SPACE_BETWEEN);
row1.setFlexDirection(YogaFlexDirection.ROW);
root.addChildAt(row1, 0);

YogaNode image1 = new YogaNode();
image1.setHeight(60 * getResources().getDisplayMetrics().density);
image1.setWidth(60 * getResources().getDisplayMetrics().density);
image1.setAlignSelf(YogaAlign.CENTER);
root.addChildAt(image1, 1);

YogaNode row2 = new YogaNode();
row2.setJustifyContent(YogaJustify.SPACE_BETWEEN);
row2.setFlexDirection(YogaFlexDirection.ROW);
root.addChildAt(row2, 2);

YogaNode image2 = new YogaNode();
image2.setHeight(60 * getResources().getDisplayMetrics().density);
image2.setWidth(60 * getResources().getDisplayMetrics().density);
image2.setMargin(YogaEdge.ALL, 5 * getResources().getDisplayMetrics().density);
row1.addChildAt(image2, 0);

YogaNode image3 = new YogaNode();
image3.setHeight(60 * getResources().getDisplayMetrics().density);
image3.setWidth(60 * getResources().getDisplayMetrics().density);
image3.setMargin(YogaEdge.ALL, 5 * getResources().getDisplayMetrics().density);
row1.addChildAt(image3, 1);

YogaNode image4 = new YogaNode();
image4.setHeight(60 * getResources().getDisplayMetrics().density);
image4.setWidth(60 * getResources().getDisplayMetrics().density);
image4.setMargin(YogaEdge.ALL, 5 * getResources().getDisplayMetrics().density);
row2.addChildAt(image4, 0);

YogaNode image5 = new YogaNode();
image5.setHeight(60 * getResources().getDisplayMetrics().density);
image5.setWidth(60 * getResources().getDisplayMetrics().density);
image5.setMargin(YogaEdge.ALL, 5 * getResources().getDisplayMetrics().density);
row2.addChildAt(image5, 1);

root.calculateLayout();

接着新建动态ImageView并且根据布局结果设置ImageView的属性

ImageView imageView1 = new ImageView(this);
imageView1.setImageResource(R.drawable.little_point);
imageView1.setX(image1.getLayoutX());
imageView1.setY(image1.getLayoutY());
layout.addView(imageView1);

ImageView imageView2 = new ImageView(this);
imageView2.setImageResource(R.drawable.little_point);
imageView2.setX(image2.getLayoutX());
imageView2.setY(image2.getLayoutY());
layout.addView(imageView2);

ImageView imageView3 = new ImageView(this);
imageView3.setImageResource(R.drawable.little_point);
imageView3.setX(image3.getLayoutX());
imageView3.setY(image3.getLayoutY());
layout.addView(imageView3);

ImageView imageView4 = new ImageView(this);
imageView4.setImageResource(R.drawable.little_point);
imageView4.setX(image4.getLayoutX() + row2.getLayoutX());
imageView4.setY(image4.getLayoutY() + row2.getLayoutY());
layout.addView(imageView4);

ImageView imageView5 = new ImageView(this);
imageView5.setImageResource(R.drawable.little_point);
imageView5.setX(image5.getLayoutX() + row2.getLayoutX());
imageView5.setY(image5.getLayoutY() + row2.getLayoutY());
layout.addView(imageView5);

执行结果如下,代码可以从https://github.com/zerosnow/YogaTest 获取。

4.总结

  • 优点

可以只写一次布局,然后在不同终端上让yoga自己计算布局情况,并进行布局。

  • 缺点
  • yoga在android上的api并不太好用,使用YogaNode.calculateLayout()计算出的结果只是一些位置信息,并且无法兼容android自带的控件,需要自行加些适配。
  • 对现有代码的入侵特别大,需要将所有的xml的布局替换成yoga的布局模式。
  • 由于属性的局限性,部分xml可以实现的布局使用yoga无法实现。

总的来说yoga的概念虽好,但是还不太成熟,对于android这块,还没有进行相应的适配,无法和android自带的控件结合起来,导致实现麻烦且很多android布局无法实现。不过如果结合起来了,又怎么跨平台布局呢?所以说yoga还有很长一段路要走。

FAQ

(FAQ主要为编译yoga的过程中出现的问题)

  1. 编译一开始会出现找不到buck-out下部分文件的错误。 解决办法:删去java目录下buck文件test相关代码之后编译正确。可能是某些library不存在,但是test部分是用于验证自己修改yoga源代码是否正确,提交前测试的,所以目前并不需要这部分,所以可以暂时删去。
  2. 然后会出现未指定ndk_home和sdk_home的错误, 解决办法是:新建一个local.properties文件指定sdk.dir或者在环境变量中设置ANDROID_HOME or ANDROID_SDK即可,因为之前都是在android studio中设置的,没有设置环境变量。
  3. No Android platform target specified. 使用default的情况,即addon-google-21,但是打开sdk的相应文件夹发现没有该文件夹,所以考虑使用sdk manage下载相应的库。(这个还有一个解决办法是指定Android platform target为 sdk\add-ons目录下存在的库)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. yoga简介
  • 2. flexbox简介
    • flexbox的基本语法
    • 3. yoga应用
      • 3.1. yoga新增内容
        • 3.2. yoga搭配android开发的环境配置
          • 3.3. yoga在android上的使用
          • 4.总结
          • FAQ
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档