I have 3 views: an ImageView, a RelativeLayout and a ListView. When the user scrolls down, I have to hide the ImageView first. When the top of the RelativeLayout touches the top of the screen it has to fix itself there and the scroll must be done by the ListView. Then when I scroll up, the top of the ListView must be visible before the ImageView starts to scroll down again.
Is there any component that does what I want without having to write my own component?
(Just to make things easier, it should work like the headers in Instagram, when the header reaches the top it stays there, but there should be only one header)
I have a very simple solution to this, hope it helps. I use one header added to the listView and the same header added to the page on top. There is a listener to toggle visibility of the fixed header. Everything is done in the onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.list);
LayoutInflater inflater = getLayoutInflater();
// your image header
View headerImage = inflater.inflate(R.layout.header_image, listView, false);
listView.addHeaderView(headerImage);
// the header that scrolls with the listview
final View fixedHeader = inflater.inflate(R.layout.header_fixed, listView, false);
listView.addHeaderView(fixedHeader);
// the header that is fixed on top of the screen
final View secondFixedHeader = findViewById(R.id.fixed_header);
secondFixedHeader.setVisibility(View.GONE);
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem > 0) {
secondFixedHeader.setVisibility(View.VISIBLE);
} else {
secondFixedHeader.setVisibility(View.GONE);
}
}
});
listView.setAdapter(new ListAdapter(this));
}
there is the activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="#+id/fixed_header"
layout="#layout/header_fixed" />
</RelativeLayout>
there is the header_fixed.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#android:color/darker_gray">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is a fixed header"
android:textAppearance="#android:style/TextAppearance.DeviceDefault.Large"/>
</RelativeLayout>
and there is the header_image.xml:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/android"/>
Related
I have a recyclerview inside SwipeRefreshLayout. and at the end of recyclerview, I'd like to add a button, but I'm unable to do so. Here the code :
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/refresh"
android:layout_width="wrap_content"
android:layout_below="#+id/toolbar"
android:paddingBottom="50dp"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"/>
<Button
android:layout_width="match_parent"
android:layout_height="50dp"
android:id="#+id/load_more"
android:layout_alignParentBottom="true"
android:textColor="#color/md_black_1000"
android:text="TEST"/>
</android.support.v4.widget.SwipeRefreshLayout>
The button does not appear at the end of the recyclerview.
SwipeRefreshLayout could have only one child.
Add RecyclerView and Button to vertical LinearLayout and put it into SwipeRefreshLayout.
<android.support.v4.widget.SwipeRefreshLayout>
<LinearLayout>
<android.support.v7.widget.RecyclerView>
<Button>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
To add Button as last item in RecyclerView you need edit adapter:
1) increase number of rows
#Override
public int getItemCount() {
return pictureArrayList.size() + 1;
}
2) show Button when list ends
#Override
public void onBindViewHolder(final ExampleHolder holder, final int position) {
final Picture picture = pictureArrayList.get(position);
if (position <= pictureArrayList.size()) {
holder.title.setVisibility(View.VISIBLE);
holder.button.setVisibility(View.GONE);
holder.title.setText(picture.getName());
holder.imageView.setImageResource(picture.getImage());
} else {
holder.title.setVisibility(View.GONE);
holder.button.setVisibility(View.VISIBLE);
}
}
You need to create a RecyclerView with different view items depending on what you want to do may be an option to use a view button at the bottom of the list, I have a blog post where I wrote a blog about the RecyclerView(Spanish).
http://erikcaffrey.github.io/2015/10/05/recyclerview/
You can see the code!
https://github.com/erikcaffrey/RecyclerView-Examples
I have a listview below a viewpager and in the initial state (when nothing has been scrolled), the viewpager shows only one item with a 10dp "preview" of the next and previous items (I have achieved this by setting a negative page margin:viewPager.setPageMargin(-48);). What I am trying to do is, on scrolling down the listview:
1) the listview should "push" the viewpager up, decreasing its height up to a certain point. On reaching that point (some minHeight for the viewpager), the listview should scroll normally with the smaller sized viewpager above it.
2) The next and the previous items in the viewpager should pull inside (towards the central item) and in the final state, three items of the viewpager should be fully displayed. (Images below to illustrate this)
Scrolling up the listview should do the opposite.
I have managed to do part (1) of my task. Here's the code
My viewpager and listview are inside a FrameLayout like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="#layout/activity_main" tools:context=".MainActivity">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
android:divider="#000000"
android:scrollbars="none" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:background="#FFFFFF"/>
</FrameLayout>
I "fake" the listview to be below the viewpager by adding a transaprent header view to the listview and making the heights of both the headeview and the viewpager same. Here's a snippet of the code:
screenWidth = // Screen width of the phone
headerHeight = // Required height of the viewpager and the headerview
headerView = inflater.inflate(R.layout.fake_list_header, listView, false);
headerView.getLayoutParams().height = headerHeight;
headerView.getLayoutParams().width = screenWidth;
viewPager.getLayoutParams().height = headerHeight;
viewPager.getLayoutParams().width = screenWidth;
viewPager.setPageMargin(negativeMargin);
listView.addHeaderView(headerView, null, false);
// Other initializations and stuff
fake_list_header layout file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Finally, my listview OnScrollListener that takes care of adjusting the viewpager height depending on the amount scrolled by the listview and stopping when we reach the minimum height for the viewpager:
OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (listview.getFirstVisiblePosition() == 0) {
View firstChild = listview.getChildAt(1); // 0th element is the fake headerview itself
int topY = 0;
if (firstChild != null) {
topY = firstChild.getTop();
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.width = screenWidth;
layoutParams.height = topY;
if (topY < headerHeight && topY >= minHeight) {
// minHeight is the minimum height the viewpager takes, after this point it stops getting smaller
//And vice-versa with headerHeight taking care of the maximum height the viewpager can take
viewpager.setLayoutParams(layoutParams);
}
}
}
}
}
Part (2) of my task is where I am stuck (and running out of ideas), I have tried changing pageMargin of the viewpager with the scroll but the results aren't good (also don't think it is the right approach for achieving something like this). Setting X position of the next(or previous) view in the pager by calling setTranslationX with scroll also isn't working.
Here are some mocks of what I am trying to achieve:
Initial state (nothing scrolled)
Final state (minHeight of viewpager achieved)
Is using viewpager and a listview right way of achieving something like this? I thought of using a horizontal recyclerview instead of a viewpager, but I need the "page by page" scroll behavior of a viewpager for the horizontal scroll/swipe of items. Any suggestions welcome
Try this in your main layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="#layout/activity_main" tools:context=".MainActivity">
<ListView
android:id="#+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:dividerHeight="1dp"
android:layout_below="#+id/pager"
android:divider="#000000"
android:scrollbars="none" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="150dp"
android:clipToPadding="false"
android:background="#FFFFFF"/>
</RelativeLayout>
I'm implementing a "parallax" effect which consists of a list view and an image view above.
When the user scrolls downwards, the offset is subtracted from the height of the image view. This works fine. I'm doing this in the scroll listener of the list view. To really update the listview and imageview in realtime I need to call requestLayout().
The problem is, that this method is asynchronous and if I scroll slowly it flickers extremely. If I just swipe quickly it looks very nice. Is there an other solution to solve this?
ScrollListener
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
View c = view.getChildAt(0);
int top = (c == null) ? 0 : (-c.getTop() + view.getFirstVisiblePosition() * c.getHeight());
//Log.d(TAG, "Offset = " + top);
mImageView.getLayoutParams().height = 400 - top;
mImageView.requestLayout();
}
Layout
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context="xxx.fragments.NewsFragment">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#android:color/holo_blue_light"
android:id="#+id/advertisement"
/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/news_feed">
</ListView>
</TableLayout>
If you just want the ImageView to scroll with the list, then you can simply add it as a header view:
listView.addHeaderView(mImageView);
Make sure you call this method before calling setAdapter. Also make sure your ImageView doesn't already have a parent, so inflate it separately or just create one in code using new ImageView.
I am developing an android application for listing the products of an on-line store. For this I have used a GridView for that. But inside a category could be a big number of products, so I have to implement a feature like, "Loading on demand", with a button at the end of a gridView telling (Load More). Of course this button will be visible only if the GridView has reached the end of the items. But I dont how to do it. Below is my solution so far, but the button is not visible when gridview goes at the end.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<com.twotoasters.jazzylistview.JazzyGridView
android:id="#+id/ebuy_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:drawSelectorOnTop="true"
android:horizontalSpacing="15dp"
android:numColumns="2"
android:verticalSpacing="15dp" />
<TextView
android:id="#+id/more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:background="#drawable/ebuy_now_background"
android:clickable="true"
android:onClick="loadMore"
android:padding="15dp"
android:text="Load More"
android:textColor="#color/ebuy_color_second_strip"
android:textSize="25sp" />
</LinearLayout>
</ScrollView>
Suggestions or other solution will be more than welcome :)
as you implement and add this listener to your Gridview this will detect for the end of Gridview as scroll position was at end of list just get more data. And during the loading data you need one flag to load data at once when the scroll position goes to end. So if data is loading and during that time you scroll up then scroll down then it will not get more data for dupplication.
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
private void isScrollCompleted() {
if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
/*** In this way I detect if there's been a scroll which has completed ***/
/*** do the work for load more date! ***/
if(!isLoading){
isLoading = true;
loadMoreDAta();
}
}
}
Make "Load More" button part of the GridView - in your getView method inflate a layout with button when "position" corresponds to last item.
got myself into a pickle trying to squeeze two ListViews in the same activity. It works, using two separate ListFragments contained in a standard (vertical) LinearLayout.
The problem is, the two lists together are longer than the screen and the second list is therefore partially hidden. Visually, the user expects to drag the whole screen up and unveil the second list. But the two lists have their own internal scrolling and they do not allow for the whole screen to scroll as one piece.
Luckily the lists actually contain very few items (5 each on average). So, theoretically, I could populate a couple of LinearLayouts containers instead. The problem is, the data being displayed by the lists comes from a Cursor and is dynamic. While I am aware of the newView() and bindView() methods of the CursorAdapter, I don't quite understand how I can connect the adapter to the LinearLayout containers instead of ListViews. I.e. how does the CursorAdapter know that it must create 5 row items out of the 5 items it finds in its cursor? Where do I create the loop that iterates over the cursor item and creates the items in the LinearLayout container? And how do I refresh the content of the LinearLayout when the data in the Cursor changes? All the examples I'm finding neatly wrap these issues into the ListView provided by the ListActivity, but I can't use ListViews!
I'm confused!
Manu
EDIT : Here is the xml layout of the (Fragment)Activity when following breceivemail suggestion. Commented out is the original LinearLayout container, prior to breceivemail's suggestion. It should also be noted the the whole activity is in turn contained by a TabHost, but I don't know if that make any difference for the problem at hand.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!--
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
-->
<TextView android:id="#+id/title"
android:textSize="36sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/SelectPlayer"/>
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/Playing"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#000000"
android:background="#999999"/>
<fragment android:name="com.myDomain.myApp.PlayerListFragment"
android:id="#+id/playing"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/Reserve"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#000000"
android:background="#999999"/>
<fragment android:name="com.myDomain.myApp.PlayerListFragment"
android:id="#+id/reserve"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
Put your listViews in a vertical scroll. You can have scrollable listView inside of a vertical scroll by the following trick. use the following code and enjoy!
private int listViewTouchAction;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
setListViewScrollable(myListView1);
setListViewScrollable(myListView2);
}
private void setListViewScrollable(final ListView list) {
list.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
listViewTouchAction = event.getAction();
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, 1);
}
return false;
}
});
list.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (listViewTouchAction == MotionEvent.ACTION_MOVE)
{
list.scrollBy(0, -1);
}
}
});
}
listViewTouchAction is a global integer value.