Подтвердить что ты не робот

Как реализовать NestedScrolling на Android?

В библиотеке support-v4 22.1.0 андроид поддерживает вложенную прокрутку (pre android 5.0). К сожалению, эта функция не документирована. Существует два интерфейса (NestedScrollingParent и NestedScrollingChild), а также два класса делегатов-помощников (NestedScrollingChildHelper и NestedScrollingParentHelper).

Кто-нибудь работал с NestedScrolling на Android?

Я попытался настроить небольшой пример, где я использую NestedScrollView, который реализует как NestedScrollingParent, так и NestedScrollingChild.

Мой макет выглядит следующим образом:

<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>

Я хочу показать header view и еще один NestedScrollView (id = child) в NestedScrollView (id = parent).

Идея заключалась в том, чтобы отрегулировать высоту детского прокрутки во время выполнения с помощью 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 не прокручивается вообще. Все события касания захватываются представлением родительской прокрутки.

4b9b3361

Ответ 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 в качестве дочернего элемента, а родительский - это. Это не относится к flings, а также я хотел бы, чтобы работа продолжалась.