mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-18 22:01:42 +08:00
Merge branch 'nestedscrollview' of github.com:Akylas/NativeScript
This commit is contained in:
@ -9,6 +9,7 @@ export class ScrollView extends ScrollViewBase {
|
||||
nativeViewProtected: org.nativescript.widgets.VerticalScrollView | org.nativescript.widgets.HorizontalScrollView;
|
||||
private _androidViewId = -1;
|
||||
private handler: android.view.ViewTreeObserver.OnScrollChangedListener;
|
||||
private scrollChangeHandler: androidx.core.widget.NestedScrollView.OnScrollChangeListener;
|
||||
|
||||
get horizontalOffset(): number {
|
||||
const nativeView = this.nativeViewProtected;
|
||||
@ -99,7 +100,13 @@ export class ScrollView extends ScrollViewBase {
|
||||
}
|
||||
|
||||
public createNativeView() {
|
||||
return this.orientation === 'horizontal' ? new org.nativescript.widgets.HorizontalScrollView(this._context) : new org.nativescript.widgets.VerticalScrollView(this._context);
|
||||
if (this.orientation === 'horizontal') {
|
||||
return new org.nativescript.widgets.HorizontalScrollView(this._context);
|
||||
} else {
|
||||
const view = new org.nativescript.widgets.VerticalScrollView(this._context);
|
||||
view.setVerticalScrollBarEnabled(true);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
public initNativeView(): void {
|
||||
@ -123,16 +130,32 @@ export class ScrollView extends ScrollViewBase {
|
||||
|
||||
protected attachNative() {
|
||||
const that = new WeakRef(this);
|
||||
this.handler = new android.view.ViewTreeObserver.OnScrollChangedListener({
|
||||
onScrollChanged: function () {
|
||||
const owner: ScrollView = that.get();
|
||||
if (owner) {
|
||||
owner._onScrollChanged();
|
||||
if (this.orientation === 'vertical') {
|
||||
this.scrollChangeHandler = new androidx.core.widget.NestedScrollView.OnScrollChangeListener({
|
||||
onScrollChange(view, scrollX, scrollY) {
|
||||
const owner: ScrollView = that.get();
|
||||
if (owner) {
|
||||
owner.notify({
|
||||
object: owner,
|
||||
eventName: ScrollView.scrollEvent,
|
||||
scrollX: layout.toDeviceIndependentPixels(scrollX),
|
||||
scrollY: layout.toDeviceIndependentPixels(scrollY)
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.nativeViewProtected.getViewTreeObserver().addOnScrollChangedListener(this.handler);
|
||||
});
|
||||
this.nativeView.setOnScrollChangeListener(this.scrollChangeHandler);
|
||||
} else {
|
||||
this.handler = new android.view.ViewTreeObserver.OnScrollChangedListener({
|
||||
onScrollChanged: function () {
|
||||
const owner: ScrollView = that.get();
|
||||
if (owner) {
|
||||
owner._onScrollChanged();
|
||||
}
|
||||
},
|
||||
});
|
||||
this.nativeViewProtected.getViewTreeObserver().addOnScrollChangedListener(this.handler);
|
||||
}
|
||||
}
|
||||
|
||||
private _lastScrollX = -1;
|
||||
@ -158,8 +181,14 @@ export class ScrollView extends ScrollViewBase {
|
||||
}
|
||||
|
||||
protected dettachNative() {
|
||||
this.nativeViewProtected.getViewTreeObserver().removeOnScrollChangedListener(this.handler);
|
||||
this.handler = null;
|
||||
if (this.handler) {
|
||||
this.nativeViewProtected.getViewTreeObserver().removeOnScrollChangedListener(this.handler);
|
||||
this.handler = null;
|
||||
}
|
||||
if (this.scrollChangeHandler) {
|
||||
this.nativeView.setOnScrollChangeListener(null);
|
||||
this.scrollChangeHandler = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,7 +359,7 @@
|
||||
constructor(context: android.content.Context);
|
||||
}
|
||||
|
||||
export class VerticalScrollView extends android.widget.ScrollView {
|
||||
export class VerticalScrollView extends androidx.core.widget.NestedScrollView {
|
||||
constructor(context: android.content.Context);
|
||||
public getScrollableLength(): number;
|
||||
public getScrollEnabled(): boolean;
|
||||
|
@ -12,70 +12,71 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ScrollView;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
|
||||
/**
|
||||
* @author hhristov
|
||||
*
|
||||
*/
|
||||
public class VerticalScrollView extends ScrollView {
|
||||
public class VerticalScrollView extends NestedScrollView {
|
||||
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public boolean getScrollEnabled() {
|
||||
return this.scrollEnabled;
|
||||
}
|
||||
public int getScrollableLength() {
|
||||
return this.scrollableLength;
|
||||
}
|
||||
|
||||
public boolean getScrollEnabled() {
|
||||
return this.scrollEnabled;
|
||||
}
|
||||
|
||||
public void setScrollEnabled(boolean value) {
|
||||
this.scrollEnabled = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
// Do nothing with intercepted touch events if we are not scrollable
|
||||
if (!this.scrollEnabled) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
// Do nothing with intercepted touch events if we are not scrollable
|
||||
if (!this.scrollEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!this.scrollEnabled && (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE)) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (!this.scrollEnabled
|
||||
&& (ev.getAction() == MotionEvent.ACTION_DOWN || ev.getAction() == MotionEvent.ACTION_MOVE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
return super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
@ -107,152 +108,156 @@ public class VerticalScrollView extends ScrollView {
|
||||
@Override
|
||||
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) {
|
||||
if (from instanceof CommonLayoutParams)
|
||||
return new CommonLayoutParams((CommonLayoutParams)from);
|
||||
return new CommonLayoutParams((CommonLayoutParams) from);
|
||||
|
||||
if (from instanceof FrameLayout.LayoutParams)
|
||||
return new CommonLayoutParams((FrameLayout.LayoutParams)from);
|
||||
return new CommonLayoutParams((FrameLayout.LayoutParams) from);
|
||||
|
||||
if (from instanceof ViewGroup.MarginLayoutParams)
|
||||
return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from);
|
||||
return new CommonLayoutParams((ViewGroup.MarginLayoutParams) from);
|
||||
|
||||
return new CommonLayoutParams(from);
|
||||
}
|
||||
|
||||
@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);
|
||||
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);
|
||||
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);
|
||||
// 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);
|
||||
// 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);
|
||||
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);
|
||||
// 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
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
this.isFirstLayout = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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();
|
||||
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;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user