Android

yoga-像写网页一样写客户端界面

这几年大前端化越演越烈,越来越有融合的趋势,前端和客户端越来越傻傻分不清了,今天介绍一个款facebook开源的布局算法库yoga,让大家可以在客户端用前端网页常用的Flex布局,绝对布局完成布局效果,也许你会问我客户端元素的ConstrainLayout,AutoLayout这类的不香吗,对于原生开发者当然香,但让前端开发来写就不那么香了,也需你会反过来问我,元素开发写网页布局方式就香吗?答案是确实香,因为前端的那套Flex布局,一句话总结就是简单,简单,很简单

Flex布局多容易理解,参考阮一峰老师的Flex介绍,我们来讲讲怎么在android客户端用这个布局方式,因为这么好的东西,网上竟然找不到太多android使用的方式,连官方都没有维护一套,那我就手撸一个。

如何使用yoga

话不多说,先建立一个android 工程,然后引入最新的yoga库

compile 'com.facebook.yoga.android:yoga-layout:1.16.0'

简单写个布局

<?xml version="1.0" encoding="utf-8"?>
<com.facebook.yoga.android.YogaLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:yoga="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    yoga:yg_flexDirection="column"
    yoga:yg_justifyContent="space_between"
    yoga:yg_alignItems="center"
    >

    <TextView
        android:id="@+id/absolute"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        yoga:yg_positionType="absolute"
        android:background="@android:color/darker_gray"
        android:textColor="@android:color/white"
        android:padding="5dp"
        android:gravity="center"
        yoga:yg_positionTop="10dp"
        yoga:yg_positionLeft="10dp"
        />

    <TextView
        android:id="@+id/top"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:textColor="@android:color/white"
        android:padding="5dp"
        yoga:yg_marginTop="60dp"
        />
    <com.facebook.yoga.android.YogaLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        yoga:yg_flexDirection="row"
        yoga:yg_justifyContent="flex_start"
        yoga:yg_alignItems="center"
        yoga:yg_alignSelf="center"
        >
        <TextView
            android:id="@+id/middle_left"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:background="@android:color/darker_gray"
            android:textColor="@android:color/white"
            android:padding="5dp"
            yoga:yg_alignSelf="center"
            android:gravity="center"
            />
        <TextView
            android:id="@+id/middle"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:background="@android:color/darker_gray"
            android:textColor="@android:color/white"
            android:padding="5dp"
            yoga:yg_alignSelf="center"
            android:gravity="center"
            />
        <TextView
            android:id="@+id/middle_right"
            android:layout_width="wrap_content"
            android:layout_height="70dp"
            android:background="@android:color/darker_gray"
            android:textColor="@android:color/white"
            android:padding="5dp"
            yoga:yg_alignSelf="center"
            android:gravity="center"
            />
    </com.facebook.yoga.android.YogaLayout>

    <TextView
        android:id="@+id/bottom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:textColor="@android:color/white"
        android:padding="5dp"
        />


</com.facebook.yoga.android.YogaLayout>

看看效果

当然了只写xml其实是不够的,还需要在Application引入下yoga的使用

public class YogaApplication extends Application{

public void onCreate(){
super.onCreate();
SoLoader.init(this, false);
LayoutInflater.from(this).setFactory(YogaViewLayoutFactory.getInstance());
}
}

整个demo代码我上传到GitHub地址

yoga细节初探

上面我们大概介绍了这个yoga的使用,现在我们来看看一些细节使用

属性值

这部分相对简单,yoga定义给android xml的属性跟flex本身的很类似

yoga:yg_flexDirection="column"

命名空间用yoga,具体属性值用yg _开头,然后加具体名称,yoga官方并没有提供所有属性值的写法,我们引入yoga之后可以在studio上通过jar直接查看到

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="yoga"><attr format="enum" name="yg_alignContent">
      <enum name="auto" value="0"/>
      <enum name="flex_start" value="1"/>
      <enum name="center" value="2"/>
      <enum name="flex_end" value="3"/>
      <enum name="stretch" value="4"/>
      <enum name="baseline" value="5"/>
    </attr><attr format="enum" name="yg_alignItems">
      <enum name="auto" value="0"/>
      <enum name="flex_start" value="1"/>
      <enum name="center" value="2"/>
      <enum name="flex_end" value="3"/>
      <enum name="stretch" value="4"/>
      <enum name="baseline" value="5"/>
    </attr><attr format="enum" name="yg_alignSelf">
      <enum name="auto" value="0"/>
      <enum name="flex_start" value="1"/>
      <enum name="center" value="2"/>
      <enum name="flex_end" value="3"/>
      <enum name="stretch" value="4"/>
      <enum name="baseline" value="5"/>
    </attr>
    <attr format="float" name="yg_aspectRatio"/>
    <attr format="dimension" name="yg_borderLeft"/>
    <attr format="dimension" name="yg_borderTop"/>
    <attr format="dimension" name="yg_borderRight"/>
    <attr format="dimension" name="yg_borderBottom"/>
    <attr format="dimension" name="yg_borderStart"/>
    <attr format="dimension" name="yg_borderEnd"/>
    <attr format="dimension" name="yg_borderHorizontal"/>
    <attr format="dimension" name="yg_borderVertical"/>
    <attr format="dimension" name="yg_borderAll"/>
    <attr format="enum" name="yg_direction">
      <enum name="inherit" value="0"/>
      <enum name="ltr" value="1"/>
      <enum name="rtl" value="2"/>
    </attr><attr format="enum" name="yg_display">
      <enum name="flex" value="0"/>
      <enum name="none" value="1"/>
    </attr>
    <attr format="float" name="yg_flex"/>
    <attr format="float|string" name="yg_flexBasis"/>
    <attr format="enum" name="yg_flexDirection">
      <enum name="column" value="0"/>
      <enum name="column_reverse" value="1"/>
      <enum name="row" value="2"/>
      <enum name="row_reverse" value="3"/>
    </attr>
    <attr format="float" name="yg_flexGrow"/>
    <attr format="float" name="yg_flexShrink"/>
    <attr format="dimension|string" name="yg_height"/>
    <attr format="enum" name="yg_justifyContent">
      <enum name="flex_start" value="0"/>
      <enum name="center" value="1"/>
      <enum name="flex_end" value="2"/>
      <enum name="space_between" value="3"/>
      <enum name="space_around" value="4"/>
    </attr>
    <attr format="dimension|string" name="yg_marginLeft"/>
    <attr format="dimension|string" name="yg_marginTop"/>
    <attr format="dimension|string" name="yg_marginRight"/>
    <attr format="dimension|string" name="yg_marginBottom"/>
    <attr format="dimension|string" name="yg_marginStart"/>
    <attr format="dimension|string" name="yg_marginEnd"/>
    <attr format="dimension|string" name="yg_marginHorizontal"/>
    <attr format="dimension|string" name="yg_marginVertical"/>
    <attr format="dimension|string" name="yg_marginAll"/>
    <attr format="dimension|string" name="yg_maxHeight"/>
    <attr format="dimension|string" name="yg_maxWidth"/>
    <attr format="dimension|string" name="yg_minHeight"/>
    <attr format="dimension|string" name="yg_minWidth"/>
    <attr format="enum" name="yg_overflow">
      <enum name="visible" value="0"/>
      <enum name="hidden" value="1"/>
      <enum name="scroll" value="2"/>
    </attr>
    <attr format="dimension|string" name="yg_paddingLeft"/>
    <attr format="dimension|string" name="yg_paddingTop"/>
    <attr format="dimension|string" name="yg_paddingRight"/>
    <attr format="dimension|string" name="yg_paddingBottom"/>
    <attr format="dimension|string" name="yg_paddingStart"/>
    <attr format="dimension|string" name="yg_paddingEnd"/>
    <attr format="dimension|string" name="yg_paddingHorizontal"/>
    <attr format="dimension|string" name="yg_paddingVertical"/>
    <attr format="dimension|string" name="yg_paddingAll"/>
    <attr format="dimension|string" name="yg_positionLeft"/>
    <attr format="dimension|string" name="yg_positionTop"/>
    <attr format="dimension|string" name="yg_positionRight"/>
    <attr format="dimension|string" name="yg_positionBottom"/>
    <attr format="dimension|string" name="yg_positionStart"/>
    <attr format="dimension|string" name="yg_positionEnd"/>
    <attr format="dimension|string" name="yg_positionHorizontal"/>
    <attr format="dimension|string" name="yg_positionVertical"/>
    <attr format="dimension|string" name="yg_positionAll"/>
    <attr format="enum" name="yg_positionType">
      <enum name="relative" value="0"/>
      <enum name="absolute" value="1"/>
    </attr><attr format="dimension|string" name="yg_width"/><attr format="enum" name="yg_wrap">
      <enum name="no_wrap" value="0"/>
      <enum name="wrap" value="1"/>
    </attr></declare-styleable>
</resources>

设置控件工场

SoLoader.init(this, false);
LayoutInflater.from(this).setFactory(YogaViewLayoutFactory.getInstance());

我们的demo在Application有两行的初始化操作,第一个是加载yoga的so库,第二步其实就是设置一个yoga的控件工场,里面的核心部分其实就是解析出yoga提供的YogaLayout和VirtualYogaLayout。

@Override
  public View onCreateView(String name, Context context, AttributeSet attrs) {
    if (YogaLayout.class.getSimpleName().equals(name)) {
      return new YogaLayout(context, attrs);
    }
    if (VirtualYogaLayout.class.getSimpleName().equals(name)) {
      return new VirtualYogaLayout(context, attrs);
    }
    return null;
  }

setFactory也算是android对外提供的一个hook接口,让我们可以自定义对xml的一些操作,比如一些昼夜换肤方案,修改字体库等方案。

再来看看YogaLayout内部如何实现

YogaLayout其实就是一层封装,所有跟flex布局有关的子控件都被转成一个YogaNode

YogaNode的实质又只是一个YogaNodeJNIFinalizer,YogaNodeJNIFinalizer继承了YogaNodeJNIBase,内部都是native方法,所以其实Yoga的布局算法都是native实现的,这就保证了yoga的算法效率和跨平台特性,因此我们可以大胆放心地使用yoga库。

结尾

yoga本身的使用并不复杂,但最大的启发是利用c++的跨平台优势和性能优势做多端统一,同时对外给各个端提供简洁易于使用接近各自平台语言的开发方式,可以最大程度地推广自身,比如yoga其实就用在了像react-native,Lito,ComponentKit的底层框架上,笔者所在公司也有类似的使用,相信随着时间推移,整个大前端方向会有越来越多的类似方案出现。

Tagged

About chenzujie

非著名码农一枚,认真工作,快乐生活
View all posts by chenzujie →

3 thoughts on “yoga-像写网页一样写客户端界面

发表评论

邮箱地址不会被公开。 必填项已用*标注