Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-25792] Android: Improved nested horizontal scrolling support #9985

Merged
merged 7 commits into from
Apr 18, 2018
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View;
import android.view.ViewParent;

/**
* View group used to display a refresh progress indicator when the user swipes down.
Expand All @@ -34,6 +35,29 @@ public TiSwipeRefreshLayout(Context context)
super(context);
}

/**
* Enables or disables touch interception for this view and all parent views.
* <p>
* Disabling prevents the onInterceptTouchEvent() method from getting called, which is used by
* a parent ScrollView or ListView to scroll the container when touch-dragging a child view.
* @param value Set true to disable touch interception in this view and all parent views. Set false to re-enable.
*/
@Override
public void requestDisallowInterceptTouchEvent(boolean value)
{
// Enable/disable touch interception for this view.
super.requestDisallowInterceptTouchEvent(value);

// Google's "SwipeRefreshLayout" ignores above method call if child view does not support nested scrolling,
// which is the case for horizontal scrolling views such as HorizontalScrollView and TextInputLayout.
// We need parent vertical scrolling to be disallowed while scrolling these views horizontally.
// Work-Around: Send request to the SwipeRefreshLayout's parent ourselves since it might not do it.
ViewParent parentView = getParent();
if (parentView != null) {
parentView.requestDisallowInterceptTouchEvent(value);
}
}

/**
* Determines if touch input and swipe-down support is enabled.
* @return Returns true if enabled. Returns false if disabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;

/**
* EditText derived class used by Titanium's "Ti.UI.TextField" and "Ti.UI.TextArea" types.
Expand Down Expand Up @@ -135,6 +136,29 @@ public boolean onKeyPreIme(int keyCode, KeyEvent event)
return super.onKeyPreIme(keyCode, event);
}

/**
* Called when view's contents has scrolled by the given amount.
* This method will be called when scrolling via touch events and method calls such as scrollBy().
* @param currentX Current horizontal scroll position.
* @param currentY Current vertical scroll position.
* @param previousX Previous horizontal scroll position.
* @param previousY Previous vertical scroll position.
*/
@Override
public void onScrollChanged(int currentX, int currentY, int previousX, int previousY)
{
// Handle scroll change event.
super.onScrollChanged(currentX, currentY, previousX, previousY);

// Disable parent view touch interception while the EditText is being scrolled.
// Prevents vertical scroll view from stealing touch events while horizontally scrolling single-line field.
// Note: Android will re-enable touch interception on next touch ACTION_DOWN or ACTION_UP event.
ViewParent parentView = getParent();
if (parentView != null) {
parentView.requestDisallowInterceptTouchEvent(true);
}
}

/**
* Called when a touch down/move/up event has been received.
* @param event Provides information about the touch event.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.widget.NestedScrollView;
import android.view.GestureDetector;
import android.view.MotionEvent;
Expand Down Expand Up @@ -468,16 +470,25 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
}
}

private class TiHorizontalScrollView extends HorizontalScrollView
private class TiHorizontalScrollView extends HorizontalScrollView implements NestedScrollingChild
{
private TiScrollViewLayout layout;
private NestedScrollingChildHelper nestedScrollingChildHelper;

public TiHorizontalScrollView(Context context, LayoutArrangement arrangement)
{
super(context);
setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
setScrollContainer(true);

// Set up nested scrolling. Improves "SwipeRefreshLayout" touch interception handling.
// Note: On Android 5.0 and above, all views support nested child scrolling. We just need to enable it.
// The "NestedScrollingChildHelper" is only needed for older Android OS versions.
if (Build.VERSION.SDK_INT < 21) {
this.nestedScrollingChildHelper = new NestedScrollingChildHelper(this);
}
setNestedScrollingEnabled(true);

layout = new TiScrollViewLayout(context, arrangement);
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
Expand Down Expand Up @@ -589,6 +600,91 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}

@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed)
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
return super.dispatchNestedFling(velocityX, velocityY, consumed);
}

@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY)
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
return super.dispatchNestedPreFling(velocityX, velocityY);
}

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow)
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
int[] offsetInWindow)
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed, offsetInWindow);
}
return super.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}

@Override
public boolean hasNestedScrollingParent()
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.hasNestedScrollingParent();
}
return super.hasNestedScrollingParent();
}

@Override
public boolean isNestedScrollingEnabled()
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.isNestedScrollingEnabled();
}
return super.isNestedScrollingEnabled();
}

@Override
public void setNestedScrollingEnabled(boolean enabled)
{
if (this.nestedScrollingChildHelper != null) {
this.nestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
} else {
super.setNestedScrollingEnabled(enabled);
}
}

@Override
public boolean startNestedScroll(int axes)
{
if (this.nestedScrollingChildHelper != null) {
return this.nestedScrollingChildHelper.startNestedScroll(axes);
}
return super.startNestedScroll(axes);
}

@Override
public void stopNestedScroll()
{
if (this.nestedScrollingChildHelper != null) {
this.nestedScrollingChildHelper.stopNestedScroll();
} else {
super.stopNestedScroll();
}
}
}

public TiUIScrollView(TiViewProxy proxy)
Expand Down