我想在NestedScrollView中拥有滚动侦听器,以使我的顶级ImageView在用户滚动时获得缩放。像this
以上库使用ScrollView
,在我的情况下,我需要NestedScrollView
。因此,我想采用开发人员的相同方法,但是在解决一些问题时遇到了一些麻烦。
在View
中有一个protected
中使用的overScrollBy
方法ScrollView
,开发人员在其CustomScrollView
中将其覆盖。不幸的是,它不是overScrollBy
NestedScrollView
而是使用自己的overScrollByCombat
,它是私有的,因此我无法覆盖它。因此,我对如何在"overScrollListener"
中获得CustomNestedScrollView
颇有兴趣。
我能想到的唯一解决方案是实际上将我的PreCustomNestedScrollView
复制粘贴到NestedScrollView
的源代码中,并将overScrollByCombat
设置为公共。它有效,但我认为这不是一种优雅的方法。
如果已经有任何与NestedScrollView
产生相同效果的库,欢迎您推荐。
答案 0 :(得分:1)
这里有两种获取方法。
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
public class OverScrollBounceBehavior extends CoordinatorLayout.Behavior<View> {
private static final String TAG = "Behavior";
private int mNormalHeight = 0;
private int mMaxHeight = 0;
private float mFactor = 1.8f;
private int mOverScrollY;
private View mTargetView;
private OnScrollChangeListener mListener;
public OverScrollBounceBehavior() {
}
public OverScrollBounceBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull View child,
@NonNull View directTargetChild,
@NonNull View target,
int nestedScrollAxes, int type) {
findTargetView();
Log.d(TAG, "onStartNestedScroll " + "type = " + type);
//TYPE_TOUCH handle over scroll
if (checkTouchType(type) && checkTargetView()) {
mOverScrollY = 0;
mNormalHeight = mTargetView.getHeight();
mMaxHeight = (int) (mNormalHeight * mFactor);
}
return true;
}
public void setFactor(float factor) {
this.mFactor = factor;
}
public void setOnScrollChangeListener(OnScrollChangeListener listener) {
this.mListener = listener;
}
public void setTargetView(View targetView) {
//set a target view from outside, target view should be NestedScrollView child
this.mTargetView = targetView;
}
private void findTargetView() {
//implement a fixed find target view as you wish
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull View child,
@NonNull View target,
int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed,
int type) {
//unconsumed == 0 no overScroll
//unconsumed > 0 overScroll up
if (dyUnconsumed >= 0) {
return;
}
Log.d(TAG, "onNestedScroll : dyUnconsumed = " + dyUnconsumed);
mOverScrollY -= dyUnconsumed;
Log.d(TAG, "onNestedScroll : mOverScrollY = " + mOverScrollY + "type = " + type);
//TYPE_TOUCH handle over scroll
if (checkTouchType(type) && checkTargetView()) {
if (mOverScrollY > 0 && mTargetView.getLayoutParams().height + Math.abs(mOverScrollY) <= mMaxHeight) {
mTargetView.getLayoutParams().height += Math.abs(mOverScrollY);
mTargetView.requestLayout();
if (mListener != null) {
mListener.onScrollChanged(calculateRate(mTargetView, mMaxHeight, mNormalHeight));
}
}
}
}
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull View child,
@NonNull View target,
int type) {
Log.d(TAG, "onStopNestedScroll" + "type = " + type);
//TYPE_TOUCH handle over scroll
if (checkTouchType(type)
&& checkTargetView()
&& mTargetView.getHeight() > mNormalHeight) {
ResetAnimation animation = new ResetAnimation(mTargetView, mNormalHeight, mListener);
animation.setDuration(300);
mTargetView.startAnimation(animation);
}
}
private boolean checkTouchType(int type) {
return type == ViewCompat.TYPE_TOUCH;
}
private boolean checkTargetView() {
return mTargetView != null;
}
public static class ResetAnimation extends Animation {
int targetHeight;
int originalHeight;
int extraHeight;
View view;
OnScrollChangeListener listener;
ResetAnimation(View view, int targetHeight, OnScrollChangeListener listener) {
this.view = view;
this.targetHeight = targetHeight;
this.originalHeight = view.getHeight();
this.extraHeight = this.targetHeight - originalHeight;
this.listener = listener;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) (targetHeight - extraHeight * (1 - interpolatedTime));
view.getLayoutParams().height = newHeight;
view.requestLayout();
if (listener != null) {
listener.onScrollChanged(calculateRate(view, originalHeight, targetHeight));
}
}
}
public interface OnScrollChangeListener {
void onScrollChanged(float rate);
}
private static float calculateRate(View targetView, int maxHeight, int targetHeight) {
float rate = 0;
if (targetView != null) {
rate = (maxHeight - (float) targetView.getLayoutParams().height) / (maxHeight - targetHeight);
}
return rate;
}
}
(1)。在包android.support.v4.widget
and override `overScrollByCompat()` to invoke customized `openedOverScrollByCompat()` method.
(2)。创建所有者StretchTopNestedScrollView
覆盖
openedOverScrollByCompat()
,然后您可以做自己想做的事。
委托视图
package android.support.v4.widget;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
public class OpenedNestedScrollView extends NestedScrollView {
public OpenedNestedScrollView(@NonNull Context context) {
this(context, null);
}
public OpenedNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public OpenedNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
boolean overScrollByCompat(int deltaX, int deltaY,
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY,
boolean isTouchEvent) {
return openedOverScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
protected boolean openedOverScrollByCompat(int deltaX, int deltaY,
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY,
boolean isTouchEvent) {
return super.overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
}
}
您的所有者视图
ublic class StretchTopNestedScrollView extends OpenedNestedScrollView {
private View mTopView, mBottomView;
private int mNormalHeight, mMaxHeight;
private onOverScrollChanged mChangeListener;
private float mFactor = 1.6f;
private interface OnTouchEventListener {
void onTouchEvent(MotionEvent ev);
}
public StretchTopNestedScrollView(Context context) {
this(context, null);
}
public StretchTopNestedScrollView(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public StretchTopNestedScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setFactor(float f) {
mFactor = f;
mTopView.postDelayed(new Runnable() {
@Override
public void run() {
mNormalHeight = mTopView.getHeight();
mMaxHeight = (int) (mNormalHeight * mFactor);
}
}, 50);
}
public View getTopView() {
return mTopView;
}
public View getBottomView() {
return mBottomView;
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() > 1)
throw new IllegalArgumentException("Root layout must be a LinearLayout, and only one child on this view!");
if (getChildCount() == 0 || !(getChildAt(0) instanceof LinearLayout))
throw new IllegalArgumentException("Root layout is not a LinearLayout!");
if (getChildCount() == 1 && (getChildAt(0) instanceof LinearLayout)) {
LinearLayout parent = (LinearLayout) getChildAt(0);
if (parent.getChildCount() != 2) {
throw new IllegalArgumentException("Root LinearLayout's has not EXACTLY two Views!");
} else {
mTopView = parent.getChildAt(0);
mBottomView = parent.getChildAt(1);
mTopView.postDelayed(new Runnable() {
@Override
public void run() {
mNormalHeight = mTopView.getHeight();
mMaxHeight = (int) (mNormalHeight * mFactor);
}
}, 50);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
@Override
protected boolean openedOverScrollByCompat(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
if (scrollY == 0) {
//down, zoom in
if (deltaY < 0 && mTopView.getLayoutParams().height + Math.abs(deltaY) > mMaxHeight) {
mTopView.getLayoutParams().height = mMaxHeight;
} else if (deltaY < 0 && mTopView.getLayoutParams().height + Math.abs(deltaY) <= mMaxHeight) {
mTopView.getLayoutParams().height += Math.abs(deltaY);
}
//up, zoom out
else if (deltaY > 0 && mTopView.getLayoutParams().height - Math.abs(deltaY) < mNormalHeight) {
mTopView.getLayoutParams().height = mNormalHeight;
} else if (deltaY > 0 && mTopView.getLayoutParams().height - Math.abs(deltaY) >= mNormalHeight) {
mTopView.getLayoutParams().height -= Math.abs(deltaY);
}
}
if (mChangeListener != null) mChangeListener.onChanged(
(mMaxHeight - (float) mTopView.getLayoutParams().height) / (mMaxHeight - mNormalHeight)
);
if (deltaY != 0 && scrollY == 0) {
mTopView.requestLayout();
mBottomView.requestLayout();
}
if (mTopView.getLayoutParams().height == mNormalHeight) {
super.overScrollBy(deltaX, deltaY, scrollX,
scrollY, scrollRangeX, scrollRangeY,
maxOverScrollX, maxOverScrollY, isTouchEvent);
}
return true;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
touchListener.onTouchEvent(ev);
return super.onTouchEvent(ev);
}
public interface onOverScrollChanged {
void onChanged(float v);
}
public void setChangeListener(onOverScrollChanged changeListener) {
mChangeListener = changeListener;
}
private OnTouchEventListener touchListener = new OnTouchEventListener() {
@Override
public void onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (mTopView != null && mTopView.getHeight() > mNormalHeight) {
ResetAnimation animation = new ResetAnimation(mTopView, mNormalHeight);
animation.setDuration(400);
mTopView.startAnimation(animation);
}
}
}
};
public class ResetAnimation extends Animation {
int targetHeight;
int originalHeight;
int extraHeight;
View mView;
ResetAnimation(View view, int targetHeight) {
this.mView = view;
this.targetHeight = targetHeight;
originalHeight = view.getHeight();
extraHeight = this.targetHeight - originalHeight;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) (targetHeight - extraHeight * (1 - interpolatedTime));
mView.getLayoutParams().height = newHeight;
mView.requestLayout();
if (mChangeListener != null) mChangeListener.onChanged(
(mMaxHeight - (float) mTopView.getLayoutParams().height) / (mMaxHeight - mNormalHeight)
);
}
}
}