I am implementing a RecyclerView inside a ScrollView. In order to have only one scrolling behaviour on the entire page I implement a NonScrollRecyclerView version. The implementation is as follows:
public class NonScrollRecyclerView extends RecyclerView {
public NonScrollRecyclerView(Context context) { super(context); }
public NonScrollRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
if(ev.getAction() == MotionEvent.ACTION_MOVE)
return true;
return super.dispatchTouchEvent(ev);
}
}
Once i update my build and target settings to SDK 23, I have trouble scrolling the page which contains the NonScrollRecyclerView. The specific problem is that the page scrolls OK until i reach the recycler view portion and once i scroll onto this view I am unable to scroll anymore, either up or down.
I DONOT face this problem with SDK 22 and below
My xml is as follows:
XML #layout/rv contains the recycler view
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/background_gray">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background_gray"
android:orientation="vertical">
<include
layout="#layout/row_mall_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cv_mall_header"
android:layout_marginTop="8dp"/>
<include
layout="#layout/row_mall_shops"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cv_mall_shops"
android:layout_marginTop="8dp"/>
<include
layout="#layout/row_mall_coupons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cv_mall_coupons"
android:layout_marginTop="8dp"/>
<include
layout="#layout/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/cv_mall_feeds"
android:layout_marginTop="8dp"/>
</LinearLayout>
</ScrollView>
XML - #layout/rv
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/background_gray"
android:id="#+id/ll_mall_feeds">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_feedcount"
android:textColor="#color/semi_theme_blue"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginLeft="12dp"
android:layout_centerVertical="true" />
</RelativeLayout>
<com.project.ui.NonScrollRecyclerView
android:id="#+id/nrv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#android:color/transparent"
android:listSelector="#android:color/transparent" />
</LinearLayout>
RecyclerView and ListView are not recommended inside a ScrollView because elements hights are not calculated when rendering the ScrollView. This means, your adapter might not be populated when the ScrollView is shown and later when the RecyclerView is notified about data changes (for instance when you initialize your adapter), there's not way to recalculate the elements heights.
It's really a pain in the neck because you have to try to calculate the elements heights and it's never accurate, so you will have discrepancies when showing a ListView or a RecyclerView inside a ScrollView. Some examples of how to calculate the elements heights can be checked here and here.
My advice is to turn your RecyclerView into a LinearLayout and add elements programmatically, so you emulates the ListView or RecyclerView behaviour:
LinearLayout layout = (LinearLayout) rootView.findViewById(R.id.files);
layout.removeAllViews();
for (int i = 0; i < fileAdapter.getCount(); i++) {
final View item = fileAdapter.getView(i, null, null);
item.setClickable(true);
item.setId(i);
item.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
fileContentRowPosition = v.getId();
# Your click action here
}
});
layout.addView(item);
}
Here its the XML with the files definition:
<ScrollView
android:id="#+id/scrollView1"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<LinearLayout
android:id="#+id/files"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
The whole java code can be checked here and the whole Layout here.
On the other hand, if you still want to continue with your implementation, and regarding your issue, you can check this article about Handling Scrollable Controls in Scrollview
Best regards,
For only one scrolling in the entire page you can use NestedScrollView instead of ScrollView and set recyclerView.setNestedScrollingEnabled(false);
Related
I am new to Android development and feel like this is a really trivial problem, but I cannot word it well enough to find a solution online, so I might as well ask the question here.
My goal is to create a reusable component that is essentially an expandable card like the one described here: https://material.io/design/components/cards.html#behavior.
To do it, I created a custom view that extends a CardView:
public class ExpandableCardView extends CardView {
public ExpandableCardView(Context context) {
super(context);
}
public ExpandableCardView(Context context, AttributeSet attrs) {
super(context, attrs);
// get custom attributes
TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExpandableCardView, 0, 0);
String heading = array.getString(R.styleable.ExpandableCardView_heading);
array.recycle();
// inflate the layout
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.expandable_card_view, this, true);
// set values
TextView headingTextView = findViewById(R.id.card_heading);
headingTextView.setText(heading.toUpperCase());
// set collapse/expand click listener
ImageView collapseExpandButton = findViewById(R.id.collapse_expand_card_button);
collapseExpandButton.setOnClickListener((View v) -> toggleCardBodyVisibility());
}
private void toggleCardBodyVisibility() {
LinearLayout description = findViewById(R.id.card_body);
ImageView imageButton = findViewById(R.id.collapse_expand_card_button);
if (description.getVisibility() == View.GONE) {
description.setVisibility(View.VISIBLE);
imageButton.setImageResource(R.drawable.ic_arrow_up);
} else {
description.setVisibility(View.GONE);
imageButton.setImageResource(R.drawable.ic_arrow_down);
}
}
}
And the layout:
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/expandable_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="16dp"
android:animateLayoutChanges="true"
app:cardCornerRadius="4dp">
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_header"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal" >
<TextView
android:id="#+id/card_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#color/colorPrimary"
android:layout_alignParentLeft="true"
android:text="HEADING"/>
<ImageView
android:id="#+id/collapse_expand_card_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
app:srcCompat="#drawable/ic_arrow_down"/>
</RelativeLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_body"
android:padding="12dp"
android:layout_marginTop="28dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="gone" >
</LinearLayout>
</androidx.cardview.widget.CardView>
Ultimately I want to be able to use it like so in my activities, usually multiple instances per activity:
<xx.xyz.yy.customviews.ExpandableCardView
android:id="#+id/card_xyz"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom_xxx:heading="SOME HEADING" >
<SomeView></SomeView>
</xx.xyz.yy.customviews.ExpandableCardView>
Where SomeView is any text, image, layout or another custom view altogether, typically with data bound from the activity.
How do I get it to render SomeView inside the card body? I want to take whatever child structure is defined within the custom view and show it in the card body when it is expanded. Hope I made it easy to understand.
I think that a better approach would be to define the layout that will be inserted into the CardView ("SomeView") in a separate file and reference it with a custom attribute like this:
<xx.xyz.yy.customviews.ExpandableCardView
android:id="#+id/card_xyz"
android:layout_width="match_parent"
android:layout_height="match_parent"
custom_xxx:heading="SOME HEADING"
custom_xxx:expandedView="#layout/some_view"/>
I'll explain my rationale at the end, but let's look at an answer to your question as stated.
What you are probably seeing with your code is SomeView and expandable_card_view appearing all at once in the layout. This is because SomeView is implicitly inflated with the CardView and then expandable_card_view is added through an explicit inflation. Since working with layout XML files directly is difficult, we will let the implicit inflation occur such that the custom CardView just contains SomeView.
We will then remove SomeView from the layout, stash it, and insert expandable_card_view in its place. Once this is done, SomeView will be reinserted into the LinearLayout with the id card_body. All this has to be done after the completion of the initial layout. To get control after the initial layout is complete, we will use ViewTreeObserver.OnGlobalLayoutListener. Here is the updated code. (I have removed a few things to simplify the example.)
ExpandableCardView
public class ExpandableCardView extends CardView {
public ExpandableCardView(Context context) {
super(context);
}
public ExpandableCardView(Context context, AttributeSet attrs) {
super(context, attrs);
// Get control after layout is complete.
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// Remove listener so it won't be called again
getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Get the view we want to insert into the LinearLayut called "card_body" and
// remove it from the custom CardView.
View childView = getChildAt(0);
removeAllViews();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.expandable_card_view, ExpandableCardView.this, true);
// Insert the view into the LinearLayout.
((LinearLayout) findViewById(R.id.card_body)).addView(childView);
// And the rest of the stuff...
TextView headingTextView = findViewById(R.id.card_heading);
headingTextView.setText("THE HEADING");
// set collapse/expand click listener
ImageView collapseExpandButton = findViewById(R.id.collapse_expand_card_button);
collapseExpandButton.setOnClickListener((View v) -> toggleCardBodyVisibility());
}
});
}
private void toggleCardBodyVisibility() {
LinearLayout description = findViewById(R.id.card_body);
ImageView imageButton = findViewById(R.id.collapse_expand_card_button);
if (description.getVisibility() == View.GONE) {
description.setVisibility(View.VISIBLE);
imageButton.setImageResource(R.drawable.ic_arrow_up);
} else {
description.setVisibility(View.GONE);
imageButton.setImageResource(R.drawable.ic_arrow_down);
}
}
}
expandable_card_view.java
The CardView tag is changed to merge to avoid a CardView directly nested within a CardView.
<merge
android:id="#+id/expandable_card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="16dp"
android:animateLayoutChanges="true"
app:cardCornerRadius="4dp">
<RelativeLayout
android:id="#+id/card_header"
android:padding="12dp"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal" >
<TextView
android:id="#+id/card_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textColor="#color/colorPrimary"
android:layout_alignParentLeft="true"
android:text="HEADING"/>
<ImageView
android:id="#+id/collapse_expand_card_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
app:srcCompat="#drawable/ic_arrow_down"/>
</RelativeLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/card_body"
android:padding="12dp"
android:layout_marginTop="28dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:visibility="gone" >
</LinearLayout>
</merge>
activity_main.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.customcardview.ExpandableCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_android" />
<TextView
android:id="#+id/childView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Say my name."
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.example.customcardview.ExpandableCardView>
</LinearLayout>
So, why do I suggest that you use a custom attribute to include SomeView in the layout as I identified at the beginning? In the way outlined above, SomeView will always be inflated and there is some effort to switch the layout around although SomeView may never be shown. This would be expensive if you have a lot of custom CardViews in a RecyclerView for instance. By using a custom attribute to reference an external layout, you would only need to inflate SomeView when it is being shown and the code would be a lot simpler and easier to understand. Just my two cents and it may not really matter depending upon how you intend to use the custom view.
This thing is getting me nuts.
I have been able to get this behavior (exactly what I want): http://i.imgur.com/PGhL22k.gif
And this is the behavior is has when I scroll down very fast: http://i.imgur.com/kk7icAc.gif and http://i.imgur.com/YNPNiA6.gif
I am sorry but the GIFs are larger than 2Mb and I cannot upload them here...
I want the pagination bar at the bottom to hide the same amount the toolbar does. When scrolling slowly it goes very well, but when fast scrolling it has a really strange behavior as you can see at the GIFs provided above.
This is the layout XML:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:kiosk="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
layout="#layout/android_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
kiosk:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/vpPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
kiosk:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<include
layout="#layout/paginator_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
kiosk:layout_behavior="carl.fri.fer.views.KioskPaginator.KioskPaginatorScrollBehaviour"/>
</android.support.design.widget.CoordinatorLayout>
The "android_toolbar" include is as follows:
<?xml version="1.0" encoding="utf-8"?>
<carl.fri.fer.views.KioskToolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/primaryColor"
android:minHeight="?attr/actionBarSize"/>
And the "paginator_layout" is the following:
<?xml version="1.0" encoding="utf-8"?>
<carl.fri.fer.views.KioskPaginator.KioskPaginator
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/kpPaginator"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:animateLayoutChanges="true"
android:background="#color/primaryColor"
android:clickable="true"
android:orientation="horizontal">
<TextView
android:id="#+id/tvCurrentPage"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:padding="10dp"
android:textColor="#color/color_pure_black"
android:textSize="17sp"/>
<LinearLayout
android:id="#+id/llMoreOptions"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="#android:color/transparent"
android:gravity="center_vertical"
android:orientation="horizontal"/>
<TextView
android:id="#+id/tvTotalPages"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:padding="10dp"
android:textColor="#color/color_pure_black"
android:textSize="17sp"/>
</carl.fri.fer.views.KioskPaginator.KioskPaginator>
And the ScrollBehavior is as follows:
public class KioskPaginatorScrollBehaviour extends AppBarLayout.ScrollingViewBehavior {
public KioskPaginatorScrollBehaviour(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float depY = - dependency.getHeight();
depY -= dependency.getY();
Utils.log("dependency", "" + depY);
child.setTranslationY(depY);
return true;
}
}
Inside of every page of the ViewPager, there is a Fragment. And the content of this Fragment is a RecyclerView with dynamic content. All the content from the RecyclerView is loaded from database and images are loaded at runtime.
Can someone please tell me why is this strange behavior happening? How can I fix it?
Thank you in advance...
EDIT 1:
I just spotted the cause of the weird behavior! The ViewPager loads the current page and the adjoining ones. The content of the RecyclerView loads from the Internet and as soon as it is loaded, it goes into the RecyclerView. The ViewPager first loads the current page and then the adjoining ones. If I have scrolled down the current page RecyclerView (Toolbar is hidden) when the 2nd RecyclerView just loads and shows the content, it resets the current page AppBarLayout behavior, so it resets my custom view behavior... How can I fix that? I want to avoid not loading adjoining views..
EDIT 2:
Ok, it happens when loading adjoining pages of ViewPager and also when loading from the Internet images inside the RecyclerView... this is crazy.
So finally resolved your problem - make your custom behavior extending CoordinatorLayout.Behavior instead of ScrollingViewBehavior and it will work as expected. Just set the value to your view's translationY, that is opposite to Y of AppBarLayout:
public class KioskPaginatorScrollBehaviour extends CoordinatorLayout.Behavior<View> {
public KioskPaginatorScrollBehaviour(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float depY = -dependency.getY();
child.setTranslationY(depY);
return true;
}
}
currently I am developing a settings menu in a scrollview. The basic structure looks like the following:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="#+id/scrollview_settings"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="#dimen/paddingSmall">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/transparent_white_settings"
android:paddingTop="#dimen/paddingMedium"
android:paddingBottom="#dimen/paddingMedium">
<ImageView
android:id="#+id/logo"
android:layout_width="68dp"
android:layout_height="wrap_content"
android:src="#drawable/newspaper_logo_color"
android:layout_alignParentRight="true"
android:layout_margin="#dimen/paddingBig"
android:adjustViewBounds="true"/>
</RelativeLayout>
<!-- Layout News Uebersicht -->
<include layout="#layout/separator_line_grey"/>
<de.basecom.noznewsapp.views.FontTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="true"
android:textColor="#color/buttonHoverBackground"
android:textSize="#dimen/sliding_image_kicker_font_size"
android:paddingLeft="#dimen/paddingLarge"
android:paddingTop="#dimen/paddingMedium"
android:paddingBottom="#dimen/paddingMedium"
android:text="#string/news_overview"
android:textScaleX="#dimen/letter_spacing"
android:textStyle="bold"
android:gravity="center_vertical"
android:background="#color/settings_header_color"
android:layout_gravity="center_vertical"/>
<include layout="#layout/separator_line_grey"/>
<!-- News-Overview Issues -->
<LinearLayout
android:id="#+id/issue_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#color/transparent_white_settings">
<de.basecom.noznewsapp.views.NewsScrollListView
android:id="#+id/listview_issues"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:divider="#null"
android:dividerHeight="0dp" />
</LinearLayout>
<!-- Layout Einstellungen -->
<de.basecom.noznewsapp.views.FontTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="true"
android:textColor="#color/buttonHoverBackground"
android:textSize="#dimen/sliding_image_kicker_font_size"
android:paddingLeft="#dimen/paddingLarge"
android:paddingTop="#dimen/paddingMedium"
android:paddingBottom="#dimen/paddingMedium"
android:text="#string/settings"
android:textStyle="bold"
android:textScaleX="#dimen/letter_spacing"
android:gravity="center_vertical"
android:background="#color/settings_header_color"
android:layout_gravity="center_vertical"/>
<include layout="#layout/separator_line_grey"/>
<!-- Einstellungs- Werte -->
<LinearLayout
android:id="#+id/settings_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#color/transparent_white_settings">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="#dimen/paddingLarge">
<de.basecom.noznewsapp.views.FontTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/receive_pushmessages"
android:textAllCaps="true"
android:textScaleX="#dimen/letter_spacing"
android:textSize="#dimen/sliding_image_kicker_font_size"
android:layout_centerVertical="true"
android:textColor="#color/buttonBackground"/>
<Switch
android:id="#+id/receive_pushmessages_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="#string/empty_string"/>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
For the listview I used a custom implementation, to give it a fix height and make it able to scroll within the scrollview (note: I have to use the listview within the scrollview, because I have lots of elements, so that using a linearlayout wouldn't be nice).
public class CustomListView extends ListView {
public CustomListView(Context context) {
super(context);
}
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomListView(Context context, AttributeSet attrs,int defStyle) {
super(context, attrs, defStyle);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
Each item of the listview contains a label and a gridview, which is all shown correctly. But now I want to implement the functionality, that the user can click on the label to show and hide the visibility of the gridview.
But if I click on the label and the gridview is shown, the height of the listview doesn't adapt to the new open element. It always stays at the same height and if I open an element, the elements below are no longer visible. This also even happens, if I call the requestLayout() method of the listview to trigger its onMeasure again.
Anybody got an idea what I am doing wrong?
EDIT: Updated my xml file :)
Finally I was only able to solve this issue by replacing Listview with a LinearLayout and add my items in code to the Layout.
SwipeRefresh is not working after setting an empty view for listview which is the only child of a SwipeRefresh layout. How to solve this issue?
Here is the solution:
You can simply use this view hierarchy :
<FrameLayout ...>
<android.support.v4.widget.SwipeRefreshLayout ...>
<ListView
android:id="#android:id/list" ... />
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:id="#android:id/empty" ...
android:text="#string/empty_list"/>
</FrameLayout>
Then, in code, you just call:
_listView.setEmptyView(findViewById(android.R.id.empty));
That's it.
I had this issue too, and solved it without any additional code in the activity by using the layout below.
If you are using a ListActivity or ListFragment it handles showing/hiding the empty view for you, and refreshing works with an empty list as well with this layout structure.
No hacks needed, everything is in the SwipeRefreshLayout, as it should be.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/Refresher"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#null" />
<ScrollView
android:id="#android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Geen formulieren gevonden"
style="#style/text.EmptyView" />
</ScrollView>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
Hope this helps you. If so, don't forget to accept the answer.
Note: this is a duplicate of my answer to this question, but that question is pretty much a duplicate of this question... :)
UPDATE
If the SwipeRefreshLayout is activated too early, you can implement ListView.OnScroll with:
_refresher.Enabled = e.FirstVisibleItem == 0;
This disables the refresher until you scrolled to the top. This is only needed when using a ListView, the new RecyclerView works as expected.
The problem for me was that when the empty view was visible then the refreshing circle wasn't shown although the refreshing method worked. For me this code worked, I hope it helps.
the xml layout:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="none"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center">
<TextView
android:id="#+id/empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:visibility="gone"/>
</ScrollView>
</FrameLayout>
the custom SwipeRefreshLayout:
package misc;
import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.widget.ListView;
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
private ListView mListView;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public void setListView(ListView listView) {
mListView = listView;
}
#Override
public boolean canChildScrollUp() {
if (mListView == null) {
return true;
}
return mListView.canScrollVertically(-1);
}
}
and in my Fragment where I use the layout I only set the swipe to refresh layout in this way:
mSwipeRefreshLayout.setListView(mListView);
the ListView's empty view is set in this way:
TextView emptyView = (TextView) view.findViewById(R.id.empty_view);
emptyView.setText("No data");
mListView.setEmptyView(emptyView);
I hope it helps.
here's how i did this (probably not very elegant solution)
private void createEmptyView() {
ViewGroup parent = (ViewGroup) listView.getParent();
emptyView = (TextView) getLayoutInflater(null)
.inflate(R.layout.view_empty, parent, false);
parent.addView(emptyView);
listView.setEmptyView(emptyView);
}
public class FrameSwipeRefreshLayout extends SwipeRefreshLayout {
private ViewGroup listView;
public void setListView(ViewGroup list) {
listView = list;
}
#Override
public boolean canChildScrollUp() {
if (listView != null) {
View child = listView.getChildAt(0);
return child != null && child.getTop() != 0;
}
return super.canChildScrollUp();
}
}
empty layout
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:clickable="true" />
list layout
<FrameSwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
</FrameSwipeRefreshLayout>
You can check out this issue. I posted a work around solution.
Android - SwipeRefreshLayout with empty textview
I fixed this issue using two SwipeToRefreshLayouts.
One for wrapping the ListView
The other as an emptyView
I posted my code on GitHub.
As #Dinesh written above, i have used clickable="true" like below with empty RecyclerView:
mRecyclerView.setVisibility(View.VISIBLE);
mRecyclerView.setClickable(true);
myText.setVisibility(View.VISIBLE);
xml layout:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipeRefreshKartvizitKisilerim"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:id="#+id/myText"
android:visibility="gone"
android:text="#string/noListItem"
android:textSize="18sp"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
RecyclerView has height match_parent and SwipeRefresh has wrap_content. When there is item in list, don't forget to make text gone.
I have another idea which works well.
Subclass ListView and override setAdapter() and setEmptyView().
setEmptyView should just store the view to use for the empty view, and
setAdapter should register a DataSetObserver that will hide/show the empty view without altering the list view's visibility. You would probably want to use a FrameLayout that holds your SwipeToRefreshLayout and an empty view (as siblings). You can then position the empty view at the top/middle/bottom etc pretty easily using gravity and layout gravity. You could also use a RelativeLayout but I haven't tried. You will want to somehow unregister the dataSetObserver, I believe you may want to do that in onDetachFromWindow as I did below.
public class SwipeToRefreshCompatibleList extends ListView {
private boolean mIsEmpty = false;
private View mEmptyView;
private Adapter mAdapter;
private DataSetObserver mLocalObserver = new DataSetObserver() {
#Override
public void onChanged() {
boolean isEmpty = mAdapter == null || mAdapter.getCount() == 0;
if (mEmptyView != null) {
if (isEmpty != mIsEmpty) {
mEmptyView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
}
}
mIsEmpty = isEmpty;
}
};
public SwipeToRefreshCompatibleList(Context context) {
super(context);
}
public SwipeToRefreshCompatibleList(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SwipeToRefreshCompatibleList(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwipeToRefreshCompatibleList(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
public void setAdapter(ListAdapter adapter) {
mAdapter = adapter;
adapter.registerDataSetObserver(mLocalObserver);
super.setAdapter(adapter);
}
#Override
public void setEmptyView(View view) {
mEmptyView = view;
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(mLocalObserver);
}
}
}
Example layout file:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.company.widget.SwipeToRefreshCompatibleList
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/BasicList"
android:divider="#null"
/>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_gravity="center"
android:textSize="18sp"
android:id="#android:id/empty"
/>
</FrameLayout>
While this takes a bit more code than simply subclassing ListView and altering setVisibility such that it won't set the list to GONE/HIDDEN, I feel like this is less of a hack and still allows the user to set the list to GONE/Hidden if they needed.
I have tried UI hack but it didn't work, the only solution worked is set adapter.
pass empty value make sure the value will not be null.
NotificationListAdapter notificationListAdapter;
notificationListAdapter = new NotificationListAdapter(mContext,notificationResponse);
reCycleViewNotificationList.setAdapter(notificationListAdapter); // weather ListView or RecyclerView
This question already has answers here:
ListView inside ScrollView is not scrolling on Android
(27 answers)
Closed 3 months ago.
I want to set listview to show all items without scroll.
Below is my layout:
<LinearLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/tv1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Large Text"
android:textColor="#android:color/black"
android:textAppearance="?android:attr/textAppearanceLarge" />
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
<TextView
android:id="#+id/tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Large Text"
android:textColor="#android:color/black"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
The linearlayout is belong to a scrollview.
So I want to set the listview all items and scroll by the parent's scroll.
How can I do it?
If the reason for this requirement is similar to what I needed, the class below can help. It is a replacement ListView, one that observes an adapter and its changes and reacts accordingly, but uses a manually populated LinearLayout under the hood, actually. I guess I found this code somewhere on the net but I couldn't locate it now in order to link to it.
My requirements were to use the advantages of a ListView, namely its adapter functionality, to display a very few items (1 to 5) on a page, with possibly more than one such replacement ListView on a single screen. Once displayed, the elements themselves become part of the larger page, so they aren't scrolled separately inside their own ListView, rather with the page as a whole.
public class StaticListView extends LinearLayout {
protected Adapter adapter;
protected Observer observer = new Observer(this);
public StaticListView(Context context) {
super(context);
}
public StaticListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAdapter(Adapter adapter) {
if (this.adapter != null)
this.adapter.unregisterDataSetObserver(observer);
this.adapter = adapter;
adapter.registerDataSetObserver(observer);
observer.onChanged();
}
private class Observer extends DataSetObserver {
StaticListView context;
public Observer(StaticListView context) {
this.context = context;
}
#Override
public void onChanged() {
List<View> oldViews = new ArrayList<View>(context.getChildCount());
for (int i = 0; i < context.getChildCount(); i++)
oldViews.add(context.getChildAt(i));
Iterator<View> iter = oldViews.iterator();
context.removeAllViews();
for (int i = 0; i < context.adapter.getCount(); i++) {
View convertView = iter.hasNext() ? iter.next() : null;
context.addView(context.adapter.getView(i, convertView, context));
}
super.onChanged();
}
#Override
public void onInvalidated() {
context.removeAllViews();
super.onInvalidated();
}
}
}
Do not put listview inside a scrollview.
http://developer.android.com/reference/android/widget/ScrollView.html
Quoting from docs
You should never use a ScrollView with a ListView, because ListView takes care of its own vertical scrolling. Most importantly, doing this defeats all of the important optimizations in ListView for dealing with large lists, since it effectively forces the ListView to display its entire list of items to fill up the infinite container supplied by ScrollView.
You can add your textview's as a header and footer to listview.
http://developer.android.com/reference/android/widget/ListView.html
Check the header and footer methods in the above link
You can have a relative layout add textviews at the top and bottom. Relative to the textviews have the listview between the textview's
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="TextView1" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/textView1"
android:layout_alignParentBottom="true"
android:text="TextView1" />
<ListView
android:id="#+id/listView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/textView1"
android:layout_above="#+id/textView2"
>
</ListView>
</RelativeLayout>
I think it is not possible. If we override list view scroll behavior using parent layout scroll,list view did not work properly.