yoga 初探

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目录下存在的库)。

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

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

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏hbbliyong

你不可错过的二维码生成与解析-java后台与前端js都有

1.二维码分类   二维条码也有许多不同的码制,就码制的编码原理而言,通常分为三种类型。 线性堆叠式二维码 编码原理: 建立在一维条码基础之上,按需要堆积成...

4638
来自专栏分享达人秀

响应Android系统的事件

在开发Android应用时,有时候可能需要让应用程序随系统设置而进行调整,比如判断系统的屏幕方向、判断系统方向的方向导航设备等。除此之外,有时候可能还需...

1989
来自专栏Sorrower的专栏

Android弹窗二则: PopupWindow和AlertDialog

android.support.v7.app.AlertDialog.Builder builder = new android.support.v7.app....

1075
来自专栏生信技能树

转录组数据拼接之应用篇

前前后后接触了一些基因组和转录组拼接的工作,而且后期还会持续进行。期间遇到了各种各样莫名其妙的坑,也尝试了一些不同的方法和软件,简单做一个阶段性小结。上周的今天...

4326
来自专栏青蛙要fly的专栏

项目需求讨论-标题栏上的搜索功能

今天讲的就是一个很简单的具体开始时候遇到的需求,在标题栏中实现搜索功能,而且美工要求需要实现下面GIF图的效果,我就实现了下,可能不是最好的,有哪里可以更方便请...

581
来自专栏進无尽的文章

实践-小细节Ⅵ

有时候,UITableView 的cell个数很少,可是UITableView的headView又是一个有颜色背景的View,当我们下拉的时候,拉扯出来的区域也...

542
来自专栏xx_Cc的学习总结专栏

六天完成一个简单iOS App - 第一天

2715
来自专栏向治洪

记一个SwipeMenuListView侧滑删除错乱的Bug

做侧滑删除网上有很多方案,比如重写Listview实现滑动的监听,今天说下一个SwipeListView,这个是之前一个朋友在网上开源的一个封装组件,能够适用于...

2077
来自专栏小蠢驴iOS专题

使用Category+runtime简单解决高德地图定位问题

1825
来自专栏大数据挖掘DT机器学习

利用Python绘制MySQL数据图实现数据可视化

第1步:确保MySQL已安装且在运行 安装教程: 亲测:MySQL安装与python下的MySQLdb使用(附软件与模块包) 第2步:使用Python连接...

5255

扫码关注云+社区