Separate YogaLayout library from sample app

Summary: The sample app has library-like elements (i.e. the `YogaLayout` and associated files).  This separates them out into a separate tree.

Reviewed By: emilsjolander

Differential Revision: D4558263

fbshipit-source-id: 33f8cec99661ef20e0e882663b554c12ae33c12f
This commit is contained in:
Robert Spencer
2017-02-14 07:54:20 -08:00
committed by Facebook Github Bot
parent d0bdd4dc91
commit a66339d63c
12 changed files with 100 additions and 13 deletions

View File

@@ -1,9 +1,8 @@
# Copyright (c) 2014-present, Facebook, Inc.
# Copyright 2014-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# This source code is licensed under the license found in the
# LICENSE-examples file in the root directory of this source tree.
include_defs('//YOGA_DEFS')
@@ -21,6 +20,9 @@ android_resource(
name = 'res',
res = 'res',
package = 'com.facebook.samples.yoga',
deps = [
ANDROID_RES_TARGET,
],
visibility = [
'PUBLIC',
],

View File

@@ -11,17 +11,13 @@ android_library(
name = 'yoga',
srcs = glob(['**/*.java']),
deps = [
ANDROID_JAVA_TARGET,
ANDROID_SAMPLE_RES_TARGET,
ANDROID_SUPPORT_TARGET,
APPCOMPAT_TARGET,
JAVA_TARGET,
SOLOADER_TARGET,
],
visibility = [
'PUBLIC',
]
)
project_config(
src_target = ":yoga"
)

View File

@@ -13,6 +13,7 @@ import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import com.facebook.samples.yoga.R;
import com.facebook.yoga.android.YogaViewLayoutFactory;
/**
* An activity to show off Yoga in Android. This activity shows a simple layout (defined in

View File

@@ -17,6 +17,7 @@ import android.view.LayoutInflater;
import com.facebook.samples.yoga.R;
import com.facebook.soloader.SoLoader;
import com.facebook.yoga.android.YogaViewLayoutFactory;
/**
* A (non-interactive) splash screen. Displays for two seconds before calling the main activity.

View File

@@ -1,150 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.samples.yoga;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.yoga.YogaNode;
/**
* Much like a {@link YogaLayout}, except this class does not render itself (the container) to the
* screen. As a result, <i>do not use this if you wish the container to have a background or
* foreground</i>. However, all of its children will still render as expected.
*
* <p>
* In practice, this class never added to the View tree, and all its children become children of its
* parent. As a result, all the layout (such as the traversal of the tree) is performed by Yoga
* (and so natively) increasing performance.
*/
public class VirtualYogaLayout extends ViewGroup {
final private List<View> mChildren = new LinkedList<>();
final private Map<View, YogaNode> mYogaNodes = new HashMap<>();
final private YogaNode mYogaNode = new YogaNode();
public VirtualYogaLayout(Context context) {
super(context);
}
public VirtualYogaLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VirtualYogaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
YogaLayout.LayoutParams lp = new YogaLayout.LayoutParams(context, attrs);
YogaLayout.applyLayoutParams(lp, mYogaNode, this);
}
public YogaNode getYogaNode() {
return mYogaNode;
}
/**
* Called to add a view, creating a new yoga node for it and adding that yoga node to the parent.
* If the child is a {@link VirtualYogaLayout}, we simply transfer all its children to this one
* in a manner that maintains the tree, and add its root to the tree.
*
* @param child the View to add
* @param index the position at which to add it (ignored)
* @param params the layout parameters to apply
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof VirtualYogaLayout) {
((VirtualYogaLayout) child).transferChildren(this);
final YogaNode childNode = ((VirtualYogaLayout) child).getYogaNode();
mYogaNode.addChildAt(childNode, mYogaNode.getChildCount());
return;
}
YogaNode node = new YogaNode();
YogaLayout.LayoutParams lp = new YogaLayout.LayoutParams(params);
YogaLayout.applyLayoutParams(lp, node, child);
node.setData(child);
node.setMeasureFunction(new YogaLayout.ViewMeasureFunction());
mYogaNode.addChildAt(node, mYogaNode.getChildCount());
addView(child, node);
}
/**
* Called to add a view with a corresponding node, but not to change the Yoga tree in any way.
*
* @param child the View to add
* @param node the corresponding yoga node
*/
public void addView(View child, YogaNode node) {
mChildren.add(child);
mYogaNodes.put(child, node);
}
/**
* Gives up children {@code View}s to the parent, maintaining the Yoga tree. This function calls
* {@link YogaLayout#addView(View, YogaNode)} or {@link VirtualYogaLayout#addView(View, YogaNode)}
* on the parent to add the {@code View} without generating new yoga nodes.
*
* @param parent the parent to pass children to (must be a YogaLayout or a VirtualYogaLayout)
*/
protected void transferChildren(ViewGroup parent) {
if (parent instanceof VirtualYogaLayout) {
for (View child : mChildren) {
((VirtualYogaLayout) parent).addView(child, mYogaNodes.get(child));
}
} else if (parent instanceof YogaLayout) {
for (View child : mChildren) {
((YogaLayout) parent).addView(child, mYogaNodes.get(child));
}
} else {
throw new RuntimeException("VirtualYogaLayout cannot transfer children to ViewGroup of type "
+parent.getClass().getCanonicalName()+". Must either be a VirtualYogaLayout or a " +
"YogaLayout.");
}
mChildren.clear();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
throw new RuntimeException("Attempting to layout a VirtualYogaLayout");
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new YogaLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new YogaLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new YogaLayout.LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof YogaLayout.LayoutParams;
}
}

View File

@@ -1,737 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.samples.yoga;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.samples.yoga.R;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaEdge;
import com.facebook.yoga.YogaFlexDirection;
import com.facebook.yoga.YogaJustify;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaNodeAPI;
import com.facebook.yoga.YogaOverflow;
import com.facebook.yoga.YogaPositionType;
import com.facebook.yoga.YogaWrap;
/**
* A {@code ViewGroup} based on the Yoga layout engine.
*
* <p>
* This class is designed to be as "plug and play" as possible. That is, you can use it in XML
* like this (note: to use {@code YogaLayout} you need to use the {@link YogaViewLayoutFactory}):
* <p>
* <pre>{@code
* <YogaLayout
* xmlns:android="http://schemas.android.com/apk/res/android"
* xmlns:yoga="http://schemas.android.com/apk/res-auto"
* android:layout_width="match_parent"
* android:layout_height="match_parent"
* yoga:flex_direction="row"
* yoga:padding_all="10dp"
* >
* <TextView
* android:layout_width="match_parent"
* android:layout_height="match_parent"
* android:text="Hello, World!"
* yoga:flex="1"
* />
* </YogaLayout>
* }</pre>
*
* Under the hood, all views added to this {@code ViewGroup} are laid out using flexbox rules
* and the Yoga engine.
*/
public class YogaLayout extends ViewGroup {
private final Map<View, YogaNode> mYogaNodes;
private final YogaNode mYogaNode;
public YogaLayout(Context context) {
this(context, null, 0);
}
public YogaLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public YogaLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mYogaNode = new YogaNode();
mYogaNodes = new HashMap<>();
mYogaNode.setData(this);
mYogaNode.setMeasureFunction(new ViewMeasureFunction());
final LayoutParams layoutParams = new LayoutParams(context, attrs);
applyLayoutParams(layoutParams, mYogaNode, this);
}
YogaNode getYogaNode() {
return mYogaNode;
}
YogaNode getYogaNodeForView(View view) {
return mYogaNodes.get(view);
}
/**
* Adds a child view with the specified layout parameters.
*
* In the typical View is added, this constructs a {@code YogaNode} for this child and applies all
* the {@code yoga:*} attributes. The Toga node is added to the Yoga tree and the child is added
* to this ViewGroup.
*
* If the child is a {@link YogaLayout} itself, we do not construct a new Yoga node for that
* child, but use its root node instead.
*
* If the child is a {@link VirtualYogaLayout}, we also use its Yoga node, but we also instruct it
* to transfer all of its children to this {@link YogaLayout} while preserving the Yoga tree (so
* that the layout of its children is correct). The {@link VirtualYogaLayout} is then not added
* to the View hierarchy.
*
* <p><strong>Note:</strong> do not invoke this method from
* {@code #draw(android.graphics.Canvas)}, {@code onDraw(android.graphics.Canvas)},
* {@code #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
* @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
// Internal nodes (which this is now) cannot have measure functions
mYogaNode.setMeasureFunction(null);
if (child instanceof VirtualYogaLayout) {
((VirtualYogaLayout) child).transferChildren(this);
final YogaNode childNode = ((VirtualYogaLayout) child).getYogaNode();
mYogaNode.addChildAt(childNode, mYogaNode.getChildCount());
return;
}
super.addView(child, index, params);
// It is possible that addView is being called as part of a transferal of children, in which
// case we already know about the YogaNode and only need the Android View tree to be aware
// that we now own this child. If so, we don't need to do anything further
if (mYogaNodes.containsKey(child)) {
return;
}
YogaNode childNode;
if (child instanceof YogaLayout) {
childNode = ((YogaLayout) child).getYogaNode();
} else {
childNode = new YogaNode();
childNode.setData(child);
childNode.setMeasureFunction(new ViewMeasureFunction());
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
applyLayoutParams(lp, childNode, child);
mYogaNodes.put(child, childNode);
mYogaNode.addChildAt(childNode, mYogaNode.getChildCount());
}
/**
* Adds a view to this {@code ViewGroup} with an already given {@code YogaNode}. Use
* this function if you already have a Yoga node (and perhaps tree) associated with the view you
* are adding, that you would like to preserve.
*
* @param child The view to add
* @param node The Yoga node belonging to the view
*/
public void addView(View child, YogaNode node) {
mYogaNodes.put(child, node);
addView(child);
}
@Override
public void removeView(View view) {
removeViewFromYogaTree(view, false);
super.removeView(view);
}
@Override
public void removeViewAt(int index) {
removeViewFromYogaTree(getChildAt(index), false);
super.removeViewAt(index);
}
@Override
public void removeViewInLayout(View view) {
removeViewFromYogaTree(view, true);
super.removeViewInLayout(view);
}
@Override
public void removeViews(int start, int count) {
for (int i = start; i < start + count; i++) {
removeViewFromYogaTree(getChildAt(i), false);
}
super.removeViews(start, count);
}
@Override
public void removeViewsInLayout(int start, int count) {
for (int i = start; i < start + count; i++) {
removeViewFromYogaTree(getChildAt(i), true);
}
super.removeViewsInLayout(start, count);
}
@Override
public void removeAllViews() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
removeViewFromYogaTree(getChildAt(i), false);
}
super.removeAllViews();
}
@Override
public void removeAllViewsInLayout() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
removeViewFromYogaTree(getChildAt(i), true);
}
super.removeAllViewsInLayout();
}
private void removeViewFromYogaTree(View view, boolean inLayout) {
final YogaNode node = mYogaNodes.get(view);
if (node == null) {
return;
}
final YogaNode parent = node.getParent();
for (int i = 0; i < parent.getChildCount(); i++) {
if (parent.getChildAt(i).equals(node)) {
parent.removeChildAt(i);
break;
}
}
node.setData(null);
mYogaNodes.remove(view);
if (inLayout) {
mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED);
}
}
private void applyLayoutRecursive(YogaNode node, float xOffset, float yOffset) {
View view = (View) node.getData();
if (view != null && view != this) {
if (view.getVisibility() == GONE) {
return;
}
view.layout(
Math.round(xOffset + node.getLayoutX()),
Math.round(yOffset + node.getLayoutY()),
Math.round(xOffset + node.getLayoutX() + node.getLayoutWidth()),
Math.round(yOffset + node.getLayoutY() + node.getLayoutHeight()));
}
final int childrenCount = node.getChildCount();
for (int i = 0; i < childrenCount; i++) {
if (this.equals(view)) {
applyLayoutRecursive(node.getChildAt(i), xOffset, yOffset);
} else if (view instanceof YogaLayout) {
continue;
} else {
applyLayoutRecursive(
node.getChildAt(i),
xOffset + node.getLayoutX(),
yOffset + node.getLayoutY());
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Either we are a root of a tree, or this function is called by our parent's onLayout, in which
// case our r-l and b-t are the size of our node.
if (!(getParent() instanceof YogaLayout)) {
createLayout(
MeasureSpec.makeMeasureSpec(r - l, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(b - t, MeasureSpec.EXACTLY));
}
applyLayoutRecursive(mYogaNode, 0, 0);
}
/**
* This function is mostly unneeded, because Yoga is doing the measuring. Hence we only need to
* return accurate results if we are the root.
*
* @param widthMeasureSpec the suggested specification for the width
* @param heightMeasureSpec the suggested specification for the height
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!(getParent() instanceof YogaLayout)) {
createLayout(widthMeasureSpec, heightMeasureSpec);
}
setMeasuredDimension(
Math.round(mYogaNode.getLayoutWidth()),
Math.round(mYogaNode.getLayoutHeight()));
}
private void createLayout(int widthMeasureSpec, int heightMeasureSpec) {
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mYogaNode.setHeight(heightSize);
}
if (widthMode == MeasureSpec.EXACTLY) {
mYogaNode.setWidth(widthSize);
}
if (heightMode == MeasureSpec.AT_MOST) {
mYogaNode.setMaxHeight(heightSize);
}
if (widthMode == MeasureSpec.AT_MOST) {
mYogaNode.setMaxWidth(widthSize);
}
mYogaNode.calculateLayout(YogaConstants.UNDEFINED, YogaConstants.UNDEFINED);
}
/**
* Applies the layout parameters to the YogaNode. That is, this function is a translator from
* {@code yoga:X="Y"} to {@code yogaNode.setX(Y);}, with some reasonable defaults.
*
* <p>
* If the SDK version is high enough, and the {@code yoga:direction} is not set on
* the component, the direction (LTR or RTL) is set according to the locale.
*
* <p>
* The attributes {@code padding_top}, {@code padding_right} etc. default to those of the view's
* drawable background, if it has one.
*
* @param layoutParameters The source set of params
* @param node The destination node
*/
protected static void applyLayoutParams(LayoutParams layoutParameters, YogaNode node, View view) {
// JELLY_BEAN_MR1 (17) is the first version supporting getLayoutDirection()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Configuration configuration = view.getResources().getConfiguration();
if (configuration.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
node.setDirection(YogaDirection.RTL);
}
}
Drawable background = view.getBackground();
if (background != null) {
final Rect backgroundPadding = new Rect();
if (background.getPadding(backgroundPadding)) {
node.setPadding(YogaEdge.LEFT, backgroundPadding.left);
node.setPadding(YogaEdge.TOP, backgroundPadding.top);
node.setPadding(YogaEdge.RIGHT, backgroundPadding.right);
node.setPadding(YogaEdge.BOTTOM, backgroundPadding.bottom);
}
}
for (int i = 0; i < layoutParameters.attributes.size(); i++) {
final int attribute = layoutParameters.attributes.keyAt(i);
final float value = layoutParameters.attributes.valueAt(i);
if (attribute == R.styleable.yoga_align_content) {
node.setAlignContent(YogaAlign.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_align_items) {
node.setAlignItems(YogaAlign.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_align_self) {
node.setAlignSelf(YogaAlign.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_aspect_ratio) {
node.setAspectRatio(value);
} else if (attribute == R.styleable.yoga_border_left) {
node.setBorder(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_border_top) {
node.setBorder(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_border_right) {
node.setBorder(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_border_bottom) {
node.setBorder(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_border_start) {
node.setBorder(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_border_end) {
node.setBorder(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_border_horizontal) {
node.setBorder(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_border_vertical) {
node.setBorder(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_border_all) {
node.setBorder(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_direction) {
node.setDirection(YogaDirection.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_flex) {
node.setFlex(value);
} else if (attribute == R.styleable.yoga_flex_basis) {
node.setFlexBasis(value);
} else if (attribute == R.styleable.yoga_flex_basis_percent) {
node.setFlexBasisPercent(value);
} else if (attribute == R.styleable.yoga_flex_direction) {
node.setFlexDirection(YogaFlexDirection.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_flex_grow) {
node.setFlexGrow(value);
} else if (attribute == R.styleable.yoga_flex_shrink) {
node.setFlexShrink(value);
} else if (attribute == R.styleable.yoga_height) {
node.setHeight(value);
} else if (attribute == R.styleable.yoga_height_percent) {
node.setHeightPercent(value);
} else if (attribute == R.styleable.yoga_margin_left) {
node.setMargin(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_justify_content) {
node.setJustifyContent(YogaJustify.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_margin_top) {
node.setMargin(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_margin_right) {
node.setMargin(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_margin_bottom) {
node.setMargin(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_margin_start) {
node.setMargin(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_margin_end) {
node.setMargin(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_margin_horizontal) {
node.setMargin(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_margin_vertical) {
node.setMargin(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_margin_all) {
node.setMargin(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_margin_percent_left) {
node.setMarginPercent(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_margin_percent_top) {
node.setMarginPercent(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_margin_percent_right) {
node.setMarginPercent(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_margin_percent_bottom) {
node.setMarginPercent(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_margin_percent_start) {
node.setMarginPercent(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_margin_percent_end) {
node.setMarginPercent(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_margin_percent_horizontal) {
node.setMarginPercent(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_margin_percent_vertical) {
node.setMarginPercent(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_margin_percent_all) {
node.setMarginPercent(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_max_height) {
node.setMaxHeight(value);
} else if (attribute == R.styleable.yoga_max_height_percent) {
node.setMaxHeightPercent(value);
} else if (attribute == R.styleable.yoga_max_width) {
node.setMaxWidth(value);
} else if (attribute == R.styleable.yoga_max_width_percent) {
node.setMaxWidthPercent(value);
} else if (attribute == R.styleable.yoga_min_height) {
node.setMinHeight(value);
} else if (attribute == R.styleable.yoga_min_height_percent) {
node.setMinHeightPercent(value);
} else if (attribute == R.styleable.yoga_min_width) {
node.setMinWidth(value);
} else if (attribute == R.styleable.yoga_min_width_percent) {
node.setMinWidthPercent(value);
} else if (attribute == R.styleable.yoga_overflow) {
node.setOverflow(YogaOverflow.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_padding_left) {
node.setPadding(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_padding_top) {
node.setPadding(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_padding_right) {
node.setPadding(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_padding_bottom) {
node.setPadding(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_padding_start) {
node.setPadding(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_padding_end) {
node.setPadding(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_padding_horizontal) {
node.setPadding(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_padding_vertical) {
node.setPadding(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_padding_all) {
node.setPadding(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_padding_percent_left) {
node.setPaddingPercent(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_padding_percent_top) {
node.setPaddingPercent(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_padding_percent_right) {
node.setPaddingPercent(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_padding_percent_bottom) {
node.setPaddingPercent(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_padding_percent_start) {
node.setPaddingPercent(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_padding_percent_end) {
node.setPaddingPercent(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_padding_percent_horizontal) {
node.setPaddingPercent(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_padding_percent_vertical) {
node.setPaddingPercent(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_padding_percent_all) {
node.setPaddingPercent(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_position_left) {
node.setPosition(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_position_top) {
node.setPosition(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_position_right) {
node.setPosition(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_position_bottom) {
node.setPosition(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_position_start) {
node.setPosition(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_position_end) {
node.setPosition(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_position_horizontal) {
node.setPosition(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_position_vertical) {
node.setPosition(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_position_all) {
node.setPosition(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_position_percent_left) {
node.setPositionPercent(YogaEdge.LEFT, value);
} else if (attribute == R.styleable.yoga_position_percent_top) {
node.setPositionPercent(YogaEdge.TOP, value);
} else if (attribute == R.styleable.yoga_position_percent_right) {
node.setPositionPercent(YogaEdge.RIGHT, value);
} else if (attribute == R.styleable.yoga_position_percent_bottom) {
node.setPositionPercent(YogaEdge.BOTTOM, value);
} else if (attribute == R.styleable.yoga_position_percent_start) {
node.setPositionPercent(YogaEdge.START, value);
} else if (attribute == R.styleable.yoga_position_percent_end) {
node.setPositionPercent(YogaEdge.END, value);
} else if (attribute == R.styleable.yoga_position_percent_horizontal) {
node.setPositionPercent(YogaEdge.HORIZONTAL, value);
} else if (attribute == R.styleable.yoga_position_percent_vertical) {
node.setPositionPercent(YogaEdge.VERTICAL, value);
} else if (attribute == R.styleable.yoga_position_percent_all) {
node.setPositionPercent(YogaEdge.ALL, value);
} else if (attribute == R.styleable.yoga_position_type) {
node.setPositionType(YogaPositionType.fromInt(Math.round(value)));
} else if (attribute == R.styleable.yoga_width) {
node.setWidth(value);
} else if (attribute == R.styleable.yoga_width_percent) {
node.setWidthPercent(value);
} else if (attribute == R.styleable.yoga_wrap) {
node.setWrap(YogaWrap.fromInt(Math.round(value)));
}
}
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new YogaLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new YogaLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new YogaLayout.LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams;
}
/**
* {@code YogaLayout.LayoutParams} are used by views to tell {@link YogaLayout} how they want to
* be laid out. More precisely, the specify the yoga parameters of the view.
*
* <p>
* This is actually mostly a wrapper around a {@code SparseArray} that holds a mapping between
* styleable id's ({@code R.styleable.yoga_*}) and the float of their values. In cases where the
* value is an enum or an integer, they should first be cast to int (with rounding) before using.
*/
public static class LayoutParams extends ViewGroup.LayoutParams {
/**
* A mapping from attribute keys ({@code R.styleable.yoga_*}) to the float of their values.
* For attributes like position_percent_left (float), this is the native type. For attributes
* like align_self (enums), the integer enum value is cast (rounding is used on the other side
* to prevent precision errors). Dimension attributes are stored as float pixels.
*/
SparseArray<Float> attributes;
/**
* Constructs a set of layout params from a source set. In the case that the source set is
* actually a {@link YogaLayout.LayoutParams}, we can copy all the yoga attributes. Otherwise
* we start with a blank slate.
*
* @param source The layout params to copy from
*/
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
if (source instanceof LayoutParams) {
attributes = ((LayoutParams) source).attributes.clone();
} else {
attributes = new SparseArray<>();
// Negative values include MATCH_PARENT and WRAP_CONTENT
if (source.width >= 0) {
attributes.put(R.styleable.yoga_width, (float) width);
}
if (source.height >= 0) {
attributes.put(R.styleable.yoga_height, (float) height);
}
}
}
/**
* Constructs a set of layout params, given width and height specs. In this case, we can set
* the {@code yoga:width} and {@code yoga:height} if we are given them explicitly. If other
* options (such as {@code match_parent} or {@code wrap_content} are given, then the parent
* LayoutParams will store them, and we deal with them during layout. (see
* {@link YogaLayout#createLayout})
*
* @param width the requested width, either a pixel size, {@code WRAP_CONTENT} or
* {@code MATCH_PARENT}.
* @param height the requested height, either a pixel size, {@code WRAP_CONTENT} or
* {@code MATCH_PARENT}.
*/
public LayoutParams(int width, int height) {
super(width, height);
attributes = new SparseArray<>();
// Negative values include MATCH_PARENT and WRAP_CONTENT
if (width >= 0) {
attributes.put(R.styleable.yoga_width, (float) width);
}
if (height >= 0) {
attributes.put(R.styleable.yoga_height, (float) height);
}
}
/**
* Constructs a set of layout params, given attributes. Grabs all the {@code yoga:*}
* defined in {@code ALL_YOGA_ATTRIBUTES} and collects the ones that are set in {@code attrs}.
*
* @param context the application environment
* @param attrs the set of attributes from which to extract the yoga specific attributes
*/
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
attributes = new SparseArray<>();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.yoga);
// Negative values include MATCH_PARENT and WRAP_CONTENT
if (width >= 0) {
attributes.put(R.styleable.yoga_width, (float) width);
}
if (height >= 0) {
attributes.put(R.styleable.yoga_height, (float) height);
}
final int attributeCount = a.getIndexCount();
for (int i = 0; i < attributeCount; i++) {
final int attribute = a.getIndex(i);
final TypedValue val = new TypedValue();
a.getValue(attribute, val);
if (val.type == TypedValue.TYPE_DIMENSION) {
attributes.put(
attribute,
(float) a.getDimensionPixelSize(attribute, 0));
} else {
attributes.put(attribute, a.getFloat(attribute, 0));
}
}
a.recycle();
}
}
/**
* Wrapper around measure function for yoga leaves.
*/
public static class ViewMeasureFunction implements YogaMeasureFunction {
/**
* A function to measure leaves of the Yoga tree. Yoga needs some way to know how large
* elements want to be. This function passes that question directly through to the relevant
* {@code View}'s measure function.
*
* @param node The yoga node to measure
* @param width The suggested width from the parent
* @param widthMode The type of suggestion for the width
* @param height The suggested height from the parent
* @param heightMode The type of suggestion for the height
* @return A measurement output ({@code YogaMeasureOutput}) for the node
*/
public long measure(
YogaNodeAPI node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
final View view = (View) node.getData();
if (view == null || view instanceof YogaLayout) {
return YogaMeasureOutput.make(0, 0);
}
final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(
(int) width,
viewMeasureSpecFromYogaMeasureMode(widthMode));
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
(int) height,
viewMeasureSpecFromYogaMeasureMode(heightMode));
view.measure(widthMeasureSpec, heightMeasureSpec);
return YogaMeasureOutput.make(view.getMeasuredWidth(), view.getMeasuredHeight());
}
private int viewMeasureSpecFromYogaMeasureMode(YogaMeasureMode mode) {
if (mode == YogaMeasureMode.AT_MOST) {
return MeasureSpec.AT_MOST;
} else if (mode == YogaMeasureMode.EXACTLY) {
return MeasureSpec.EXACTLY;
} else {
return MeasureSpec.UNSPECIFIED;
}
}
}
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.samples.yoga;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
/**
* A layout inflater factory. This provides our custom {@link YogaViewLayoutFactory#onCreateView}
* to the XML inflation system, allowing us to replace XML tags.
*/
public class YogaViewLayoutFactory implements LayoutInflater.Factory {
private static YogaViewLayoutFactory sYogaViewLayoutFactory;
/**
* Obtains (and initialises if necessary) the singleton {@link YogaViewLayoutFactory}.
*
* @return The singleton instance
*/
public static YogaViewLayoutFactory getInstance() {
if (sYogaViewLayoutFactory == null) {
sYogaViewLayoutFactory = new YogaViewLayoutFactory();
}
return sYogaViewLayoutFactory;
}
YogaViewLayoutFactory() {}
/**
* Hook for inflating from a LayoutInflater. This hook replaces the cumbersome
* {@code com.facebook.etc.YogaLayout} with simply {@code YogaLayout} in your XML and the same
* with {@code VirtualYogaLayout}.
*
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default behavior.
*/
@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;
}
}

View File

@@ -1,186 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (c) 2014-present, Facebook, Inc.
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
-->
<resources>
<declare-styleable name="yoga">
<attr name="align_content" format="enum">
<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 name="align_items" format="enum">
<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 name="align_self" format="enum">
<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 name="aspect_ratio" format="float"/>
<attr name="border_left" format="dimension"/>
<attr name="border_top" format="dimension"/>
<attr name="border_right" format="dimension"/>
<attr name="border_bottom" format="dimension"/>
<attr name="border_start" format="dimension"/>
<attr name="border_end" format="dimension"/>
<attr name="border_horizontal" format="dimension"/>
<attr name="border_vertical" format="dimension"/>
<attr name="border_all" format="dimension"/>
<attr name="direction" format="enum">
<enum name="inherit" value="0"/>
<enum name="ltr" value="1"/>
<enum name="rtl" value="2"/>
</attr>
<attr name="flex" format="float"/>
<attr name="flex_basis" format="float"/>
<attr name="flex_basis_percent" format="float"/>
<attr name="flex_direction" format="enum">
<enum name="column" value="0"/>
<enum name="column-reverse" value="1"/>
<enum name="row" value="2"/>
<enum name="row-reverse" value="3"/>
</attr>
<attr name="flex_grow" format="float"/>
<attr name="flex_shrink" format="float"/>
<!-- Height format not included as it is in the parent -->
<attr name="height"/>
<attr name="height_percent" format="float"/>
<attr name="justify_content" format="enum">
<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 name="margin_left" format="dimension"/>
<attr name="margin_top" format="dimension"/>
<attr name="margin_right" format="dimension"/>
<attr name="margin_bottom" format="dimension"/>
<attr name="margin_start" format="dimension"/>
<attr name="margin_end" format="dimension"/>
<attr name="margin_horizontal" format="dimension"/>
<attr name="margin_vertical" format="dimension"/>
<attr name="margin_all" format="dimension"/>
<attr name="margin_percent_left" format="dimension"/>
<attr name="margin_percent_top" format="dimension"/>
<attr name="margin_percent_right" format="dimension"/>
<attr name="margin_percent_bottom" format="dimension"/>
<attr name="margin_percent_start" format="dimension"/>
<attr name="margin_percent_end" format="dimension"/>
<attr name="margin_percent_horizontal" format="dimension"/>
<attr name="margin_percent_vertical" format="dimension"/>
<attr name="margin_percent_all" format="dimension"/>
<attr name="max_height" format="dimension"/>
<attr name="max_height_percent" format="float"/>
<attr name="max_width" format="dimension"/>
<attr name="max_width_percent" format="float"/>
<attr name="min_height" format="dimension"/>
<attr name="min_height_percent" format="float"/>
<attr name="min_width" format="dimension"/>
<attr name="min_width_percent" format="float"/>
<attr name="overflow" format="enum">
<enum name="visible" value="0"/>
<enum name="hidden" value="1"/>
<enum name="scroll" value="2"/>
</attr>
<attr name="padding_left" format="dimension"/>
<attr name="padding_top" format="dimension"/>
<attr name="padding_right" format="dimension"/>
<attr name="padding_bottom" format="dimension"/>
<attr name="padding_start" format="dimension"/>
<attr name="padding_end" format="dimension"/>
<attr name="padding_horizontal" format="dimension"/>
<attr name="padding_vertical" format="dimension"/>
<attr name="padding_all" format="dimension"/>
<attr name="padding_percent_left" format="float"/>
<attr name="padding_percent_top" format="float"/>
<attr name="padding_percent_right" format="float"/>
<attr name="padding_percent_bottom" format="float"/>
<attr name="padding_percent_start" format="float"/>
<attr name="padding_percent_end" format="float"/>
<attr name="padding_percent_horizontal" format="float"/>
<attr name="padding_percent_vertical" format="float"/>
<attr name="padding_percent_all" format="float"/>
<attr name="position_left" format="dimension"/>
<attr name="position_top" format="dimension"/>
<attr name="position_right" format="dimension"/>
<attr name="position_bottom" format="dimension"/>
<attr name="position_start" format="dimension"/>
<attr name="position_end" format="dimension"/>
<attr name="position_horizontal" format="dimension"/>
<attr name="position_vertical" format="dimension"/>
<attr name="position_all" format="dimension"/>
<attr name="position_percent_left" format="float"/>
<attr name="position_percent_top" format="float"/>
<attr name="position_percent_right" format="float"/>
<attr name="position_percent_bottom" format="float"/>
<attr name="position_percent_start" format="float"/>
<attr name="position_percent_end" format="float"/>
<attr name="position_percent_horizontal" format="float"/>
<attr name="position_percent_vertical" format="float"/>
<attr name="position_percent_all" format="float"/>
<attr name="position_type" format="enum">
<enum name="relative" value="0"/>
<enum name="absolute" value="1"/>
</attr>
<attr name="width" format="dimension"/>
<attr name="width_percent" format="float"/>
<attr name="wrap" format="enum">
<enum name="no-wrap" value="0"/>
<enum name="wrap" value="1"/>
</attr>
</declare-styleable>
</resources>