TL;DR - ViewPager as ListView Header creating some issues.
my activity has a ListView that presents several types of data..
I have a HeaderView, a sticky view, and the rest of the data is a "normal" list item.
I am using this Library -> https://github.com/LarsWerkman/QuickReturnListView
for my list view.
In my HeaderView i have a view pager for 2 profile images of my users.
2 problems :
scrolling the pager right to the next image doesn't open the image to the full screen width..
(but if scrolling down in the list view, scrolling back up to the top of the screen "redraw" the header view and then if you scroll the pager again it's fixed!)
sometimes the images do not load to my view pager.
you can see that bug in those images, first image is of the state from trying to scroll the pager to the right, and the second image is the bug
image one
image two
anyone encountered such a problem before?
I've read in some places that using a view pager inside a list view is not exactly optimal, is there a different way to achieve my goal?
I had that same problem,
try removing that third party library of the list view,
back in the day i was using that same one and it's really buggy.
use a normal listview instead.
A few months ago i did a similar trick as you want to do. I tried almost all libraries in web about this quick return and all of them has bugs and not suitable for me. Also not suitable for a header which contains a viewpager in.
Later i implemented my own scroll listener. This is not exactly a quick return header pattern but you can add animations in if you have time.
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#SuppressLint("NewApi")
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int scrollOffset = 0;
float transitionY;
if (firstVisibleItem > 0) {
scrollOffset += headerHeight;
if (firstVisibleItem > 1) {
scrollOffset += (firstVisibleItem - 1) * cellHeight;
}
}
if (listView.getChildCount() > 0) {
scrollOffset += -listView.getChildAt(0).getTop();
scrollOffset = -scrollOffset;
}
float scrollDelta = scrollOffset - prevOffset;
float nextY = mQuickReturnView.getY() + scrollDelta;
if (nextY < minRawY) {
transitionY = minRawY;
}
else if (nextY > qReturnDelta) {
transitionY = qReturnDelta;
}
else {
transitionY = nextY;
}
mQuickReturnView.setY(transitionY);
prevOffset = scrollOffset;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
And i used a view pager and a pageradapter in header. In my solution you have to use fixed size for listview items and declare it as CellHeight and a fixed size for header. It's QuickReturnHeight
It's a bit hard to implement this pattern with ListView + Header + ViewPager.
I hope this'll help you.
Related
I'm developing a chat application and I'm trying to load 10 messages at a time, and when the user scrolls up it loads 10 more. therefore I used stackFromBottom=true to load the messages from the bottom :
<ListView
android:id="#+id/messagesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transcriptMode="normal"
android:stackFromBottom="true">
And I have a scroll listener to know when the user reached the top (firstVisibleItem == 0 , i.e the first message) so I can load more messages:
messagesList.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (firstVisibleItem ==0 && !loading && !startApp) {
loading = true;
skip += 10;
fetchMessages = new FetchMessages(); //load more items
fetchMessages.execute(true);
}
}
});
The issue is when you scroll up and reach the top, 10 more messages load but the Listview automatically scrolls to the bottom. I want to stay in the exact same position when new items are added to the Listview, just like any regular chat application.
This is the add messages method:
#Override
protected void onPostExecute(String json) {
if(json!=null){
...
//After Parse JSON ...
chatMessageAdapter.list.add(0,chatMessage); //add the message to the top of the list
chatMessageAdapter.notifyDataSetChanged(); //listview scrolls to the bottom afterwards
}
loading = false;
startApp = false;
}
I tried many of the suggestions in other threads but none of them actually worked. I hope someone could help here.
Many thanks.
what did work for me was that:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// notify dataset changed or re-assign adapter here
// restore the position of listview
mList.setSelectionFromTop(index, top);
Do note you have to remove android:transcriptMode="normal" from your xml, otherwise it wouldn't work.
Actually I don't know how it properly called - overlay, parallax or slideUP, whatever, I have an Activity called "cafe details" which presents a Container(LinearLayout) with header information (name, min price, delivery time min e.t.c) and other container (ViewPager) which contains a ExpandableListView with something information (menus&dishes) and all I want to do is slide up my Viewpager when scrolls listview to scpecific Y position to cover(or overlay) header information.
A similar effect (but with parallax that I don't need to use) looks like this
I can detect when user scrolling listview down or up but how I can move container with ViewPager to overlay other container? Please give me ideas, regards.
UPD
I have tried a huge number of ways how to implement it and all of them unfortunately are not suitable. So now I have come to next variant - add scroll listener to ListView, calculate scrollY position of view and then based on that move the viewpager on y axis by calling setTranslationY();
Here is some code
1) ViewPager's fragment
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
#Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (getActivity() != null) {
((MainActivity) getActivity()).resizePagerContainer(absListView);
}
}
});
2) MainActivity
//object fields
int previousPos;
private float minTranslation;
private float minHeight;
<--------somewhere in onCreate
minTranslation = - (llVendorDescHeaders.getMeasuredHeight()+llVendorDescNote.getMeasuredHeight());
//llVendorDescHeaders is linearLayout with headers that should be hidden
//lVendorDescNote is a textView on green background;
minHeight = llVendorDescriptionPagerContainer.getMeasuredHeight();
//llVendorDescriptionPagerContainer is a container which contains ViewPager
--------->
public void resizePagerContainer(AbsListView absListView){
final int scrollY = getScrollY(absListView);
if (scrollY != previousPos) {
final float translationY = Math.max(-scrollY, minTranslation);
llVendorDescriptionPagerContainer.setTranslationY(translationY);
previousPos = scrollY;
}
}
private int getScrollY(AbsListView view) {
View child = view.getChildAt(0);
if (child == null) {
return 0;
}
int firstVisiblePosition = view.getFirstVisiblePosition();
int top = child.getTop();
return -top + firstVisiblePosition * child.getHeight() ;
}
This simple solution unfortunately has a problem - it is blinking and twitching (I don't know how to call it right) when scrolls slowly. So instead setTranslationY() I've used an objectAnimator:
public void resizePagerContainer(AbsListView absListView){
.............
ObjectAnimator moveAnim = ObjectAnimator.ofFloat(llVendorDescriptionPagerContainer, "translationY", translationY);
moveAnim.start();
..............
}
I don't like this solution because 1) anyway it does resize viewpager with delay, not instantly 2) I don't think that is good idea to create many ObjectAnimator's objects every time when I scroll my listView.
Need your help and fresh ideas. Regards.
I'm assuming that you are scrolling the top header (the ImageView is a child of the header) based on the scrollY of the ListView/ScrollView, as shown below:
float translationY = Math.max(-scrollY, mMinHeaderTranslation);
mHeader.setTranslationY(translationY);
mTopImage.setTranslationY(-translationY / 3); // For parallax effect
If you want to stick the header/image to a certain dimension and continue the scrolling without moving it anymore, then you can change the value of mMinHeaderTranslation to achieve that effect.
//change this value to increase the dimension of header stuck on the top
int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height);
mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.header_height);
mMinHeaderTranslation = -mHeaderHeight + tabHeight;
The code snippets above are in reference to my demo but I think it's still general enough for you.
If you're interested you can check out my demo
https://github.com/boxme/ParallaxHeaderViewPager
Have you tried CoordinatorLayout from this new android's design support library? It looks like it's what you need. Check this video from 3:40 or this blog post.
I want to find out the position or ids related to a ListView's items: only those ones which are completely visible on the screen.
Using listview.getFirstVisibleposition and listview.getLastVisibleposition takes partial list items into account.
I followed a little bit similar approach as suggested by Rich, to suit my requirement which was to fetch completely visible items on screen when List View is scrolled every time.
This is what i did
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//Loop to get tids of all completely visible List View's item scrolled on screen
for (int listItemIndex = 0; listItemIndex <= getListView().getLastVisiblePosition() - getListView().getFirstVisiblePosition(); listItemIndex++) {
View listItem = getListView().getChildAt(listItemIndex);
TextView tvNewPostLabel = (TextView) listItem.findViewById(R.id.tvNewPostLabel);
if (tvNewPostLabel != null && tvNewPostLabel.getVisibility() == View.VISIBLE) {
int listTid = (int) tvNewPostLabel.getTag();
if (listItem.getBottom() < getListView().getHeight()) {//If List View's item is not partially visible
listItemTids.add(listTid);
}
}
}
}
I have not tried this, but here are the pieces of the framework that I believe will get you to what you're looking for (at least this is what I'd try first)
As you've stated, you should get the last visible position from the list view using ListView.getLastVisiblePosition()
You can then access the View representing this position using ListView.getChildAt(position)
You now have a reference to the view, which you can call a combination of View.getLocationOnScreen(location) and View.getHeight()
Also call View.getLocationOnScreen(location) and View.getHeight() on the ListView. y + height of the View should be less than or equal to y + height of the ListView if it is fully visible.
I have two list views. I need to scroll one list view automatically
when the other list view is scrolled. both list views should have this ability
i implemented the onScrollListner in both listviews
for listview 1
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (l1.getChildAt(0) != null) {
Rect r = new Rect();
l1.getChildVisibleRect(l1.getChildAt(0), r, null);
l2.setSelectionFromTop(l1.getFirstVisiblePosition(), r.top);
}
}
for listview 2
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (l2.getChildAt(0) != null) {
Rect r = new Rect();
l2.getChildVisibleRect(l2.getChildAt(0), r, null);
l1.setSelectionFromTop(l2.getFirstVisiblePosition(), r.top);
}
}
I have 2 problems regarding this
1 - the lists do not scroll smoothly. (not like normall listview)
2 - I can only scroll both listviews using one list view.(when i scroll using l2
both get scrolled. but it does not work when i scroll using l1. both stay fixed)
Thanks in advance
It happened becoz you put listview inside listview :)
You can achieve you problem using this.
suppose L1 is inside L2 then
L1.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if(arg1.getAction() == MotionEvent.ACTION_DOWN || arg1.getAction() == MotionEvent.ACTION_MOVE)
{
L2.requestDisallowInterceptTouchEvent(true);
}
return false;
}
});
It's your application so you could do whatever you want but I am confused with what you want to achieve. The sole idea of having two list views is to have separate representation of two different categories of data so that they can be accessed(scrolled or selected) separately.
If you have requirement of representing two categories of data that need to be displayed side by side as a list then you can have a list row with two columns.
something like this will work:
You can populate these two rows with different kind of data and this can be scrolled together.
I found out a different way to get the same result. I remove my two list views and replaced it
with a grid view with two columns. this way i can scroll both without implementing onScrollListner and i can even use the custom adapter i used for the two listviews.
I would like to listen to if list view is scrolling from bottom to up.
I have tried many work around's but couldn't get the logic for this. The answer on this post did not help me either and also answer on this post helped me to detect if listView is scrolled, (no idea weather it's top to bottom or bottom to top). Can any one help me to detect if list view is scrolled up (bottom to up)
Thanks.
I've edited the snippet from the second post:
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(currentFirstVisibleItem > firstVisibleItem){
//scrolling to top of list
}else if (currentFirstVisibleItem < firstVisibleItem){
//scrolling to bottom of list
}
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
}