简体   繁体   中英

CollapsingToolbarLayout and floating action button position inside CollapsingToolbarLayout

I am referring to Cheesesquare app. I have slightly different design requirement at the moment..

Something like this (ignore the part below image and name)

在此输入图像描述

I want floating action button on right bottom corner of circular image and Name of the person below it (which will be CollapsingToolbarLayout title).

So far, I am able to achieve this -

在此输入图像描述

Problem in this layout is, I am not able to pull down the title below image and I can't re-position floating action button..

This is the layout I am using (slightly modified from cheesesquare app)

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"                                                 
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/detail_backdrop_height"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsing_toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="bottom"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        android:fitsSystemWindows="true"
        app:contentScrim="?attr/colorPrimary"
        app:expandedTitleMarginStart="48dp"
        app:expandedTitleTextAppearance="@style/HeaderTitleStyle"
        app:expandedTitleMarginEnd="64dp">


      <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:layout_collapseMode="parallax"
                    android:fitsSystemWindows="true">

          <ImageView
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:id="@+id/imgProfileUserImage"
              android:adjustViewBounds="true"
              android:scaleType="fitXY"
              android:src="@drawable/cheese_1"
              android:alpha="0.35" />


        <Cheesesquare.Utils.CircleImageView
                android:layout_width="180dp"
                android:layout_height="180.0dp"
                android:id="@+id/imgProfileCircleImage"
                android:src="@drawable/cheese_2"
                custom:border="true"
                custom:border_color="#d5d5d5"
                custom:border_width="4dp"
                custom:shadow="true"
                android:layout_gravity="center"
                android:minHeight="80dp"
                android:minWidth="80dp" />



      </FrameLayout>


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

    </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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/card_margin">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Info"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/card_margin"
            android:layout_marginLeft="@dimen/card_margin"
            android:layout_marginRight="@dimen/card_margin">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Friends"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/card_margin"
            android:layout_marginLeft="@dimen/card_margin"
            android:layout_marginRight="@dimen/card_margin">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Related"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

    </LinearLayout>

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

        <android.support.design.widget.FloatingActionButton
              android:layout_height="wrap_content"
              android:id="@+id/uploadPhotoButton"
              android:layout_width="wrap_content"
              app:layout_anchor="@id/appbar"
              app:layout_anchorGravity="bottom|right|end"
              android:src="@drawable/ic_discuss"
              android:layout_margin="@dimen/fab_margin"
              android:clickable="true"/>

Little help would be appreciated :-)

Update

  1. I managed to center the text and make it look like the required user interface.. The only thing remaining is to set the circular ImageView as an anchor of FAB and then make it disappear as soon as CollapsingToolbarLayout goes off..

在此输入图像描述

Question 1. Hide anchored view on collapsing the associated view.

As far as i noticed, an anchor attribute really has some weird influence on possibility of the view to be hidden on collapsing. So well-tried solution is to perform it programmatically:

appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
    @Override
    public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        /**
         * verticalOffset changes in diapason
         * from 0 - appBar is fully unwrapped
         * to -appBarLayout's height - appBar is totally collapsed
         * so in example we hide FAB when user folds half of the appBarLayout
         */
        if (appBarLayout.getHeight() / 2 < -verticalOffset) {
            fab.setVisibility(View.GONE);
        } else {
            fab.setVisibility(View.VISIBLE);
        }
    }
});

Question 2. How to bind view directly to border of circle image.

Unfortunately "CircleView" is an ordinary view with the usual rectangular shape. You can easily verify this by setting it's background parameter.

So in your case FAB is anchored to view's corner but not the point at circular border. The solution I can suggest is as follows:

  1. Put the FAB into wrapper layout
  2. Apply to that wrapper attributes relevant to the anchor regulations (and remove these attributes from the FAB)
  3. Correct the FAB position by setting the layout padding. Value for padding is (0.2928 * CircleImage.width)

Here we use the wrapper layout in order to avoid potential issues with relocation of anchored view through its margins.

Value 0.2928 is a coefficient to calculate the distance from square's corner to the closest point of circle which is inscribed to it.

After all this magic FAB should turn into something like this (assume that we bind the FAB to the circle image with width == 180dp as in the question):

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingBottom="53dp"
    android:paddingRight="53dp"
    app:layout_anchor="@+id/imgProfileCircleImage"
    app:layout_anchorGravity="bottom|right">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/uploadPhotoButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:src="@drawable/ic_discuss" />
</FrameLayout>

Edited

The second solution can be improved to avoid manual calculation of paddings. We just need the custom layout which can perform them itself:

public class CustomFrameLayout extends FrameLayout {
    public CustomFrameLayout(Context context) {
        super(context);
    }

    public CustomFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setupPaddings(context, attrs);
    }

    public CustomFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setupPaddings(context, attrs);
    }

    private void setupPaddings(Context context, AttributeSet attrs) {
        int diameter = 0;
        TypedArray attrArray = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.FabLayout,
                0, 0);
        try {
            diameter = attrArray.getInteger(R.styleable.FabLayout_anchor_diameter, 0);
        } finally {
            attrArray.recycle();
        }

        int padding = (int) Math.round((double) diameter * (1d - 1d / (Math.sqrt(2d)))); // in dips
        int paddingPx = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, padding, getResources().getDisplayMetrics()));

        String xmlAnchorGravity = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "layout_anchorGravity");
        int gravity = Integer.parseInt(xmlAnchorGravity.substring(2), 16);

        int top = ((gravity & 0x30) == 0x30) ? 1 : 0;
        int bottom = ((gravity & 0x50) == 0x50) ? 1 : 0;
        int left = ((gravity & 0x03) == 0x03) ? 1 : 0;
        int right = ((gravity & 0x05) == 0x05) ? 1 : 0;

        setPadding(left * paddingPx,
                top * paddingPx,
                right * paddingPx,
                bottom * paddingPx);
    }
}

And declare additional attribute for it in declare-styleable:

<declare-styleable name="FabLayout">
    <attr name="anchor_diameter" format="integer" />
</declare-styleable>

And after that we are able to replace this:

<FrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingBottom="53dp"
    android:paddingRight="53dp"
    ...

with more suitable form:

<com.example.CustomFrameLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:anchor_diameter="180"
    ...

The issue you are having is that the CircleImageView is a circle inside a square box, when you set the FAB to anchor to the CircleImageView it anchors to the corner of the bounding box and not the actual ImageView itself.

Normally you could correct this by adding a margin to the right and bottom to move the FAB into position but it seems there's a bug in the Design Support Library that causes margins to be ignored.

Here's another SO issue discussing it further.

Here's an issue that has been logged with Google.

The issue has been assigned to someone on the Android team so hopefully it gets fixed in a later release.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM