Search code examples
androidimageviewandroid-collapsingtoolbarlayoutandroid-appbarlayout

Is it possible to touch the ImageView inside CollapsingToolbarLayout and scroll it?


I'm trying to get a fullscreen CollapsingToolbar but when I set match_parent to the height of AppBarLayout I'm not able to scroll the ImageView which is inside CollapsingToolbarLayout. I have to leave some space so that I can touch the "white" of the activity (in AppBarLayout I added android:layout-marginBottom:"16dp" ) and only then, after I touched it, I can scroll the ImageView otherwise I can't. This happens everytime I run the app and touch the layout for the first time. So I have to touch the white first and then scroll the image.

Could you help me?

<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/drawer">

<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="16dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            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">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax"
                android:contentDescription="@null"
                android:src="@drawable/background" />

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

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

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

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/scroll"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        </FrameLayout>

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

</android.support.design.widget.CoordinatorLayout>

<com.myapplication.ScrimInsetsFrameLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="304dp"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="true"
    app:insetForeground="#4000"
    android:clickable="true"
    android:background="#ffffff">

    ...

</com.myapplication.ScrimInsetsFrameLayout>

</android.support.v4.widget.DrawerLayout>

EDIT @PPartisan I've done what you said but here's what I got:

enter image description here


Solution

  • This isn't a nice solution, but it does work on my test device. It kick starts the scrolling process by explicitly assigning a touch listener to the AppBar that triggers a nested scroll.

    First, create a custom class that extends NestedScrollView and add the following method so it look something like this:

    public class CustomNestedScrollView extends NestedScrollView {
    
        private int y;
    
        public CustomNestedScrollView(Context context) {
            super(context);
        }
    
        public CustomNestedScrollView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public boolean dispatchHandlerScroll(MotionEvent e) {
            switch (e.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    y = (int) e.getY();
                    startNestedScroll(2);
                    break;
                case MotionEvent.ACTION_MOVE:
                    int dY = y - ((int)e.getY());
                    dispatchNestedPreScroll(0, dY, null, null);
                    dispatchNestedScroll(0, 0, 0, dY, null);
                    break;
                case MotionEvent.ACTION_UP:
                    stopNestedScroll();
                    break;
            }
            return true;
        }
    }
    

    Then, inside your Activity class, assign a TouchListener to your AppBarLayout:

    appBarLayout.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return customNestedScrollView.dispatchHandlerScroll(event);
        }
    });
    

    and remove it when the AppBar collapses fully for the first time:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (Math.abs(appBarLayout.getY()) == appBarLayout.getTotalScrollRange()) {
            appBarLayout.setOnTouchListener(null);
        }
        return super.dispatchTouchEvent(ev);
    }
    

    Edit

    Take the following steps to get it up and running:

    • Replace the NestedScrollView in your xml(android.support.v4.widget.NestedScrollView) with the CustomNestedScrollView (which will take the form of com.something.somethingelse.CustomNestedScrollView, depending on where it is in your project).

    • Assign it to a variable in your Activity onCreate()(i.e. CustomScrollView customScrollView = (CustomScrollView) findViewById(R.id.custom_scroll_view_id);)

    • Set up the TouchListener on your appBarLayout as you have done in your edit. Now when you call dispatchHandlerScroll(), it will be on your customNestedScrollView instance.

    • dispatchTouchEvent() is a method you override in your Activity class, so it should be outside the TouchListener

    So, for example:

    public class MainActivity extends AppCompatActivity {
    
        private AppBarLayout appBarLayout;
        private CustomNestedScrollView customNestedScrollView;
        //...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            //...
            customNestedScrollView = (CustomNestedScrollView) findViewById(R.id.scroll);
    
            appBarLayout = (AppBarLayout) findViewById(R.id.app_bar_layout);
            appBarLayout.setOnTouchListener(new View.OnTouchListener() {
                @Override
                 public boolean onTouch(View v, MotionEvent event) {
                     return customNestedScrollView.dispatchHandlerScroll(event);
                 }
            });
        }    
    
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (Math.abs(appBarLayout.getY()) == appBarLayout.getTotalScrollRange()) {
                appBarLayout.setOnTouchListener(null);
            }
            return super.dispatchTouchEvent(ev);
        }
    }
    

    Hope that's cleared things up.