() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/ImageView.java b/widgets/src/main/java/org/nativescript/widgets/ImageView.java
new file mode 100644
index 000000000..0d8876f96
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/ImageView.java
@@ -0,0 +1,168 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+import android.content.Context;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+
+/**
+ * @author hhristov
+ *
+ */
+public class ImageView extends android.widget.ImageView {
+ private float cornerRadius = 0;
+ private float borderWidth = 0;
+
+ private Path path = new Path();
+ private RectF rect = new RectF();
+
+ private double scaleW = 1;
+ private double scaleH = 1;
+
+ public ImageView(Context context) {
+ super(context);
+ this.setScaleType(ScaleType.FIT_CENTER);
+ }
+
+ public float getCornerRadius() {
+ return this.cornerRadius;
+ }
+
+ public void setCornerRadius(float radius) {
+ if (radius != this.cornerRadius) {
+ this.cornerRadius = radius;
+ this.invalidate();
+ }
+ }
+
+ public float getBorderWidth() {
+ return this.borderWidth;
+ }
+
+ public void setBorderWidth(float radius) {
+ if (radius != this.borderWidth) {
+ this.borderWidth = radius;
+ this.invalidate();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ Drawable drawable = this.getDrawable();
+ int measureWidth;
+ int measureHeight;
+ if (drawable != null) {
+ measureWidth = drawable.getIntrinsicWidth();
+ measureHeight = drawable.getIntrinsicHeight();
+ } else {
+ measureWidth = 0;
+ measureHeight = 0;
+ }
+
+ boolean finiteWidth = widthMode != MeasureSpec.UNSPECIFIED;
+ boolean finiteHeight = heightMode != MeasureSpec.UNSPECIFIED;
+
+ if (measureWidth != 0 && measureHeight != 0 && (finiteWidth || finiteHeight)) {
+ this.computeScaleFactor(width, height, finiteWidth, finiteHeight, measureWidth, measureHeight);
+ int resultW = (int) Math.floor(measureWidth * this.scaleW);
+ int resultH = (int) Math.floor(measureHeight * this.scaleH);
+
+ measureWidth = finiteWidth ? Math.min(resultW, width) : resultW;
+ measureHeight = finiteHeight ? Math.min(resultH, height) : resultH;
+ }
+
+ measureWidth += this.getPaddingLeft() + this.getPaddingRight();
+ measureHeight += this.getPaddingTop() + this.getPaddingBottom();
+
+ measureWidth = Math.max(measureWidth, getSuggestedMinimumWidth());
+ measureHeight = Math.max(measureHeight, getSuggestedMinimumHeight());
+
+ if (CommonLayoutParams.debuggable > 0) {
+ StringBuilder sb = CommonLayoutParams.getStringBuilder();
+ sb.append("ImageView onMeasure: ");
+ sb.append(MeasureSpec.toString(widthMeasureSpec));
+ sb.append(", ");
+ sb.append(MeasureSpec.toString(heightMeasureSpec));
+ sb.append(", stretch: ");
+ sb.append(this.getScaleType());
+ sb.append(", measureWidth: ");
+ sb.append(measureWidth);
+ sb.append(", measureHeight: ");
+ sb.append(measureHeight);
+
+ CommonLayoutParams.log(CommonLayoutParams.TAG, sb.toString());
+ }
+
+ int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0);
+ int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0);
+
+ this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+ }
+
+ private void computeScaleFactor(int measureWidth, int measureHeight, boolean widthIsFinite, boolean heightIsFinite, double nativeWidth, double nativeHeight) {
+
+ this.scaleW = 1;
+ this.scaleH = 1;
+
+ ScaleType scale = this.getScaleType();
+ if ((scale == ScaleType.CENTER_CROP || scale == ScaleType.FIT_CENTER || scale == ScaleType.FIT_XY) &&
+ (widthIsFinite || heightIsFinite)) {
+
+ this.scaleW = (nativeWidth > 0) ? measureWidth / nativeWidth : 0d;
+ this.scaleH = (nativeHeight > 0) ? measureHeight / nativeHeight : 0d;
+
+ if (!widthIsFinite) {
+ this.scaleW = scaleH;
+ } else if (!heightIsFinite) {
+ this.scaleH = scaleW;
+ } else {
+ // No infinite dimensions.
+ switch (scale) {
+ case FIT_CENTER:
+ this.scaleH = this.scaleW < this.scaleH ? this.scaleW : this.scaleH;
+ this.scaleW = this.scaleH;
+ break;
+ case CENTER_CROP:
+ this.scaleH = this.scaleW > this.scaleH ? this.scaleW : this.scaleH;
+ this.scaleW = this.scaleH;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // floor the border width to avoid gaps between the border and the image
+ float roundedBorderWidth = (float) Math.floor(this.borderWidth);
+ float innerRadius = Math.max(0, this.cornerRadius - roundedBorderWidth);
+
+ // The border width is included in the padding so there is no need for
+ // clip if there is no inner border radius.
+ if (innerRadius != 0) {
+ this.rect.set(
+ roundedBorderWidth,
+ roundedBorderWidth,
+ this.getWidth() - roundedBorderWidth,
+ this.getHeight() - roundedBorderWidth);
+
+ this.path.reset();
+ this.path.addRoundRect(rect, innerRadius, innerRadius, Path.Direction.CW);
+
+ canvas.clipPath(this.path);
+ }
+
+ super.onDraw(canvas);
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/ItemSpec.java b/widgets/src/main/java/org/nativescript/widgets/ItemSpec.java
new file mode 100644
index 000000000..64fc3a884
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/ItemSpec.java
@@ -0,0 +1,60 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+/**
+ * @author hhristov
+ *
+ */
+public class ItemSpec {
+
+ private int _value;
+ private GridUnitType _unitType;
+
+ public ItemSpec() {
+ this(1, GridUnitType.star);
+ }
+
+ public ItemSpec(int value, GridUnitType unitType) {
+ this._value = value;
+ this._unitType = unitType;
+ }
+
+ GridLayout owner;
+ int _actualLength = 0;
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ItemSpec)) {
+ return false;
+ }
+
+ ItemSpec other = (ItemSpec)o;
+ return (this._unitType == other._unitType) && (this._value == other._value) && (this.owner == other.owner);
+ }
+
+ public GridUnitType getGridUnitType() {
+ return this._unitType;
+ }
+
+ public boolean getIsAbsolute() {
+ return this._unitType == GridUnitType.pixel;
+ }
+
+ public boolean getIsAuto() {
+ return this._unitType == GridUnitType.auto;
+ }
+
+ public boolean getIsStar() {
+ return this._unitType == GridUnitType.star;
+ }
+
+ public int getValue() {
+ return this._value;
+ }
+
+ public int getActualLength() {
+ return this._actualLength;
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java b/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java
new file mode 100644
index 000000000..06db0b1ec
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java
@@ -0,0 +1,67 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+/**
+ * @author hhristov
+ *
+ */
+public abstract class LayoutBase extends ViewGroup {
+
+ public LayoutBase(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new CommonLayoutParams();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new CommonLayoutParams();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean checkLayoutParams(LayoutParams p) {
+ return p instanceof CommonLayoutParams;
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(LayoutParams p) {
+ return new CommonLayoutParams();
+ }
+
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
+ }
+
+ protected static int getGravity(View view) {
+ int gravity = -1;
+ LayoutParams params = view.getLayoutParams();
+ if (params instanceof FrameLayout.LayoutParams) {
+ gravity = ((FrameLayout.LayoutParams)params).gravity;
+ }
+
+ if (gravity == -1) {
+ gravity = Gravity.FILL;
+ }
+
+ return gravity;
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/Orientation.java b/widgets/src/main/java/org/nativescript/widgets/Orientation.java
new file mode 100644
index 000000000..bd91e6cfc
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/Orientation.java
@@ -0,0 +1,13 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+/**
+ * @author hhristov
+ *
+ */
+public enum Orientation {
+ horzontal,
+ vertical
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/StackLayout.java b/widgets/src/main/java/org/nativescript/widgets/StackLayout.java
new file mode 100644
index 000000000..4186ffac7
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/StackLayout.java
@@ -0,0 +1,218 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+
+/**
+ * @author hhristov
+ *
+ */
+public class StackLayout extends LayoutBase {
+
+ private int _totalLength = 0;
+ private Orientation _orientation = Orientation.vertical;
+
+ public StackLayout(Context context) {
+ super(context);
+ }
+
+ public Orientation getOrientation() {
+ return this._orientation;
+ }
+ public void setOrientation(Orientation value) {
+ this._orientation = value;
+ this.requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);
+
+ int childState = 0;
+ int measureWidth = 0;
+ int measureHeight = 0;
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ boolean isVertical = this._orientation == Orientation.vertical;
+ int verticalPadding = this.getPaddingTop() + this.getPaddingBottom();
+ int horizontalPadding = this.getPaddingLeft() + this.getPaddingRight();
+
+ int count = this.getChildCount();
+ int measureSpecMode;
+ int remainingLength;
+
+ int mode = isVertical ? heightMode : widthMode;
+ if (mode == MeasureSpec.UNSPECIFIED) {
+ measureSpecMode = MeasureSpec.UNSPECIFIED;
+ remainingLength = 0;
+ }
+ else {
+ measureSpecMode = MeasureSpec.AT_MOST;
+ remainingLength = isVertical ? height - verticalPadding : width - horizontalPadding;
+ }
+
+ int childMeasureSpec;
+ if (isVertical) {
+ int childWidth = (widthMode == MeasureSpec.UNSPECIFIED) ? 0 : width - horizontalPadding;
+ childWidth = Math.max(0, childWidth);
+ childMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, widthMode);
+ }
+ else {
+ int childHeight = (heightMode == MeasureSpec.UNSPECIFIED) ? 0 : height - verticalPadding;
+ childHeight = Math.max(0, childHeight);
+ childMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode);
+ }
+
+ for (int i = 0; i < count; i++) {
+ View child = this.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ if (isVertical) {
+ CommonLayoutParams.measureChild(child, childMeasureSpec, MeasureSpec.makeMeasureSpec(remainingLength, measureSpecMode));
+ final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child);
+ final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child);
+
+ measureWidth = Math.max(measureWidth, childMeasuredWidth);
+ measureHeight += childMeasuredHeight;
+ remainingLength = Math.max(0, remainingLength - childMeasuredHeight);
+ }
+ else {
+ CommonLayoutParams.measureChild(child, MeasureSpec.makeMeasureSpec(remainingLength, measureSpecMode), childMeasureSpec);
+ final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child);
+ final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child);
+
+ measureHeight = Math.max(measureHeight, childMeasuredHeight);
+ measureWidth += childMeasuredWidth;
+ remainingLength = Math.max(0, remainingLength - childMeasuredWidth);
+ }
+
+ childState = combineMeasuredStates(childState, child.getMeasuredState());
+ }
+
+ // Add in our padding
+ measureWidth += horizontalPadding;
+ measureHeight += verticalPadding;
+
+ // Check against our minimum sizes
+ measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth());
+ measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight());
+
+ this._totalLength = isVertical ? measureHeight : measureWidth;
+
+ int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, isVertical ? childState : 0);
+ int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, isVertical ? 0 : childState);
+
+ this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (this._orientation == Orientation.vertical) {
+ this.layoutVertical(l, t, r, b);
+ }
+ else {
+ this.layoutHorizontal(l, t, r, b);
+ }
+
+ CommonLayoutParams.restoreOriginalParams(this);
+ }
+
+ private void layoutVertical(int left, int top, int right, int bottom) {
+
+ int paddingLeft = this.getPaddingLeft();
+ int paddingRight = this.getPaddingRight();
+ int paddingTop = this.getPaddingTop();
+ int paddingBottom = this.getPaddingBottom();
+
+ int childTop = 0;
+ int childLeft = paddingLeft;
+ int childRight = right - left - paddingRight;
+
+ int gravity = LayoutBase.getGravity(this);
+ final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+ switch (verticalGravity) {
+ case Gravity.CENTER_VERTICAL:
+ childTop = (bottom - top - this._totalLength) / 2 + paddingTop - paddingBottom;
+ break;
+
+ case Gravity.BOTTOM:
+ childTop = bottom - top - this._totalLength + paddingTop - paddingBottom;
+ break;
+
+ case Gravity.TOP:
+ case Gravity.FILL_VERTICAL:
+ default:
+ childTop = paddingTop;
+ break;
+ }
+
+ int count = this.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = this.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ int childHeight = CommonLayoutParams.getDesiredHeight(child);
+ CommonLayoutParams.layoutChild(child, childLeft, childTop, childRight, childTop + childHeight);
+ childTop += childHeight;
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ private void layoutHorizontal(int left, int top, int right, int bottom) {
+
+ int paddingLeft = this.getPaddingLeft();
+ int paddingRight = this.getPaddingRight();
+ int paddingTop = this.getPaddingTop();
+ int paddingBottom = this.getPaddingBottom();
+
+ int childTop = paddingTop;
+ int childLeft = 0;
+ int childBottom = bottom - top - paddingBottom;
+
+ int gravity = LayoutBase.getGravity(this);
+ final int horizontalGravity = Gravity.getAbsoluteGravity(gravity, this.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+ switch (horizontalGravity) {
+ case Gravity.CENTER_HORIZONTAL:
+ childLeft = (right - left - this._totalLength) / 2 + paddingLeft - paddingRight;
+ break;
+
+ case Gravity.RIGHT:
+ childLeft = right - left - this._totalLength + paddingLeft - paddingRight;
+ break;
+
+ case Gravity.LEFT:
+ case Gravity.FILL_HORIZONTAL:
+ default:
+ childLeft = paddingLeft;
+ break;
+ }
+
+ int count = this.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = this.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ int childWidth = CommonLayoutParams.getDesiredWidth(child);
+ CommonLayoutParams.layoutChild(child, childLeft, childTop, childLeft + childWidth, childBottom);
+ childLeft += childWidth;
+ }
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java b/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java
new file mode 100644
index 000000000..16bc1e021
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java
@@ -0,0 +1,9 @@
+package org.nativescript.widgets;
+
+import android.graphics.drawable.Drawable;
+
+public class TabItemSpec {
+ public String title;
+ public int iconId;
+ public Drawable iconDrawable;
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/TabLayout.java b/widgets/src/main/java/org/nativescript/widgets/TabLayout.java
new file mode 100644
index 000000000..f4eb393f7
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/TabLayout.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.nativescript.widgets;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * To be used with ViewPager to provide a tab indicator component which give
+ * constant feedback as to the user's scroll progress.
+ *
+ * To use the component, simply add it to your view hierarchy. Then in your
+ * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
+ * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is
+ * being used for.
+ *
+ * The colors can be customized in two ways. The first and simplest is to
+ * provide an array of colors via {@link #setSelectedIndicatorColors(int...)}.
+ * The alternative is via the {@link TabColorizer} interface which provides you
+ * complete control over which color is used for any individual position.
+ *
+ */
+public class TabLayout extends HorizontalScrollView {
+ /**
+ * Allows complete control over the colors drawn in the tab layout. Set with
+ * {@link #setCustomTabColorizer(TabColorizer)}.
+ */
+ public interface TabColorizer {
+
+ /**
+ * @return return the color of the indicator used when {@code position}
+ * is selected.
+ */
+ int getIndicatorColor(int position);
+
+ }
+
+ private static final int TITLE_OFFSET_DIPS = 24;
+ private static final int TAB_VIEW_PADDING_DIPS = 16;
+ private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
+ private static final int TEXT_MAX_WIDHT = 180;
+ private static final int SMALL_MIN_HEIGHT = 48;
+ private static final int LARGE_MIN_HEIGHT = 72;
+
+ private int mTitleOffset;
+
+ private boolean mDistributeEvenly = true;
+
+ private TabItemSpec[] mTabItems;
+ private ViewPager mViewPager;
+ private SparseArray mContentDescriptions = new SparseArray();
+ private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
+
+ private final TabStrip mTabStrip;
+
+ public TabLayout(Context context) {
+ this(context, null);
+ }
+
+ public TabLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TabLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ // Disable the Scroll Bar
+ setHorizontalScrollBarEnabled(false);
+ // Make sure that the Tab Strips fills this View
+ setFillViewport(true);
+
+ mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
+
+ mTabStrip = new TabStrip(context);
+ addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+ }
+
+ /**
+ * Set the custom {@link TabColorizer} to be used.
+ *
+ * If you only require simple customisation then you can use
+ * {@link #setSelectedIndicatorColors(int...)} to achieve similar effects.
+ */
+ public void setCustomTabColorizer(TabColorizer tabColorizer) {
+ mTabStrip.setCustomTabColorizer(tabColorizer);
+ }
+
+ public void setDistributeEvenly(boolean distributeEvenly) {
+ mDistributeEvenly = distributeEvenly;
+ }
+
+ /**
+ * Sets the colors to be used for indicating the selected tab. These colors
+ * are treated as a circular array. Providing one color will mean that all
+ * tabs are indicated with the same color.
+ */
+ public void setSelectedIndicatorColors(int... colors) {
+ mTabStrip.setSelectedIndicatorColors(colors);
+ }
+
+ /**
+ * Set the {@link ViewPager.OnPageChangeListener}. When using
+ * {@link TabLayout} you are required to set any
+ * {@link ViewPager.OnPageChangeListener} through this method. This is so
+ * that the layout can update it's scroll position correctly.
+ *
+ * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
+ */
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mViewPagerPageChangeListener = listener;
+ }
+
+ /**
+ * Sets the associated view pager. Note that the assumption here is that the
+ * pager content (number of tabs and tab titles) does not change after this
+ * call has been made.
+ */
+ public void setViewPager(ViewPager viewPager) {
+ this.setItems(null, viewPager);
+ }
+
+ public void setItems(TabItemSpec[] items, ViewPager viewPager) {
+ mTabStrip.removeAllViews();
+
+ mViewPager = viewPager;
+ mTabItems = items;
+ if (viewPager != null) {
+ viewPager.addOnPageChangeListener(new InternalViewPagerListener());
+ populateTabStrip();
+ }
+ }
+
+ /**
+ * Updates the UI of an item at specified index
+ */
+ public void updateItemAt(int position, TabItemSpec tabItem) {
+ LinearLayout ll = (LinearLayout)mTabStrip.getChildAt(position);
+ ImageView imgView = (ImageView)ll.getChildAt(0);
+ TextView textView = (TextView)ll.getChildAt(1);
+ this.setupItem(ll, textView, imgView, tabItem);
+ }
+
+ /**
+ * Gets the TextView for tab item at index
+ */
+ public TextView getTextViewForItemAt(int index){
+ LinearLayout ll = this.getViewForItemAt(index);
+ return (ll != null) ? (TextView)ll.getChildAt(1) : null;
+ }
+
+ /**
+ * Gets the LinearLayout container for tab item at index
+ */
+ public LinearLayout getViewForItemAt(int index){
+ LinearLayout result = null;
+
+ if(this.mTabStrip.getChildCount() > index){
+ result = (LinearLayout)this.mTabStrip.getChildAt(index);
+ }
+
+ return result;
+ }
+
+ /**
+ * Create a default view to be used for tabs.
+ */
+ protected View createDefaultTabView(Context context, TabItemSpec tabItem) {
+ float density = getResources().getDisplayMetrics().density;
+ int padding = (int) (TAB_VIEW_PADDING_DIPS * density);
+
+ LinearLayout ll = new LinearLayout(context);
+ ll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ ll.setGravity(Gravity.CENTER);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ TypedValue outValue = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
+ ll.setBackgroundResource(outValue.resourceId);
+
+ ImageView imgView = new ImageView(context);
+ imgView.setScaleType(ScaleType.FIT_CENTER);
+ LinearLayout.LayoutParams imgLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ imgLP.gravity = Gravity.CENTER;
+ imgView.setLayoutParams(imgLP);
+
+ TextView textView = new TextView(context);
+ textView.setGravity(Gravity.CENTER);
+ textView.setMaxWidth((int) (TEXT_MAX_WIDHT * density));
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
+ textView.setTypeface(Typeface.DEFAULT_BOLD);
+ textView.setEllipsize(TextUtils.TruncateAt.END);
+ textView.setAllCaps(true);
+ textView.setMaxLines(2);
+ textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ textView.setPadding(padding, 0, padding, 0);
+
+ this.setupItem(ll, textView, imgView, tabItem);
+
+ ll.addView(imgView);
+ ll.addView(textView);
+ return ll;
+ }
+
+ private void setupItem(LinearLayout ll, TextView textView,ImageView imgView, TabItemSpec tabItem){
+ float density = getResources().getDisplayMetrics().density;
+
+ if (tabItem.iconId != 0) {
+ imgView.setImageResource(tabItem.iconId);
+ imgView.setVisibility(VISIBLE);
+ } else if (tabItem.iconDrawable != null) {
+ imgView.setImageDrawable(tabItem.iconDrawable);
+ imgView.setVisibility(VISIBLE);
+ } else {
+ imgView.setVisibility(GONE);
+ }
+
+ if (tabItem.title != null && !tabItem.title.isEmpty()) {
+ textView.setText(tabItem.title);
+ textView.setVisibility(VISIBLE);
+ } else {
+ textView.setVisibility(GONE);
+ }
+
+ if (imgView.getVisibility() == VISIBLE && textView.getVisibility() == VISIBLE) {
+ ll.setMinimumHeight((int) (LARGE_MIN_HEIGHT * density));
+ } else {
+ ll.setMinimumHeight((int) (SMALL_MIN_HEIGHT * density));
+ }
+
+ if (mDistributeEvenly) {
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams();
+ lp.width = 0;
+ lp.weight = 1;
+ }
+ }
+
+ private void populateTabStrip() {
+ final PagerAdapter adapter = mViewPager.getAdapter();
+ final OnClickListener tabClickListener = new TabClickListener();
+
+ for (int i = 0; i < adapter.getCount(); i++) {
+ View tabView = null;
+
+ TabItemSpec tabItem;
+ if (this.mTabItems != null && this.mTabItems.length > i) {
+ tabItem = this.mTabItems[i];
+ } else {
+ tabItem = new TabItemSpec();
+ tabItem.title = adapter.getPageTitle(i).toString();
+ }
+
+ tabView = createDefaultTabView(getContext(), tabItem);
+
+ tabView.setOnClickListener(tabClickListener);
+ String desc = mContentDescriptions.get(i, null);
+ if (desc != null) {
+ tabView.setContentDescription(desc);
+ }
+
+ mTabStrip.addView(tabView);
+ if (i == mViewPager.getCurrentItem()) {
+ tabView.setSelected(true);
+ }
+ }
+ }
+
+ public void setContentDescription(int i, String desc) {
+ mContentDescriptions.put(i, desc);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mViewPager != null) {
+ scrollToTab(mViewPager.getCurrentItem(), 0);
+ }
+ }
+
+ private void scrollToTab(int tabIndex, int positionOffset) {
+ final int tabStripChildCount = mTabStrip.getChildCount();
+ if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
+ return;
+ }
+
+ View selectedChild = mTabStrip.getChildAt(tabIndex);
+ if (selectedChild != null) {
+ int targetScrollX = selectedChild.getLeft() + positionOffset;
+
+ if (tabIndex > 0 || positionOffset > 0) {
+ // If we're not at the first child and are mid-scroll, make sure
+ // we obey the offset
+ targetScrollX -= mTitleOffset;
+ }
+
+ scrollTo(targetScrollX, 0);
+ }
+ }
+
+ private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
+ private int mScrollState;
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ int tabStripChildCount = mTabStrip.getChildCount();
+ if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+ return;
+ }
+
+ mTabStrip.onViewPagerPageChanged(position, positionOffset);
+
+ View selectedTitle = mTabStrip.getChildAt(position);
+ int extraOffset = (selectedTitle != null) ? (int) (positionOffset * selectedTitle.getWidth()) : 0;
+ scrollToTab(position, extraOffset);
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mTabStrip.onViewPagerPageChanged(position, 0f);
+ scrollToTab(position, 0);
+ }
+ for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+ mTabStrip.getChildAt(i).setSelected(position == i);
+ }
+ if (mViewPagerPageChangeListener != null) {
+ mViewPagerPageChangeListener.onPageSelected(position);
+ }
+ }
+
+ }
+
+ private class TabClickListener implements OnClickListener {
+ @Override
+ public void onClick(View v) {
+ for (int i = 0; i < mTabStrip.getChildCount(); i++) {
+ if (v == mTabStrip.getChildAt(i)) {
+ mViewPager.setCurrentItem(i);
+ return;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/widgets/src/main/java/org/nativescript/widgets/TabStrip.java b/widgets/src/main/java/org/nativescript/widgets/TabStrip.java
new file mode 100644
index 000000000..fa6636e16
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/TabStrip.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.nativescript.widgets;
+
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+import android.widget.LinearLayout;
+
+class TabStrip extends LinearLayout {
+
+ private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0;
+ private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
+ private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3;
+ private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
+
+ private final int mBottomBorderThickness;
+ private final Paint mBottomBorderPaint;
+
+ private final int mSelectedIndicatorThickness;
+ private final Paint mSelectedIndicatorPaint;
+
+ private final int mDefaultBottomBorderColor;
+
+ private int mSelectedPosition;
+ private float mSelectionOffset;
+
+ private TabLayout.TabColorizer mCustomTabColorizer;
+ private final SimpleTabColorizer mDefaultTabColorizer;
+
+ TabStrip(Context context) {
+ this(context, null);
+ }
+
+ TabStrip(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setWillNotDraw(false);
+
+ final float density = getResources().getDisplayMetrics().density;
+
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true);
+ final int themeForegroundColor = outValue.data;
+
+ mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
+ DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
+
+ mDefaultTabColorizer = new SimpleTabColorizer();
+ mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
+
+ mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
+ mBottomBorderPaint = new Paint();
+ mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
+
+ mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
+ mSelectedIndicatorPaint = new Paint();
+ }
+
+ void setCustomTabColorizer(TabLayout.TabColorizer customTabColorizer) {
+ mCustomTabColorizer = customTabColorizer;
+ invalidate();
+ }
+
+ void setSelectedIndicatorColors(int... colors) {
+ // Make sure that the custom colorizer is removed
+ mCustomTabColorizer = null;
+ mDefaultTabColorizer.setIndicatorColors(colors);
+ invalidate();
+ }
+
+ void onViewPagerPageChanged(int position, float positionOffset) {
+ mSelectedPosition = position;
+ mSelectionOffset = positionOffset;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ final int height = getHeight();
+ final int childCount = getChildCount();
+ final TabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
+ ? mCustomTabColorizer
+ : mDefaultTabColorizer;
+
+ // Thick colored underline below the current selection
+ if (childCount > 0) {
+ View selectedTitle = getChildAt(mSelectedPosition);
+ int left = selectedTitle.getLeft();
+ int right = selectedTitle.getRight();
+ int color = tabColorizer.getIndicatorColor(mSelectedPosition);
+
+ if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
+ int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
+ if (color != nextColor) {
+ color = blendColors(nextColor, color, mSelectionOffset);
+ }
+
+ // Draw the selection partway between the tabs
+ View nextTitle = getChildAt(mSelectedPosition + 1);
+ left = (int) (mSelectionOffset * nextTitle.getLeft() +
+ (1.0f - mSelectionOffset) * left);
+ right = (int) (mSelectionOffset * nextTitle.getRight() +
+ (1.0f - mSelectionOffset) * right);
+ }
+
+ mSelectedIndicatorPaint.setColor(color);
+
+ canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
+ height, mSelectedIndicatorPaint);
+ }
+
+ // Thin underline along the entire bottom edge
+ canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
+ }
+
+ /**
+ * Set the alpha value of the {@code color} to be the given {@code alpha} value.
+ */
+ private static int setColorAlpha(int color, byte alpha) {
+ return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ /**
+ * Blend {@code color1} and {@code color2} using the given ratio.
+ *
+ * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
+ * 0.0 will return {@code color2}.
+ */
+ private static int blendColors(int color1, int color2, float ratio) {
+ final float inverseRation = 1f - ratio;
+ float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
+ float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
+ float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
+ return Color.rgb((int) r, (int) g, (int) b);
+ }
+
+ private static class SimpleTabColorizer implements TabLayout.TabColorizer {
+ private int[] mIndicatorColors;
+
+ @Override
+ public final int getIndicatorColor(int position) {
+ return mIndicatorColors[position % mIndicatorColors.length];
+ }
+
+ void setIndicatorColors(int... colors) {
+ mIndicatorColors = colors;
+ }
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/VerticalScrollView.java b/widgets/src/main/java/org/nativescript/widgets/VerticalScrollView.java
new file mode 100644
index 000000000..53aa9d817
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/VerticalScrollView.java
@@ -0,0 +1,191 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+import org.nativescript.widgets.HorizontalScrollView.SavedState;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Parcelable;
+import android.view.View;
+import android.widget.ScrollView;
+
+/**
+ * @author hhristov
+ *
+ */
+public class VerticalScrollView extends ScrollView {
+
+ private final Rect mTempRect = new Rect();
+
+ private int contentMeasuredWidth = 0;
+ private int contentMeasuredHeight = 0;
+ private int scrollableLength = 0;
+ private SavedState mSavedState;
+ private boolean isFirstLayout = true;
+
+ /**
+ * True when the layout has changed but the traversal has not come through yet.
+ * Ideally the view hierarchy would keep track of this for us.
+ */
+ private boolean mIsLayoutDirty = true;
+
+ /**
+ * The child to give focus to in the event that a child has requested focus while the
+ * layout is dirty. This prevents the scroll from being wrong if the child has not been
+ * laid out before requesting focus.
+ */
+ private View mChildToScrollTo = null;
+
+ public VerticalScrollView(Context context) {
+ super(context);
+ }
+
+ public int getScrollableLength() {
+ return this.scrollableLength;
+ }
+
+ @Override
+ public void requestLayout() {
+ this.mIsLayoutDirty = true;
+ super.requestLayout();
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ if (!this.mIsLayoutDirty) {
+ this.scrollToChild(focused);
+ }
+ else {
+ // The child may not be laid out yet, we can't compute the scroll yet
+ this.mChildToScrollTo = focused;
+ }
+ super.requestChildFocus(child, focused);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);
+
+ // Don't call measure because it will measure content twice.
+ // ScrollView is expected to have single child so we measure only the first child.
+ View child = this.getChildCount() > 0 ? this.getChildAt(0) : null;
+ if (child == null) {
+ this.scrollableLength = 0;
+ this.contentMeasuredWidth = 0;
+ this.contentMeasuredHeight = 0;
+ this.setPadding(0, 0, 0, 0);
+ }
+ else {
+ CommonLayoutParams.measureChild(child, widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ this.contentMeasuredWidth = CommonLayoutParams.getDesiredWidth(child);
+ this.contentMeasuredHeight = CommonLayoutParams.getDesiredHeight(child);
+
+ // Android ScrollView does not account to child margins so we set them as paddings. Otherwise you can never scroll to bottom.
+ CommonLayoutParams lp = (CommonLayoutParams)child.getLayoutParams();
+ this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin);
+ }
+
+ // Don't add in our paddings because they are already added as child margins. (we will include them twice if we add them).
+ // check the previous line - this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin);
+// this.contentMeasuredWidth += this.getPaddingLeft() + this.getPaddingRight();
+// this.contentMeasuredHeight += this.getPaddingTop() + this.getPaddingBottom();
+
+ // Check against our minimum height
+ this.contentMeasuredWidth = Math.max(this.contentMeasuredWidth, this.getSuggestedMinimumWidth());
+ this.contentMeasuredHeight = Math.max(this.contentMeasuredHeight, this.getSuggestedMinimumHeight());
+
+ int widthSizeAndState = resolveSizeAndState(this.contentMeasuredWidth, widthMeasureSpec, 0);
+ int heightSizeAndState = resolveSizeAndState(this.contentMeasuredHeight, heightMeasureSpec, 0);
+
+ this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ int childHeight = 0;
+ if (this.getChildCount() > 0) {
+ View child = this.getChildAt(0);
+ childHeight = child.getMeasuredHeight();
+
+ int width = right - left;
+ int height = bottom - top;
+
+ this.scrollableLength = this.contentMeasuredHeight - height;
+ CommonLayoutParams.layoutChild(child, 0, 0, width, Math.max(this.contentMeasuredHeight, height));
+ this.scrollableLength = Math.max(0, this.scrollableLength);
+ }
+
+ this.mIsLayoutDirty = false;
+ // Give a child focus if it needs it
+ if (this.mChildToScrollTo != null && HorizontalScrollView.isViewDescendantOf(this.mChildToScrollTo, this)) {
+ this.scrollToChild(this.mChildToScrollTo);
+ }
+
+ this.mChildToScrollTo = null;
+
+ int scrollX = this.getScrollX();
+ int scrollY = this.getScrollY();
+ if (this.isFirstLayout) {
+ this.isFirstLayout = false;
+
+ final int scrollRange = Math.max(0, childHeight - (bottom - top - this.getPaddingTop() - this.getPaddingBottom()));
+ if (this.mSavedState != null) {
+ scrollY = mSavedState.scrollPosition;
+ mSavedState = null;
+ }
+
+ // Don't forget to clamp
+ if (scrollY > scrollRange) {
+ scrollY = scrollRange;
+ } else if (scrollY < 0) {
+ scrollY = 0;
+ }
+ }
+
+ // Calling this with the present values causes it to re-claim them
+ this.scrollTo(scrollX, scrollY);
+
+ CommonLayoutParams.restoreOriginalParams(this);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ this.isFirstLayout = true;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ this.isFirstLayout = true;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ this.mSavedState = ss;
+ this.requestLayout();
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.scrollPosition = this.getScrollY();
+ return ss;
+ }
+
+ private void scrollToChild(View child) {
+ child.getDrawingRect(mTempRect);
+
+ /* Offset from child's local coordinates to ScrollView coordinates */
+ offsetDescendantRectToMyCoords(child, mTempRect);
+
+ int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
+ if (scrollDelta != 0) {
+ this.scrollBy(scrollDelta, 0);
+ }
+ }
+}
diff --git a/widgets/src/main/java/org/nativescript/widgets/WrapLayout.java b/widgets/src/main/java/org/nativescript/widgets/WrapLayout.java
new file mode 100644
index 000000000..e246ed923
--- /dev/null
+++ b/widgets/src/main/java/org/nativescript/widgets/WrapLayout.java
@@ -0,0 +1,237 @@
+/**
+ *
+ */
+package org.nativescript.widgets;
+
+import java.util.ArrayList;
+import android.content.Context;
+import android.view.View;
+
+/**
+ * @author hhristov
+ *
+ */
+public class WrapLayout extends LayoutBase {
+
+ private int _itemWidth = -1;
+ private int _itemHeight = -1;
+ private Orientation _orientation = Orientation.horzontal;
+ private ArrayList _lengths = new ArrayList();
+
+ public WrapLayout(Context context) {
+ super(context);
+ }
+
+ public Orientation getOrientation() {
+ return this._orientation;
+ }
+ public void setOrientation(Orientation value) {
+ this._orientation = value;
+ this.requestLayout();
+ }
+
+ public int getItemWidth() {
+ return this._itemWidth;
+ }
+ public void setItemWidth(int value) {
+ this._itemWidth = value;
+ this.requestLayout();
+ }
+
+ public int getItemHeight() {
+ return this._itemHeight;
+ }
+ public void setItemHeight(int value) {
+ this._itemHeight = value;
+ this.requestLayout();
+ }
+
+ private static int getViewMeasureSpec(int parentMode, int parentLength, int itemLength) {
+ if (itemLength > 0) {
+ return MeasureSpec.makeMeasureSpec(itemLength, MeasureSpec.EXACTLY);
+ }
+ else if (parentMode == MeasureSpec.UNSPECIFIED) {
+ return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ else {
+ return MeasureSpec.makeMeasureSpec(parentLength, MeasureSpec.AT_MOST);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);
+
+ int measureWidth = 0;
+ int measureHeight = 0;
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+ boolean isVertical = this._orientation == Orientation.vertical;
+ int verticalPadding = this.getPaddingTop() + this.getPaddingBottom();
+ int horizontalPadding = this.getPaddingLeft() + this.getPaddingRight();
+
+ int childWidthMeasureSpec = getViewMeasureSpec(widthMode, width, this._itemWidth);
+ int childHeightMeasureSpec = getViewMeasureSpec(heightMode, height, this._itemHeight);
+
+ int remainingWidth = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : width - horizontalPadding;
+ int remainingHeight = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : height - verticalPadding;
+
+ int count = this.getChildCount();
+
+ this._lengths.clear();
+ int rowOrColumn = 0;
+ int maxLength = 0;
+
+ for (int i = 0; i < count; i++) {
+ View child = this.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ CommonLayoutParams.measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec);
+ final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child);
+ final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child);
+
+ if (isVertical) {
+ if (childMeasuredHeight > remainingHeight) {
+ rowOrColumn++;
+ maxLength = Math.max(maxLength, measureHeight);
+ measureHeight = childMeasuredHeight;
+ remainingWidth = height - childMeasuredHeight;
+ this._lengths.add(rowOrColumn, childMeasuredWidth);
+ }
+ else {
+ remainingHeight -= childMeasuredHeight;
+ measureHeight += childMeasuredHeight;
+ }
+ }
+ else {
+ if (childMeasuredWidth > remainingWidth) {
+ rowOrColumn++;
+ maxLength = Math.max(maxLength, measureWidth);
+ measureWidth = childMeasuredWidth;
+ remainingWidth = width - childMeasuredWidth;
+ this._lengths.add(rowOrColumn, childMeasuredHeight);
+ }
+ else {
+ remainingWidth -= childMeasuredWidth;
+ measureWidth += childMeasuredWidth;
+ }
+ }
+
+ if(this._lengths.size() <= rowOrColumn) {
+ this._lengths.add(rowOrColumn, isVertical ? childMeasuredWidth : childMeasuredHeight);
+ }
+ else {
+ this._lengths.set(rowOrColumn, Math.max(this._lengths.get(rowOrColumn), isVertical ? childMeasuredWidth : childMeasuredHeight));
+ }
+ }
+
+ count = this._lengths.size();
+ if (isVertical) {
+ measureHeight = Math.max(maxLength, measureHeight);
+ for (int i = 0; i < count; i++) {
+ measureWidth += this._lengths.get(i);
+ }
+ }
+ else {
+ measureWidth = Math.max(maxLength, measureWidth);
+ for (int i = 0; i < count; i++) {
+ measureHeight += this._lengths.get(i);
+ }
+ }
+
+ // Add in our padding
+ measureWidth += horizontalPadding;
+ measureHeight += verticalPadding;
+
+ // Check against our minimum sizes
+ measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth());
+ measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight());
+
+ int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0);
+ int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0);
+
+ this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ boolean isVertical = this._orientation == Orientation.vertical;
+ int paddingLeft = this.getPaddingLeft();
+ int paddingRight = this.getPaddingRight();
+ int paddingTop = this.getPaddingTop();
+ int paddingBottom = this.getPaddingBottom();
+
+ int childLeft = paddingLeft;
+ int childTop = paddingTop;
+ int childrenLength = isVertical ? bottom - top - paddingBottom : right - left - paddingRight;
+
+ int rowOrColumn = 0;
+ int count = this.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = this.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ // Add margins because layoutChild will subtract them.
+ int childWidth = CommonLayoutParams.getDesiredWidth(child);
+ int childHeight = CommonLayoutParams.getDesiredHeight(child);
+
+ int length = this._lengths.get(rowOrColumn);
+ if (isVertical) {
+ childWidth = length;
+ childHeight = this._itemHeight > 0 ? this._itemHeight : childHeight;
+ if (childTop + childHeight > childrenLength) {
+ // Move to top.
+ childTop = paddingTop;
+
+ // Move to right with current column width.
+ childLeft += length;
+
+ // Move to next column.
+ rowOrColumn++;
+
+ // Take current column width.
+ childWidth = length = this._lengths.get(rowOrColumn);
+ }
+ }
+ else {
+ childWidth = this._itemWidth > 0 ? this._itemWidth : childWidth;
+ childHeight = length;
+ if (childLeft + childWidth > childrenLength) {
+ // Move to left.
+ childLeft = paddingLeft;
+
+ // Move to bottom with current row height.
+ childTop += length;
+
+ // Move to next column.
+ rowOrColumn++;
+
+ // Take current row height.
+ childHeight = length = this._lengths.get(rowOrColumn);
+ }
+ }
+
+ CommonLayoutParams.layoutChild(child, childLeft, childTop, childLeft + childWidth, childTop + childHeight);
+
+ if (isVertical) {
+ // Move next child Top position to bottom.
+ childTop += childHeight;
+ }
+ else {
+ // Move next child Left position to right.
+ childLeft += childWidth;
+ }
+ }
+
+ CommonLayoutParams.restoreOriginalParams(this);
+ }
+}
\ No newline at end of file
diff --git a/widgets/src/main/res/values/strings.xml b/widgets/src/main/res/values/strings.xml
new file mode 100644
index 000000000..38c1813a7
--- /dev/null
+++ b/widgets/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Widgets
+
diff --git a/widgets/src/test/java/org/nativescript/widgets/ExampleUnitTest.java b/widgets/src/test/java/org/nativescript/widgets/ExampleUnitTest.java
new file mode 100644
index 000000000..4f5c4921b
--- /dev/null
+++ b/widgets/src/test/java/org/nativescript/widgets/ExampleUnitTest.java
@@ -0,0 +1,15 @@
+package org.nativescript.widgets;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * To work on unit tests, switch the Test Artifact in the Build Variants view.
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() throws Exception {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file