How to fix a view in collapsing toolbar while scrolling?
Asked Answered
P

1

8

I want to implement collapsing toolbar with two EditText in it, for the purpose of user input. I'm following this answer. The answer gives perfect solution for adding two EditText into the collapsing toolbar. But the behavior is not as expected.

What I've achieved:

enter image description here

Expected Behavior:

enter image description here

My XML code

<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="wrap_content"
android:fitsSystemWindows="true">

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"      >

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsing_tool_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:elevation="0dp"
        app:expandedTitleTextAppearance="@style/Widget.AppCompat.ActionBar.TabText"
        app:layout_scrollFlags="scroll|enterAlways"
        app:statusBarScrim="?attr/colorAccent">


        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar_1"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/primary"
            android:minHeight="?attr/actionBarSize"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_collapseMode="none"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
        </android.support.v7.widget.Toolbar>

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

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/primary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:layout_collapseMode="none"
        app:elevation="0dp"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingLeft="32dp"
            android:paddingTop="16dp"
            android:paddingBottom="56dp"
            android:paddingRight="16dp">

            <android.support.design.widget.TextInputLayout
                android:id="@+id/lNameLayout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/fNameLayout"
                android:layout_marginTop="10dp">

                <EditText
                    android:id="@+id/ltitle"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="Title"/>
            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:id="@+id/lNameLayout2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/fNameLayout"
                android:layout_marginTop="10dp"
                android:layout_marginBottom="10dp">

                <EditText
                    android:id="@+id/ldesc"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:hint="Description"/>
            </android.support.design.widget.TextInputLayout>


        </LinearLayout>
    </android.support.v7.widget.Toolbar>

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


<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingTop="10dp">

        <!--my widgets here-->

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

I know I can do this type of styling using scrollFlag. I've read this post about scroll flags.But can't find exactly how to use it for this purpose.
I also want to change the font size of the EditText as shown in the above GIF.

But first question is how to fix one view in toolbar and hide another as user scrolls up. It would be nice if someone explain with suitable example.

Pungy answered 19/6, 2017 at 17:28 Comment(3)
So you want to hide the bottom EditText while scrolling and leave the top one on screen right?Jevons
Yeah! exactly..Pungy
@rom4ek Yeah! exactlyPungy
J
15

I'm sure Todoist is doing it another way, but still...

Here is the xml layout. The main idea is that the view, that supposed to be pinned, should be inside Toolbar, when another view, which you want to hide, should be inside CollapsingToolbarLayout with a decent top margin, to prevent overlapping:

<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.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_tool_bar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:statusBarScrim="?attr/colorAccent">

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_collapseMode="parallax"
                android:layout_marginLeft="72dp"
                android:layout_marginRight="16dp"
                android:layout_marginBottom="32dp"
                android:layout_marginTop="136dp">

                <android.support.design.widget.TextInputLayout
                    android:id="@+id/lNameLayout2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

                    <android.support.design.widget.TextInputEditText
                        android:id="@+id/ldesc"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:ems="10"
                        android:hint="Description"/>
                </android.support.design.widget.TextInputLayout>

            </FrameLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/primary"
                android:minHeight="?attr/actionBarSize"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

                <android.support.design.widget.TextInputLayout
                    android:id="@+id/lNameLayout"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginRight="16dp"
                    android:layout_marginBottom="16dp"
                    android:layout_marginTop="48dp">

                    <android.support.design.widget.TextInputEditText
                        android:id="@+id/title"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:hint="Title"
                        android:textSize="30sp"
                        android:textColor="@android:color/white"
                        android:ems="10"/>
                </android.support.design.widget.TextInputLayout>

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

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


    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <!-- your content here -->

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

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

Then, to achieve the same font size and top margin animation, we can implement AppBarLayout.OnOffsetChangedListener and animate our properties according to scrolling offset changes. Here is the activity class:

public class MainActivity extends AppCompatActivity
    implements AppBarLayout.OnOffsetChangedListener {

    private static final float COLLAPSED_TEXT_SIZE_SP = 18f;

    private static final float COLLAPSED_TOP_MARGIN_DP = 24f;

    private static final float MARGIN_SCROLLER_MULTIPLIER = 4f;

    private float expandedTextSize;
    private float collapsedTextSize;

    private int expandedTopMargin;
    private int collapsedTopMargin;

    private AppBarLayout mAppBarLayout;
    private Toolbar mToolbar;
    private TextInputEditText editText;
    private TextInputLayout textInputLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        mAppBarLayout = (AppBarLayout) findViewById(R.id.appbar);
        editText = (TextInputEditText) findViewById(R.id.title);
        textInputLayout = (TextInputLayout) findViewById(R.id.lNameLayout);

        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        mAppBarLayout.addOnOffsetChangedListener(this);

        expandedTextSize = editText.getTextSize();
        collapsedTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, COLLAPSED_TEXT_SIZE_SP, getResources().getDisplayMetrics());

        expandedTopMargin = ((ViewGroup.MarginLayoutParams) textInputLayout.getLayoutParams()).topMargin;
        collapsedTopMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, COLLAPSED_TOP_MARGIN_DP, getResources().getDisplayMetrics());
    }

    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int offset) {
        int maxScroll = appBarLayout.getTotalScrollRange();
        float percentage = (float) Math.abs(offset) / maxScroll;
        float textSizeDiff = Math.abs(expandedTextSize - collapsedTextSize);
        int marginDiff = Math.abs(expandedTopMargin - collapsedTopMargin);
        //change text size along with scrolling
        editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, expandedTextSize - textSizeDiff * percentage);
        //change top view margin along with scrolling
        ((ViewGroup.MarginLayoutParams) textInputLayout.getLayoutParams()).topMargin = (int) (expandedTopMargin - marginDiff * Math.min(1, percentage * MARGIN_SCROLLER_MULTIPLIER));
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
} 

So, here for expanded state I've used default values, that we applied in our xml. For collapsed state I've defined simple constants. Feel free to fit them for your needs.

Note, when you add a back button or menu items to the Toolbar, you should play around with left and right margins of the second view (the one you want to hide), because the first EditText is a part of Toolbar, therefore its margins will be changed according to what you did add.

Result:

Happy coding!

Jevons answered 22/6, 2017 at 22:30 Comment(2)
Thank you very much! This is exactly what I wanted. Awarding bounty. Can you please tell me that how you came up with that logic written in onOffsetChanged listener?Pungy
Well, firstly, I calculate the percentage of scrolling by dividing offset on max scroll range. This value is always in range 0...1. Then we need to calculate the difference between our expanded and collapsed values. So it's some kind of a "path", that our margin and textsize should pass through. And after that we simply multiply our diffs by percentage, so this is some kind of a current step, that we should subtract from initial expanded value, to get the current value according to scroll offset.Jevons

© 2022 - 2024 — McMap. All rights reserved.