Reputation: 3284
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'
}
Upvotes: 2
Views: 1253
Reputation: 41
When we do the fling on scrolling view (i.e. 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, i.e. 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)
}
}
Upvotes: 3