【问题标题】:How to implement NestedScrolling on Android?如何在 Android 上实现 NestedScrolling?
【发布时间】:2015-04-26 23:53:53
【问题描述】:

通过 support-v4 库 22.1.0,android 支持嵌套滚动(android 5.0 之前)。不幸的是,此功能并未真正记录在案。有两个接口(@98​​7654322@ 和 NestedScrollingChild)以及两个助手委托类(NestedScrollingChildHelperNestedScrollingParentHelper)。

有人在 Android 上使用过 NestedScrolling 吗?

我尝试设置一个小例子,我使用NestedScrollView 实现NestedScrollingParentNestedScrollingChild

我的布局是这样的:

<android.support.v4.widget.NestedScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical">

    <View
        android:id="@+id/header"
        android:layout_width="match_parent" android:layout_height="100dp"
        android:background="#AF1233"/>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/child"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >

      <FrameLayout
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#12AF33"
            android:text="@string/long_text"/>

      </FrameLayout>
    </android.support.v4.widget.NestedScrollView>

  </LinearLayout>

</android.support.v4.widget.NestedScrollView>

我想在NestedScrollView(id = 父级)中显示一个header view 和另一个NestedScrollView(id = 子级)。

想法是,在运行时使用OnPredrawListener调整子滚动视图的高度:

public class MainActivity extends Activity {

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final NestedScrollView parentScroll = (NestedScrollView) findViewById(R.id.parent);
    final NestedScrollView nestedScroll = (NestedScrollView) findViewById(R.id.child);
    parentScroll.setNestedScrollingEnabled(false);
    final View header = findViewById(R.id.header);

    parentScroll.getViewTreeObserver()
        .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
          @Override public boolean onPreDraw() {
            if (parentScroll.getHeight() > 0) {
              parentScroll.getViewTreeObserver().removeOnPreDrawListener(this);
              nestedScroll.getLayoutParams().height = parentScroll.getHeight() - 40;
              nestedScroll.setLayoutParams(nestedScroll.getLayoutParams());
              nestedScroll.invalidate();
              return false;
            }
            return true;
          }
        });

  }
}

所以标题视图将被部分滚动,40 像素将保持可见,因为我将嵌套子滚动视图的高度设置为parentScroll.getHeight() - 40。 好的,在运行时设置高度并滚动父滚动视图的工作方式与预期一样(标题滚动出去,40 像素保持可见,然后子滚动视图填充标题下方的其余屏幕)。

我希望“NestedScrolling”意味着我可以在屏幕上的任何位置做出滚动手势(父滚动视图捕获的触摸事件),并且如果父滚动视图已到达末尾,嵌套的子滚动视图开始滚动。 然而,情况似乎并非如此(简单的滚动手势和滑动手势都不是)。

如果触摸事件从其边界开始,则触摸事件总是由嵌套的子滚动视图处理,否则由父滚动视图处理。

这是“嵌套滚动”的预期行为还是可以选择更改该行为?

我还尝试用NestedRecyclerView 替换嵌套的子滚动视图。我继承了RecyclerView 并实现了NestedScrollingChild,我将所有方法委托给NestedScrollingChildHelper

public class NestedRecyclerView extends RecyclerView implements NestedScrollingChild {

  private final NestedScrollingChildHelper scrollingChildHelper =
      new NestedScrollingChildHelper(this);


  public void setNestedScrollingEnabled(boolean enabled) {
    scrollingChildHelper.setNestedScrollingEnabled(enabled);
  }

  public boolean isNestedScrollingEnabled() {
    return scrollingChildHelper.isNestedScrollingEnabled();
  }

  public boolean startNestedScroll(int axes) {
    return scrollingChildHelper.startNestedScroll(axes);
  }

  public void stopNestedScroll() {
    scrollingChildHelper.stopNestedScroll();
  }

  public boolean hasNestedScrollingParent() {
    return scrollingChildHelper.hasNestedScrollingParent();
  }

  public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
      int dyUnconsumed, int[] offsetInWindow) {

    return scrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed,
        dyUnconsumed, offsetInWindow);
  }

  public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
    return scrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
  }

  public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
    return scrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
  }

  public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
    return scrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
  }
}

NestedRecyclerView 根本不滚动。所有的触摸事件都被父滚动视图捕获。

【问题讨论】:

  • 我面临同样的问题,并认为应该改进这个小部件以允许父级将未使用的滚动分派给它的子级。
  • 我认为让回收器视图成为一个nestedScrollingChild 并不是那么容易,因为它使用 LayoutManager 来滚动和定位它的项目。因此,仅使用 NestedScrollingChildHelper 实现 NestedScrollingChild 将不起作用,因为在滚动期间不会调用这些函数。
  • 我不确定。我猜 NestedScrollingParent / NestedScrollingChild 是用来转发触摸事件的。我希望NestedScrollingChild 在内部这样做,我看不出有任何理由为什么它应该适用于ScrollView 但不适用于RecyclerView ...LayoutManager 不应该是一个问题...
  • 如果你查看RecyclerView的源代码,在onTouchEvent 方法中,没有调用那些NestedScrollingChild方法,而NestedScrollView有。
  • 如果您想要实现的只是从父级开始触摸事件并继续滚动到子级。您可以在 OnInterceptionTouchEvent 中偏移运动事件,以使触摸事件似乎是从子滚动视图开始的。然后在 onPreNestedScroll 方法中,你消耗 dy 直到父滚动视图的 scrollY 达到一定的限制。通过使用这个技巧,当您不再希望在 OnNestedPreScroll 上消耗任何 dy 时,请记住消耗应用于运动事件的额外偏移量是 OnInterceptionTouchEvent

标签: android android-appcompat


【解决方案1】:

我在这上面花了很多时间,只是通过 android 代码试图弄清楚 NestedScrollView 中发生了什么。以下应该可以工作。

public abstract class ParentOfNestedScrollView extends NestedScrollView{

    public ParentOfNestedScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /* 
    Have this return the range you want to scroll to until the 
    footer starts scrolling I have it as headerCard.getHeight() 
    on most implementations
    */
    protected abstract int getScrollRange();

    /*
    This has the parent do all the scrolling that happens until 
    you are ready for the child to scroll.
    */
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed){
        if (dy > 0 && getScrollY() < getScrollRange()) {
            int oldScrollY = getScrollY();
            scrollBy(0, dy);
            consumed[1] = getScrollY() - oldScrollY;
        }
    }

    /*
    Sometimes the parent scroll intercepts the event when you don't
    want it to.  This prevents this from ever happening.
    */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }
}

我的一些代码是从这个question 借来的。从这里我只是根据需要扩展这个类。我的 xml 将孩子作为 NestedScrollView 作为孩子,将父母作为孩子。这不能像我想要的那样处理投掷,这是一项正在进行的工作。

【讨论】:

  • 我在这个问题上输了一天,你的解决方案救了我,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-18
  • 2021-04-08
  • 2011-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多