CollapsingToolbarLayout tidak mengenali scroll fling


93

Saya telah membuat CollapsingToolbarLayout sederhana dan bekerja seperti pesona. Masalah saya adalah, jika saya mencoba menggunakan fling scroll pada tampilan gulungan bersarang , itu hanya berhenti ketika saya melepaskan jari saya. Pengguliran normal berfungsi seperti seharusnya.

Kode aktivitas saya tidak berubah => aktivitas kosong yang dibuat otomatis . (Saya baru saja mengklik buat aktivitas kosong baru di studio android dan belum mengedit XML).

Saya baca di sini, bahwa gerakan gulir pada tampilan gambar itu sendiri bermasalah, tetapi tidak, bahwa pengguliran itu sendiri bermasalah: lihat di sini .

Saya mencoba mengaktifkan "scrolling mulus" melalui kode java. Sepertinya jika saya menggulir cukup jauh sehingga tampilan gambar tidak terlihat lagi, gerakan melempar kemudian dikenali.

TLDR: Mengapa gerakan melempar tidak berfungsi selama imageview terlihat? Kode XML saya terlihat seperti ini:

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

Menarik, saya mencatat peristiwa sentuh pada tampilan gulir bersarang selama lemparan yang terpengaruh. Itu mendapat ACTION_DOWN y=98 -> ACTION_MOVE y=-40 -> ACTION_MOVE y=-33 -> ACTION_UP y=97. Sepertinya peristiwa sentuhan terakhir salah melaporkan dirinya sendiri sebagai peristiwa berikutnya yang pertama.
Xiao

Versi pustaka dukungan desain mana yang Anda gunakan?
Radu Topor

apakah Anda mengganti acara sentuh apa pun? coba atur nestedScrollView.getParent().requestDisallowInterceptTouchEvent(true);ke tampilan gulir bersarang Anda
Bhargav

Jawaban:


20

Saya memiliki masalah yang persis sama dengan CollapsingToolbarLayout dengan ImageView di dalam dan NestedScrollView . Gulir ayun berhenti saat jari dilepaskan.

Namun, saya memperhatikan sesuatu yang aneh. Jika Anda mulai menggulir dengan jari Anda dari tampilan dengan OnClickListener (mis. Tombol), pengguliran lempar bekerja dengan sempurna.

Jadi saya memperbaikinya dengan solusi yang aneh. Setel OnClickListener (yang tidak melakukan apa-apa) pada turunan langsung NestedScrollView . Maka itu bekerja dengan sempurna!

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

Beri anak langsung (LinearLayout) sebuah id dan setel OnClickListener di Activity

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

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

Catatan:

Diuji menggunakan Support Design Library 25.0.1

CollapsingToolbarLayout dengan scrollFlags = "scroll | enterAlwaysCollapsed"


AOSP harus ditambal dengan solusi hebat ini: D
deviant

Harus mendapatkan lebih banyak suara lol. Btw ini membuat CollapsingToolbarLayout sangat sensitif untuk digulir, tetapi lebih baik daripada perilaku rusak saat ini.
Ahmad Fadli

1
ini terlalu bagus untuk menjadi kenyataan jadi saya mencobanya dan itu tidak berhasil untuk saya
Gilbert Mendoza

ini adalah solusi paling gila yang saya coba sejauh ini di SO.
Techfist

: D observasi yang bagus @jinang !!
Srichakradhar

10

Saya tahu pertanyaan ini telah diajukan lebih dari setahun yang lalu tetapi masalah ini tampaknya masih belum terselesaikan di pustaka Dukungan / Desain. Anda dapat memberi bintang pada masalah ini sehingga masalah tersebut bergerak lebih jauh dalam antrian prioritas.

Yang mengatakan, saya mencoba sebagian besar solusi yang diposting untuk ini, termasuk yang oleh patrick-iv tidak berhasil. Satu-satunya cara agar saya dapat bekerja adalah dengan meniru lemparan dan menyebutnya secara terprogram jika sekumpulan kondisi tertentu terdeteksi di onPreNestedScroll(). Dalam beberapa jam debugging saya, saya perhatikan bahwa onNestedFling()tidak pernah dipanggil ke atas (gulir ke bawah) melempar dan sepertinya dikonsumsi sebelum waktunya. Saya tidak dapat mengatakan dengan kepastian 100% ini akan bekerja untuk 100% implementasi tetapi bekerja cukup baik untuk penggunaan saya jadi saya akhirnya menyelesaikan ini, meskipun itu cukup hacky dan jelas bukan apa yang ingin saya lakukan.

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);
    }
}

Dan terapkan ke AppBar

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

Demo CheeseSquare: Sebelum Sesudah


Ini memang lebih baik daripada tidak sama sekali, tetapi tidak seperti yang diharapkan oleh pengguna Android berpengalaman. Terima kasih telah menautkan masalah, saya telah memberinya bintang.
Raphael Royer-Rivard

Harus menghapus enterAlwayslayout_ScrollFlag agar berfungsi, tetapi berfungsi dengan baik sekarang
Alexandre G

3

Saya mencoba solusi Floofer tetapi masih belum cukup baik untuk saya. Jadi saya datang dengan versi yang lebih baik dari Perilakunya. AppBarLayout sekarang mengembang dan menciut dengan mulus saat dilempar.

Catatan: Saya menggunakan refleksi untuk meretas jalan saya ke ini, jadi itu mungkin tidak berfungsi dengan sempurna dengan versi pustaka Desain Android yang berbeda dari 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);
    }
}

Untuk menggunakannya, setel Behavior baru ke AppBarLayout Anda.

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

Kelas Anda menuntut int dalam 'konstruktornya, namun dalam kode Anda tidak mengirimkan apa pun ke konstruktor
bluesummers

Saya buruk, saya menambahkannya.
Raphael Royer-Rivard

Ini terlihat bagus, itu membuat pengguliran lancar, tetapi saya punya satu pertanyaan, apakah mungkin membiarkan NestedScrollView menggulir ke AppBarLayout setelah AppBarLayout mencapai puncak, dan juga, ketika saya menggulir ke bawah, AppBarLayout muncul pada akhirnya, ketika NestedScrollView digulir sepenuhnya, lalu AppBarLayout mulai meluas.
Zijian Wang

@ZijianWang Tolong jelaskan apa yang Anda maksud dengan "gulir ke AppBarLayout" dan saya juga tidak mengerti pertanyaan kedua Anda, dapatkah Anda mengulanginya?
Raphael Royer-Rivard

0

Jawaban ini memecahkan masalah ini untuk saya. Buat kustom AppBarLayout.Behaviorseperti ini:

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

dan tambahkan AppBarLayoutseperti ini:

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

1
Tidak berfungsi karena masalah di pertanyaan lain adalah RecyclerView yang tidak digunakan di sini.
Felix Edelmann

0

Saya hanya memposting ini di sini agar orang lain tidak ketinggalan di Komentar. Jawaban oleh Jinang bekerja dengan baik, tetapi pujian untuk AntPachon karena menunjukkan metode yang jauh lebih sederhana untuk hal yang sama. Alih-alih menerapkan OnClickmetode secara Child of the NestedScrollViewterprogram, cara yang lebih baik adalah menyetel clickable=truexml untuk anak.

(Menggunakan contoh yang sama seperti Jinang )

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

-1

Dalam kode :https://android.googlesource.com/platform/frameworks/support/+/master/core-ui/java/android/support/v4/widget/NestedScrollView.java#834

       case MotionEvent.ACTION_UP:
            if (mIsBeingDragged) {
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
                        mActivePointerId);
                if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                    flingWithNestedDispatch(-initialVelocity);
                } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                        getScrollRange())) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
            }
            mActivePointerId = INVALID_POINTER;
            endDrag();
            break;

Ketika saya menggunakan fling scroll pada NestedScrollView terkadang "mIsBeingDragged = false", Jadi NestedScrollView tidak mengirimkan acara fling.

Ketika saya menghapus if (mIsBeingDragged)pernyataan itu.

 case MotionEvent.ACTION_UP:
        //if (mIsBeingDragged) {
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
                    mActivePointerId);
            if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                flingWithNestedDispatch(-initialVelocity);
            } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                    getScrollRange())) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        //}
        mActivePointerId = INVALID_POINTER;
        endDrag();
        break;

tidak akan ada masalah. Tetapi saya tidak tahu masalah serius apa lagi yang akan ditimbulkan


Tambahkan lebih banyak detail untuk mempermudah pemahaman jawaban, Anda menulis Ketika saya menghapus if ... dari tempat Anda berbicara untuk menghapus if ?
Devendra Singh
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.