Android CollapsingToolbarLayout with custom View

20,394

Solution 1

I had the same problem and spend many hours trying to find a solution. My solution was to add the collapsing Views (ImageView and TextView) inside the CollapsingToolbarLayout and then handle the transition in code. This way it's more flexible and simpler than extending from CollapsingToolbarLayout.

First you'll need to add your Views inside the CollapsingToolbarLayout with the parallax properties:

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingTop:"80dp"
            android:src="@drawable/icon"
            app:layout_collapseMode="parallax"
            app:layout_collapseParallaxMultiplier="0.8"/> //set vertical transition here

Then set the scaling of the Views with the help of an OnOffsetchangeListner:

  private static final float SCALE_MINIMUM=0.5f;
  appBarLayout.setOnWorkingOffsetChange(new  ControllableAppBarLayout.OnWorkingOffsetChange() {
        @Override
        public void onOffsetChange(int offSet, float collapseDistance) {
            imageView.setScaleX(1 + (collapseDistance * SCALE_MINIMUM));
            imageView.setScaleY(1 + (collapseDistance * SCALE_MINIMUM));

            textView.setScaleX(1 + (collapseDistance * SCALE_MINIMUM));
            textView.setScaleY(1 + (collapseDistance * SCALE_MINIMUM));

            // You can also setTransitionY/X, setAlpha, setColor etc.
        }
    });

Somehow the default offsetChangedListener didn't work properly for me (you probably still should try it with the default listener first), so I used the ControllableAppBarLayout from https://gist.github.com/blipinsk/3f8fb37209de6d3eea99 and added the following:

private OnWorkingOffsetChange onWorkingOffsetChange;

@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int i) {
    if (!isInEditMode()) {
        onWorkingOffsetChange.onOffsetChange(i, (float) i / appBarLayout.getTotalScrollRange());
    }
}

public void setOnWorkingOffsetChange(OnWorkingOffsetChange listener) {
    this.onWorkingOffsetChange = listener;
}

public interface OnWorkingOffsetChange {
    void onOffsetChange(int offSet, float collapseDistance);
}

The only problem is, that you would need to set app:contentScrim="#00000000" (transparent) for your CollapsingToolbarLayout, so your views are still visible when the toolbar is collapsed. If you really need the collapsing-background effect I'm sure you could "fake" this by setting the alpha of a background ImageView in the OffsetChangeListener. ;)

Solution 2

Going to edit Christopher's answer slightly to show how you can get your custom view to not disappear on collapse:

First you'll need to add your Views inside the CollapsingToolbarLayout with the parallax properties:

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingTop:"80dp"
            android:src="@drawable/icon"
            app:layout_collapseMode="parallax"
            app:layout_collapseParallaxMultiplier="0.8"/> //set vertical transition here

Instead add the custom view's programmatically and it won't disappear on collapse. For example here is a view that contains a title and a subtitle:

    final FrameLayout frameLayout = new FrameLayout(mActivity);
    FrameLayout.LayoutParams frameLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT);
    frameLayout.setLayoutParams(frameLayoutParams);


    // Create new LinearLayout
    final LinearLayout linearLayout = new LinearLayout(mActivity);
    frameLayoutParams =new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, dpToPixels(78));
    frameLayoutParams.gravity = Gravity.BOTTOM;
    linearLayout.setLayoutParams(frameLayoutParams);
    linearLayout.setOrientation(LinearLayout.VERTICAL);


    // Add textviews
    final TextView textView1 = new TextView(mActivity);
    LinearLayout.LayoutParams linearLayoutParams =new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    frameLayoutParams.gravity = Gravity.BOTTOM;
    textView1.setLayoutParams(linearLayoutParams);
    textView1.setText("Title");
    textView1.setTextColor(ContextCompat.getColor(mActivity, R.color.colorWhite));
    textView1.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40);
    linearLayout.addView(textView1);


    final TextView textView2 = new TextView(mActivity);
    linearLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    textView2.setLayoutParams(linearLayoutParams);
    textView2.setText("Subtitle");
    textView2.setTextColor(ContextCompat.getColor(mActivity, R.color.colorWhite));
    textView2.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
    linearLayout.addView(textView2);

    frameLayout.addView(linearLayout);


    collapsingToolbar.addView(frameLayout);
    final float SCALE_MIN=0.4f;
    AppBarLayout appBarLayout = (AppBarLayout) mActivity.findViewById(R.id.appBarLayout);
    appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int offSet) {
            float collapsedRatio = (float) offSet / appBarLayout.getTotalScrollRange();
            linearLayout.setScaleX(1 + (collapsedRatio * SCALE_MIN));
            linearLayout.setScaleY(1 + (collapsedRatio * SCALE_MIN));
            FrameLayout.LayoutParams frameLayoutParams =new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, dpToPixels(78));
            frameLayoutParams.gravity = Gravity.BOTTOM;
            frameLayoutParams.setMargins(Math.round(dpToPixels(48) * (1+collapsedRatio)), 0, 0, Math.round(dpToPixels(15) * collapsedRatio));
            linearLayout.setLayoutParams(frameLayoutParams);
            // You can also setTransitionY/X, setAlpha, setColor etc.
        }
    });

/////

float lastCollapsedRatio = -2;

////

private int dpToPixels(int padding_in_dp){
    final float scale = getResources().getDisplayMetrics().density;
    int padding_in_px = (int) (padding_in_dp * scale + 0.5f);
    return padding_in_px;
}

Solution 3

From the widget itself there doesn't seem to be a way to enable this directly, like it was possible to add custom views to the Toolbar.

What you could try to do however, is open the source of the CollapsingToolbarLayout.class and check out how the CollapsingTextHelper.class is used to have the title set. You could try to make your own widget by extending from the the CollapsingToolbarLayout.

These links can help you out with creating custom components/views, if you haven't created them before: Custom Views, Custom Components

I haven't tried this yet, but it's actually something I was thinking about trying to achieve a similar solution as you are looking for. Steps I tihkn I would follow, so far:

  1. Create custom attributes for subtitle settings in attrs.xml
  2. Create your own MyCollapsingToolbarLayout by extending the original one.
  3. Make sure to call super in the constructors, so the original component will stay intact.
  4. Create a subtitleTextHelper by adding a new CollapsingTextHelper to your component.
  5. Override onDraw to actually draw your subtitle.
  6. Update the layout containing your CollapingsToolbarLayout with your subtitle attributes (default styling and such, maybe a fixed subtitle Text).
  7. Apply the changes in the Activity containing your CollapsingToolbar. (Convert CollapsingToolbarlayout to MyCollapingsToolbarLayout, set subtitles, extra custom settings, etc).
  8. Cross fingers, test.

Going to have a look at it now.

Share:
20,394

Related videos on Youtube

CeccoCQ
Author by

CeccoCQ

Software Engineer.

Updated on July 09, 2022

Comments

  • CeccoCQ
    CeccoCQ almost 2 years

    I'm following the Cheesesquare example project to understand the new design material library.

    I'm wondering if there's a way to use a custom view (like Telegram) with ImageView, title and subtitle instead of the simple Title provided by CollapsingToolbarLayout widget.

    Thanks.

    • shkschneider
      shkschneider almost 9 years
      Have you tried a CollapsingToolbarLayout containing an ImageView first and then a LinearLayout with 2 TextViews?
    • CeccoCQ
      CeccoCQ almost 9 years
      I've tried, but my purpose is the replacement the Title with my CustomView to preserve the animation of the title in the toolbar.
    • Devendra Singh
      Devendra Singh over 7 years
      @shkschneider would you explain a bit more. i want to use
  • CeccoCQ
    CeccoCQ almost 9 years
    It was a really hard work. CollapsingToolbarLayout is a terrible widget.
  • Max
    Max almost 9 years
    @StingRay5 Can you help me what to do inside onDraw()? Or please share your code.
  • Devendra Singh
    Devendra Singh over 7 years
    would you please explain a bit more. i have the same problem, i just want to use an icon and a title in the centre of CollapsingToolBarLayout when i scroll up than the icon must fly to toolbar's left side where normally the app icon exists and the title is in the right side of icon.
  • Roy Solberg
    Roy Solberg over 7 years
    Great trick. Except that I suddenly got my logs filled with the following. Any ideas on how to avoid that? (Gah, why can't SO comments support line breaks?) requestLayout() improperly called by android.support.design.widget.CollapsingToolbarLayout{a9c724‌​4 V.ED..... ......I. 0,0-1440,405 #7f1100fb app:id/collapsingToolbarLayout} during layout: running second layout pass requestLayout() improperly called by android.widget.LinearLayout{4058f2d V.E...... ......I. 168,132-1440,405} during layout: running second layout pass
  • Roy Solberg
    Roy Solberg over 7 years
    I added a check to see if collapsedRatio actually has changed and only do the scaling in those cases. That made all the complaining about second layout pass disappear.
  • luca992
    luca992 over 7 years
    I haven't used this code in a while. But I do remember encountering that. Where did you add the check? I can update my answer. @RoySolberg