2019-03-26
React Native 并没有使用 Android 原生的布局引擎,而是使用了 Facebook 的 Yoga 布局引擎。Yoga 引擎是跨平台的,它实现了 Flexbox 布局模式。在本文中,我们就来看看如何使用 React Native 的底层布局引擎 Yoga。
关于本文的实例代码请参见 MaxieeRNLab 的 YogaActivity。如果你觉得这个项目有所帮助,欢迎 Star!
在最初解除 Yoga 的时候,完全搞不懂它是怎么来进行布局的。原因是我对 Android 布局的概念先入为主了。
首先需要知道的是,Yoga 是一套完全独立的布局引擎,这也就是说它没有用到 Android 的布局逻辑。
为了完成这个思维转变,让我们先想想 Android 原生是如何布局的?
我们可能会写如下的布局:
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!"/> </LinearLayout>
在写下这段布局的时候,我们就已经在使用 Android 原生的布局逻辑了:
但是在本文中,我们要使用 Yoga 引擎来替换 Android 原生的布局引擎,因此上面的这些知识全部不会用到!我们需要将认知清空。
清空到什么程度呢?现在屏幕上只有一个坐标系。假设我们创建一个 TextView,如何将它摆到指定位置呢?通过向它指定 x、y 坐标。
Yoga 布局引擎实际上是一个数值计算引擎,它实际上是与 UI 视图无关的。
在 Yoga 中,布局的每个元素都是一个节点(YogaNode),这个 YogaNode 会代表界面上的某个元素(比如一个 TextView),但是两者没有直接关联。
比如假设我们要在屏幕上横向展示三个方块,对应的代码如下(Activity 的 onCreate 方法):
FrameLayout container = new FrameLayout(this); /** * 布局纯数值计算 */ float screenWidth = getWindowManager().getDefaultDisplay().getWidth(); float screenHeight = getWindowManager().getDefaultDisplay().getHeight(); YogaNode root = new YogaNode(); root.setWidth(screenWidth); root.setHeight(screenHeight); root.setFlexDirection(YogaFlexDirection.ROW); YogaNode rect1 = new YogaNode(); rect1.setHeight(VIEW_WIDTH); rect1.setWidth(VIEW_WIDTH); rect1.setMargin(YogaEdge.ALL, 20); YogaNode rect2 = new YogaNode(); rect2.setHeight(VIEW_WIDTH); rect2.setWidth(VIEW_WIDTH); rect2.setMargin(YogaEdge.ALL, 20); YogaNode rect3 = new YogaNode(); rect3.setHeight(VIEW_WIDTH); rect3.setWidth(VIEW_WIDTH); rect3.setMargin(YogaEdge.ALL, 20); root.addChildAt(rect1, 0); root.addChildAt(rect2, 1); root.addChildAt(rect3, 2); // 给定屏幕长宽,求解屏幕元素位置 root.calculateLayout(screenWidth, screenHeight); /** * Android 视图创建于定位 */ ViewGroup.LayoutParams lp = new FrameLayout.LayoutParams(VIEW_WIDTH, VIEW_WIDTH); View v1 = new View(this); v1.setLayoutParams(lp); v1.setBackgroundColor(Color.parseColor("#d50000")); View v2 = new View(this); v2.setLayoutParams(lp); v2.setBackgroundColor(Color.parseColor("#ff1744")); View v3 = new View(this); v3.setLayoutParams(lp); v3.setBackgroundColor(Color.parseColor("#ff5252")); container.addView(v1); container.addView(v2); container.addView(v3); v1.setX(rect1.getLayoutX()); v1.setY(rect1.getLayoutY()); v2.setX(rect2.getLayoutX()); v2.setY(rect2.getLayoutY()); v3.setX(rect3.getLayoutX()); v3.setY(rect3.getLayoutY()); setContentView(container);
其中:
这段代码执行(clone MaxieeRNLab 后编译参见效果)的效果如下:
有了上一节的基础后,下面让我们来看一个更复杂一些的例子(完整代码):
package com.maxieernlab.yoga; import android.graphics.Color; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import com.facebook.yoga.YogaEdge; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaNode; import java.util.ArrayList; public class YogaActivity1 extends AppCompatActivity { private static final int VIEW_WIDTH = 200; private float screenHeight; private float screenWidth; private ArrayList<View> poolView = new ArrayList<>(); private ArrayList<YogaNode> poolNode = new ArrayList<>(); private String[][] colors = new String[][] { new String[] { "#d50000", "#ff1744", "#ff5252", "#ff8a80" }, new String[] { "#c51162", "#f50057", "#ff4081", "#ff80ab" }, new String[] { "#aa00ff", "#d500f9", "#e040fb", "#ea80fc" }, new String[] { "#6200ea", "#651fff", "#7c4dff", "#b388ff" } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); FrameLayout container = new FrameLayout(this); screenWidth = getWindowManager().getDefaultDisplay().getWidth(); screenHeight = getWindowManager().getDefaultDisplay().getHeight(); log("Screen size = (%f, %f)", screenWidth, screenHeight); YogaNode root = new YogaNode(); root.setWidth(screenWidth); root.setHeight(screenHeight); root.setFlexDirection(YogaFlexDirection.COLUMN); log("start"); createRow1(root, 0); createRow1(root, 1); createRow1(root, 2); createRow1(root, 3); root.calculateLayout(screenWidth, screenHeight); for (int i = 0; i < poolView.size(); i++) { View v = poolView.get(i); YogaNode n = poolNode.get(i); YogaNode r = n.getOwner(); v.setX(r.getLayoutX() + n.getLayoutX()); v.setY(r.getLayoutY() + n.getLayoutY()); log("v%d position=(%f, %f)", i, r.getLayoutX() + n.getLayoutX(), r.getLayoutY() + n.getLayoutY()); container.addView(v); } log("end"); setContentView(container); } private void createRow1(YogaNode root, int index) { log("create index = " + index); YogaNode row = new YogaNode(); row.setHeight(VIEW_WIDTH); row.setWidth(VIEW_WIDTH * 4); row.setFlexDirection(YogaFlexDirection.ROW); row.setMargin(YogaEdge.ALL, 20); for (int i = 0; i < 4; i++) { YogaNode n = new YogaNode(); n.setWidth(VIEW_WIDTH); n.setHeight(VIEW_WIDTH); View v = createView(colors[index][i]); row.addChildAt(n, i); poolNode.add(n); poolView.add(v); } root.addChildAt(row, index); } private View createView(String color) { View v = new View(this); v.setBackgroundColor(Color.parseColor(color)); ViewGroup.LayoutParams lp = new FrameLayout.LayoutParams(VIEW_WIDTH, VIEW_WIDTH); v.setLayoutParams(lp); return v; } private void log(String template, Object... objects) { Log.d("max-yoga", String.format(template, objects)); } }
效果图如下:
其中:
至此我们就完成了对 React Native 底层布局引擎 Yoga 的学习。
有了这一基础,下一节我们就可以向 React Native 的 UI 部分发起进攻了!😆