简体   繁体   中英

Fling scroll on CollapsingToolbarLayout stops when it collapses

I am using CoordinatorLayout with AppBarLayout and CollapsingToolbarLayout.There is also a NestedScrollView for the content. When i do fling scroll on CollapsingToolbarLayout, scrolling stops when CollapsingToolbarLayout collapses. What i want is to continue scrolling with the content of NestedScrollView. When i do fling scroll on NestedScrollView, it scrolls all the way to the end of content. And the CollapsingToolbarLayout collapses as expected.

Video link: http://sendvid.com/m7d0mq2q

In the video, as you see in the first scrolling, it stops scrolling when CollapsingToolbarLayout collapses.

My layout:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
    android:fitsSystemWindows="true"
    tools:context="com.journaldev.collapsingtoolbarlayout.ScrollingActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">


            <ImageView
                android:id="@+id/expandedImage"
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:scaleType="centerCrop"
                android:src="@drawable/photo"/>

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/text_margin"
            android:text="@string/large_text" />

    </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

My Activity:

package com.journaldev.collapsingtoolbarlayout;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.google.android.material.appbar.AppBarLayout;

public class ScrollingActivity extends AppCompatActivity {


    private Menu menu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        final Toolbar mToolbar = this.findViewById(R.id.toolbar);
        setSupportActionBar(mToolbar);

        AppBarLayout mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            boolean isShow = false;
            int scrollRange = -1;

            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                if (scrollRange == -1) {
                    scrollRange = appBarLayout.getTotalScrollRange();
                }
                if (scrollRange + verticalOffset == 0) {
                    isShow = true;
                    showOption(R.id.action_info);
                } else if (isShow) {
                    isShow = false;
                    hideOption(R.id.action_info);
                }
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        this.menu = menu;
        getMenuInflater().inflate(R.menu.menu_scrolling, menu);
        hideOption(R.id.action_info);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        } else if (id == R.id.action_info) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void hideOption(int id) {
        MenuItem item = menu.findItem(id);
        item.setVisible(false);
    }

    private void showOption(int id) {
        MenuItem item = menu.findItem(id);
        item.setVisible(true);
    }
}
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.journaldev.collapsingtoolbarlayout"
        minSdkVersion 21
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    implementation "androidx.appcompat:appcompat:1.0.2"
    implementation "com.google.android.material:material:1.1.0-alpha04"
    testCompile 'junit:junit:4.12'
}

When we do the fling on scrolling view (ie RecyclerView ), everything works as expected. So the idea of my solution is to ignore the fling of AppBarLayout and pass exact same parameters to the scrolling view (in my case it is RecyclerView ). So, we just need to provide to this class appropriate scrolling view (most likely, it will work on other views too, ie NestedScrollView ). As a bonus, my custom behavior also solves the ugly jitter issue that appears on most huawei devices and not only (if not interested, just remove onNestedPreFling method). For more details of this issue: CollapsingToolbarLayout flickers while scrolling down

/**
 * Customized app bar provides 2 features:
 * 1. Force AppBarLayout to stop fling when nested view starts fling, otherwise scrolling view fling collides with AppBarLayout fling
 * 2. Ignore AppBarLayout fling and transfer it to scrolling view. This is done in order to continue page scroll when AppBarLayout
 * fully collapses (now after collapse scroll stops)
 */

class SmoothAppBarBehavior : AppBarLayout.Behavior() {

    companion object {
        private const val FLING_UNITS = 1000 //copied from base class
    }

    var recyclerView: RecyclerView? = null
    private var overScroller: OverScroller? = null
    private var pointerId = -1
    private var velocityTracker: VelocityTracker? = null

    override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout,
                                  child: AppBarLayout,
                                  target: View,
                                  velocityX: Float,
                                  velocityY: Float): Boolean {
        stopAppBarLayoutFling()
        return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
    }

    override fun onInterceptTouchEvent(parent: CoordinatorLayout,
                                       child: AppBarLayout,
                                       ev: MotionEvent): Boolean {
        val consumed = super.onInterceptTouchEvent(parent, child, ev)
        when (ev.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                ensureVelocityTracker()              
                recyclerView?.stopScroll()
                pointerId = ev.getPointerId(0)
            }
            MotionEvent.ACTION_CANCEL -> {
                velocityTracker?.recycle()
                velocityTracker = null
                pointerId = -1
            }
            else -> {}
        }
        return consumed
    }

    override fun onTouchEvent(parent: CoordinatorLayout,
                              child: AppBarLayout,
                              ev: MotionEvent): Boolean {
        val consumed = super.onTouchEvent(parent, child, ev)
        recyclerView?.let {
            when (ev.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    ensureVelocityTracker()
                    pointerId = ev.getPointerId(0)
                }
                MotionEvent.ACTION_UP -> {
                    stopAppBarLayoutFling()
                    recyclerView?.fling(0, getYVelocity(ev))
                }
                MotionEvent.ACTION_CANCEL -> {
                    velocityTracker?.recycle()
                    velocityTracker = null
                    pointerId = -1
                }
                else -> {}
            }
            velocityTracker?.addMovement(ev)
        }
        return consumed
    }

    private fun ensureVelocityTracker() {
        if (velocityTracker == null) {
            velocityTracker = VelocityTracker.obtain()
        }
    }

    private fun getYVelocity(event: MotionEvent): Int {
        velocityTracker?.let {
            it.addMovement(event)
            it.computeCurrentVelocity(FLING_UNITS)
            return - it.getYVelocity(pointerId).toInt()
        }
        return 0
    }

    private fun stopAppBarLayoutFling() {
        if (overScroller == null) {
            val scrollerField = javaClass.superclass.superclass.superclass.getDeclaredField("scroller")
            scrollerField.isAccessible = true
            overScroller = scrollerField.get(this) as? OverScroller
        }
        overScroller?.forceFinished(true)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM