CoordinatorLayout with multiple snapping points - android

Here I've got quite a complex animation that may be resolved (I believe) in a simple way using the CoordinatorLayout. It has 3 states:
Initial (left screen) - Header view is shown fully (orange
background): Toolbar, grey roundrect (it's actually a photo there)
plus some other views below (TextViews, RatingBar etc)
Scrolling the content up (middle
screen) - roundrect is zooming up with a changing green foreground alpha level over it, so it becomes green while scrolling (well, it is not obvious with these screens. Green background is actually a zoomed roundrect with a green foreground over it, and that is the cause the header background becomes green and not orange)
Scrolling once more (right screen) - the rest of the header should be scrolled up
Scrolling down the content should lead to the appearing of the views in a reverse way accordingly.
I had some experience working with the CoordinatorLayout, but I'm really not sure I understand how to handle 2 anchor points. I understand how the scroll flags work and that for zooming (p. 2) and for changing the foreground alpha I need a custom Behavior implementation, but for now I cannot understand how shall I handle all of this in a complex.
All I've found so far is Saúl Molinero's tutorial and also this tutorial with examples.
So please sorry for the poor description here, I'll update my question of course and will add the source code when I have some success with this issue, but for now I'd be glad to get some hints maybe or tutorials I've missed. Hope someone had something similar in the projects.
Here's my test repo with the code and here is a link to my layout.xml file.

You can get two snapping points with just setting the scroll flags as follows:
<android.support.design.widget.CollapsingToolbarLayout
...stuff...
app:layout_scrollFlags="scroll|enterAlways|snap">
So, fully expanded is one stopping point and with just the toolbar visible is the second stopping point. When the view is scrolled further, the toolbar disappears. So this is how you want things to work when scrolling up.
Now when the app bar is fully collapsed, the app bar will start showing immediately when scrolling down. That is not a surprise, since that is what enterAlways does. If the top of the content has been scrolled out of view, then you won't see it again until after the app bar is fully expanded. So, if this is the behavior you want, we'll just stop there.
However, I think that what you want is the exiting behavior outlined above but with a different entry behavior. You will get the late entry behavior if you set the scroll flags as follows:
<android.support.design.widget.CollapsingToolbarLayout
...stuff...
app:layout_scrollFlags="scroll|snap">
This just deleted the enterAlways flag. With these scroll flags, the app bar will not reappear (once collapsed) until the top of the content is visible and "pulls" the app bar into view.
So, one solution (of what is probably many) is to write a new behavior that will be attached to the AppBarLayout some code that will change the scroll flags once the app bar is fully collapsed and change them back as it opens again. That way you can change the behavior to be what you want and still use the Android machinery to figure out what the specific operations are at the view level. This can be done in a custom view or in the activity - wherever you have access to the scroll state of the app bar and the scrolling flags. It can also be done in a behavior but that is probably not the best place for it.
Oh, and as you have discovered, snapping is janky on API 26.
Here is an implementation of the concept. For simplicity, the implementation is in an activity:
ScrollingActivity.java
public class ScrollingActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
final AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);
appBar.post(new Runnable() {
#Override
public void run() {
CollapsingToolbarLayout toolbarLayout =
(CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);
setupAppBar(appBar, toolbarLayout);
}
});
}
private void setupAppBar(AppBarLayout appBar, final CollapsingToolbarLayout toolbarLayout) {
// Scroll range is positive but offsets are negative. Make signs agree for camparisons.
final int mScrollRange = -appBar.getTotalScrollRange();
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
private boolean mAppBarCollapsed = false;
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if (verticalOffset == mScrollRange) { // App bar just collapsed
mAppBarCollapsed = true;
AppBarLayout.LayoutParams lp =
(AppBarLayout.LayoutParams) toolbarLayout.getLayoutParams();
int flags = lp.getScrollFlags()
& ~AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS;
lp.setScrollFlags(flags);
toolbarLayout.setLayoutParams(lp);
} else if (mAppBarCollapsed) { // App bar is opening back up
mAppBarCollapsed = false;
AppBarLayout.LayoutParams lp =
(AppBarLayout.LayoutParams) toolbarLayout.getLayoutParams();
int flags = lp.getScrollFlags()
| AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS;
lp.setScrollFlags(flags);
toolbarLayout.setLayoutParams(lp);
}
}
});
}
}

Related

Collapsing toolbar blur image when collapsed

i need some help with my toolbar.
Right now i use a collapsing toolbar with image wich collapsed when i scroll up.
I know i can use contentScrim to make the Toolbar transparent and therefore see the image as "toolbar background".
However, i want the image to blur(/fade) when the toolbar is collapsed.
Any suggestions how to achieve this?
You can use this library. (RealTimeBlurView)
For the blur effect, just put the imageview behind the blurview.
To achieve what you want just change blurview's alpha when the app bar is scrolled.
appbar.addOnOffsetChangedListener(new OnOffsetChangedListener() {
#Override
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
float offsetAlpha = (appBarLayout.getY() / appbar.getTotalScrollRange());
blurView.setAlpha( 1 - (offsetAlpha * -1));
}
});
UPDATE
FastBlur
Here's another benchmarking project to showcase all the possible blurring methods in android. Just get the fastest algorithm from the demo and use it in your project.
Hope this helps!

Create custom AppBarLayout child with minimum height?

I want to create a custom view that will be a child of an AppBarLayout. I need this view to collapse partially as I scroll up, but not completely. It will have a minimum height and stay fixed to the top of the AppBarLayout in it's small size mode and then expand back to it's large size mode when the view is scrolled back down.
I've spent a lot of time looking through the source of the AppBarLayout and CoordinatorLayout, and so far I don't see a way to do what I want. It looks like children of AppBarLayout must either stay visible or disappear completely when the view is scrolled up.
Can anyone suggest a way to create a child of an AppBarLayout that will behave this way?
Thank you
Here's the recipe:
If you set android:minHeight, the AppBarLayout will respect that value by not scrolling beyond the point that would make your component smaller. So your XML layout might be something like this:
<com.example.CustomCollapsingLayout
android:layout_width="match_parent"
android:layout_height="320dp"
android:minHeight="108dp"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
Next you want to have your class register an OnOffsetChangedListener with the parent AppBarLayout. Your component will get events as the app bar is scrolled so that you know how to configure your view.
class OnOffsetChangedListener implements AppBarLayout.OnOffsetChangedListener {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
final int scrollRange = appBarLayout.getTotalScrollRange();
float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
...
This shows you how to find the total scroll range and then find the ratio between the total scroll range and the current scroll position i.e. where the app bar is in its scroll.
You should do what CollapsingToolbarLayout does; override onAttachedToWindow and add the listener there:
// Add an OnOffsetChangedListener if possible
final ViewParent parent = getParent();
if (parent instanceof AppBarLayout) {
if (mOnOffsetChangedListener == null) {
mOnOffsetChangedListener = new OnOffsetChangedListener();
}
((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
}
Take a look at the source code for CollapsingToolbarLayout as it will give you some ideas. Your view needs to do a lot of the same things.
You can also look at my sample project that has an image that scales and moves as the toolbar is scrolled: https://github.com/klarson2/Collapsing-Image

Block scroll along with Expand/Collapse Collapsible Toolbar

I was using Collapsible Toolbar in my app. On activity launch Collapsible Toolbar is expanded state with scrolling enabled and its working well normally. But now I have a requirement to show a full screen error layout in case my API fails. In that case I have to collapsed toolbar with scrolling effect blocked.
Error Layout shows a Retry Button. On Retry I make API call again and if API gives success I have to again expand Toolbar and enable scrolling effect.
I was able to collapse toolbar with setExpanded(flag, animate) but in that case I am not able to block scrolling effect of Collapsible Toolbar while error layout is shown.
I need to provide a way to block as well as unblock scroll effect + Expand/Collapse Toolbar. Any help would be really appreciated.. !!!
Make your error layout such that it will overlap Collapsible Toolbar. Also set android:clickable="true" to your error layout.
When you set visibility to your error layout, set Toolbar scrolling accordingly.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f3f3f3"
android:orientation="vertical"
>
<!-- Add your other layout including Collapsible Toolbar here.-->
<RelativeLayout
android:id="#+id/errorLayout"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
I created a library AppBarrr to lock the screen in expanded mode, based on my previous answer.
As I said, the height of the Toolbar is the key: the CollapsingToolbarLayout will collapse until the Toolbar's height and will expand until the AppBarLayout's height.
With this library, you must set two layouts as the Toolbar and your Expanded Layout (used to lock the screen and the scroll), it will create a CollapsingToolbarLayout and inflate these layouts inside.
You can declare the animations duration, the color of the inner CollapsingToolbarLayout, the collapsed/expanded title's style, even the height of the locked layout... You could also hide the Expanded Layout if you click outside it. It can support NestedScrollView and ScrollView inside the Expanded Layout. The documentation and a sample app are available on Github.
For those who don't want to use the library, my previous answer shows the way to do it. Here's the output of the previous answer:
Basically, this is the same concept, but no need to write a full class, with the lib you just need to have a simple widget in xml and that's it!
Feel free to use, fork or test. Hope it will be useful ;)
If you use AlertDialog to communicate the error and a ProgressDialog (spinner) to show you are doing stuff, you can block user input while your app is doing it's thing.
A simple solution that you can apply is just use the property
android:visibility="gone"
for the content that you don't want to show and just make your error layout visible by using property android:visibility="visible"
place the error layout at the bottom of your parent layout
once the contents are not visible on screen and error layout is just visible you will achieve the desired result that you want. Hope this helps you.
You can implement the interface and call its methods when to enable or disable the collapsing effect.
public interface AppbarRequestListener {
void unlockAppBarOpen();
void lockAppBarClosed();
}
#Override
public void unlockAppBarOpen() {
appBarLayout.setExpanded(true, false);
appBarLayout.setActivated(true);
setAppBarDragging(false);
}
#Override
public void lockAppBarClosed() {
appBarLayout.setExpanded(false, false);
appBarLayout.setActivated(false);
setAppBarDragging(false);
}
private void setAppBarDragging(final boolean isEnabled) {
CoordinatorLayout.LayoutParams params =
(CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return isEnabled;
}
});
params.setBehavior(behavior);
}

Android Placing Text (bar) at bottom of Application

I want to be able to display text (a bar if you will) at the bottom of my application dynamically to indicate if the the application is online or not (an endless issue for users currently). I don't need for this to have any action, but I do need it to be able to control it displaying or not based on them toggling between online and offline mode. So the split action bar is not what I am looking for.
Not enough reputation points to post an example. Doh. Here is the link:
Ugly example, look at bottom
Something simple is fine - I am good with XML based or dynamic (though I will also need to hide and show it dynamically).
Thanks!
Dynamic solution. Define your method that the activity will use to hide / display your bottom TextView (text bar?).
In your XML:
<TextView
android:id="#+id/text_bar"
...
...
android:visibility="gone"/>
In your activity:
public MyActivity extends Activity{
private TextView mTextBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_activity_xml);
...
...
mTextBar = (TextView) findViewById(R.id.text_bar);
}
public void updateVisibility(boolean visible){
int visibility = (visible) ? View.VISIBLE: View.GONE;
mTextBar.setVisibility(visibility);
}
}
Initially, the TextView (your text bar) has it's visibiity set to gone. Dynamically, when you want to show it call the updateVisibility(true) method to show it, and updateVisibility(false) to hide it.
Since I'm not sure how you are handling the check for the network status (in queries, in the onCreate, etc). I don't know what you intended to do for the TextView. If you need advice on how to position the TextView on the bottom of the screen I can provide example code for that as well.

fitSystemWindows programmatically for status bar transparency

My app has one activity that hosts different fragments for each section. I have recently made the status bar translucent by setting fitSystemWindows to true, which has set it to the background colour of the app. This is fine for fragments that have a toolbar, where the colours match, like so:
However one of my fragments has a photo and a translucent toolbar, so I'd like to have the photo occupy the space of the status bar too, rather than the background colour.
I believe the solution is to set fitSystemWindows to false for that fragment only, and manually add padding to the translucent toolbar. Doing this programmatically seems to have no effect, what could I be doing wrong?
Here is my main activity layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/main_parent_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Container for various fragment layouts, including nav drawer and toolbar -->
</RelativeLayout>
And from within my fragment's onCreateView():
RelativeLayout daddyLayout = (RelativeLayout)getActivity().findViewById(R.id.main_parent_view);
daddyLayout.setFitsSystemWindows(false);
daddyLayout.invalidate();
This seems to have no effect, like so:
If I set fitSystemWindows to false in the main_parent_view, the status bar padding is gone and it works but obviously affects every fragment.
Well, you are in dilemma situation there, because from one hand you need to apply insets (because Toolbar should be correctly padded), and on the other hand you should not apply insets (because you want ImageView to be drawn under status bar).
Turns out there's a nice API provided by the framework for that case:
ViewCompat.setOnApplyWindowInsetsListener(toolbar, (v, insets) -> {
((ViewGroup.MarginLayoutParams) v.getLayoutParams()).topMargin =
insets.getSystemWindowInsetTop();
return insets.consumeSystemWindowInsets();
});
Assuming your root layout has android:fitsSystemWindows="true", now appropriate insets would be applied to your Toolbar only, and not the ImageView.
But, there's a problem.
The problem is that your root layout is RelativeLayout, which doesn't dispatch its children any information about insets. Neither do its sibling layouts (LinearLayout, FrameLayout).
If you had as a root layout one of "materialish" layouts (CoordinatorLayout, DrawerLayout), then children would be dispatched those window insets.
The other option is to subclass RelativeLayout and dispatch WindowInsets to
children manually.
#TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
#Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++)
getChildAt(index).dispatchApplyWindowInsets(insets); // let children know about WindowInsets
return insets;
}
You can see this answer for a detailed explanation with precisely same requirement you have.
I have resolve this question in 4.4
if(test){
Log.d(TAG, "fit true ");
relativeLayout.setFitsSystemWindows(true);
relativeLayout.requestFitSystemWindows();
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}else {
Log.d(TAG, "fit false");
relativeLayout.setFitsSystemWindows(false);
relativeLayout.requestFitSystemWindows();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
}
You can use a CoordinatorLayout as your activity root view and then setFitsSystemWindows(boolean) will work.
This is because, as explained in this blog post , DrawerLayout and CoordinatorLayout both have different rules on how fitsSystemWindows applies to them - they both use it to inset their child Views, but also call dispatchApplyWindowInsets() on each child, allowing them access to the fitsSystemWindows="true" property.
This is a difference from the default behavior with layouts such as FrameLayout where when you use fitsSystemWindows="true" is consumes all insets, blindly applying padding without informing any child views (that's the 'depth first' part of the blog post).
Saw the same issue. Solved it in my app by removing fitSystemWindows from the activity declaration and adding paddingTop to the fragment. Obviously not an ideal solution but seems to be working.
simply put
View decorView = getActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
and for status bar color use:
getActivity().getWindow().setBackgroundDrawableResource(R.color.green);

Categories

Resources