I am a newbie to CoordinatorLayout
and this is a really strange behavior I am getting in CoordinatorLayout
.I have an ImageView
(or more specifically a subclass of ImageView
called CircleImageView
(it houses the Profile pic in the center here)) as one of the children of the CoordinatorLayout
. I have anchored this CircleImageView
to the AppbarLayout
(which is another child of the CoordinatorLayout
). Here is the entire layout I have:
So far so good. I am currently able to scroll the AppbarLayout
and the NestedScrollView
moves along with it. But, I thought of animating the Profile pic to move to right as we scroll up and decided to rely on custom CoordinatorLayour.Behavior
. I ended up with a custom behavior that tries translation of the CircleImageView
. It's not complete yet but is supposed to translate the view roughly some amount except now, the CircleImageView
has disappeared completely with the introduction of custom behavior.
What might be the reason for this?
Note: I have tried replacing the CircleImageView with an ImageView and the behavior remains the same.
Here is the layout for reference:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="false"> <android.support.design.widget.AppBarLayout android:id="@+id/main.appbar" android:layout_width="match_parent" android:layout_height="200dp" android:fitsSystemWindows="false" 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"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_collapseMode="pin" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"> </android.support.v7.widget.Toolbar> <android.support.v7.widget.AppCompatImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginBottom="24dp" android:layout_marginEnd="24dp" android:layout_marginRight="24dp" android:layout_weight="1" android:tint="@color/white" app:srcCompat="@drawable/ic_settings_24px" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/profile_pic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:elevation="4dp" app:layout_behavior="com.learncity.learner.account.profile.ProfilePicBehavior" android:src="@drawable/avatar_boy_2" app:layout_anchor="@id/main.appbar" app:layout_anchorGravity="bottom|center" /> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/user_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="64dp" android:lineSpacingExtra="8dp" android:padding="@dimen/activity_horizontal_margin" android:text="@string/account_person_name_label" android:textAlignment="center" android:textSize="40sp" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <android.support.v7.widget.AppCompatImageView android:id="@+id/id_phone_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="24dp" android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:tint="@color/colorPrimary" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@+id/user_name" app:srcCompat="@drawable/ic_phone_black_24dp" /> <TextView android:id="@+id/id_phone_no" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:text="Phone No" app:layout_constraintBottom_toBottomOf="@+id/id_phone_icon" app:layout_constraintLeft_toRightOf="@+id/id_phone_icon" app:layout_constraintTop_toTopOf="@+id/id_phone_icon" app:layout_constraintVertical_bias="0.571" /> <android.support.v7.widget.AppCompatImageView android:id="@+id/id_email_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="24dp" android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:tint="@color/colorPrimary" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@+id/id_phone_icon" app:srcCompat="@drawable/ic_email_black_24dp" /> <TextView android:id="@+id/id_email_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:text="Email Id" app:layout_constraintBottom_toBottomOf="@+id/id_email_icon" app:layout_constraintLeft_toRightOf="@+id/id_email_icon" app:layout_constraintTop_toTopOf="@+id/id_email_icon" /> <View style="@style/Divider" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/id_email_icon" /> </android.support.constraint.ConstraintLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
And the custom behavior:(I know the calculations might not be sound but I am trying for an initial crude animation)
public class ProfilePicBehavior extends CoordinatorLayout.Behavior<CircleImageView>{ public ProfilePicBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) { return dependency instanceof AppBarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) { // Translate the CircleImageView to the right // Calculate first, what fraction the AppBarLayout has shrunk by float proportion = dependency.getHeight() / 200f; // Translate the child by this proportion float translationX = parent.getWidth() * proportion; child.setTranslationX(translationX); return true; } }
4 Answers
Answers 1
According to this answer: https://stackoverflow.com/a/40023161/6248491
It is stating that The contained CollapsingToolbarLayout
fiddles with parent's elevation
Try giving elevation of the AppBarLayout
to 0dp. Also, the CircleImageView
should hold more (higher) elevation in order to not get "lifted" on top.
Hope this helps. Let me know if it works.
Answers 2
I got the same problem with you a few months ago. I solve it by a custom CoordinatorLayout.Behavior
as below (I dont remember the link of same question):
public class CollapsingImageBehavior extends CoordinatorLayout.Behavior<View> { private final static int X = 0; private final static int Y = 1; private final static int WIDTH = 2; private final static int HEIGHT = 3; private int mTargetId; private int[] mView; private int[] mTarget; public CollapsingImageBehavior() { } public CollapsingImageBehavior(Context context, AttributeSet attrs) { if (attrs != null) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CollapsingImageBehavior); mTargetId = a.getResourceId(R.styleable.CollapsingImageBehavior_collapsedTarget, 0); a.recycle(); } if (mTargetId == 0) { throw new IllegalStateException("collapsedTarget attribute not specified on view for behavior"); } } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency instanceof AppBarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { setup(parent, child); AppBarLayout appBarLayout = (AppBarLayout) dependency; int range = appBarLayout.getTotalScrollRange(); float factor = -appBarLayout.getY() / range; int left = mView[X] + (int) (factor * (mTarget[X] - mView[X])); int top = mView[Y] + (int) (factor * (mTarget[Y] - mView[Y])); int width = mView[WIDTH] + (int) (factor * (mTarget[WIDTH] - mView[WIDTH])); int height = mView[HEIGHT] + (int) (factor * (mTarget[HEIGHT] - mView[HEIGHT])); CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); lp.width = width; lp.height = height; child.setLayoutParams(lp); child.setX(left); child.setY(top); return true; } private void setup(CoordinatorLayout parent, View child) { if (mView != null) return; mView = new int[4]; mTarget = new int[4]; mView[X] = (int) child.getX(); mView[Y] = (int) child.getY(); mView[WIDTH] = child.getWidth(); mView[HEIGHT] = child.getHeight(); View target = parent.findViewById(mTargetId); if (target == null) { throw new IllegalStateException("target view not found"); } mTarget[WIDTH] += target.getWidth(); mTarget[HEIGHT] += target.getHeight(); View view = target; while (view != parent) { mTarget[X] += (int) view.getX(); mTarget[Y] += (int) view.getY(); view = (View) view.getParent(); } } }
You have to add a custom styleable attrs.xml
:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CollapsingImageBehavior"> <attr name="collapsedTarget" format="integer"></attr> </declare-styleable> </resources>
After that, you can define your xml as below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout android:id="@+id/main.appbar" android:layout_width="match_parent" android:layout_height="200dp" android:fitsSystemWindows="false" 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"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:layout_collapseMode="pin" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"> <Space android:id="@+id/circle_collapsed_target" android:layout_width="40dp" android:layout_height="40dp" /> </android.support.v7.widget.Toolbar> <android.support.v7.widget.AppCompatImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginBottom="24dp" android:layout_marginEnd="24dp" android:layout_marginRight="24dp" android:layout_weight="1" app:srcCompat="@drawable/ic_tab_angel" /> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <ImageView android:id="@+id/profile_pic" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="top|center_horizontal" android:layout_marginTop="220dp" android:elevation="4dp" android:src="@mipmap/icon_app" app:collapsedTarget="@id/circle_collapsed_target" app:layout_behavior="com.kp_corp.angelalarm.activity.CollapsingImageBehavior" /> <!----> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:fillViewport="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/user_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginStart="8dp" android:layout_marginTop="64dp" android:lineSpacingExtra="8dp" android:padding="@dimen/activity_horizontal_margin" android:text="Test" android:textAlignment="center" android:textSize="40sp" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> </android.support.v4.widget.NestedScrollView>
Answers 3
Last year I had to do something similar.
The AvatarImageBehavior
is based on the GitHub project by Saul Molinero (saulmm)
As you can notice, the CircleImageView
is the last element of the layout. Maybe it is your issue? Hope it helps.
fragment's layout
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:custom="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:ignore="RtlHardcoded"> <android.support.design.widget.AppBarLayout android:id="@+id/main_appbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/main.collapsing" android:layout_width="match_parent" android:layout_height="550dp" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> <ImageView android:id="@+id/iv_product_background" android:layout_width="match_parent" android:layout_height="400dp" android:scaleType="centerCrop" android:tint="#11000000" app:layout_collapseMode="parallax"/> <FrameLayout android:id="@+id/main_framelayout_title" android:layout_width="match_parent" android:layout_height="150dp" android:layout_gravity="bottom|center_horizontal" android:background="@color/white" android:orientation="vertical" app:layout_collapseMode="parallax"> <LinearLayout android:id="@+id/main_linearlayout_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="vertical"> <TextView android:id="@+id/tv_product_title_open" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="bottom|center" tools:text="Title" android:textColor="@android:color/white" android:textSize="30sp"/> <TextView android:id="@+id/tv_product_tagline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="4dp" android:gravity="center" android:paddingEnd="@dimen/standard_margin_space" android:paddingStart="@dimen/standard_margin_space" tools:text="Tagline" android:textColor="@android:color/white"/> </LinearLayout> </FrameLayout> </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:background="@color/white" android:scrollbars="none" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:id="@+id/products_view_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"/> </android.support.v4.widget.NestedScrollView> <android.support.v7.widget.Toolbar android:id="@+id/main_toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/white" app:layout_anchor="@id/main_framelayout_title" app:theme="@style/ThemeOverlay.AppCompat.Dark" app:title=""> <include layout="@layout/toolbar_buttons"/> <TextView android:id="@+id/tv_product_title_closed" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="71dp" android:gravity="center_vertical" tools:text="Title" android:textColor="@android:color/white" android:textSize="26sp"/> <!--</LinearLayout>--> </android.support.v7.widget.Toolbar> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/iv_product_avatar" android:layout_width="@dimen/product_avatar_width" android:layout_height="@dimen/product_avatar_width" android:layout_gravity="top|center_horizontal" android:layout_marginTop="235dp" android:src="@drawable/img_products_total16_avatar" app:border_color="@color/grey" app:border_width="0dp" app:layout_behavior="com.myname.AvatarImageBehavior"/> </android.support.design.widget.CoordinatorLayout>
AvatarImageBehavior
public class AvatarImageBehavior extends CoordinatorLayout.Behavior<CircleImageView> { private final static String TAG = AvatarImageBehavior.class.getSimpleName(); private final Context mContext; private boolean isInitialized = false; private float mStartX; private float mMaxXMove; private float mStartY; private float mMaxYMove; private float mMaxScroll; private float mStartHeight; private float mMaxHeightChange; private float mFinalHeight; private float mFinalX; private float mFinalY; public AvatarImageBehavior(Context context, AttributeSet attrs) { mContext = context; } @Override public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) { return dependency instanceof Toolbar; } private void initProperties(CircleImageView child, View dependency) { mMaxScroll = dependency.getY(); mStartHeight = child.getHeight(); mFinalHeight = mContext.getResources().getDimensionPixelOffset(R.dimen.product_avatar_final_width); mMaxHeightChange = mStartHeight - mFinalHeight; mStartX = child.getX(); mFinalX = mContext.getResources().getDimensionPixelOffset(R.dimen.product_avatar_margin_left); mMaxXMove = mStartX - mFinalX; mStartY = child.getY(); mFinalY = (dependency.getHeight() - mFinalHeight) / 2f; mMaxYMove = mStartY - mFinalY; isInitialized = true; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) { if (!isInitialized) initProperties(child, dependency); final float currScrollDist = dependency.getY(); if (currScrollDist == 0) { setParams(child, (int) mFinalX, (int) mFinalY, (int) mFinalHeight); } else if (currScrollDist == mMaxScroll) { // reset the values if the scroll is at the max setParams(child, (int) mStartX, (int) mStartY, (int) mStartHeight); } else { float scrollFactor = currScrollDist / mMaxScroll; float factor = 1f - scrollFactor; float currX = mStartX - (mMaxXMove * factor); float currY = mStartY - (mMaxYMove * factor); float currHeight = mStartHeight - (mMaxHeightChange * factor); setParams(child, (int) currX, (int) currY, (int) currHeight); } return true; } private void setParams(CircleImageView view, int xPos, int yPos, int height) { view.setX(xPos); view.setY(yPos); CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) view.getLayoutParams(); lp.width = height; lp.height = height; view.setLayoutParams(lp); } }
Answers 4
I have figured it out finally. I narrowed down the problem to the custom behavior(obviously) and the problem was the anomalous values of the translation for the child view(CircleImageView
).
Here is the custom behavior after fine tuning the values:
public class ProfilePicBehavior extends CoordinatorLayout.Behavior<CircleImageView>{ private int mDependencyHeight; private int mProfilePicMargin; private int mActionBarHeight; public ProfilePicBehavior(Context context) { init(context); } public ProfilePicBehavior(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context){ mDependencyHeight = (int)context.getResources() .getDimension(R.dimen.appbarlayout_learner_home_height); mProfilePicMargin = (int)ViewUtils.dpToPx(context, 8f); mActionBarHeight = (int)ActivityUtils.getActionBarHeight(context); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) { if(dependency instanceof AppBarLayout){ return true; } return false; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) { // Translate the CircleImageView to the right // Calculate first, what fraction the AppBarLayout has shrunk by int bottom = dependency.getBottom(); int top = dependency.getTop(); int viewHeight = bottom; float proportion = Math.min(1, 1 - ((viewHeight - mActionBarHeight) / (float)(mDependencyHeight - mActionBarHeight))); // Translate the child by this proportion float translationX = (parent.getWidth()/2 - child.getWidth()/2 - mProfilePicMargin) * proportion; float translationY = (child.getHeight()/2 - mProfilePicMargin) * proportion; child.setTranslationX(translationX); child.setTranslationY(translationY); return true; } }
You can see that it is simpler than @Eselfar's answer which manipulates the properties through the LayoutParams
.(I haven't tested his answer though because my animation was a bit different)
The logic is really simple: Translate the CircleImageView
by the proportion that AppBarLayout
has shrunk by.
0 comments:
Post a Comment