Android NestedScrollView has wrong size after app:layout_behavior - android

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

Related

Android scrollTo does not work in complicated view hierarchy

I have the following View hierarchy:
CoordinatorLayout
<include statement (other file begins here)>
RelativeLayout
NestedScrollView
RelativeLayout
<include view above recyclerview that mimics a recycler card item>
<include view above recyclerview that mimics a second recycler card item>
RecyclerView
Now, I need to scroll to a specific index of the RecyclerView adapater dataset but none of the following code works:
recycler.post(new Runnable()
{
#Override
public void run()
{
//scrollView.smoothScrollTo(0, ((int) recycler.getChildAt(pos).getY()));
//recycler.scrollToPosition(pos);
linearLayoutManager.scrollToPosition(pos);
}
});//, 250);
I figure it might have something to do with the nesting of the recyclerview inside the scrollview, but I really have no idea. The reason there is a nestedscrollview at all is because the two include statements before my recyclerview are actually cardviews that control the dataset of the recyclerview. The user can hide these two cards at any time, and there is a fade animation that brings the recyclerview up to the position where the card was. If that sounds complicated, it is, and I think including these "control cards" as I call them would be less efficient, harder to animate and significantly harder to work with if I included them as different view types in the recycler instead of placing them independently above it, as I have them now.
Try calling like this
yourRecyclerView.post(new Runnable() {
#Override
public void run() {
linearLayoutManager.scrollToPosition(pos);
}
});

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

RecyclerView does not fully expand to show all elements

I've created an EditText, a Button and a RecyclerView (composed of 1 TextView and 1 ImageView children) to add TextViews that looks like this . In the EditText at the top, users may enter text and hit the + button. This adds their text to a List(String) which is used to update the RecyclerView. The user may hit the x at the right to delete an entry from the RecyclerView. Here is an image of the overall fragment layout
The issue, which you can see in the image, is that after a few submissions, the RecyclerView stops expanding and remains at a fixed size (notice the small blip in the bottom right). You may scroll in the RecyclerView to view the items, items are still added to it, but it will not expand to the full size (the one in the image has 20+ items). If I delete an item, the height grows for some reason but it still won't display all elements and it shrinks back when I add a new item.
Things I've tried
Here is the RecyclerView code. The heirarchy goes
<LinearLayout>
<ScrollView>
<LinearLayout>
<RecyclerView />
</LinearLayout>
</ScrollView>
</LinearLayout>
All heights and widths are set to match parent, all orientations to vertical:
<android.support.v7.widget.RecyclerView
android:id="#+id/ingredientsRecyclerView"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
>
The code to add an entry to the RecyclerView (outside of the adapter):
ingredientsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (ingredientsText.getText().toString().length() > 0) {
mIngredients.add(ingredientsText.getText().toString());
ingredientsText.setText("");
mAdapter.notifyDataSetChanged();
mRecyclerView.setBackgroundResource(R.drawable.rounded_edittext);
}
}
});
And, finally, the code to delete an entry from the RecyclerView (inside the RV adapter):
cancelImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mIngredients.remove(getAdapterPosition());
mAdapter.notifyItemRemoved(getAdapterPosition());
mAdapter.notifyItemRangeChanged(getAdapterPosition(), mIngredients.size());
if(mAdapter.getItemCount() == 0)
mRecyclerView.setBackgroundResource(0);
}
});
Any assistance would be GREATLY appreciated, as I can't figure this out for the life of me!
use NestedScrollView instead of ScrollView and setNestedScrollEnabled(false) on the RecyclerView
k0sh is absolutely right. Just to add, use the full class name, which is android.support.v4.widget.NestedScrollView (make sure you have the v4 support library in your build.gradle file), or Android won't find NestedScrollView class. Source: Error inflating class - NestedScrollView - class not found
Use NestedScrollView instead of ScrollView and on your NestedScrollView set
isNestedScrollingEnabled = false

notifyItemChanged() make the RecyclerView scroll and jump to UP

i have a RecycleView with an adapter that show a list of servers
and the user must select one server.
when i call notifyItemChanged(previousPosition) inside the onClick() method
to make the old server unselected and the new server selected,
that's make the RecycleView list jump to up exactly in the middle of list.
and this problem happen just when i click on one of the last 2 or 3 servers inside the RecycleView list
here is the code of my RecyclerView.Adapter :
public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ServerViewHolder> {
private List<Server> listServers = new ArrayList<>();
private int[] icons = new int[]{R.drawable.server1,R.drawable.server2,R.drawable.server3,R.drawable.server4,R.drawable.server5,R.drawable.server6,R.drawable.offline};
private int selected = 0;
private int previousSelected = 0;
public ServerAdapter(List<Server> listServers){
this.listServers = listServers;
}
#Override
public ServerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.server_relative_layout,parent,false);
return new ServerViewHolder(view);
}
#Override
public void onBindViewHolder(final ServerViewHolder holder, final int position) {
if(position == selected){
holder.getBackground().setSelected(true);
}else{
holder.getBackground().setSelected(false);
}
holder.getBackground().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(position != selected){
previousSelected = selected;
selected = position;
holder.getBackground().setSelected(true);
notifyItemChanged(previousSelected);
}
}
});
holder.getImageServer().setImageResource(icons[position%6]);
holder.getTextNameServer().setText(listServers.get(position).getName());
holder.getTextConnected().setText(listServers.get(position).getUrl());
}
#Override
public int getItemCount() {
return listServers.size();
}
public class ServerViewHolder extends RecyclerView.ViewHolder{
private ImageView imageServer;
private TextView textNameServer;
private TextView textConnected;
private View background;
public ServerViewHolder(View itemView) {
super(itemView);
imageServer = (ImageView)itemView.findViewById(R.id.imageServer);
textNameServer = (TextView)itemView.findViewById(R.id.textNameServer);
textConnected = (TextView)itemView.findViewById(R.id.textConnected);
background = itemView;
}
public ImageView getImageServer() {
return imageServer;
}
public TextView getTextConnected() {
return textConnected;
}
public TextView getTextNameServer() {
return textNameServer;
}
public View getBackground() {
return background;
}
}
}
any solutions to solve this problem ? thanks.
The problem happened exactly when i specify the layout height and do not let it to wrap_content
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="400dp"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
/>
or when i put it below something for expample like that :
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
android:layout_below="#+id/image"/>
my code exactly is :
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/serverRecyclerView"
android:layout_margin="10dp"
android:layout_alignTop="#+id/imageBall"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toRightOf="#+id/camera"
android:layout_toEndOf="#+id/camera"/>
Looks like this is a bug: https://code.google.com/p/android/issues/detail?id=203574
The best workaround seems to be Bart's answer to set the RecyclerView's LinearLayoutManager's AutoMeasure property to false.
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setAutoMeasureEnabled(false);
recyclerView.setLayoutManager(llm);
The set FixedSize to true solution had way too many side-effects...
RecyclerView.setHasFixedSize(true)
I don't know why, but I used:
RecyclerView.setHasFixedSize(true)
This worked for me. I hope it can help.
android:descendantFocusability="blocksDescendants"
android:descendantFocusability="blocksDescendants"
this attr solve my bug
RecyclerView.ItemAnimator animator = myRecyclerListView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator)animator).setSupportsChangeAnimations(false);
}
My RecyclerView was inside ConstraintLayout, and I also had such problem and calling setAutoMeasureEnabled(false) of RecyclerView's LayoutManager did not fix the issue for me, furthermore this method is deprecated in 28.0.0 version. What I did is that, I wrapped my RecyclerView with RelativeLayout and now it works like a charm. As mentioned in bugtracker, this "issue" is intented behaviour in LinearLayout and is not going to be fixed. So if it is possible, just wrap your RecyclerView something like this:
<RelativeLayout
android:id="#+id/container_messages_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#drawable/chat_back_pattern"
app:layout_constraintBottom_toTopOf="#+id/bottom_view"
app:layout_constraintTop_toBottomOf="#+id/toolbar">
<android.support.v7.widget.RecyclerView
android:id="#+id/messages_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
for anyone who stumbles upon this issue, try using
yourRecyclerView.notifyItemChanged(int position, Object payload);
This one did the trick for me.
Using
setAutoMeasureEnabled(false);
also worked but in some edge cases recycler view was acting weird. Good luck!
RecyclerView can perform several optimizations if it can know in advance that RecyclerView's size is not affected by the adapter contents. RecyclerView can still change its size based on other factors (e.g. its parent's size) but this size calculation cannot depend on the size of its children or contents of its adapter (except the number of items in the adapter).
If your use of RecyclerView falls into this category, set this to true. It will allow RecyclerView to avoid invalidating the whole layout when its adapter contents change.
If we have a RecyclerView with match_parent as height/width, we should add setHasFixedSize(true) since the size of the RecyclerView itself does not change inserting or deleting items into it.
setHasFixedSize should be false if we have a RecyclerView with wrap_content as height/width because each element inserted by the adapter could change the size of the RecyclerView depending on the items inserted/deleted, so, the size of the RecyclerView will be different each time we add/delete items.
recyclerView.setHasFixedSize(true);
true if adapter changes cannot affect the size of the RecyclerView.
References
Android Developers Reference - RecyclerView
Understanding RecyclerView setHasFixedSize - Gastón Saillén
I came across the similar problem, just take care of the xml layout file.
Do not use the layout_below , layout_above or others similar properties in RecyclerView or RecyclerView's parent view.
You can use LinearLayout weight , layout_marginBottom or sth to achieve
layout_below or other.
The late answer better than nothing, if you're using NestedScrollView as the parent view of RecyclerView you should delete it.
I had a similar problem and I tryed all solutions listed above, but noone worked.
I was already padding the "Payloads" to "notifyItemChanged(position, payloads)" because I just needed to "upload" a checkbox value so I was passing the value inside "Payloads" without recalling the update of the entire viewholder.
This solution worked for all view holders in my recycler view except for the last one (and probably for all "recycled" ones, I mean those who recall the "onBindViewHolder" by "recycling" an existing view).
I think using "notifyItemChanged" will works if you have only the recyclerview and I also think that this problem of "auto-scrolling" is raised by nested scroll views & recycler views.
I was in the case exposed by "raed", so "ScroolView -> RecyclerView -> "n" x RecyclerView". I have a scroolview wich contains a recyclerview whose viewholders can contains a recycler views.
Delete the parent ScrollView is a really weird solution and I couldn't use it, so I setted the "onStopNestedScroll" inside the "ScrollView" and not inside the RecyclerView.
Personally I used it programmatically before the code part which calls the "notifyItemChanged" method by doing:
msvContainer.onStopNestedScroll(mRecyclerView);
Where "msvContainer" is my ScrollView which contains the RecyclerView, and "mRecyclerView" is my RecyclerView contained by the ScrollView.
This way worked 99% because the first time I call "notifyItemChanged" the view scroll up only for the ScrollView, so it hides a button inside my ScrollView which is below my RecyclerView but it doesn't scroll the RecyclerView items. After the first call "notifyItemChanged" works properly.
I found that calling:
msvContainer.stopNestedScroll();
works too. But i suggest to use the first method with the target view if you have multiple nested scroll views.
Anyway you should call "startNestedScroll" after you ran out of the critical part of re-updating your view holder because the targeted view, so in my case the RecyclerView, won't scroll until you call this method so it won't recycler his view holders too.
(In my case that I have multiple Recycler View inside a parent Recycler View inside a parent Scroll View if I was in need to call "notifyItemChanged" inside the most inner Recycler View i would use the "stopNestedScroll" method for every parent view and then re-activated the scroll after the scroll-critical part)
Hope this is helpful, have a nice coding!
Bye
In my case, all I did was to set the height of the recyclerview to "match_parent". Then in your MainActivity, do;
recyclerView.smoothScrollToPosition(yourAdapter.getItemCount()-1);
Thats all...

Set height of children in RecyclerView?

I am trying to use RecyclerView in a similar way to ViewPager (Vertical).
I have setup fling and scroll gestures so the views are scrolled in exactly the same way as ViewPager but now I would like to setup children views (LinearLayoutManager) and LinearLayout used for each child view to fill screen instead of wrap_content. I have made all views to match_parent and fill_parent but that does not work.
I did this:
holder.layout.setMinimumHeight(this.recyclerView.getMeasuredHeight());
Which is nice but when the orientation changes then layout overflows the screen which makes smooth scrolling/gestures flicker etc. I don't want it to overflow the screen.
Any good tips how to make it properly so all children are laid out to maximum height of the screen but do not overflow?
Thanks
Edit: I changed setting minimum height to this:
holder.layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, this.recyclerView.getHeight()));
which solves flickering issue but is there a better way?
I have low reputation so unable to comment down. thats why answering here .
to over come the overflow issue lets say you have XMl of single item like this
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:id="#+id/mainLayout" >
<-- some views --->>
</RelativeLayout>
now in your adapter get this Layout in viewHolder
and make a boolean variable where you are setting adabpter.. which you can use for knowing orientation changed . when orientation get changed then change its value .
in your adapter make a function
public void orientaiotnChange(){
notifyDataSetChanged();
}
in your function
#Override
public void onBindViewHolder(final ViewHolder viewHolder, final int arg1) {
if(isOrientationHorizontal){
viewHolder.Mainlayout.setOrientation(horizontal);
}
else{
viewHolder.Mainlayout.setOrientation(vertical);
}
also change the orientation of RecyclerView in activity from where you set adapter and from where you are checking orientation is changed or not .
hopefully it will work so :)

Categories

Resources