I want to create a custom contentScrim for my collapsingToolbarLayout. My collapsingToolbarLayout has an imageview inside. When it scrolls, in theory the imageview is suppose to fade out and my scrim color is suppose to fade in.
We all know we can create a scrim like this:
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="?attr/colorPrimary"
android:background="#color/white"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp"
android:fitsSystemWindows="true"
app:expandedTitleTextAppearance="#color/transparent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
>
However the android default scrim only seems to start working when you scroll 3/4 up the screen. Before you that point, the imageview is still fully shown. Also when you reach 3/4 of the screen, the scrim kicks in and automatically changes the color of the collapsingtoolbarlayout to your scrim color:
Instead of having it fill the screen fully when you scroll 3/4 of the way up and before we reached the toolbar, I want it to fade gently till your scrolled up to the toolbar.
If you want to see an example of the effect I want to create, have a look at the Facebook app. This is the Facebook app when the collapsingToolbarLayout is fully expanded:
When you scroll to about 3/4 of the way down, the collapsingToolbarLayout has a faded blue color and the blue is not completely saturating:
So I have create the following inside my collapsingToolbarLayout:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/frame_picture">
<com.customshapes.SquareImageView
android:id="#+id/backdrop"
android:contentDescription="#string/photo"
android:transitionName="#string/transition"
android:layout_width="match_parent"
android:layout_height="240dp"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
/>
<com.customshapes.SquareImageView
android:id="#+id/fading_backdrop"
android:layout_width="match_parent"
android:background="?attr/colorPrimary"
android:layout_height="240dp"
android:alpha="0"
android:fitsSystemWindows="true"
/>
</FrameLayout>
The framelayout comprises of the backdrop imageview which holds the picture that is displayed inside my collapsingToolbarLayout and in front of it is an imageview which just holds the 'scrimColor' ?attr/colorPrimary with an alpha of 0 so that the backdrop imageview will shine through.
Then I implemented the appBarLayout's onOffsetChangedListener:
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
fading_backdrop.setImageAlpha(verticalOffset);
fading_backdrop.invalidate();
}
We are setting the alpha of the fading_backdrop so that it will appear when we scroll up. I'm trying to create a scrim artifically.
However, this doesn't seem to work properly. There is no fading at all when I run this code. Does anyone know what I'm doing wrong?
This is how I managed to create a custom scrim, which seems to be used in quite a few apps nowadays - if you look at Facebook, you will see they do not use the standard contentScrim for their collapsingToolbarLayout. The code below duplicates what Facebook does in their app.
You must set a AppBarLayout.OnOffsetChangedListener to your activity and then use the following code below to measure the size of the toolbar and the size of the appBarLayout.
When your listener is called, the verticalOffset actually measures the offset of the collapsingToolbarLayout from when it is fully expanded to the point when it touches the pinned toolbar. Therefore, the verticalOffset EXCLUDES the height of the toolbar. To compare apples with apples, we need to also EXCLUDE the toolbar height from the height of the appBarLayout so that when we divide the verticalOffset by the totalHeight, neither the verticalOffset nor the totalHeight contains the toolbar height. This is necessary in order to obtain a percentage to apply your 255 alpha value to.
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
//measuring for alpha
int toolBarHeight = toolbar.getMeasuredHeight();
int appBarHeight = appBarLayout.getMeasuredHeight();
Float f = ((((float) appBarHeight - toolBarHeight) + verticalOffset) / ( (float) appBarHeight - toolBarHeight)) * 255;
fading_backdrop.getBackground().setAlpha(255 - Math.round(f));
}
When you scroll now, the fading_backdrop will gradually gain alpha when you scroll upwards so that it overlays nicely with the image in the collapsingToolbarLayout, similar to the facebook custom contentScrim.
Related
So, I have a special case where the height of my appbarlayout fills the whole screen. I am using scroll|snap layout behavior for collapsingtoolbarlayout. The problem is that by default appbarlayout is collapsed when the user reaches more than 50 percent of the height of appbarlayout. For me, this behavior is not appropriate because with snap behavior this is difficult for the user to expand and collapse the appbarlayout.
I have already tried to expand or collapse it programmatically by putting checks on offset value in offset change listener but that doesn't help because the offset change listener gets called a lot of times and checks are not enough to serve the purpose.
<android.support.design.widget.AppBarLayout
android:id="#+id/activity_campaign_details_app_bar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/activity_campaign_details_collapsing_toolbar_bar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|snap">
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
I've implemented floating Toolbar in my app (hides with scroll down, shows on up) and now I see some flickering views inside WebView, to be precise these sticked to the bottom. I've noticed that this happens when I'm resizing WebView due to Toolbar offset change like this:
appBarLayout.addOnOffsetChangedListener(this);
#Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
boolean scrollFlagsSet = ((AppBarLayout.LayoutParams) getToolbar().getLayoutParams()).
getScrollFlags() != 0;
int bottom = (!scrollFlagsSet ? 0 : getToolbarHeightAttr()) + verticalOffset;
swipeRefreshLayout.setPadding(0, 0, 0, bottom);
swipeRefreshLayout.requestLayout();
}
swipeRefreshLayout is WebViews parent. Method causes proper resizing WebView and it's pretty efficient, no lags (EDIT: a bit laggy on older devices). But when WebView loads site with some views sticked to the bottom then these views are flickering when resizing occurs. I can even see text in gap between bottom WebViews edge (gap present when scrolling down, view are cutted on the bottom when scrolling up). I've catched this behavior on screens (blue - Toolbar, gray - WebView, light blue & orange - in-webview views sticked to bottom). It looks like this only for a couple of millis (one frame?) and get back to the bottom fixed position. How to fix this?
Same page loaded in Chrome or Firefox behaves correctly, these views are sticked to bottom and have fixed position, doesn't flicker with scroll/toolbar offset change
I'm using this NestedWebView. My app uses a lot of fragments loaded into Activities, which all are extending abstract BaseActivity in which I have CoordinatorLayout. WebViews belong to fragments and are placed in different containers, there is no option to place WebView straight into CoordinatorLayout
edit: XMLs
activity:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/activity_main_cooridnator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="#+id/activity_main_content_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white" />
fragment:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/fragment_webview_swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<my.package.widgets.web.NestedWebView
android:id="#+id/fragment_webview_webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay" />
I am currently trying to use the CoordinatorLayout class to adapt a Toolbar opacity depending on a ScrollView scroll position. I succeed doing this "manually linking" the two views, listening to the ScrollView scroll events and reporting them on the alpha value of my Toolbar background.
The wanted behavior is :
The toolbar is starting in a transparent state (both text and background) and end up totally white background and black text when one view is totally scrolled.
The behavior is roughly this one : Youtube video.
Current implementation
layout file (simplified)
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<ScrollView
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<my.package.widgets.DetailsTop
android:id="#+id/layout_details_top"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<my.package.widgets.DetailsBottom
android:id="#+id/layout_details_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00FFFFFF"/>
</RelativeLayout>
Fragment implementation
public class SearchVehicleDetailsFragment extends BaseFragment<SearchContract.ParentView> {
#Bind(R.id.layout_details_top) DetailsTop detailsTopLayout;
#Bind(R.id.layout_details_bottom) DetailsBottom detailsBottomLayout;
#Bind(R.id.scrollView) ScrollView scrollView;
#Bind(R.id.toolbar) Toolbar toolbar;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
float alpha = (float) scrollView.getScrollY() / vehicleDetailsTopLayout.getHeight();
toolbar.setBackgroundColor(getColorWithAlpha(alpha, getResources().getColor(R.color.white)));
toolbar.setTitleTextColor(getColorWithAlpha(alpha, getResources().getColor(R.color.dark_grey)));
}
});
}
public static int getColorWithAlpha(float alpha, int baseColor) {
int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24;
int rgb = 0x00ffffff & baseColor;
return a + rgb;
}
}
Problem
However, even if the previous snippets are a particularly simple solution that seems to work perfectly, I am just a little confused about why it didn't work with CoordinatorLayout. Also, I found a guy who seems to have succeed on this task with a custom CoordinatorLayout.Behavior<android.support.v7.widget.Toolbar> implementation.
You can take a look at his custom CoordinatorLayout.Behavior implementation. He also gives some details on Medium. However, due to a lot of just <android.support.v7.widget.Toolbar .../> instead of the full Toolbar tag and a strong lack of understanding of how this part of the Support Design library works, I was never able to implement it...
Question
I would like to understand how CoordinatorLayout, and all the classes around it, works together. So I will probably be able to implement the behavior I am looking for, which is : linking a ScrollView scroll position to the alpha value of a toolbar background color.
Obviously, if you also know how to implement all this, I will be really happy to see your snippet :)
I've been experimenting with behaviors, I am happy you found my article partially helpful.
I redacted the code of the Toolbar chunk because it wasn't important. Simple Toolbar attributes, nothing special about it:
<android.support.v7.widget.Toolbar
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
android:id="#+id/toolbar"
app:layout_behavior=".view.ToolbarBackgroundAlphaBehavior"/>
I would like to understand how CoordinatorLayout, and all the classes
around it, works together. So I will probably be able to implement the
behavior I am looking for, which is : linking a ScrollView scroll
position to the alpha value of a toolbar background color.
In your layout you are missing the CoordinatorLayout as being top parent that wraps everything. Instead of using ScrollView you should use NestedScrollView it works just like ScrollView but the difference is that NestedScrollView can perform nested scrolls in older version, unlike ScrollView which nested scrolls only after API 23.
For a CoordinatorLayout.Behavior to work the target view must be direct descendant of the CoordinatorLayout. As you can see in the article, the Toolbar is direct descendant of the coordinator. Otherwise, the behavior will not work.
In the example NestedScrollView has appbar layout behavior, so that AppBarLayout can expand and collapse, you can find plenty of examples about this.
I'm creating a RecyclerView with header where the header collapses as you scroll up the RecyclerView. I can achieve this very closely with the layout below, with a transparent AppBarLayout, and MyCoolView which is the header. The parallax effect works great.
However, if the header is still visible and I fling the RecyclerView, the RV scrolls slowly to the top and some of the items are under the Toolbar until the RV reaches the top of the view. I've been playing around with the scrollFlags but haven't achieved a desirable result. Any suggestions on how to improve the fling experience so the items don't get clipped?
View the video and watch when its flinged --- https://www.dropbox.com/s/jppd6m7zo41k23z/20160609_151309.mp4?dl=0
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
android:background="#00000000">
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<com.android.myapp.MyCoolView
app:layout_collapseMode="parallax"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView/>
</android.support.design.widget.CoordinatorLayout>
Possible solution (untested). Add an OnOffsetChangedListener to your AppBarLayout, and keep note of the offset value. First, declare this field:
private boolean shouldScroll = false;
Then, onCreate:
AppBarLayout appbar = findViewById(...);
appbar.addOnOffsetChangedListener(new OnOffsetChangedListener() {
#Override
void onOffsetChanged(AppBarLayout appbar, int offset) {
// Allow recycler scrolling only if we started collapsing.
this.shouldScroll = offset != 0;
}
});
Now, add a scroll listener to your RecyclerView. Whenever it tries to scroll, revert the scroll if the AppBarLayout is still expanded:
RecyclerView recycler = findViewById(...);
recycler.addOnScrollListener(new OnScrollListener() {
#Override
void onScrolled(RecyclerView recycler, int dx, int dy) {
// If AppBar is fully expanded, revert the scroll.
if (!shouldScroll) {
recycler.scrollTo(0,0);
}
}
});
This might need some tweaking though. I see two issues:
Possible stack overflow if scrollTo() calls onScrolled() back. Can be solved with a boolean or by removing/adding the scroll listener
Possibly you want to prevent scrolling not only when AppBarLayout is fully expanded, but more generally when AppBarLayout is not collapsed. This means you don’t have to check for offset != 0, but rather for offset == appBarLayout.getTotalScrollRange(). I think.
Maybe you can add layout_behavior="#string/appbar_scrolling_view_behavior" to your RecylerView like this.
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Wrapping the RecyclerView in a FrameLayout solves this problem.
You also need move the appbar_scrolling_view_behavior from the RecyclerView to the FrameLayout so it will be positioned below the AppBarLayout properly.
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout
android:background="#00000000">
<android.support.design.widget.CollapsingToolbarLayout
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<com.android.myapp.MyCoolView
app:layout_collapseMode="parallax"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- BEGIN SOLUTION -->
<!-- the layout behavior needs to be set on the FrameLayout, not the RecyclerView -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<!--This RecyclerView MUST be wrapped in a FrameLayout-->
<!--This prevents the RecyclerView from going behind the AppBarLayout-->
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<!-- END SOLUTION -->
</android.support.design.widget.CoordinatorLayout>
What is layout_collapseParallaxMultiplier used in CollapsingToolbarLayout ?
I have seen the android developer doc, but I didn't understand.
please explain this parameter & it's effect when it used with layout_collapseMode with example.
Below is a simple example of xml.
<android.support.design.widget.CollapsingToolbarLayout ... >
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:src="#drawable/random_pattern"
android:scaleType="fitXY"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.75"/>
</android.support.design.widget.CollapsingToolbarLayout>
In very simple words:
When scrolling we see the following - appbar image starts hiding under the content and beyond the top edge of the screen.
Parameter layout_collapseParallaxMultiplier determines what part of the image (in percent) will be hidden under the bottom content.
So, for example, setting this parameter to value 1.0 means that top boundary of appbar's image is bound to the top edge of the screen and doesn't move when scrolling. And main content is moving up the top of the image.
When the parameter is not set this corresponds to the value 0.5 and image will be overlapped above and below synchronously.
This was explained on Android Design Support Library:
In addition to pinning a view, you can use app:layout_collapseMode="parallax" (and optionally app:layout_collapseParallaxMultiplier="0.7" to set the parallax multiplier) to implement parallax scrolling (say of a sibling ImageView within the CollapsingToolbarLayout). This use case pairs nicely with the app:contentScrim="?attr/colorPrimary" attribute for CollapsingToolbarLayout, adding a full bleed scrim when the view is collapsed.
This is a behavior example when you use app:layout_collapseMode="parallax".