yoga本是Facebook在React Native里引入的一种跨平台的基于CSS的布局系统,它实现了Flexbox规范,随着该系统不断完善,Facebook对其进行重启发布,并取名为yoga。详情可以参考https://facebook.github.io/ 。
yoga有如下特性:
2009年,W3C提出了一种新的方案——Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。
目前的前端主要是使用html/css/js实现,其中css用于前端的布局。任何一个html的容器可以通过css指定为flex布局,一旦一个容器被指定为flex布局,其子元素就可以按照flexbox的语法进行布局,但是设为Flex布局后,子元素的float、clear和vertical-align会失效。
指定flex布局的方式如下
.box{ display: flex;}
想要使用yoga,首先必须要了解flexbox的基本语法,因为yoga布局中的属性均是使用flexbox的语法进行设置的,只是具体的api不一样而已。flexbox的基本语法不是本文想要讲解的内容,但是在网上有很多学习资料,在yoga官网的( https://facebook.github.io/yoga/docs/flex-direction/ ) 中也有部分对flexbox的基本介绍,可以通过其进行一些了解。
3.1.1. Aspect ratio(宽高比)
Aspect ratio(宽高比)是yoga较之于flexbox规范新增的属性,主要适用于已知一个方向的长度以及宽高比的布局情况,比如说video,images或其他多媒体类型,它接受大于0的float类型的值。 Aspect ratio的特性
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,用于适应某些国家或某些人的书写方向的习惯。
方法一:自己安装使用buck编译git项目进行环境配置
方法二:从示例工程获取已存在的yoga模块导入使用
可以看到通过方法二便可以使用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的特性。
如何在android上使用yoga布局呢?这里首先要复习一下http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html , 因为在yoga的javaApi与css中设置flex属性有极大的类似。这里对其分别简述一下方便对比。
3.3.1. Flexbox在html/css上的使用
3.3.2. Yoga在android上的使用
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);
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);
<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 获取。
可以只写一次布局,然后在不同终端上让yoga自己计算布局情况,并进行布局。
总的来说yoga的概念虽好,但是还不太成熟,对于android这块,还没有进行相应的适配,无法和android自带的控件结合起来,导致实现麻烦且很多android布局无法实现。不过如果结合起来了,又怎么跨平台布局呢?所以说yoga还有很长一段路要走。
(FAQ主要为编译yoga的过程中出现的问题)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。