CollapsingToolbarLayout kaydırma hareketini tanımıyor


94

Basit bir CollapsingToolbarLayout oluşturdum ve bir cazibe gibi çalışıyor. Benim sorunum, iç içe geçmiş kaydırma görünümünde bir fling scroll kullanmaya çalışırsam, parmağımı bıraktığımda sadece durması. Normal kaydırma olması gerektiği gibi çalışır.

Aktivite kodum değişmedi => otomatik olarak oluşturulan boş aktivite. (Android stüdyosunda yeni boş etkinlik oluştur'a tıkladım ve XML'i henüz düzenledim).

Burada, görüntü görünümünün kendisindeki kaydırma hareketlerinin hatalı olduğunu okudum, ancak kaydırmanın kendisinin hatalı olduğunu değil: buraya bakın .

Java kodu ile "yumuşak kaydırmayı" etkinleştirmeyi denedim . Görünüşe göre, görüntü görüntüsünün artık görünmemesine yetecek kadar kaydırırsam, hızlı hareketler tanınır.

TLDR: Görüntü görünümü göründüğü sürece fırlatma hareketi neden çalışmıyor? XML Kodum şöyle görünüyor:

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

İlginçtir, etkilenen bir fırlatma sırasında dokunma olaylarını iç içe kaydırma görünümüne kaydetmiştim. Alır ACTION_DOWN y=98 -> ACTION_MOVE y=-40 -> ACTION_MOVE y=-33 -> ACTION_UP y=97. Görünüşe göre son dokunuş olayı, ilk dokunuşun yanında olarak kendisini yanlış bildiriyor.
Xiao

Tasarım destek kitaplığının hangi sürümünü kullanıyorsunuz?
Radu Topor

herhangi bir dokunma olayını geçersiz kılıyor musunuz? nestedScrollView.getParent().requestDisallowInterceptTouchEvent(true);iç içe kaydırma görünümünüzü ayarlamayı deneyin
Bhargav

Yanıtlar:


20

ImageView içinde ve NestedScrollView ile CollapsingToolbarLayout ile tam olarak aynı sorunu yaşadım . Parmağınızı bıraktığınızda fırlatma kaydırma durur.

Ancak tuhaf bir şey fark ettim. OnClickListener (örneğin Button) ile bir görünümden parmağınızla kaydırmaya başlarsanız, hızlı kaydırma mükemmel şekilde çalışır.

Bu yüzden garip bir çözümle düzelttim. OnClickListener'ı (hiçbir şey yapmayan) NestedScrollView öğesinin doğrudan alt öğesi üzerinde ayarlayın . O zaman mükemmel çalışıyor!

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

Doğrudan alt öğeye (LinearLayout) bir kimlik verin ve OnClickListener'ı Activity'de ayarlayın

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

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

Notlar:

Support Design Library 25.0.1 kullanılarak test edildi

CollapsingToolbarLayout with scrollFlags = "scroll | enterAlwaysCollapsed"


AOSP, bu harika çözümle yamalanmalı: D
sapkın

Daha fazla oy almalı lol. Btw bu, CollapsingToolbarLayout'u kaydırmaya çok duyarlı hale getirdi, ancak mevcut bozuk davranıştan daha iyi.
Ahmad Fadli

1
bu gerçek olamayacak kadar iyiydi, bu yüzden denedim ve benim için çalışmıyor
Gilbert Mendoza

SO'da şimdiye kadar denediğim en çılgın çözüm bu.
Techfist

: D harika gözlem @jinang !!
Srichakradhar

10

Bu sorunun bir yıldan uzun bir süre önce sorulduğunu biliyorum, ancak bu sorun hala Destek / Tasarım kitaplıklarında çözülmüş görünmüyor. Öncelik kuyruğunda daha da yukarı çıkması için bu soruna yıldız ekleyebilirsiniz .

Bununla birlikte, patrick-iv tarafından başarısız olan çözüm de dahil olmak üzere bunun için yayınlanan çözümlerin çoğunu denedim. İşe girebilmemin tek yolu, kaçışı taklit etmek ve belirli bir dizi koşul tespit edildiğinde onu programlı olarak adlandırmaktı onPreNestedScroll(). Hata ayıklamamın birkaç saatinde onNestedFling(), hiçbir zaman yukarı doğru (aşağı kaydır) fırlatmaya çağrılmadığını ve zamanından önce tüketilmiş gibi göründüğünü fark ettim . % 100 kesinlikle bunun uygulamaların% 100'ünde işe yarayacağını söyleyemem, ancak kullanımlarım için yeterince iyi çalışıyor, bu yüzden oldukça karmaşık olmasına ve kesinlikle yapmak istediğim şey olmamasına rağmen buna karar verdim.

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

Ve AppBar'a uygulayın

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

CheeseSquare demosu: Önce Sonra


Gerçekten hiç yoktan iyidir, ancak deneyimli bir Android kullanıcısının beklediği gibi değildir. Sorunu bağladığınız için teşekkürler, ona yıldız ekledim.
Raphael Royer-Rivard

enterAlwaysÇalışması için layout_ScrollFlag'i kaldırmak zorunda kaldı, ancak şimdi iyi çalışıyor
Alexandre G

3

Floofer'ın çözümünü denedim ama yine de benim için yeterince iyi değildi. Bu yüzden onun Davranışının daha iyi bir versiyonunu buldum. AppBarLayout artık fırlatma sırasında sorunsuz bir şekilde genişler ve daralır.

Not: Bunu yapmak için yansıma kullandım, bu nedenle Android Tasarım kitaplığının 25.0.0'dan farklı bir sürümüyle mükemmel şekilde çalışmayabilir.

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

Kullanmak için AppBarLayout'unuza yeni bir Davranış ayarlayın.

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

Sınıfınız 'kurucusunda bir int ister, ancak kodda
kurucuya

Benim hatam, onu ekledim.
Raphael Royer-Rivard

Bu iyi görünüyor, kaydırmayı pürüzsüz hale getiriyor, ancak bir sorum var, AppBarLayout en üste ulaştığında NestedScrollView’un AppBarLayout’a kaydırmasına izin vermek ve ayrıca aşağı kaydırdığımda AppBarLayout en sonunda NestedScrollView tamamen dışarı kaydırılır, ardından AppBarLayout genişlemeye başlar.
Zijian Wang

@ZijianWang Lütfen "AppBarLayout'a kaydırın" ile ne demek istediğinizi açıklayın ve ben de ikinci sorunuzu anlamıyorum, yeniden ifade edebilir misiniz?
Raphael Royer-Rivard

0

Bu cevap benim için bu sorunu çözdü. Bunun AppBarLayout.Behaviorgibi bir özel oluşturun :

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

ve buna benzer şekilde ekleyin AppBarLayout:

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

1
Diğer sorudaki sorun burada kullanılmayan RecyclerView olduğu için işe yaramadı.
Felix Edelmann

0

Başkalarının Yorumlarda kaçırmaması için bunu buraya gönderiyorum. Jinang'ın cevabı çok güzel çalışıyor, ancak AntPachon'a aynı şekilde çok daha basit bir yöntemi işaret ettiği için tebrikler . Programatik olarak bir OnClickyöntem uygulamak yerine, Child of the NestedScrollViewdaha iyi bir yol, clickable=trueçocuk için xml'de ayarlamaktır .

( Jinang'ınki ile aynı örneği kullanarak )

<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

Kodda :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;

NestedScrollView üzerinde fling scroll kullandığımda bazen "mIsBeingDragged = false", So NestedScrollView fling olayı göndermiyor.

İfadeyi sildiğimde if (mIsBeingDragged).

 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;

sorun olmayacak. Ama başka hangi ciddi sorunlara neden olacağını bilmiyorum


Daha ayrıntılı kolay anlaşılması cevabını yapmak Ekle, sen yazdın ben eğer ... kaldırdığınızda kaldırmak konuştuğunuz yerden eğer ?
Devendra Singh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.