web-dev-qa-db-ja.com

CollapsingToolbarLayoutはスクロールフリングを認識しません

簡単なCollapsingToolbarLayoutを作成しましたが、これは魅力のように機能します。私の問題は、nestedscrollviewでフリングスクロールを使用しようとすると、指を離すと停止することです。通常のスクロールは、本来のように機能します。

私のアクティビティコードは、変更なし=> auto生成された空のアクティビティです。 (Android studioで新しい空のアクティビティを作成するをクリックし、XMLを編集しました)。

私はここで、イメージビュー自体のスクロールジェスチャにはバグがあることを読みましたが、スクロール自体にはバグがあることはありません: こちらを参照

"smooth scrolling"Java code。 imageviewが表示されなくなるまでスクロールすると、フリングジェスチャが認識されるようです。

TLDR:画像ビューが表示されている限り、投げ飛ばしジェスチャーが機能しないのはなぜですか?私のXMLコードは次のようになります。

    <Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/profile_app_bar_layout"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        Android:fitsSystemWindows="true">

        <Android.support.design.widget.CollapsingToolbarLayout
            Android:id="@+id/profile_collapsing_toolbar_layout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp"
            Android:fitsSystemWindows="true">

            <ImageView
                Android:id="@+id/image"
                Android:layout_width="match_parent"
                Android:layout_height="420dp"
                Android:scaleType="centerCrop"
                Android:fitsSystemWindows="true"
                Android:src="@drawable/headerbg"
                Android:maxHeight="192dp"

                app:layout_collapseMode="parallax"/>

            <Android.support.v7.widget.Toolbar
                Android:id="@+id/toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin" />

        </Android.support.design.widget.CollapsingToolbarLayout>

    </Android.support.design.widget.AppBarLayout>

    <Android.support.design.widget.FloatingActionButton
        Android:id="@+id/fab"
        app:layout_anchor="@id/profile_app_bar_layout"
        app:layout_anchorGravity="bottom|right|end"
        Android:layout_height="@dimen/fab_size_normal"
        Android:layout_width="@dimen/fab_size_normal"
        app:elevation="2dp"
        app:pressedTranslationZ="12dp"
        Android:layout_marginRight="8dp"
        Android:layout_marginEnd="8dp"/>

    <Android.support.v4.widget.NestedScrollView
        Android:id="@+id/profile_content_scroll"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:clipToPadding="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        Android:layout_gravity="fill_vertical"
        Android:minHeight="192dp"
        Android:overScrollMode="ifContentScrolls"
        >

        <RelativeLayout
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content">

            <TextView
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:text="@string/LoremIpsum"/>

        </RelativeLayout>

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

</Android.support.design.widget.CoordinatorLayout>
93
user1951516

CollapsingToolbarLayout with ImageView insideおよびNestedScrollViewとまったく同じ問題がありました。指を離すと、フリングスクロールが停止します。

しかし、私は奇妙なことに気づきました。 OnClickListener(ボタンなど)を使用してビューから指でスクロールを開始すると、フリングスクロールは完全に機能します。

したがって、私は奇妙な解決策でそれを修正しました。 NestedScrollViewの直接の子にOnClickListener(何もしない)を設定します。その後、完全に動作します!

<Android.support.v4.widget.NestedScrollView 
   xmlns:Android="http://schemas.Android.com/apk/res/Android"
   xmlns:app="http://schemas.Android.com/apk/res-auto"
   xmlns:tools="http://schemas.Android.com/tools"
   Android:layout_width="match_parent"
   Android:layout_height="match_parent"
   app:layout_behavior="@string/appbar_scrolling_view_behavior">

  <LinearLayout
      Android:id="@+id/content_container"
      Android:layout_width="match_parent"
      Android:layout_height="wrap_content"
      Android:orientation="vertical">

    <!-- Page Content -->

  </LinearLayout>

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

直接の子(LinearLayout)にIDを与え、アクティビティでOnClickListenerを設定します

ViewGroup mContentContainer = (ViewGroup) findViewById(R.id.content_container);    
mContentContainer.setOnClickListener(this);

@Override
public void onClick(View view) {
    int viewId = view.getId();
}

注:

サポートデザインライブラリ25.0.1を使用してテスト済み

ScrollFlags = "scroll | enterAlwaysCollapsed"を含むCollapsingToolbarLayout

19
jinang

この質問は1年以上前に尋ねられたことは知っていますが、この問題はまだサポート/設計ライブラリで解決されていないようです。 star this を発行して、優先度キューでさらに上に移動できます。

そうは言っても、私はこのために投稿されたソリューションのほとんどを試しましたが、patrick-ivによるものも含めて成功しませんでした。 onPreNestedScroll()で特定の条件セットが検出された場合、私が仕事に着くことができた唯一の方法は、フリングを模倣し、プログラムでフリングを呼び出すことでした。デバッグの数時間で、onNestedFling()が上方向(スクロールダウン)で呼び出されることはなく、時期尚早に消費されるように見えました。 100%の確実性でこれが100%の実装で機能するとは言えませんが、私の用途には十分に機能するので、かなりハックで、間違いなく私がやりたいことではありませんでしたが、私はこれで落ち着きました。

public class NestedScrollViewBehavior extends AppBarLayout.Behavior {

    // Lower value means fling action is more easily triggered
    static final int MIN_DY_DELTA = 4;
    // Lower values mean less velocity, higher means higher velocity
    static final int FLING_FACTOR = 20;

    int mTotalDy;
    int mPreviousDy;
    WeakReference<AppBarLayout> mPreScrollChildRef;

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                                  View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        // Reset the total fling delta distance if the user starts scrolling back up
        if(dy < 0) {
            mTotalDy = 0;
        }
        // Only track move distance if the movement is positive (since the bug is only present
        // in upward flings), equal to the consumed value and the move distance is greater
        // than the minimum difference value
        if(dy > 0 && consumed[1] == dy && MIN_DY_DELTA < Math.abs(mPreviousDy - dy)) {
            mPreScrollChildRef = new WeakReference<>(child);
            mTotalDy += dy * FLING_FACTOR;
        }
        mPreviousDy = dy;
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        // Stop any previous fling animations that may be running
        onNestedFling(parent, child, target, 0, 0, false);
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout parent, AppBarLayout abl, View target) {
        if(mTotalDy > 0 && mPreScrollChildRef != null && mPreScrollChildRef.get() != null) {
            // Programmatically trigger fling if all conditions are met
            onNestedFling(parent, mPreScrollChildRef.get(), target, 0, mTotalDy, false);
            mTotalDy = 0;
            mPreviousDy = 0;
            mPreScrollChildRef = null;
        }
        super.onStopNestedScroll(parent, abl, target);
    }
}

そして、それをAppBarに適用します

AppBarLayout scrollView = (AppBarLayout)findViewById(R.id.appbar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)scrollView.getLayoutParams();
params.setBehavior(new NestedScrollViewBehavior());

CheeseSquareデモ: BeforeAfter

10
wchristiansen

Flooferのソリューションを試してみましたが、それでもまだ十分ではありませんでした。だから私は彼の行動のより良いバージョンを思いついた。 AppBarLayoutは、フリングするときにスムーズに拡大および縮小するようになりました。

注:私はリフレクションを使用してこの方法をハックしたため、Android 25.0.0とは異なるデザインライブラリで完全に機能しない場合があります。

public class SmoothScrollBehavior extends AppBarLayout.Behavior {
    private static final String TAG = "SmoothScrollBehavior";
    //The higher this value is, the faster the user must scroll for the AppBarLayout to collapse by itself
    private static final int SCROLL_SENSIBILITY = 5;
    //The real fling velocity calculation seems complex, in this case it is simplified with a multiplier
    private static final int FLING_VELOCITY_MULTIPLIER = 60;

    private boolean alreadyFlung = false;
    private boolean request = false;
    private boolean expand = false;
    private int velocity = 0;
    private int nestedScrollViewId;

    public SmoothScrollBehavior(int nestedScrollViewId) {
        this.nestedScrollViewId = nestedScrollViewId;
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                                  View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        if(Math.abs(dy) >= SCROLL_SENSIBILITY) {
            request = true;
            expand = dy < 0;
            velocity = dy * FLING_VELOCITY_MULTIPLIER;
        } else {
            request = false;
        }
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                                       View directTargetChild, View target, int nestedScrollAxes) {
        request = false;
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, View target) {
        if(request) {
            NestedScrollView nestedScrollView = (NestedScrollView) coordinatorLayout.findViewById(nestedScrollViewId);
            if (expand) {
                //No need to force expand if it is already ready expanding
                if (nestedScrollView.getScrollY() > 0) {
                    int finalY = getPredictedScrollY(nestedScrollView);
                    if (finalY <= 0) {
                        //since onNestedFling does not work to expand the AppBarLayout, we need to manually expand it
                        expandAppBarLayoutWithVelocity(coordinatorLayout, appBarLayout, velocity);
                    }
                }
            } else {
                //onNestedFling will collapse the AppBarLayout with an animation time relative to the velocity
                onNestedFling(coordinatorLayout, appBarLayout, target, 0, velocity, true);

                if(!alreadyFlung) {
                    //TODO wait for AppBarLayout to be collapsed before scrolling for even smoother visual
                    nestedScrollView.fling(velocity);
                }
            }
        }
        alreadyFlung = false;
        super.onStopNestedScroll(coordinatorLayout, appBarLayout, target);
    }

    private int getPredictedScrollY(NestedScrollView nestedScrollView) {
        int finalY = 0;
        try {
            //With reflection, we can get the ScrollerCompat from the NestedScrollView to predict where the scroll will end
            Field scrollerField = nestedScrollView.getClass().getDeclaredField("mScroller");
            scrollerField.setAccessible(true);
            Object object = scrollerField.get(nestedScrollView);
            ScrollerCompat scrollerCompat = (ScrollerCompat) object;
            finalY = scrollerCompat.getFinalY();
        } catch (Exception e ) {
            e.printStackTrace();
            //If the reflection fails, it will return 0, which means the scroll has reached top
            Log.e(TAG, "Failed to get mScroller field from NestedScrollView through reflection. Will assume that the scroll reached the top.");
        }
        return finalY;
    }

    private void expandAppBarLayoutWithVelocity(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout, float velocity) {
        try {
            //With reflection, we can call the private method of Behavior that expands the AppBarLayout with specified velocity
            Method animateOffsetTo = getClass().getSuperclass().getDeclaredMethod("animateOffsetTo", CoordinatorLayout.class, AppBarLayout.class, int.class, float.class);
            animateOffsetTo.setAccessible(true);
            animateOffsetTo.invoke(this, coordinatorLayout, appBarLayout, 0, velocity);
        } catch (Exception e) {
            e.printStackTrace();
            //If the reflection fails, we fall back to the public method setExpanded that expands the AppBarLayout with a fixed velocity
            Log.e(TAG, "Failed to get animateOffsetTo method from AppBarLayout.Behavior through reflection. Falling back to setExpanded.");
            appBarLayout.setExpanded(true, true);
        }
    }

    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) {
        alreadyFlung = true;
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
    }
}

それを使用するには、AppBarLayoutに新しいBehaviorを設定します。

AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
params.setBehavior(new SmoothScrollBehavior(R.id.nested_scroll_view));

他の人がコメントで見逃さないように、ここに投稿しています。 Jinang による答えは見事に機能しますが、 AntPachon に感謝します。 Child of the NestedScrollViewにプログラムでOnClickメソッドを実装する代わりに、子のxmlにclickable=trueを設定するのがより良い方法です。

Jinang's と同じ例を使用)

<Android.support.v4.widget.NestedScrollView 
   xmlns:Android="http://schemas.Android.com/apk/res/Android"
   xmlns:app="http://schemas.Android.com/apk/res-auto"
   xmlns:tools="http://schemas.Android.com/tools"
   Android:layout_width="match_parent"
   Android:layout_height="match_parent"
   app:layout_behavior="@string/appbar_scrolling_view_behavior">

  <LinearLayout
      Android:id="@+id/content_container"
      Android:layout_width="match_parent"
      Android:layout_height="wrap_content"
      Android:orientation="vertical"
      Android:clickable="true" >                  <!-- new -->

    <!-- Page Content -->

  </LinearLayout>

</Android.support.v4.widget.NestedScrollView>
0
Kaushik NP

この回答 この問題を解決してくれました。カスタムAppBarLayout.Behavior このような:

public final class FlingBehavior extends AppBarLayout.Behavior {
    private static final int TOP_CHILD_FLING_THRESHOLD = 3;
    private boolean isPositive;

    public FlingBehavior() {
    }

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

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
            velocityY = velocityY * -1;
        }
        if (target instanceof RecyclerView && velocityY < 0) {
            final RecyclerView recyclerView = (RecyclerView) target;
            final View firstChild = recyclerView.getChildAt(0);
            final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
            consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        isPositive = dy > 0;
    }
}

次のようにAppBarLayoutに追加します:

<Android.support.design.widget.AppBarLayout
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        ...
        app:layout_behavior="com.example.test.FlingBehavior">
0