diff --git a/.buckconfig b/.buckconfig index 74a40a76..095cc442 100644 --- a/.buckconfig +++ b/.buckconfig @@ -1,2 +1,9 @@ [cxx] gtest_dep = //lib/gtest:gtest +[android] + target = Google Inc.:Google APIs:19 +[ndk] + ndk_version = r10e + compiler = clang + app_platform = android-19 + cpu_abis = armv7, x86 diff --git a/YOGA_DEFS b/YOGA_DEFS index 8d17d58f..7f2f082a 100644 --- a/YOGA_DEFS +++ b/YOGA_DEFS @@ -1,5 +1,6 @@ YOGA_ROOT = '//...' +JAVA_TARGET = '//java:java' INFER_ANNOTATIONS_TARGET = '//lib/infer-annotations:infer-annotations' JSR_305_TARGET = '//lib/jsr-305:jsr-305' JUNIT_TARGET = '//lib/junit:junit' @@ -8,6 +9,10 @@ SOLOADER_TARGET = '//lib/soloader:soloader' GTEST_TARGET = '//lib/gtest:gtest' JNI_TARGET = '//lib/jni:jni' FBJNI_TARGET = '//lib/fb:fbjni' +APPCOMPAT_TARGET = '//lib/appcompat:appcompat' +ANDROID_SUPPORT_TARGET = '//lib/android-support:android-support' +ANDROID_SAMPLE_JAVA_TARGET = '//android/sample/java/com/facebook/samples/yoga:yoga' +ANDROID_SAMPLE_RES_TARGET = '//android/sample/res/com/facebook/samples/yoga:res' THIS_IS_FBOBJC = False diff --git a/android/sample/AndroidManifest.xml b/android/sample/AndroidManifest.xml new file mode 100644 index 00000000..7e3c5284 --- /dev/null +++ b/android/sample/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/sample/BUCK b/android/sample/BUCK new file mode 100644 index 00000000..5eeb672e --- /dev/null +++ b/android/sample/BUCK @@ -0,0 +1,28 @@ +# 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_binary( + name = 'sample', + manifest = 'AndroidManifest.xml', + keystore = ':debug_keystore', + deps = [ + ANDROID_SAMPLE_JAVA_TARGET, + ANDROID_SAMPLE_RES_TARGET, + ], +) + +keystore( + name='debug_keystore', + store='debug.keystore', + properties='debug.keystore.properties', +) + +project_config( + src_target = ':sample', +) diff --git a/android/sample/debug.keystore b/android/sample/debug.keystore new file mode 100644 index 00000000..3df12b5d Binary files /dev/null and b/android/sample/debug.keystore differ diff --git a/android/sample/debug.keystore.properties b/android/sample/debug.keystore.properties new file mode 100644 index 00000000..3c06c8e4 --- /dev/null +++ b/android/sample/debug.keystore.properties @@ -0,0 +1,3 @@ +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/android/sample/java/com/facebook/samples/yoga/BUCK b/android/sample/java/com/facebook/samples/yoga/BUCK new file mode 100644 index 00000000..bc411285 --- /dev/null +++ b/android/sample/java/com/facebook/samples/yoga/BUCK @@ -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" +) diff --git a/android/sample/java/com/facebook/samples/yoga/MainActivity.java b/android/sample/java/com/facebook/samples/yoga/MainActivity.java new file mode 100644 index 00000000..12de8c91 --- /dev/null +++ b/android/sample/java/com/facebook/samples/yoga/MainActivity.java @@ -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); + } +} diff --git a/android/sample/java/com/facebook/samples/yoga/SplashScreenActivity.java b/android/sample/java/com/facebook/samples/yoga/SplashScreenActivity.java new file mode 100644 index 00000000..5553360d --- /dev/null +++ b/android/sample/java/com/facebook/samples/yoga/SplashScreenActivity.java @@ -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(); + } +} diff --git a/android/sample/java/com/facebook/samples/yoga/VirtualYogaLayout.java b/android/sample/java/com/facebook/samples/yoga/VirtualYogaLayout.java new file mode 100644 index 00000000..865a02da --- /dev/null +++ b/android/sample/java/com/facebook/samples/yoga/VirtualYogaLayout.java @@ -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, do not use this if you wish the container to have a background or + * foreground. However, all of its children will still render as expected. + * + *

+ * 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 mChildren = new LinkedList<>(); + final private Map 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; + } +} diff --git a/android/sample/java/com/facebook/samples/yoga/YogaLayout.java b/android/sample/java/com/facebook/samples/yoga/YogaLayout.java new file mode 100644 index 00000000..23dbc747 --- /dev/null +++ b/android/sample/java/com/facebook/samples/yoga/YogaLayout.java @@ -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. + * + *

+ * 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}): + *

+ *

{@code
+ * 
+ *     
+ * 
+ * }
+ * + * 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 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. + * + *

Note: 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.

+ * + * @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. + * + *

+ * 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. + * + *

+ * 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. + * + *

+ * 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 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; + } + } + } +} diff --git a/android/sample/java/com/facebook/samples/yoga/YogaViewLayoutFactory.java b/android/sample/java/com/facebook/samples/yoga/YogaViewLayoutFactory.java new file mode 100644 index 00000000..45df625f --- /dev/null +++ b/android/sample/java/com/facebook/samples/yoga/YogaViewLayoutFactory.java @@ -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; + } +} diff --git a/android/sample/res/com/facebook/samples/yoga/BUCK b/android/sample/res/com/facebook/samples/yoga/BUCK new file mode 100644 index 00000000..83268716 --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/BUCK @@ -0,0 +1,21 @@ +# 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_resource( + name = 'res', + res = 'res', + package = 'com.facebook.samples.yoga', + visibility = [ + 'PUBLIC', + ], +) + +project_config( + src_target = ':res' +) diff --git a/android/sample/res/com/facebook/samples/yoga/res/drawable/action_bar_background.xml b/android/sample/res/com/facebook/samples/yoga/res/drawable/action_bar_background.xml new file mode 100644 index 00000000..6c70b26b --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/drawable/action_bar_background.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/android/sample/res/com/facebook/samples/yoga/res/drawable/ic_launcher.png b/android/sample/res/com/facebook/samples/yoga/res/drawable/ic_launcher.png new file mode 100644 index 00000000..c99f9812 Binary files /dev/null and b/android/sample/res/com/facebook/samples/yoga/res/drawable/ic_launcher.png differ diff --git a/android/sample/res/com/facebook/samples/yoga/res/drawable/sample_children_background.xml b/android/sample/res/com/facebook/samples/yoga/res/drawable/sample_children_background.xml new file mode 100644 index 00000000..8d63336a --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/drawable/sample_children_background.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/android/sample/res/com/facebook/samples/yoga/res/layout/main_layout.xml b/android/sample/res/com/facebook/samples/yoga/res/layout/main_layout.xml new file mode 100644 index 00000000..295a3a68 --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/layout/main_layout.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/sample/res/com/facebook/samples/yoga/res/layout/splash_layout.xml b/android/sample/res/com/facebook/samples/yoga/res/layout/splash_layout.xml new file mode 100644 index 00000000..051276ca --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/layout/splash_layout.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/android/sample/res/com/facebook/samples/yoga/res/values/attrs.xml b/android/sample/res/com/facebook/samples/yoga/res/values/attrs.xml new file mode 100644 index 00000000..61c8c643 --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/values/attrs.xml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/sample/res/com/facebook/samples/yoga/res/values/colors.xml b/android/sample/res/com/facebook/samples/yoga/res/values/colors.xml new file mode 100644 index 00000000..e1a99a61 --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/values/colors.xml @@ -0,0 +1,20 @@ + + + + + + + #FF303846 + #FF97DCCF + + #FFFFFFFF + #665890ff + #FF23355b + + diff --git a/android/sample/res/com/facebook/samples/yoga/res/values/strings.xml b/android/sample/res/com/facebook/samples/yoga/res/values/strings.xml new file mode 100644 index 00000000..8b884c57 --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/values/strings.xml @@ -0,0 +1,33 @@ + + + + + + Yoga + Hello. I am Yoga! + I am a layout engine! + I run natively. + So I\'m fast. + Who are you? + diff --git a/android/sample/res/com/facebook/samples/yoga/res/values/styles.xml b/android/sample/res/com/facebook/samples/yoga/res/values/styles.xml new file mode 100644 index 00000000..0276b8df --- /dev/null +++ b/android/sample/res/com/facebook/samples/yoga/res/values/styles.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + diff --git a/lib/android-support/BUCK b/lib/android-support/BUCK new file mode 100644 index 00000000..64a3434f --- /dev/null +++ b/lib/android-support/BUCK @@ -0,0 +1,14 @@ +# 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') + +prebuilt_jar( + name = 'android-support', + binary_jar = 'android-support-v4.jar', + visibility = [YOGA_ROOT], +) diff --git a/lib/android-support/android-support-v4.jar b/lib/android-support/android-support-v4.jar new file mode 100644 index 00000000..aa0b1a5c Binary files /dev/null and b/lib/android-support/android-support-v4.jar differ diff --git a/lib/appcompat/BUCK b/lib/appcompat/BUCK new file mode 100644 index 00000000..45df9d74 --- /dev/null +++ b/lib/appcompat/BUCK @@ -0,0 +1,14 @@ +# 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_prebuilt_aar( + name = 'appcompat', + aar = 'appcompat-v7-19.1.0.aar', + visibility = [YOGA_ROOT], +) diff --git a/lib/appcompat/appcompat-v7-19.1.0.aar b/lib/appcompat/appcompat-v7-19.1.0.aar new file mode 100644 index 00000000..bf15013b Binary files /dev/null and b/lib/appcompat/appcompat-v7-19.1.0.aar differ