4

I am creating an app with a recyclerview. And above the RV I have an image, which should get smaller, when i scroll. This works, but the RV scrolls also. I want that first the image gets smaller and then the recyclerview starts scrolling. But how can I do this? Here is my XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/b"
        android:id="@+id/test_photo"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="test"/>

    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        app:layout_anchor="@+id/test_photo"
        android:background="@color/colorPrimary"
        app:layout_anchorGravity="bottom|start">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/colorWhite"
            android:textSize="30sp"
            android:text="username"/>
    </LinearLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/user_view_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

And this is the code to resize the image:

rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
                float state = 0.0f;

                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, final int dy) {
                    Log.e("Y",Integer.toString(dy));
                    state+=dy;
                    LinearLayout img = (LinearLayout) findViewById(R.id.test_photo);
                    Log.e("STATE", Float.toString(state));
                    if(state >= 500){
                        img.getLayoutParams().height = minWidth;
                        img.getLayoutParams().width = minWidth;
                        img.requestLayout();
                    }
                    if(state <= 0){
                        img.getLayoutParams().height = imgHeight;
                        img.getLayoutParams().width = imgHeight;
                        img.requestLayout();
                    }
                    if(state > 0 && state < 500){

                        //up
                        img.getLayoutParams().height = (int)(imgHeight - ((float)(imgHeight-minWidth)/500)*state);
                        img.getLayoutParams().width = (int)(imgHeight - ((float)(imgHeight-minWidth)/500)*state);
                        img.requestLayout();

                    }
                }
            });

Thanks for the help!

EDIT:

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.AppBarLayout
    android:id="@+id/app_bar"
    android:layout_width="match_parent"
    android:layout_height="320dp"
    android:fitsSystemWindows="true"
    android:theme="@style/AppTheme.AppBarOverlay">

    <com.obware.alifsto.HelpClasses.CollapsingImageLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:minHeight="108dp"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

        <ImageView
            android:id="@+id/background"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            android:scaleType="centerCrop"
            android:src="@drawable/sunset" />

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize" />

        <ImageView
            android:id="@+id/avatar"
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:layout_gravity="bottom|center_horizontal"
            android:layout_marginBottom="96dp"
            android:src="@drawable/logo_blau_weiss"
            android:transitionName="@string/transition_userview_image"/>

        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center_horizontal"
            android:layout_marginBottom="48dp"
            android:text="Title"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|center_horizontal"
            android:layout_marginBottom="24dp"
            android:text="Subtitle "
            android:transitionName="@string/transition_userview_username"
            android:textAppearance="?android:attr/textAppearanceMedium" />

    </com.obware.alifsto.HelpClasses.CollapsingImageLayout>

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


<android.support.v7.widget.RecyclerView
    android:id="@+id/user_interface_recycler"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipToPadding="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">


</android.support.v7.widget.RecyclerView>

user6586661
  • 432
  • 1
  • 11
  • 24

2 Answers2

4

The way you want to do this is with CoordinatorLayout and AppBarLayout and use all that Material Design scrolling goodness.

So essentially what you do is create a specialized layout similar to CollapsingToolbarLayout. For my demo, I used code from that class as inspiration to get my collapsing image layout to work.

What makes it work is adding the layout as a direct child of AppBarLayout, then creating an AppBarLayout.OnOffsetChangeListener and registering it with the AppBarLayout. When you do this, you will get notifications when the user scrolls and the layout is scrolled up.

Another big part of this is setting a minimum height. AppBarLayout uses the minimum height to determine when to stop scrolling your layout, leaving you with a collapsed layout area.

Here's a code excerpt:

    class OnOffsetChangedListener implements AppBarLayout.OnOffsetChangedListener {

        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {

            final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
            final int scrollRange = appBarLayout.getTotalScrollRange();
            float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
            Log.d(TAG, "onOffsetChanged(), offsetFactor = " + offsetFactor);


            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);

                if (child instanceof Toolbar) {
                    if (getHeight() - insetTop + verticalOffset >= child.getHeight()) {
                        offsetHelper.setTopAndBottomOffset(-verticalOffset); // pin
                    }
                }

                if (child.getId() == R.id.background) {
                    int offset = Math.round(-verticalOffset * .5F);
                    offsetHelper.setTopAndBottomOffset(offset); // parallax
                }

                if (child.getId() == R.id.avatar) {

                    float scaleFactor = 1F - offsetFactor * .5F ;
                    child.setScaleX(scaleFactor);
                    child.setScaleY(scaleFactor);

                    int topOffset = (int) ((mImageTopCollapsed - mImageTopExpanded) * offsetFactor) - verticalOffset;
                    int leftOffset = (int) ((mImageLeftCollapsed - mImageLeftExpanded) * offsetFactor);
                    child.setPivotX(0);
                    child.setPivotY(0);
                    offsetHelper.setTopAndBottomOffset(topOffset);
                    offsetHelper.setLeftAndRightOffset(leftOffset);
                }

                if (child.getId() == R.id.title) {

                    int topOffset = (int) ((mTitleTopCollapsed - mTitleTopExpanded) * offsetFactor) - verticalOffset;
                    int leftOffset = (int) ((mTitleLeftCollapsed - mTitleLeftExpanded) * offsetFactor);
                    offsetHelper.setTopAndBottomOffset(topOffset);
                    offsetHelper.setLeftAndRightOffset(leftOffset);
                    Log.d(TAG, "onOffsetChanged(), offsetting title top = " + topOffset + ", left = " + leftOffset);
                    Log.d(TAG, "onOffsetChanged(), offsetting title mTitleLeftCollapsed = " + mTitleLeftCollapsed + ", mTitleLeftExpanded = " + mTitleLeftExpanded);
                }

                if (child.getId() == R.id.subtitle) {

                    int topOffset = (int) ((mSubtitleTopCollapsed - mSubtitleTopExpanded) * offsetFactor) - verticalOffset;
                    int leftOffset = (int) ((mSubtitleLeftCollapsed - mSubtitleLeftExpanded) * offsetFactor);
                    offsetHelper.setTopAndBottomOffset(topOffset);
                    offsetHelper.setLeftAndRightOffset(leftOffset);
                }
            }
        }
    }

The lines child.setScaleX() and child.setScaleY() are the code that actually changes the size of the image.

Demo app is on GitHub at https://github.com/klarson2/Collapsing-Image. Enjoy.


EDIT: After adding a TabLayout I realized one mistake I made in my layout, which was to make the AppBarLayout a fixed height, then make the custom collapsing component height be match_parent. This makes it so you can't see the TabLayout that is added to the app bar. I changed the layout so that AppBarLayout height was wrap_content and the custom collapsing component had the fixed height. This makes it possible to add additional components like a TabLayout to the AppBarLayout. This has been corrected in the latest revision on GitHub.

kris larson
  • 30,387
  • 5
  • 62
  • 74
  • Thanks for your answer! It's nearly great! But there is a simple problem. I've changed your Scroll to a RecyclerView. But when I try to scroll, it doesn't scroll to the bottom. It only shows like 2 or three elements, but there should be 10. Do you know what to do? – user6586661 Aug 11 '16 at 16:20
  • Does your `RecyclerView` have attribute `app:layout_behavior="@string/appbar_scrolling_view_behavior"` on it? – kris larson Aug 11 '16 at 16:24
  • Please add the layout XML that has the `CoordinatorLayout` to your question so I can take a look at it. – kris larson Aug 12 '16 at 15:17
  • I have. There CoordinatorLayout doesn't get shown. But it is there :-) – user6586661 Aug 12 '16 at 15:37
  • Change the width and height on your `RecyclerView` to `match_parent`. – kris larson Aug 12 '16 at 15:45
  • Okay, this is what I did: I went back and modified my GitHub project so it uses a RecyclerView. Start from there and hopefully you can figure out what the fix is. – kris larson Aug 12 '16 at 17:01
  • Hi @user6586661 I was wondering if you checked out my updated project and/or if you finally got your code working – kris larson Aug 23 '16 at 19:53
  • Now I can ask you the question. I have your CollapsingImage in a tabactivity. It looks like this: https://puu.sh/qMLqZ/7e1540784c.png But I want, that the MainActivity hides when I scroll down. That it looks like this: https://puu.sh/qMLB2/1c8104ac4b.png In the left tab I only have a recyclerview, and there it works. – user6586661 Aug 24 '16 at 15:00
  • What scrolling component do you have in the right tab? – kris larson Aug 24 '16 at 18:17
  • also a recyclerview – user6586661 Aug 24 '16 at 18:27
  • From your pictures, it looks like your layout XML has changed from what you posted earlier. Could you please post the updated layout XML and also the layout XML for what is in the tab pages. – kris larson Aug 24 '16 at 19:13
  • I have posted the newest version. Like the last time, the coordinator layout doesn't get shown here. Don't know why. – user6586661 Aug 24 '16 at 19:33
  • We've got a disconnect somewhere. I'm looking at your picture, and it looks like you have a `TabLayout` in there, and you must have a `ViewPager` too if you have tabs, but I don't see either of those in your XML layout. We can do one of two things here: Either I can update my project to have tabs so you can look at it, or you can post your entire project somewhere online so that I can see all the layouts and the code. I'd prefer to look at your project. – kris larson Aug 25 '16 at 03:13
  • I have here the scripts for the activity. If this isn't enough, I can post it on Bitbucket. I am not a fan of GitHub. Activity: http://pastebin.com/Vq2hk8J7 ListAdapter: http://pastebin.com/EZb3LmiT Activity XML: http://pastebin.com/BFsGe88z CardView XML (For each element) : http://pastebin.com/3NsTs0vT It would be great, if you could say what I can do better. I am new to android :-) – user6586661 Aug 25 '16 at 16:26
  • In your layout XML in the question, you have the custom collapsing image component, but no tab layout. In your pastebin, the layout XML has a tab layout, but no collapsing image component. The PNGs on puu.sh from your earlier comment have both. I'm trying to help you, but I'm at a loss here. I will update my GitHub project one last time to have a tabbed activity, and you can look at that. – kris larson Aug 26 '16 at 14:42
  • In addition, your activity class references a SwipeRefreshLayout that isn't in the activity layout XML. – kris larson Aug 26 '16 at 15:06
  • I think I haven't posted it. Do you have an Bitbucket account? – user6586661 Aug 26 '16 at 15:27
  • I don't. But I updated the project on GitHub to have a tab layout, have a look. I updated my answer to explain how I found a mistake I made with the app bar layout where you couldn't see the tab layout. – kris larson Aug 26 '16 at 15:37
  • Thank you for your help. I thougth something else, but thanks! You helped me a lot :-) – user6586661 Aug 26 '16 at 22:22
  • Glad to help. If you found this answer helpful, I'd be grateful if you would mark it as accepted. Cheers – kris larson Aug 27 '16 at 01:29
  • Sure, no problem! One last question. Where have you learned this? Specially the design elements? Do you have a good book or video tutorial? – user6586661 Aug 27 '16 at 09:47
  • For the design support lib stuff, I picked up a lot from Chris Banes' sample app CheeseSquare, which is on GitHub. I find that to really understand anything in Android, I have to look at source code. Chris did post an article explaining `CoordinatorLayout` and how the components/behaviors interact, which also helped. For the collapsing image, I stole a lot from `CollapsingToolbarLayout` source code. I had only seen video clips of the image resize, but knowing how the collapsing toolbar worked along with knowing what my final product had to look like, I was able to connect the dots. – kris larson Aug 27 '16 at 12:42
  • 1
    @ITDevelopers clone the project from https://github.com/klarson2/Collapsing-Image build it and try it out. It might give you some insight into implementing a feature like this for your own project. If you run into trouble, post a question to the SO community & post a comment here with a link to the question, I'll try and provide an answer. – kris larson Jul 19 '19 at 16:18
  • @kris I'm going to run it cloning it. Anyway I tried it for the particular case of the image, that is, not in a generic way. And I had problems with: the path of the image, the initial and final position, and worst of all when I scrolled the image quickly lost the notion of how much to resize in the scrolling – Braian Coronel Jul 19 '19 at 16:46
2

With the following code I resize the image according to the scrolling. So that you can see it collapsed in the AppBar.

Play with the values of the duration of the animation and the value of the scaling when the AppBar is collapsed.

In my case I have the Toolbar as transparent and I manage the colors of the AppBar elements at run times.

@Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        /**
         * Collapsed
         */
        if (Math.abs(verticalOffset) == appBarLayout.getTotalScrollRange()) {
            myImage.animate().scaleX((float)0.4).setDuration(3000);
            myImage.animate().scaleY((float)0.4).setDuration(3000);
            myImage.animate().alpha(1).setDuration(0);

        /**
         * Expanded
         */
        } else if (verticalOffset == 0) {
            myImage.animate().scaleX((float)1).setDuration(100);
            myImage.animate().scaleY((float)1).setDuration(100);
            myImage.animate().alpha(1).setDuration(0);
        /**
         * Somewhere in between
         */
        } else {
            final int scrollRange = appBarLayout.getTotalScrollRange();
            float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
            float scaleFactor = 1F - offsetFactor * .5F;
            myImage.animate().scaleX(scaleFactor);
            myImage.animate().scaleY(scaleFactor);
        }
    }

PD: This works regardless of whether the image exceeds the limits of the AppBar, as if the image were a floating button.

GL

Sources

Braian Coronel
  • 22,105
  • 4
  • 57
  • 62