Skip to content
Binary file modified packages/core/platforms/android/widgets-release.aar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,33 @@ protected static int getDesiredHeight(View view) {
return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}

protected static int getLayoutGravity(View child, boolean fitsWidth, boolean fitsHeight) {
CommonLayoutParams lp = (CommonLayoutParams) child.getLayoutParams();
int gravity = lp.gravity;

if (gravity == -1) {
gravity = Gravity.FILL;
}

// If gravity is FILL and content does not fill parent, we need it to be centered otherwise our explicit size won't be taken into account.
if (fitsWidth) {
int horizontalGravity = Gravity.getAbsoluteGravity(gravity, child.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
if (horizontalGravity == Gravity.FILL_HORIZONTAL) {
gravity &= ~Gravity.FILL_HORIZONTAL;
gravity |= Gravity.CENTER_HORIZONTAL;
}
}
if (fitsHeight) {
int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
if (verticalGravity == Gravity.FILL_VERTICAL) {
gravity &= ~Gravity.FILL_VERTICAL;
gravity |= Gravity.CENTER_VERTICAL;
}
}

return gravity;
}

// We use our own layout method because the one in FrameLayout is broken when margins are set and gravity is CENTER_VERTICAL or CENTER_HORIZONTAL.
@SuppressLint("RtlHardcoded")
protected static void layoutChild(View child, int left, int top, int right, int bottom) {
Expand All @@ -110,18 +137,9 @@ protected static void layoutChild(View child, int left, int top, int right, int
int childHeight = child.getMeasuredHeight();

CommonLayoutParams lp = (CommonLayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity == -1) {
gravity = Gravity.FILL;
}

int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

// If we have explicit height and gravity is FILL we need to be centered otherwise our explicit height won't be taken into account.
if ((lp.height >= 0 || lp.heightPercent > 0) && verticalGravity == Gravity.FILL_VERTICAL) {
verticalGravity = Gravity.CENTER_VERTICAL;
}

final int gravity = CommonLayoutParams.getLayoutGravity(child, (lp.width >= 0 || lp.widthPercent > 0), (lp.height >= 0 || lp.heightPercent > 0));

final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (verticalGravity) {
case Gravity.TOP:
childTop = top + lp.topMargin;
Expand All @@ -142,13 +160,7 @@ protected static void layoutChild(View child, int left, int top, int right, int
break;
}

int horizontalGravity = Gravity.getAbsoluteGravity(gravity, child.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;

// If we have explicit width and gravity is FILL we need to be centered otherwise our explicit width won't be taken into account.
if ((lp.width >= 0 || lp.widthPercent > 0) && horizontalGravity == Gravity.FILL_HORIZONTAL) {
horizontalGravity = Gravity.CENTER_HORIZONTAL;
}

final int horizontalGravity = Gravity.getAbsoluteGravity(gravity, child.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
switch (horizontalGravity) {
case Gravity.LEFT:
childLeft = left + lp.leftMargin;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
package org.nativescript.widgets;

import android.content.Context;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;

/**
* @author hhristov
*/
public class HorizontalScrollView extends android.widget.HorizontalScrollView {

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;
private boolean scrollEnabled = 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 HorizontalScrollView(Context context) {
super(context);
}
Expand Down Expand Up @@ -75,12 +52,6 @@ public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(ev);
}

@Override
public void requestLayout() {
this.mIsLayoutDirty = true;
super.requestLayout();
}

@Override
protected CommonLayoutParams generateDefaultLayoutParams() {
return new CommonLayoutParams();
Expand Down Expand Up @@ -116,17 +87,6 @@ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams fro
return new CommonLayoutParams(from);
}

@Override
public void requestChildFocus(View child, View focused) {
if (!mIsLayoutDirty) {
this.scrollToChild(focused);
} else {
// The child may not be laid out yet, we can't compute the scroll yet
mChildToScrollTo = focused;
}
super.requestChildFocus(child, focused);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);
Expand All @@ -138,13 +98,26 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
this.scrollableLength = 0;
this.contentMeasuredWidth = 0;
this.contentMeasuredHeight = 0;

this.setPadding(0, 0, 0, 0);
} else {
CommonLayoutParams.measureChild(child, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec);
CommonLayoutParams lp = (CommonLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec;

final int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - lp.leftMargin - lp.rightMargin;
final int childWidth = child.getMeasuredWidth();
if (lp.width == LayoutParams.MATCH_PARENT && childWidth > 0 && childWidth < availableWidth) {
childWidthMeasureSpec = widthMeasureSpec;
} else {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}

CommonLayoutParams.measureChild(child, childWidthMeasureSpec, heightMeasureSpec);
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);
}

Expand All @@ -165,151 +138,44 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int childWidth = 0;
if (this.getChildCount() > 0) {
View child = this.getChildAt(0);
childWidth = child.getMeasuredWidth();

int width = right - left;
int height = bottom - top;

this.scrollableLength = this.contentMeasuredWidth - width;
CommonLayoutParams.layoutChild(child, 0, 0, Math.max(this.contentMeasuredWidth, width), height);
this.scrollableLength = Math.max(0, this.scrollableLength);
}

this.mIsLayoutDirty = false;
// Give a child focus if it needs it
if (this.mChildToScrollTo != null && isViewDescendantOf(this.mChildToScrollTo, this)) {
this.scrollToChild(this.mChildToScrollTo);
}
final int width = right - left;
final int height = bottom - top;

this.mChildToScrollTo = null;

int scrollX = this.getScrollX();
int scrollY = this.getScrollY();

if (this.isFirstLayout) {
this.isFirstLayout = false;

final int scrollRange = Math.max(0, childWidth - (right - left - this.getPaddingLeft() - this.getPaddingRight()));
if (this.mSavedState != null) {
scrollX = (this.isLayoutRtl() == mSavedState.isLayoutRtl) ? mSavedState.scrollPosition : (scrollRange - this.mSavedState.scrollPosition);
mSavedState = null;
} else {
if (this.isLayoutRtl()) {
scrollX = scrollRange - scrollX;
} // mScrollX default value is "0" for LTR
}
// Don't forget to clamp
if (scrollX > scrollRange) {
scrollX = scrollRange;
} else if (scrollX < 0) {
scrollX = 0;
}
if (this.getChildCount() > 0) {
final View child = this.getChildAt(0);
final CommonLayoutParams childLayoutParams = (CommonLayoutParams) child.getLayoutParams();

final int leftMargin = childLayoutParams.leftMargin;
final int topMargin = childLayoutParams.topMargin;
final int rightMargin = childLayoutParams.rightMargin;
final int bottomMargin = childLayoutParams.bottomMargin;
final int gravity = childLayoutParams.gravity;

// Android scrollviews don't handle margins very well, so unset margin values and set their values anew when layout calculations are done
childLayoutParams.leftMargin = 0;
childLayoutParams.topMargin = 0;
childLayoutParams.rightMargin = 0;
childLayoutParams.bottomMargin = 0;

// This will apply gravity defaults if needed
childLayoutParams.gravity = CommonLayoutParams.getLayoutGravity(child, (childLayoutParams.width >= 0 && childLayoutParams.width < width),
(childLayoutParams.height >= 0 && childLayoutParams.height < height));

super.onLayout(changed, left, top, right, bottom);

// View has finished laying out child, set margin values back
childLayoutParams.leftMargin = leftMargin;
childLayoutParams.topMargin = topMargin;
childLayoutParams.rightMargin = rightMargin;
childLayoutParams.bottomMargin = bottomMargin;

// Set previous gravity value
childLayoutParams.gravity = gravity;
} else {
super.onLayout(changed, left, top, right, bottom);
}

// Calling this with the present values causes it to re-claim them
this.scrollTo(scrollX, scrollY);
this.scrollableLength = Math.max(0, this.contentMeasuredWidth - width);
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.getScrollX();
ss.isLayoutRtl = this.isLayoutRtl();
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);
}
}

private boolean isLayoutRtl() {
return (this.getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}

/**
* Return true if child is a descendant of parent, (or equal to the parent).
*/
static boolean isViewDescendantOf(View child, View parent) {
if (child == parent) {
return true;
}

final ViewParent theParent = child.getParent();
return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
}

static class SavedState extends BaseSavedState {
public int scrollPosition;
public boolean isLayoutRtl;

SavedState(Parcelable superState) {
super(superState);
}

public SavedState(Parcel source) {
super(source);
scrollPosition = source.readInt();
isLayoutRtl = source.readInt() == 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(scrollPosition);
dest.writeInt(isLayoutRtl ? 1 : 0);
}

@NonNull
@Override
public String toString() {
return "HorizontalScrollView.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " scrollPosition=" + scrollPosition
+ " isLayoutRtl=" + isLayoutRtl + "}";
}

public static final Creator<SavedState> CREATOR
= new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}

public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
Loading