Move in yoga sample android app

Summary: The sample app is simply moved into the repo.  A few libraries are included and the buck targets rearranged.

Reviewed By: emilsjolander

Differential Revision: D4528129

fbshipit-source-id: 3e9e779857cd9219711a939876c9275d75e09929
This commit is contained in:
Robert Spencer
2017-02-14 00:05:06 -08:00
committed by Facebook Github Bot
parent ce5d52b54e
commit a580712b2f
26 changed files with 1680 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
# 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.
include_defs('//YOGA_DEFS')
android_library(
name = 'yoga',
srcs = glob(['**/*.java']),
deps = [
ANDROID_SAMPLE_RES_TARGET,
ANDROID_SUPPORT_TARGET,
APPCOMPAT_TARGET,
JAVA_TARGET,
SOLOADER_TARGET,
],
visibility = [
'PUBLIC',
]
)
project_config(
src_target = ":yoga"
)

View File

@@ -0,0 +1,31 @@
/**
* Copyright 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/
package com.facebook.samples.yoga;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import com.facebook.samples.yoga.R;
/**
* An activity to show off Yoga in Android. This activity shows a simple layout (defined in
* {@code main_layout.xml}) that shows off the awesome functionality of the Yoga layout engine
* as well as some optimisations on layout systems that it facilitates.
*/
public class MainActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
LayoutInflater.from(this).setFactory(YogaViewLayoutFactory.getInstance());
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE-examples file in the root directory of this source tree.
*/
package com.facebook.samples.yoga;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import com.facebook.samples.yoga.R;
import com.facebook.soloader.SoLoader;
/**
* A (non-interactive) splash screen. Displays for two seconds before calling the main activity.
*/
public class SplashScreenActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
LayoutInflater.from(this).setFactory(YogaViewLayoutFactory.getInstance());
super.onCreate(savedInstanceState);
SoLoader.init(this, false);
setContentView(R.layout.splash_layout);
new Handler(Looper.getMainLooper()).postDelayed(
new Runnable() {
@Override
public void run() {
startMainActivity();
}
},
2000);
}
private void startMainActivity() {
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
this.finish();
}
}

View File

@@ -0,0 +1,150 @@
/**
* 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

@@ -0,0 +1,737 @@
/**
* 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

@@ -0,0 +1,59 @@
/**
* 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;
}
}