Block scroll along with Expand/Collapse Collapsible Toolbar - android

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);
}

Related

CoordinatorLayout with multiple snapping points

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);
}
}
});
}
}

Making DialogFragment window scrollable

I'm having a problem where Dialogs don't scroll when their content is too large for the screen. I assume this is because Dialogs are not displayed within Scrollable containers.
Screenshot contains content wrapped in a ScrollView - You can see only the content is scrollable
Extra fields added to artificially increase dialog size for this example
You can see from the Android Developer Documentation that Dialogs should be wrapped within DialogFragments (This gives the benefit of having your dialogs survive an orientation change and response to lifecycle events) and this is the set up I'm trying to make work.
Much of the other answers I've found are all similar to one another and revolve around ensuring the window is set to "adjustResize". However, this would only make the parent view smaller on keyboard opening, it wouldn't make a View scrollable if it wasn't in a Scrollable container.
If someone could let me know if they have info to make a Dialog scrollable or to confirm that you cannot make a Dialog scrollable I'd appreciate it.
I have achieved scrolling in dialog fragment,
Dialog layout design
<LinearLayout>
<NestedScrollView>
<ConstrainLayout>
</ConstrainLayout>
</NestedScrollView>
</LinearLayout>
In DialogFrament class ,
override fun onStart() {
super.onStart()
dialog.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZ)
}
This is possibly the dirtiest and most likely to break solution to this problem:
After creating the Dialog in onCreateDialog() but before returning it you can achieve scrolling by adding this code:
final ViewGroup content = (ViewGroup) dialog.findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
content.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View inner = content.getChildAt(0);
content.removeViewAt(0);
ScrollView scrollView = new ScrollView(getContext());
scrollView.addView(inner);
content.addView(scrollView);
}
});
I had the same problem with DialogFragment, but I changed:
<fragment android:layout_height="425dp"
...
to this:
<fragment android:layout_height="wrap_content"
...
And now it's scrolling when the keyboard is open, or when the screen is in landscape mode.
NOTE: I have ScrollView as a rootView in DialogFragment layout, of course.

Inside CollapsingToolbarLayout ImageView Click event is not trigger Android

Image View inside CollapsingToolbarLayout , but click event is not triggering.
How to to solve this issue.
Just make sure you don't have any view above the layout. In my case I had a toolbar and it was eating the clicks.
<android.support.design.widget.CollapsingToolbarLayout
>
<include...
/>
<android.support.v7.widget.Toolbar...
/>
<android.support.design.widget.TabLayout...
/>
</android.support.design.widget.CollapsingToolbarLayout>
I had to create a new Class extending toolbar that let clicks go through it(How to click views behind a Toolbar?)
public class NonClickableToolbar extends Toolbar {
#Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
}
Question is not elaborate enough, but looks like ImageView is consuming your click event.
try using
ImageView.setOnClickListener(null);
CollapsingToolbarLayout should get the click events. if this doesn't work, you know on what line to Google for.
Try to add this XML attribute
android:descendantFocusability="blocksDescendants"
to your CollapsingToolbarLayout
I happened to drop by this problem too. It seems clicks in the area of the toolbar/appbar do not trigger.
If your UI/UX allows, try placing the ImageView outside the area of the toolbar. For example, try placing it aligned to parent bottom. The click should hopefully be triggered. Worked for me (I was working with an ImageButton though, but pretty sure ImageView should also work).
You can put the ImageView outside collapsing toolbar. I was facing the same issue. Solved it by placing the view outside collapsing toolbar and anchoring it accordingly.

Android NestedScrollView has wrong size after app:layout_behavior

Since Google has published the design support library for android, there are many nice things that can be done without implementing custom code. While i've tested the custom views in this lib, i have found a worse thing, and i didn't know if this is a bug or not.
I have found the cheesesquare project on github. In the activity_detail.xml(layout file) there are 3 CardViews inside the NestedScrollView. If you delete 2 of them, you can see that the NestedScrollView doesn't have the full size of the parent(match_parent). The NestedScrollView is bound to the bottom of the parent view. http://i.stack.imgur.com/BXl7w.png
The NestedScrollView get's his full size when i remove the app:layout_behavior="#string/appbar_scrolling_view_behavior".
But when i remove the layout behavior, the toolbar is not collapsing.
Is there any fix for this? Example layout file can be found here: https://github.com/Smove/cheesesquare/blob/stackoverflow/app/src/main/res/layout/activity_detail.xml
You can build the cheesesquare apk from my github branch stackoverflow
I had this problem and fixed adding:
android:layout_gravity="fill_vertical"
to the NestedScrollView. Then it starts behaving correctly, as I explained here also. Of course the NestedScrollView needs some kind of child (i.e. it must not be empty), otherwise the toolbar won't collapse.
Update
While this works well with small content, I noticed it has some problems showing longer contents, e.g. like the full activity_detail.xml above. The problem is that you can't scroll to the very bottom part of the content - it is unreachable at the bottom.
From my tests I could find that the missing part is as big as the collapsed toolbar (or at least that's what it looks to me). To fix this is issue, and having a solution reliable for both small and big contents, you should add a layout_marginBottom to the ScrollView, so that it gets measured and releases the missing bottom part. Thus:
android:layout_gravity="fill_vertical"
android:layout_marginBottom="?attr/actionBarSize"
or whatever size you gave to the Toolbar.
But still
Scrolling with small contents with this solution, even if the content is justly aligned at the top, isn't really smooth as scrolling with large contents. I'll use until a library fix comes.
Update2
Looks like this was fixed in v22.2.1 .
using the answer by #natario If you instead set a padding for the child (LinearLayout in my case) it will look better:
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_gravity="fill_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="?attr/actionBarSize">
<!--Rest of the code-->
Or in Kotlin you can do something like this:
coordinator.doOnLayout {
nestedScrollView.minimumHeight = resources.displayMetrics.heightPixels - with(TypedValue().also {theme.resolveAttribute(android.R.attr.actionBarSize, it, true)}) {
TypedValue.complexToDimensionPixelSize(data, resources.displayMetrics)}
}
add android:minHeight="?attr/actionBarSize" to CollapsingToolbarLayout
Workaround
Before showing my NestedScrollView and after binding the data to the NestedScrollView content, I call the method fullScroll(int direction) of my NestedScrollView instance with the View.FOCUS_UP direction as argument.
Code example for a fragment:
NestedScrollView scrollView = (NestedScrollView) getActivity().findViewById(R.id.scroll_view);
scrollView.fullScroll(View.FOCUS_UP);
use RecyclerView replace NestedScrollView fix this bug
set item count 1,that ViewHolder return your real contentView;
my code:
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
// 添加分割线
recyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext()));
recyclerView.setAdapter(new Adapter<ViewHolder>() {
#Override
public int getItemCount() {
return 1;
}
#Override
public void onBindViewHolder(ViewHolder holder, int arg1) {
WebView view = (WebView) holder.itemView;
view.getSettings().setJavaScriptEnabled(true);
view.loadUrl("http://www.baidu.com");
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup arg0, int arg1) {
return new ViewHolder(inflater.inflate(R.layout.webview, arg0, false)) {
};
}
});

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