I am using swipe refresh in a layout that contains a list view. Problem facing is that when I scroll down the list its OK but when scroll up the list it calls on refresh method. So can't access upper items in list view
So how do I make such that only after the list view is scrolled up completely refresh is called? I tried placing android.support.v4.widget.SwipeRefreshLayout at starting. Thanks in advance!
you could subcalss SwipeRefreshLayout and provide a suitable implementation for canChildScrollUp to check if your ListView reached the to (its first child is completely visible), and then use it in your Layout. E.g.
public class MySwipeRefreshLayout extends SwipeRefreshLayout {
private AbsListView mTargetView;
public MySwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean canChildScrollUp() {
if (getChildCount() == 0) {
return true;
}
if (mTargetView == null) {
if (!(getChildAt(0) instanceof AbsListView)) {
return true;
}
mTargetView = (AbsListView) getChildAt(0);
}
if (mTargetView.getChildCount() > 0 && mTargetView.getFirstVisiblePosition() == 0) {
return mTargetView.getChildAt(0).getTop() > 0;
}
return true;
}
}
you could adapt it to your specific use case.
Related
I am recently using CustomBottomSheetBehavior to make an googlemaps like bottom sheet behavior and it works great. I have only one problem.please look at this image
If I use it in a scrolling activity the content of tool-bar covers my list box. so I ahve to add margin-top to my list view. It works but when I draw bottomsheet up toolbar goes up and behind it, there is an empty space. This is because I have added some margin top to make my list's top visible. Is there any way to connect list's margin top to the amount of moving bottom-sheet and when It moves up decrease margin value to and when it moves down increase it?or is there any better way ?
It seems I have to develope my own TopMarginBehavior for this job but I have no idea how to do it.
thanks
Create your own class related to the behavior you want (MarginTopBehavior)
Extends it from CoordinatorLayout.Behavior
Now you have to focus on 2 methods: layoutDependsOn and onDependentViewChanged. With the first one you are selecting the view that your MarginTopBehavior is following, in this case is a NestedScrollView. With the second one you are reacting (the magic!) when the scroll get moved.
At this point you get this:
public class MarginTopBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
private FrameLayout.LayoutParams mLayoutParams;
public MarginTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
}
}
The logic to be applied in onDependentViewChanged is just this:
* Define the cap (min/max margin value) and controls when the margin value has reached one of those cap.
* Update margin value while the values are between the caps. In this point you have to implement an algorithm about what you want (parallax, linear, etc). That is what I'm calling THE_MAGIC_ECC in the next code:
public class MarginTopBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
/**
* Params of the component you want to modify the margin
*/
private FrameLayout.LayoutParams mLayoutParams;
/**
* Used to access DIMENS in your project
*/
private Context mContext;
private int mMinYvalue;
private int mMaxYValue;
public MarginTopBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof NestedScrollView;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (mLayoutParams == null) {
mLayoutParams = (FrameLayout.LayoutParams) child.getLayoutParams();
}
if (dependency.getY() <= mMinYvalue) {
mLayoutParams.setMargins(0, 0, 0, 0);
child.setLayoutParams(mLayoutParams);
return true;
}
else if (dependency.getY() > mMinYvalue && dependency.getY() <= mMaxYValue) {
int THE_MAGIC_ECC = 1 + 2 + 3;
mLayoutParams.setMargins(0, 0, 0, THE_MAGIC_ECC );
child.setLayoutParams(mLayoutParams);
return true;
}
else {
mLayoutParams.setMargins(0, 0, 0, 100);
child.setLayoutParams(mLayoutParams);
return true;
}
}
}
I am using StickyHeaderListview in my project to display contents and for refreshing the list, I am using SwipeRefreshLayout.
The problem here is, when I try to scroll up the list, it starts refreshing the list and not allowing to view the previous items of list.
I want the behavior should be such as the list get refresh only when I've reached to the first item and I try to scroll up , not everytime when i scroll up the list.
Can anyone help on this?
P.s. For implementing SwipeRefreshLayout, I am refering this example
I faced the same problem when using StickyHeaderListview as a direct child of SwipeRefreshLayout. StickyHeaderListview is in fact a FrameLayout wrapping a ListView inside. As nitesh goel explained, this would lead to problems with canChildScrollUp(). Based on nitesh goel's example, this is a full version of CustomSwipeRefreshLayout that works well for me:
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
/**
* A StickyListHeadersListView whose parent view is this SwipeRefreshLayout
*/
private StickyListHeadersListView mStickyListHeadersListView;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setStickyListHeadersListView(StickyListHeadersListView stickyListHeadersListView) {
mStickyListHeadersListView = stickyListHeadersListView;
}
#Override
public boolean canChildScrollUp() {
if (mStickyListHeadersListView != null) {
// In order to scroll a StickyListHeadersListView up:
// Firstly, the wrapped ListView must have at least one item
return (mStickyListHeadersListView.getListChildCount() > 0) &&
// And then, the first visible item must not be the first item
((mStickyListHeadersListView.getFirstVisiblePosition() > 0) ||
// If the first visible item is the first item,
// (we've reached the first item)
// make sure that its top must not cross over the padding top of the wrapped ListView
(mStickyListHeadersListView.getListChildAt(0).getTop() < 0));
// If the wrapped ListView is empty or,
// the first item is located below the padding top of the wrapped ListView,
// we can allow performing refreshing now
} else {
// Fall back to default implementation
return super.canChildScrollUp();
}
}
}
Ok I have got it working. If the SwipeRefreshLayout is the root of the layout and the ListView resides deep into the hierarchy (I had put the ListView inside a RelativeLayout along with the empty TextView) and not the direct child of the SwipeRefreshLayout, it won’t detect a swipe up on the list view properly.
You should create a custom class that extends SwipeRefreshLayout and override canChildScrollUp() method in SwipRefreshLayout
Here is a example :
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout{
private AbsListView view;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setView(AbsListView view){
this.view=view;
}
#Override
public boolean canChildScrollUp() {
return view.getFirstVisiblePosition()!=0;
}
}
I have had a similar problem, the direct child should be an instance of ScrollView (or ListView). The SwipeRefreshLayout will only take in account the direct child's scroll and not the child's of that direct child. I managed to solve this by using two SwipeRefreshLayouts.
I posted the code on github.
Hi i think i made something for a generally use :
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {
private View v;
public CustomSwipeRefreshLayout(Context context) {
super(context);
}
public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setView(View v) {
this.v = v;
}
#Override
public boolean canChildScrollUp() {
return this.v.canScrollVertically(-1);
}
}
With that solution, only set the view you want to scroll inside the SwipeRefreshLayout, after call canChildScrollUp(). like this :
this.refreshLayout.setView(aView);
this.refreshLayout.canChildScrollUp();
I don't test it a lot, but if i'm right it will work for every view at every place (direct child or not) in the SwipeRefreshLayout.
(for me it was SwipeRefreshLayout => RelativeLayout => SrcollView => linearLayout)
This is very simple solution:
list.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int topRowVerticalPosition = (list == null || list.getChildCount() == 0) ?
0 : list.getChildAt(0).getTop();
swipeRefreshLayout.setEnabled((topRowVerticalPosition >= 0));
}
});
So, if you're on the top of the listview you will be enabled to do refresh.
How do you know if your ListView has enough number of items so that it can scroll?
For instance, If I have 5 items on my ListView all of it will be displayed on a single screen. But if I have 7 or more, my ListView begins to scroll. How do I know if my List can scroll programmatically?
Diegosan's answer cannot differentiate when the last item is partially visible on the screen. Here is a solution to that problem.
First, the ListView must be rendered on the screen before we can check if its content is scrollable. This can be done with a ViewTreeObserver:
ViewTreeObserver observer = listView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (willMyListScroll()) {
// Do something
}
}
});
And here is willMyListScroll():
boolean willMyListScroll() {
int pos = listView.getLastVisiblePosition();
if (listView.getChildAt(pos).getBottom() > listView.getHeight()) {
return true;
} else {
return false;
}
}
As per my comment on Mike Ortiz' answer, I believe his answer is wrong:
getChildAt() only counts the children that are visible on screen. Meanwhile, getLastVisiblePosition returns the index based on the Adapter. So if the lastVisiblePosition is 8 because it's the 9th item in the list and there are only 4 visible items on screen, you're gonna get a crash. Verify it by calling getChildCount() and see. The indices for getChildAt are not the same as the indices for the data set. Check this out: ListView getChildAt returning null for visible children
Here's my solution:
public boolean isScrollable() {
int last = listView.getChildCount()-1; //last visible listitem view
if (listView.getChildAt(last).getBottom()>listView.getHeight() || listView.getChildAt(0).getTop()<0) { //either the first visible list item is cutoff or the last is cutoff
return true;
}
else{
if (listView.getChildCount()==listView.getCount()) { //all visible listitem views are all the items there are (nowhere to scroll)
return false;
}
else{ //no listitem views are cut off but there are other listitem views to scroll to
return true;
}
}
}
You cannot detect this before android render the screen with the listView. However, you can absolutely detect this post-render.
boolean willMyListScroll() {
if(listView.getLastVisiblePosition() + 1 == listView.getCount()) {
return false;
}
return true;
}
What this does is check if the listView visible window contains ALL your list view items. If it can, then the listView will never scroll and the getLastVisiblePosition() will always be equal to the total number of items in the list's dataAdapter.
This is the code I wrote for showing a picture after the last row of the listview:
public class ShowTheEndListview
{
private ImageView the_end_view;
private TabbedFragRootLayout main_layout;
private ListView listView;
private float pas;
private float the_end_img_height;
private int has_scroll = -1;
public ShowTheEndListview(float height)
{
the_end_img_height = height;
pas = 100 / the_end_img_height;
}
public void setData(ImageView the_end_view, TabbedFragRootLayout main_layout, ListView listView)
{
this.the_end_view = the_end_view;
this.main_layout = main_layout;
this.listView = listView;
}
public void onScroll(int totalItemCount)
{
if(totalItemCount - 1 == listView.getLastVisiblePosition())
{
int pos = totalItemCount - listView.getFirstVisiblePosition() - 1;
View last_item = listView.getChildAt(pos);
if (last_item != null)
{
if(listHasScroll(last_item))
{
// Log.e(TAG, "listHasScroll TRUE");
}
else
{
// Log.e(TAG, "listHasScroll FALSE");
}
}
}
}
private boolean listHasScroll(View last_item)
{
if(-1 == has_scroll)
{
has_scroll = last_item.getBottom() > (main_layout.getBottom() - the_end_img_height - 5) ? 1 : 0;
}
return has_scroll == 1;
}
public void resetHasScroll()
{
has_scroll = -1;
}
}
AbsListView includes this:
/**
* Check if the items in the list can be scrolled in a certain direction.
*
* #param direction Negative to check scrolling up, positive to check scrolling down.
* #return true if the list can be scrolled in the specified direction, false otherwise.
* #see #scrollListBy(int)
*/
public boolean canScrollList(int direction);
You need to override layoutChildren() and use it within that:
#Override
protected void layoutChildren() {
super.layoutChildren();
isAtBottom = !canScrollList(1);
isAtTop = !canScrollList(-1);
}
This is how i used to check
if (listView.getAdapter() != null
&& listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1
&& listView.getChildAt(listView.getChildCount() - 1).getBottom() == listView.getBottom())
if it gives true, then list is at the bottom
Use the setOnScrollListener, by applying the callback to OnScrollListener, you can determine if scrolling is taking place using the pre-defined constants and handle the situation accordingly.
boolean listBiggerThanWindow = appHeight - 50 <= mListView.getHeight();
Toast.makeText(HomeActivity.this, "list view bigger that window? " + listBiggerThanWindow, Toast.LENGTH_LONG)
.show();
if (listBiggerThanWindow) {
// do your thing here...
}
you can get dimensions in onCreate() by calling post(Runnable) on View.
I have the following structure of my app:
v4 FragmentActivity --> v4 ViewPager --> v4 Fragment
--> v4 ListFragment
I'm using ActionBarSherlock (which I would really recommend), and the structure of the Activity is based on the demo at https://github.com/JakeWharton/ActionBarSherlock/blob/master/samples/demos/src/com/actionbarsherlock/sample/demos/app/ActionBarTabsPager.java . So the two fragments are displayed as two tabs for the user.
When a user clicks an element of the ListFragment I want to load an url in a WebView in the same place as the list is. That is, I want to replace the ListFragment (put it on the back stack) with a new WebView.
So far I've tried using FragmentTransaction.replace() from the Activity. That kind of works, except two issues:
The ListFragment doesn't display the WebView before I rotate the device (i.e. the acitity is recreated).
The content of the other tab disappears (it's just blank)
What is the correct way to replace the ListFragment with another Fragment?
Alternatevely, you could use a custom HorizontalScrollView instead of a ViewPager and overwrite its onTouchEvent method to get the same snapping effect you get with a ViewPager, like this:
public class MyHorizontalScrollView extends HorizontalScrollView {
public MyHorizontalScrollView(Context context) {
super(context);
}
public MyHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
if (mScrollable) {
View child = getChildAt(0);
if (child != null) {
final int currentX = getScrollX();
final int windowWidth = getResources().getDisplayMetrics().widthPixels;
final int totalWidth = child.getWidth();
int showingElementNumber = 1;
int i = windowWidth;
while (i < currentX && i < totalWidth) {
i+=windowWidth;
showingElementNumber++;
}
int scrollTo = 0;
if (currentX < (windowWidth * (showingElementNumber - 1) + windowWidth/2)) { // Previouses widths + half the current
scrollTo = windowWidth * (showingElementNumber - 1);
} else {
scrollTo = windowWidth * showingElementNumber + marginSize;
}
smoothScrollTo(scrollTo, 0);
return false;
}
}
return super.onTouchEvent(ev);
default:
return super.onTouchEvent(ev);
}
}
}
Then you could add two Fragments inside it and use a regular FragmentTransaction with addToBackStack() to get what you want.
NOTE: If you want to use the above code, just make sure your Fragments are the same width as the entire screen and remember you structure should be something like
MyHorizontalScrollView > LinearLayout > YourFragment1
> YourFragment2
As a workaround I've done what AndroidTeam At Mail.Ru and connoisseur suggest as answers on Replace Fragment inside a ViewPager. This is of course a pretty bad way solving this. I feel kinda dirty now. :-S
I've put a WebView loading an image inside a ViewPager. When I try to scroll the image horizontally I move over to the next view instead of scrolling the image.
Is it possible to make it scroll to the end of the image before moving over to the next view?
#Override
public Object instantiateItem(View view, int i) {
WebView webview = new WebView(view.getContext());
webview.setHorizontalScrollBarEnabled(true);
webview.loadUrl("http://www.site.with.an/image.gif");
((ViewPager) view).addView(webview, 0);
return webview;
}
The following is a real working solution which will scroll the WebView on a horizontal swipe as long as it can scroll. If the WebView cannot further scroll, the next horizontal swipe will be consumed by the ViewPager to switch the page.
Extending the WebView
With API-Level 14 (ICS) the View method canScrollHorizontally() has been introduced, which we need to solve the problem. If you develop only for ICS or above you can directly use this method and skip to the next section. Otherwise we need to implement this method on our own, to make the solution work also on pre-ICS.
To do so simply derive your own class from WebView:
public class ExtendedWebView extends WebView {
public ExtendedWebView(Context context) {
super(context);
}
public ExtendedWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean canScrollHor(int direction) {
final int offset = computeHorizontalScrollOffset();
final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
if (range == 0) return false;
if (direction < 0) {
return offset > 0;
} else {
return offset < range - 1;
}
}
}
Important: Remember to reference your ExtendedWebView inside your layout file instead of the standard WebView.
Extending the ViewPager
Now you need to extend the ViewPager to handle horizontal swipes correctly. This needs to be done in any case -- no matter whether you are using ICS or not:
public class WebViewPager extends ViewPager {
public WebViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ExtendedWebView) {
return ((ExtendedWebView) v).canScrollHor(-dx);
} else {
return super.canScroll(v, checkV, dx, x, y);
}
}
}
Important: Remember to reference your WebViewPager inside your layout file instead of the standard ViewPager.
That's it!
Update 2012/07/08: I've recently noticed that the stuff shown above seems to be no longer required when using the "current" implementation of the ViewPager. The "current" implementation seems to check the sub views correctly before capturing the scroll event on it's own (see canScroll method of ViewPager here). Don't know exactly, when the implementation has been changed to handle this correctly -- I still need the code above on Android Gingerbread (2.3.x) and before.
Although Sven mentioned for layout file I want to add detail. After you extend Webview and ViewPager classes,
Inside your activity you will cast to your extended class like this:
web = (MyWebView) findViewById(R.id.webview);
Inside your layout file like this:
<your.package.name.MyWebView
android:id="#+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>