Wednesday, October 4, 2017

CoordinatorLayout: View disappears with custom behavior

Leave a Comment

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment