I have a layout with collapsing toolbar and a smooth transition of the avatar image along with title set via behavior. However, when the CircleImageView reaches the top, it suddenly gets overlapped by the toolbar. It seems like the toolbar is getting redrawn over CircleImageView (and with a shadow) when it gets fully collapsed.
Question is: how to make CircleImageView stay on top of the toolbar?
Gif with the bug at dropbox
Here is the 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"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:id="#+id/app_bar_layout"
>
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:id="#+id/collapsing_toolbar"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleTextAppearance="#style/ExpandedTitleText"
app:collapsedTitleTextAppearance="#style/CollapsedTitleText"
app:expandedTitleMarginStart="112dp"
app:expandedTitleMarginBottom="60dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax"
android:paddingTop="?attr/actionBarSize"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:paddingBottom="16dip"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="group created by"
android:textColor="#color/text_secondary_white"
android:textSize="14sp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Admin name"
android:textColor="#color/text_secondary_white"
android:textSize="14sp"
android:id="#+id/admin"
/>
</LinearLayout>
</LinearLayout>
<android.support.v7.widget.Toolbar
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:theme="#style/ActionBar"
app:layout_collapseMode="pin"
android:id="#+id/toolbar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:id="#+id/recyclerView" />
<View
android:id="#+id/viewstub"
app:layout_anchor="#id/app_bar_layout"
app:layout_anchorGravity="bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#android:color/black" />
<package.CustomView.CircleImageView
android:layout_width="80dip"
android:layout_height="80dip"
android:layout_marginLeft="16dip"
android:layout_marginTop="60dp"
tools:background="#color/color_accent"
android:id="#+id/get_avatar"
app:layout_behavior="package.AvatarImageBehavior" />
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/fab_margin"
app:layout_anchor="#id/app_bar_layout"
app:layout_anchorGravity="bottom|right|end"
app:backgroundTint="#color/color_accent"
app:borderWidth="0dp"
android:src="#drawable/ic_mode_edit_24dp"
android:id="#+id/fab" />
</android.support.design.widget.CoordinatorLayout>
AvatarImageBehavior.java - basically translates the CircleImageView to the toolbar. It depends on the stub view on the bottom of the appbar (not pretty, I know)
public class AvatarImageBehavior extends CoordinatorLayout.Behavior<CircleImageView> {
private final static float MIN_AVATAR_PERCENTAGE_SIZE = 0.3f;
private final static int EXTRA_FINAL_AVATAR_PADDING = 80;
private final static String TAG = "behavior";
private final Context mContext;
private float mAvatarMaxSize;
private float mFinalLeftAvatarPadding;
private float mStartPosition;
private int mStartXPosition;
private float mStartToolbarPosition;
public AvatarImageBehavior(Context context, AttributeSet attrs) {
mContext = context;
init();
mFinalLeftAvatarPadding = context.getResources().getDimension(
R.dimen.contact_space_left);
}
private void init() {
bindDimensions();
}
private void bindDimensions() {
mAvatarMaxSize = mContext.getResources().getDimension(R.dimen.contact_avatar);
}
private int mStartYPosition;
private int mFinalYPosition;
private int finalHeight;
private int mStartHeight;
private int mFinalXPosition;
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {
return dependency.getId() == R.id.viewstub;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
maybeInitProperties(child, dependency);
float toolbarY = parent.findViewById(R.id.toolbar).getY();
// Log.w(TAG, "-----toolbar y: " + toolbarY);
int toolbarHeight = parent.findViewById(R.id.toolbar).getHeight();
// Log.w(TAG, "-----toolbar height: " + toolbarHeight);
// Log.w(TAG, "dependency.getY(): " + dependency.getY());
final int maxScrollDistance = (int)mStartToolbarPosition - toolbarHeight;
// Log.w(TAG, "maxScrollDistance: " + maxScrollDistance);
float expandedPercentageFactor = (dependency.getY()- toolbarHeight) / maxScrollDistance;
// Log.w(TAG, "expandedPercentageFactor: " + expandedPercentageFactor);
if(expandedPercentageFactor > 1f)expandedPercentageFactor = 1f;
float distanceYToSubtract = ((mStartYPosition - mFinalYPosition)
* (1f - expandedPercentageFactor));// + (child.getHeight()/2);
// Log.w(TAG, "distanceYToSubtract: " + distanceYToSubtract);
float distanceXToSubtract = ((mStartXPosition - mFinalXPosition)
* (1f - expandedPercentageFactor)) + (child.getWidth()/2);
// Log.w(TAG, "distanceXToSubtract: " + distanceXToSubtract);
float heightToSubtract = ((mStartHeight - finalHeight) * (1f - expandedPercentageFactor));
// Log.w(TAG, "heightToSubtract: " + heightToSubtract);
child.setY(mStartYPosition - distanceYToSubtract);
// Log.w(TAG, "child.setY: " + (mStartYPosition - distanceYToSubtract));
child.setX(mStartXPosition - distanceXToSubtract);
// Log.w(TAG, "child.setX: " + (mStartXPosition - distanceXToSubtract));
int proportionalAvatarSize = (int) (mAvatarMaxSize * (expandedPercentageFactor));
// Log.w(TAG, "proportionalAvatarSize: " + proportionalAvatarSize);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (mStartHeight - heightToSubtract);
// Log.w(TAG, "lp.width: " + lp.width);
lp.height = (int) (mStartHeight - heightToSubtract);
// Log.w(TAG, "lp.height: " + lp.height);
child.setLayoutParams(lp);
// Log.w(TAG, "---------------------");
return true;
}
private void maybeInitProperties(CircleImageView child, View dependency) {
if (mStartYPosition == 0)
mStartYPosition = (int) child.getY();
// Log.w(TAG, "mStartYPosition: " + mStartYPosition);
if (mFinalYPosition == 0)
mFinalYPosition = mContext.getResources().getDimensionPixelOffset(R.dimen.contact_final_width);
// Log.w(TAG, "mFinalYPosition: " + mFinalYPosition);
if (mStartHeight == 0)
mStartHeight = child.getMeasuredHeight();
// Log.w(TAG, "mStartHeight: " + mStartHeight);
if (finalHeight == 0)
finalHeight = mContext.getResources().getDimensionPixelOffset(R.dimen.contact_final_width);
// Log.w(TAG, "finalHeight: " + finalHeight);
if (mStartXPosition == 0)
mStartXPosition = (int) (child.getX() + (child.getWidth() / 2));
// Log.w(TAG, "mStartXPosition: " + mStartXPosition);
if (mFinalXPosition == 0)
mFinalXPosition = mContext.getResources().getDimensionPixelOffset(R.dimen.abc_action_bar_content_inset_material) + (finalHeight*2);
// Log.w(TAG, "mFinalXPosition: " + mFinalXPosition);
if (mStartToolbarPosition == 0)
mStartToolbarPosition = dependency.getY();// 456
// Log.w(TAG, "mStartToolbarPosition: " + mStartToolbarPosition);
}
}
Related
I am trying to implement behaviour which is shown below (on pictures), so I need to stretch all content below AppBarLayout. I've already achieved it somehow by implementing custom CoordinatorLayout Behaviour but that solution have some issues with views recycling inside RecyclerView and overall performance. Is there easier solution to achieve what i want?
public class TestBehaviour48Margin extends CoordinatorLayout.Behavior {
public TestBehaviour48Margin() {
}
public TestBehaviour48Margin(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(#NonNull CoordinatorLayout parent, #NonNull View child, #NonNull View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(#NonNull CoordinatorLayout parent, #NonNull View child, #NonNull View dependency) {
AppBarLayout appBarLayout = (AppBarLayout) dependency;
val toolbar = appBarLayout.findViewById(R.id.toolbar);
int range = appBarLayout.getTotalScrollRange();
int totalHeight = parent.getHeight();
int appBarHeight = appBarLayout.getHeight();
int toolbarHeight = toolbar.getHeight();
int initialHeight = totalHeight - appBarHeight;
int finalHeight = totalHeight - toolbarHeight;
int differenceHeight = finalHeight - initialHeight;
float factor = -appBarLayout.getY() / range;
val layoutParams = child.getLayoutParams();
int lastHeight = layoutParams.height;
layoutParams.height = (int) (initialHeight + (differenceHeight * factor)) + LayoutUtils.dpToPx(parent.getContext(), 48);
if(lastHeight != layoutParams.height){
child.setLayoutParams(layoutParams);
}
return true;
}
}
<CoordinatorLayout ... (some content)
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="48dp"
android:layout_gravity="bottom"
app:layout_behavior=".custom_views.TestBehaviour48Margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/custom_pink"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:nestedScrollingEnabled="false"
android:id="#+id/rv_strength_items"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#color/grey_B" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#color/custom_blue_light" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="bottom"
android:background="#color/custom_green"
bind:layout_anchor="#id/scroll"
bind:layout_anchorGravity="bottom" />
</LinearLayout>
</FrameLayout>
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/colorSurfaceNight"
android:visibility="#{viewmodel.views.mainLayoutVisibility}"
app:elevation="0dp"
app:layout_behavior=".fragments.training.main_training.view_fold.BlockableAppBarLayoutBehaviour">
... rest of code
I try to create a collapsing layout on scroll, but I encounter an issue with my avatar, indeed when the CircleImageView is inside my AppBarLayout, nothing happens, I need to put it below my ToolbarLayout, here is my xml to be clearer :
<?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:id="#+id/coordinator_layout_root"
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.support.design.widget.CollapsingToolbarLayout
android:id="#+id/main.collapsing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<RelativeLayout
android:id="#+id/relative_layout_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="#dimen/activity_vertical_margin">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/circle_image_view_avatar"
android:layout_width="#dimen/menu_size_avatar"
android:layout_height="#dimen/menu_size_avatar"
android:layout_gravity="center"
android:layout_centerHorizontal="true"
android:layout_marginTop="#dimen/profile_margin_top_avatar"
android:src="#drawable/avatar_6"
app:civ_border_color="#color/white"
app:civ_border_width="#dimen/avatar_border_width"
app:finalHeight="#dimen/image_final_width"
app:layout_collapseMode="parallax"
app:layout_behavior="com.example.AvatarImageBehavior"
app:startHeight="2dp"
app:startToolbarPosition="2dp"
app:startXPosition="2dp" />
<FrameLayout
android:id="#+id/frame_layout_ranking"
android:layout_width="47dp"
android:layout_height="47dp"
android:layout_alignLeft="#id/circle_image_view_avatar"
android:layout_alignStart="#id/circle_image_view_avatar"
android:layout_alignTop="#id/circle_image_view_avatar">
<ImageView
android:layout_width="#dimen/profile_button_ranking_trophies_size"
android:layout_height="#dimen/profile_button_ranking_trophies_size"
android:background="#drawable/circle_white"
android:scaleType="centerInside"
android:src="#drawable/ic_classement" />
</FrameLayout>
<FrameLayout
android:id="#+id/frame_layout_trophies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="#id/circle_image_view_avatar"
android:layout_alignRight="#id/circle_image_view_avatar"
android:layout_alignTop="#id/circle_image_view_avatar">
<ImageView
android:layout_width="#dimen/profile_button_ranking_trophies_size"
android:layout_height="#dimen/profile_button_ranking_trophies_size"
android:background="#drawable/circle_white"
android:scaleType="centerInside"
android:src="#drawable/ic_trophees" />
</FrameLayout>
<FrameLayout
android:id="#+id/frame_layout_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/circle_image_view_avatar"
android:layout_centerHorizontal="true">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/circle_image_view_level"
android:layout_width="#dimen/menu_size_level"
android:layout_height="#dimen/menu_size_level"
app:civ_border_color="#color/white"
app:civ_border_width="#dimen/avatar_border_width" />
</FrameLayout>
<TextView
android:id="#+id/text_view_pseudo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/frame_layout_level"
android:layout_centerHorizontal="true"
android:maxLines="2"
android:textAllCaps="true"
android:textColor="#color/white"
android:textSize="#dimen/font_32" />
</RelativeLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- Content -->
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:scrollbars="none">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingTop="#dimen/activity_vertical_margin">
....
</LinearLayout>
</android.support.v7.widget.CardView>
</android.support.v4.widget.NestedScrollView>
<!-- Final toolbar -->
<android.support.v7.widget.Toolbar
android:id="#+id/main.toolbar"
android:layout_width="match_parent"
android:layout_height="54dp"
android:background="#5025A5FF"
app:layout_anchor="#id/text_view_pseudo"
app:layout_anchorGravity="center_vertical"
app:theme="#style/ThemeOverlay.AppCompat.Dark"
app:title="">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:maxLines="2"
android:textAllCaps="true"
android:text="Pseudo"
android:textColor="#color/white"
android:textSize="#dimen/font_18" />
</LinearLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CoordinatorLayout>
Here is the code of my AvatarImageBehavior class :
import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.View;
import com.example.R;
import de.hdodenhof.circleimageview.CircleImageView;
#SuppressWarnings("unused")
public class AvatarImageBehavior extends CoordinatorLayout.Behavior<CircleImageView> {
private final static float MIN_AVATAR_PERCENTAGE_SIZE = 0.3f;
private final static int EXTRA_FINAL_AVATAR_PADDING = 80;
private final static String TAG = "behavior";
private Context mContext;
private float mCustomFinalXPosition;
private float mCustomFinalYPosition;
private float mCustomStartXPosition;
private float mCustomStartToolbarPosition;
private float mCustomStartHeight;
private float mCustomFinalHeight;
private float mAvatarMaxSize;
private float mFinalLeftAvatarPadding;
private float mStartPosition;
private int mStartXPosition;
private float mStartToolbarPosition;
private int mStartYPosition;
private int mFinalYPosition;
private int mStartHeight;
private int mFinalXPosition;
private float mChangeBehaviorPoint;
public AvatarImageBehavior(Context context, AttributeSet attrs) {
mContext = context;
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AvatarImageBehavior);
mCustomStartXPosition = a.getDimension(R.styleable.AvatarImageBehavior_startXPosition, 0);
mCustomStartToolbarPosition = a.getDimension(R.styleable.AvatarImageBehavior_startToolbarPosition, 0);
mCustomStartHeight = a.getDimension(R.styleable.AvatarImageBehavior_startHeight, 0);
mCustomFinalHeight = a.getDimension(R.styleable.AvatarImageBehavior_finalHeight, 0);
a.recycle();
}
init();
mFinalLeftAvatarPadding = context.getResources().getDimension(R.dimen.activity_vertical_little_margin);
}
private void init() {
bindDimensions();
}
private void bindDimensions() {
mAvatarMaxSize = mContext.getResources().getDimension(R.dimen.game_answer_bonus);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, CircleImageView child, View dependency) {
return dependency instanceof Toolbar;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, CircleImageView child, View dependency) {
maybeInitProperties(child, dependency);
final int maxScrollDistance = (int) (mStartToolbarPosition);
float expandedPercentageFactor = dependency.getY() / maxScrollDistance;
if (expandedPercentageFactor < mChangeBehaviorPoint) {
float heightFactor = (mChangeBehaviorPoint - expandedPercentageFactor) / mChangeBehaviorPoint;
float distanceXToSubtract = ((mStartXPosition - mFinalXPosition) * heightFactor) + (child.getHeight()/2);
float distanceYToSubtract = ((mStartYPosition - mFinalYPosition) * (1f - expandedPercentageFactor)) + (child.getHeight()/2);
child.setX(mStartXPosition - distanceXToSubtract);
child.setY(mStartYPosition - distanceYToSubtract);
float heightToSubtract = ((mStartHeight - mCustomFinalHeight) * heightFactor);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (mStartHeight - heightToSubtract);
lp.height = (int) (mStartHeight - heightToSubtract);
child.setLayoutParams(lp);
} else {
float distanceYToSubtract = ((mStartYPosition - mFinalYPosition) * (1f - expandedPercentageFactor)) + (mStartHeight/2);
child.setX(mStartXPosition - child.getWidth()/2);
child.setY(mStartYPosition - distanceYToSubtract);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
lp.width = (int) (mStartHeight);
lp.height = (int) (mStartHeight);
child.setLayoutParams(lp);
}
return true;
}
private void maybeInitProperties(CircleImageView child, View dependency) {
if (mStartYPosition == 0) {
// mStartYPosition = (int) (dependency.getY());
mStartYPosition = 185;
}
if (mFinalYPosition == 0)
mFinalYPosition = (dependency.getHeight() /2);
if (mStartHeight == 0)
mStartHeight = child.getHeight();
if (mStartXPosition == 0)
mStartXPosition = (int) (child.getX() + (child.getWidth() / 2));
if (mFinalXPosition == 0) {
// mFinalXPosition = mContext.getResources().getDimensionPixelOffset(R.dimen.abc_action_bar_content_inset_material) + ((int) mCustomFinalHeight / 2);
mFinalXPosition = (int) (dependency.getWidth() - mContext.getResources().getDimension(R.dimen.image_final_width) + mContext.getResources().getDimension(R.dimen.profile_toolbar_margin_right));
}
if (mStartToolbarPosition == 0)
mStartToolbarPosition = dependency.getY();
if (mChangeBehaviorPoint == 0) {
mChangeBehaviorPoint = (child.getHeight() - mCustomFinalHeight) / (2f * (mStartYPosition - mFinalYPosition));
}
}
public int getStatusBarHeight() {
int result = 0;
int resourceId = mContext.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = mContext.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
}
Do you see why, my CircleImageView react only when it's outside the AppBarLayout. I need it to be inside because, my FrameLayout are views over the CircleImageView (an avatar). I begin with CoordinatorLayout so maybe I miss something.
Regards.
I want to implement animation like the below image.
I have already used ThreePhaseBottomLibrary and as per my experience animation should go parallel as per above image when I scroll it up!
Below is my Fragment class. It works fine except this Image parallel animation as per the screen:
Myfragment.java
public class MyFragment extends BottomSheetFragment {
private BottomSheetLayout mBottomSheetLayout;
private ImageView mBottomSheetBackgroundImageView;
private int mBottomSheetHeight;
private ImageView movingIconImageView;
private AppBarLayout mAppBarLayout;
private int mMStartMarginBottom;
private int mMStartMarginLeft;
private Toolbar mToolbar;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_my, container, false);
mBottomSheetHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
mAppBarLayout = (AppBarLayout) view.findViewById(R.id.appbar);
view.setMinimumHeight(getResources().getDisplayMetrics().heightPixels);
CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) view.findViewById(R.id.collapsing_toolbar);
//collapsingToolbar.setTitle("Title");
collapsingToolbar.setTitleEnabled(false);
mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
//final AppCompatActivity activity = (AppCompatActivity) getActivity();
//activity.setSupportActionBar(toolbar);
//final ActionBar actionBar = activity.getSupportActionBar();
//actionBar.setDisplayHomeAsUpEnabled(true);
//actionBar.setTitle(null);
mToolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mBottomSheetLayout.dismissSheet();
}
});
mToolbar.setAlpha(0);
mBottomSheetBackgroundImageView = (ImageView) view.findViewById(R.id.backdrop);
mBottomSheetBackgroundImageView.setAlpha(0.0f);
movingIconImageView = (ImageView) view.findViewById(R.id.movingIconImageView);
Glide.with(this).load(R.drawable.cheese_1).centerCrop().into(mBottomSheetBackgroundImageView);
if (mBottomSheetLayout != null)
mBottomSheetLayout.setAppBarLayout(mAppBarLayout);
final int actionBarHeight = getActionBarHeight(getActivity());
mMStartMarginBottom = getResources().getDimensionPixelSize(R.dimen.header_view_start_margin_bottom);
mMStartMarginLeft = getResources().getDimensionPixelSize(R.dimen.header_view_start_margin_left);
movingIconImageView.setPivotX(0);
final float actionBarIconPadding = getResources().getDimensionPixelSize(R.dimen.action_bar_icon_padding);
mAppBarLayout.addOnOffsetChangedListener(new OnOffsetChangedListener() {
float startY = 0;
float scaleDiff = 0;
#Override
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
if (mBottomSheetLayout != null && mBottomSheetLayout.isSheetShowing() && mBottomSheetLayout.getState() == State.EXPANDED) {
float progress = (float) -verticalOffset / mAppBarLayout.getTotalScrollRange();
movingIconImageView.setX(mMStartMarginLeft + (progress * (actionBarHeight - mMStartMarginLeft)));
if (startY == 0)
startY = movingIconImageView.getY();
if (scaleDiff == 0) {
scaleDiff = 1 - (actionBarHeight - actionBarIconPadding) / movingIconImageView.getHeight();
movingIconImageView.setPivotY(movingIconImageView.getHeight());
}
movingIconImageView.setScaleX(1f - progress * scaleDiff);
movingIconImageView.setScaleY(1f - progress * scaleDiff);
movingIconImageView.setY(startY - progress * actionBarIconPadding / 2 + mMStartMarginBottom * progress);
}
}
});
return view;
}
/**
* returns the height of the action bar
*/
public static int getActionBarHeight(final Context context) {
// based on http://stackoverflow.com/questions/12301510/how-to-get-the-actionbar-height
final TypedValue tv = new TypedValue();
int actionBarHeight = 0;
if (context.getTheme().resolveAttribute(R.attr.actionBarSize, tv, true))
actionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, context.getResources()
.getDisplayMetrics());
return actionBarHeight;
}
public void setBottomSheetLayout(final BottomSheetLayout bottomSheetLayout) {
mBottomSheetLayout = bottomSheetLayout;
if (mBottomSheetLayout != null && mAppBarLayout != null)
mBottomSheetLayout.setAppBarLayout(mAppBarLayout);
mBottomSheetLayout.addOnSheetStateChangeListener(new OnSheetStateChangeListener() {
private ViewPropertyAnimator mToolbarAnimation;
State lastState;
#Override
public void onSheetStateChanged(final State state) {
if (lastState == state)
return;
lastState = state;
if (state != State.EXPANDED) {
if (mToolbarAnimation != null)
mToolbarAnimation.cancel();
mToolbarAnimation = null;
mToolbar.setAlpha(0);
mToolbar.setVisibility(View.INVISIBLE);
} else if (mToolbarAnimation == null) {
mToolbar.setVisibility(View.VISIBLE);
mToolbar.setTranslationY(-mToolbar.getHeight() / 3);
mToolbarAnimation = mToolbar.animate().setDuration(getResources().getInteger(android.R.integer.config_longAnimTime));
mToolbarAnimation.alpha(1).translationY(0).start();
}
}
});
}
#Override
public ViewTransformer getViewTransformer() {
return new BaseViewTransformer() {
private ViewPropertyAnimator mBottomSheetBackgroundImageViewFadeInAnimation, mBottomSheetBackgroundImageViewFadeOutAnimation;
private Float mOriginalContactPhotoXCoordinate = null;
private final float mOriginalBottomSheetBackgroundImageViewTranslationY = mBottomSheetBackgroundImageView.getTranslationY();
#Override
public void transformView(final float translation, final float maxTranslation, final float peekedTranslation, final BottomSheetLayout parent, final View view) {
if (mOriginalContactPhotoXCoordinate == null)
mOriginalContactPhotoXCoordinate = movingIconImageView.getX();
if (translation < mBottomSheetHeight)
return;
if (translation == mBottomSheetHeight) {
if (mBottomSheetBackgroundImageViewFadeInAnimation != null)
mBottomSheetBackgroundImageViewFadeInAnimation.cancel();
mBottomSheetBackgroundImageViewFadeInAnimation = null;
if (mBottomSheetBackgroundImageViewFadeOutAnimation == null)
mBottomSheetBackgroundImageViewFadeOutAnimation = mBottomSheetBackgroundImageView.animate().alpha(0);
} else {
if (mBottomSheetBackgroundImageViewFadeOutAnimation != null)
mBottomSheetBackgroundImageViewFadeOutAnimation.cancel();
mBottomSheetBackgroundImageViewFadeOutAnimation = null;
if (mBottomSheetBackgroundImageViewFadeInAnimation == null) {
mBottomSheetBackgroundImageViewFadeInAnimation = mBottomSheetBackgroundImageView.animate().alpha(1);
}
}
float progress = (translation - mBottomSheetHeight) / (maxTranslation - mBottomSheetHeight);
//Log.d("AppLog", "translation:" + translation + " maxTranslation:" + maxTranslation + " progress:" + progress);
//movingIconImageView.setY(progress * (mBottomSheetHeight - movingIconImageView.getHeight()));
movingIconImageView.setY(progress * (mBottomSheetHeight - movingIconImageView.getHeight() - mMStartMarginBottom));
movingIconImageView.setX(mOriginalContactPhotoXCoordinate - progress * (mOriginalContactPhotoXCoordinate - mMStartMarginLeft));
//mBottomSheetBackgroundImageView.setAlpha(progress);
mBottomSheetBackgroundImageView.setTranslationY(mOriginalBottomSheetBackgroundImageViewTranslationY - progress * mOriginalBottomSheetBackgroundImageViewTranslationY);
}
};
}
}
Here is my xml:-
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="#+id/main_content"
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="#dimen/header_height"
android:background="#null">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="168dp"
android:layout_marginTop="40dp"
android:background="#eee">
</FrameLayout>
<ImageView
android:id="#+id/backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:translationY="40dp"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:theme="#style/ToolbarColoredBackArrow"/>
<ImageView
android:id="#+id/movingIconImageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center_horizontal" android:background="#f00"
android:src="#drawable/test"/>
</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:background="#color/window_color"
android:orientation="vertical"
android:paddingTop="24dp">
<include layout="#layout/junk_cardview"/>
<include layout="#layout/junk_cardview"/>
<include layout="#layout/junk_cardview"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<!--<android.support.design.widget.FloatingActionButton-->
<!--android:layout_width="wrap_content"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_margin="#dimen/fab_margin"-->
<!--android:clickable="true"-->
<!--android:src="#android:drawable/ic_menu_send"-->
<!--app:layout_anchor="#id/appbar"-->
<!--app:layout_anchorGravity="bottom|right|end"/>-->
I want my backdrop image to slide up which only fading out with slide!
Note: In the library sample I am getting ImageView alpha from 0 to 1 but I want to slide my imageUp not just animate as like alpha animation!
The image you posted is originally from a post about the design of the Google I/O app in 2014. A corresponding image showed what this motion would actually look like in practice [on the right]:
As stated in the article, the source for this app was made public on GitHub. I suggest you take a look at that code in order to get your answer. Though the source currently available is the 2015 version of the app, not the 2014 version mentioned in the article.
I create a mobile app for android that show recycler view and slideshow on that page. I throw scroll view before the recyclerview but it need relativelayout because I have more than one component inside the scrollview.
This is my source code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="fill_parent" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:background="#eeeeee"
app:layout_behavior="#string/appbar_scrolling_view_behavior" tools:showIn="#layout/app_bar_main"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="fill_parent">
<com.daimajia.slider.library.SliderLayout
android:id="#+id/slider"
android:layout_width="match_parent"
custom:pager_animation="Accordion"
custom:auto_cycle="true"
custom:indicator_visibility="visible"
custom:pager_animation_span="1100"
android:layout_height="200dp"/>
<com.daimajia.slider.library.Indicators.PagerIndicator
android:id="#+id/custom_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
custom:selected_color="#0095BF"
custom:unselected_color="#55333333"
custom:selected_drawable="#drawable/banner1"
custom:shape="oval"
custom:selected_padding_left="5dp"
custom:selected_padding_right="5dp"
custom:unselected_padding_left="5dp"
custom:unselected_padding_right="5dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
custom:selected_width="6dp"
custom:selected_height="6dp"
custom:unselected_width="6dp"
custom:unselected_height="6dp"
android:layout_marginBottom="20dp"
/>
<com.daimajia.slider.library.Indicators.PagerIndicator
android:id="#+id/custom_indicator2"
style="#style/AndroidImageSlider_Corner_Oval_Orange"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Produk Terbaru"
android:id="#+id/judul"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:layout_below="#id/slider"/>
<view
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/judul"
class="android.support.v7.widget.RecyclerView"
android:id="#+id/recycler_view" />
<ProgressBar
android:id="#+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
</ScrollView>
</RelativeLayout>
But, RecyclerView not expanded after I add RelativeLayout.
Just use this Layoutmanager:
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import com.apkfuns.logutils.LogUtils;
public class MainActivity{
onCreate(){
recyclerview.setLayoutManager(new FullyLinearLayoutManager(MainActivity.this))
}
public class FullyLinearLayoutManager extends LinearLayoutManager {
private static final String TAG = FullyLinearLayoutManager.class.getSimpleName();
private MeasureEndListener mMeasureEndListener;
public FullyLinearLayoutManager(Context context) {
super(context);
}
private float divHeight =0;
public FullyLinearLayoutManager(Context context,float height) {
super(context);
divHeight = height;
}
public FullyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
private int[] mMeasuredDimension = new int[2];
#Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
int widthSpec, int heightSpec) {
final int widthMode = View.MeasureSpec.getMode(widthSpec);
final int heightMode = View.MeasureSpec.getMode(heightSpec);
final int widthSize = View.MeasureSpec.getSize(widthSpec);
final int heightSize = View.MeasureSpec.getSize(heightSpec);
Log.i(TAG, "onMeasure called. \nwidthMode " + widthMode
+ " \nheightMode " + heightSpec
+ " \nwidthSize " + widthSize
+ " \nheightSize " + heightSize
+ " \ngetItemCount() " + getItemCount());
int width = 0;
int height = 0;
for (int i = 0; i < getItemCount(); i++) {
measureScrapChild(recycler, i,
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
mMeasuredDimension);
if (getOrientation() == HORIZONTAL) {
width = width + mMeasuredDimension[0];
if (i == 0) {
height = mMeasuredDimension[1];
}
} else {
// LogUtils.e(mMeasuredDimension[1]);
height = height + mMeasuredDimension[1];
if(i!= getItemCount()-1){
height += divHeight;
// LogUtils.e(divHeight + "xxx add"+DensityUtils.dp2px(divHeight));
}else{
height += 2*divHeight;
// LogUtils.e(divHeight+ "xxx no add"+ DensityUtils.dp2px(divHeight));
}
if (i == 0) {
width = mMeasuredDimension[0];
}
}
}
switch (widthMode) {
case View.MeasureSpec.EXACTLY:
width = widthSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
switch (heightMode) {
case View.MeasureSpec.EXACTLY:
height = heightSize;
case View.MeasureSpec.AT_MOST:
case View.MeasureSpec.UNSPECIFIED:
}
if(mMeasureEndListener!=null){
mMeasureEndListener.onMeasureEnd(width,height);
// new Thread().interrupt();
}
setMeasuredDimension(width, height);
}
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
int heightSpec, int[] measuredDimension) {
try {
View view = recycler.getViewForPosition(0);//fix 动态添加时报IndexOutOfBoundsException
if (view != null) {
RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
getPaddingLeft() + getPaddingRight(), p.width);
int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
getPaddingTop() + getPaddingBottom(), p.height);
view.measure(childWidthSpec, childHeightSpec);
measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
recycler.recycleView(view);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
public interface MeasureEndListener{
void onMeasureEnd(int width,int height);
}
public void setMeasureEndListener(MeasureEndListener mMeasureEndListener){
this.mMeasureEndListener = mMeasureEndListener;
}
}
}
I try to code my own parallax effect using a recycler view. It's works pretty well at the moment... until I don't change my device orientation.
At state restoration, I get back my "metrics" variable I use to calculate the header Y translation, no problem with that.
But I have troubles getting height from my different views after restoration.
basically here's what I log :
D/parallax﹕ gridHeight: 0 - toolbar: 0 - poster: 0
I tried using .measure() then getMeasuredHeight() but here's what I get :
D/parallax﹕ gridHeight: 0 - toolbar: 128 - poster: 1108
Maybe I missused Measure. Or maybe I should use my parallax method at another moment ? (clother to the runtime ?) If you got any clue...
This is my first question here after a year of reading. Hope I did this the good way. And thank you for any help ;)
Here's my code :
public class ParallaxActivity extends Activity {
private int metrics = 0;
private int resize = 0;
private int gridHeight = -1;
private int toolbarHeight = -1;
private int posterHeight = -1;
private boolean docked = false;
private Toolbar toolbar;
private ImageView poster;
private LinearLayout header;
private RecyclerView grid;
private RecyclerView.LayoutManager manager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_parallax2);
toolbar = (Toolbar)findViewById(R.id.toolbar_actionbar);
toolbar.setNavigationIcon(R.drawable.ic_launcher);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
poster = (ImageView)findViewById(R.id.poster);
grid = (RecyclerView)findViewById(R.id.list);
header = (LinearLayout)findViewById(R.id.header);
manager = new GridLayoutManager(this, 2);
grid.setLayoutManager(manager);
DefaultItemAnimator animator = new DefaultItemAnimator();
grid.setItemAnimator(animator);
RecyclerGridAdapter ad = new RecyclerGridAdapter(this);
grid.setAdapter(ad);
ad.update();
grid.setOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
metrics += dy / 3;
parallax();
}
});
}
#Override
protected void onSaveInstanceState(Bundle b)
{
super.onSaveInstanceState(b);
b.putInt("metrics", metrics);
}
#Override
protected void onRestoreInstanceState(Bundle b)
{
super.onRestoreInstanceState(b);
metrics = b.getInt("metrics", 0);
}
#Override
public void onAttachedToWindow()
{
if (metrics != 0)
parallax();
}
private void parallax()
{
if (gridHeight == -1)
{
gridHeight = grid.getHeight();
toolbarHeight = toolbar.getHeight();
posterHeight = poster.getHeight();
Log.d("parallax", "gridHeight: " + gridHeight + " - toolbar: " + toolbarHeight + " - poster: " + posterHeight);
}
if (!docked && metrics > posterHeight - toolbarHeight)
{
docked = true;
toolbar.setBackgroundColor(0xff000000);
}
if (docked && metrics < posterHeight - toolbarHeight)
{
docked = false;
toolbar.setBackgroundColor(0x00000000);
}
if (metrics < 0)
resize = 0;
else if ( metrics > posterHeight - toolbarHeight)
resize = posterHeight - toolbarHeight;
else
resize = metrics;
header.setTranslationY(-resize);
grid.setTranslationY(-resize);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) grid.getLayoutParams();
params.height = gridHeight + resize;
grid.setLayoutParams(params);
}
}
and my layout :
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar_actionbar"
android:background="#null"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:elevation="0dp">
<ImageView
android:id="#+id/poster"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:src="#drawable/jpeg"/>
<TextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SerieKids"
android:textSize="20dp"
android:textColor="#ffffff"
android:background="#000000"
android:paddingStart="100dp"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"/>
</LinearLayout>
</RelativeLayout>
And finally the way I use measure()
if (gridHeight == -1)
{
toolbar.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
poster.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
grid.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
gridHeight = grid.getMeasuredHeight();
toolbarHeight = toolbar.getMeasuredHeight();
posterHeight = poster.getMeasuredHeight();
Log.d("parallax", "gridHeight: " + gridHeight + " - toolbar: " + toolbarHeight + " - poster: " + posterHeight);
}
Add a onGlobalLayoutListener to your RecyclerView. That way you're making sure your parallax routine is only called after you view has been completly drawn. Also make sure you unregister the globalLayout otherwise it will be called every time you draw your view (if you're using it to control the rotation changes then there is no need to remove the listener I think. That way it will always be called when you rotate because the layout will be drawn again).
An example of how to register and unregister the layout listener:
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
//your parallax routine here
}
});