What I'm trying to achieve:
--- Stack Layout / Relative Layout---
- some widget (e.g. Label) -
- some widget (e.g. Label) -
- ListView -
-------------------------------------
However, I also want the following scroll behaviour:
Top widgets disappear first then ListView starts scrolling. Basically, I want a "natural" scrolling behaviour.
One way I can achieve this is by making the whole page a ListView and putting the widgets as a Header for the ListView
But that has one problem... which I think is a bug in Xamarin.Forms:
If you have a long Label (what else to hold text?), it will not display all of it. It will actually make it scrollable and display only part of it at a time. What makes this even worse is that you cannot scroll the Label "easily", you have to try multiple times to make it scroll the label instead of the page, it's obviously bugged. That happens even if the page itself has hit the end (i.e. can't scroll any more), the Label still can't be scrolled easily.
Is there another way or a workaround to achieve what I want?
As one of the comments suggests, the best is to set the HeightRequest of the Label to the needed value.
Here is how I measure the height of the text on Android (you'll need DependencyService, if you want to call this function from Xamarin.Forms):
double measureString(string text, string font, double fontSize, double width)
{
var textView = new TextView(global::Android.App.Application.Context);
textView.Typeface = Android.Graphics.Typeface.Create(font, Android.Graphics.TypefaceStyle.Normal);
textView.SetText(text, TextView.BufferType.Normal);
textView.SetTextSize(Android.Util.ComplexUnitType.Px, (float)(fontSize * Xamarin.Forms.Forms.Context.Resources.DisplayMetrics.ScaledDensity));
int widthMeasureSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec((int)(width * Xamarin.Forms.Forms.Context.Resources.DisplayMetrics.Density), width == 0 ? Android.Views.MeasureSpecMode.Unspecified : Android.Views.MeasureSpecMode.Exactly);
int heightMeasureSpec = Android.Views.View.MeasureSpec.MakeMeasureSpec(0, Android.Views.MeasureSpecMode.Unspecified);
textView.Measure(widthMeasureSpec, heightMeasureSpec);
textView.SetIncludeFontPadding(false);
return (width == 0 ? textView.MeasuredWidth : textView.MeasuredHeight) / Xamarin.Forms.Forms.Context.Resources.DisplayMetrics.Density;
}
Related
First, my apologies for the title. I thought long and hard to choose a more descriptive title but couldn't really find one.
I have a screen with a header (variable length - using sliding action bar), a middle part (scrollview) and a bottom part for showing ads (fixed length). I am able to write the screen using RelativeLayout but what I would really like to do is this:
If the scrollview's content (the middle part) is not long enough to fill the screen (including header), I would like to show the ad at the bottom of the screen
If the scrollview's content + header is longer than the screen, I would like to show the ad BELOW the scroll view's contents, meaning it's not visible unless the user scrolls to the bottom of the fragment.
Imagine the scroll view's content as a short text or an image + a long text. For the first case, the scroll view content will be short and therefore I'd ideally show the ad at the bottom of the screen since I have enough space but if it's image + text, it will be larger than the screen height and as such, I would like to show the ad after the user has scrolled to the bottom.
The reason I want to do this is so that the ad doesn't necessarily take space if there's useful content on the screen (user experience).
Is there anyway to achieve this in android without writing my own custom layout? If so, which view would you recommend?
Many thanks in advance,
I don't think there is such a layout. You can do this programmatically. The ScrollView always has a single child so you can get the content height this way:
int contentHeight = scrollView.getChildAt(0).getHeight();
You can get window height this way
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int windowHeight = size.y;
The height of the header and footer is fixed so you can do the maths decide where to put the footer.
int totalHeight = heaederHeight + contentHeight + footerHeight
if(totalHeight < windowHeight) {
// add the footer to the scrollview
} else {
// add the footer below the scrollview
}
The cons of this approach is that you have to do all the magic manually.
I hope this helped you :)
I'm implementing a ListView where the elements are expanded by clicking on them to show all the text. I'm using this implementation.
As you can see that project was made considering hardcoded strings and one of the arguments is the height of the TextView after the expansion. Since I can't know the final height o the TV because my strings are fetched from the internet I set the expanded height to:
AbsListView.LayoutParams.WRAP_CONTENT;
As you may know WRAP_CONTENT to android is just a -2 Integer. My problem is that the -2 is passed to the animaton method which does the following calculation:
float height = (ToHeight - FromHeight) * interpolatedTime + FromHeight;
Because ToHeight is -2 (and assuming FromHeight is 200) the TextView animation height goes someting like this:
200.0
191.05173
175.801
158.36632
134.70096
105.341835
78.51846
53.146957
31.025742
9.73967
-1.203598
-2.0
So the animation gives the impression is closing but after that height gets just fine because is -2 (WRAP_CONTENT). How can I solve this?
You may need to show some more code or your XML file that contains the TextView you're trying to animate. A solution that might work for you is to dynamically calculate the height your TextView should be when expanded. You can use TextView.getLineCount() to get the number of lines of text and multiply that with TextView.getLineHeight() to get the size, or at least something close, of how tall the TextView should expand out to.
In general, the WRAP_CONTENT constant is used as a flag for Android's framework to specify dimensions during layout.
from an answer to one of my other questions I found an Google Demo of a ListView subclass that allows item reorder.
The demo works great, but I am having some trouble to understand how the it works:
When an item is dragged above/below the bounds of the ListView, the ListView starts scrolling up/down to reveal new items. The necessary calculation uses different parameters of the underling ScrollView:
public boolean handleMobileCellScroll(Rect r) {
int offset = computeVerticalScrollOffset();
int height = getHeight();
int extent = computeVerticalScrollExtent();
int range = computeVerticalScrollRange();
int hoverViewTop = r.top;
int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
heightis the height of the ListView itself
offsetis the scroll position = how many units/pixels have been scrolled up/down
rangeis the height of the complete content.
extent - well, what is this?
ListView inherits computeVerticalScrollExtent() from View and the docu says:
Compute the vertical offset of the vertical scrollbar's thumb within
the horizontal range. This value is used to compute the position of
the thumb within the scrollbar's track.
If one looks at the code computeVerticalScrollExtent() is not implemented by one of the sublasses but only directly by View: It simply returns the height of the view.
This makes sense: If the ListView/ScrollView has a height of 500, the part of the scroll content that is visible at a time is also 500. Is this the meaning of the ScrollExtent? Why is ScrollExtent necessary? Why not simply use getHeight() directly?
I think I am missing something and I am happy about any hint!
compute*ScrollOffset - Defines the distance between the start of the scrollable area and the top of the current view window inside the scrollable area. So for example, if your list has 10 items and you've scrolled down so the 3rd item is at the top-most visible item, then the offset is 3 (or 3*itemHeight, see below).
compute*ScrollExtent - Defines the size of the current view window inside the scrollable area. So for example, if your list has 10 items and you can currently see 5 of those items, then the extent is 5 (or 5*itemHeight, see below).
compute*ScrollRange - Defines the size of the current scrollable area. So for example, if your list has 10 items then the range is 10 (or 10*itemHeight, see below).
Note that all these methods can return values in different units depending on their implementation, so for the examples above, I am using the indices, but in some cases these methods will return pixel values equivalent to the width or height of the items.
In particular, the LinearLayoutManager of the RecyclerView will return indices if the 'smooth scrollbar' feature is disabled, otherwise it will return pixel values. See ScrollbarHelper in the support library for more information.
Additional reading: https://groups.google.com/forum/#!topic/android-developers/Hxe-bjtQVvk
It's kinda late, but hope it's ok.
1) The method actually is implemented by the subclasses. For example, this is what it looks like for AbsListView (ListView's superclass).
2) View's height can be different from its vertical scroll's height - just imagine a View with weird top/bottom padding .
These two points pretty much make other questions irrelevant :)
This is a sample code which might help you to understand as to how to get scrollBar top and bottom using computeVerticalScrollExtent:
scrollbarTop = (float)this.computeVerticalScrollExtent()/this.computeVerticalScrollRange()*this.computeVerticalScrollOffset();
scrollbarBottom = (float)this.computeVerticalScrollExtent()/this.computeVerticalScrollRange()*(this.computeVerticalScrollExtent()+this.computeVerticalScrollOffset());
According to the article from here:
I found this explanation correct.
ListView with 30 items has scrollRange equals to 3000, that is due to scrollRange = numberOfItems * 100, thus scrollExtent = numberOfVisibleItems * 100 and scrollOffset = numberOfScrolledItems * 100. You can find evidance of these words in the source code of AbsListView
This question is very specific, What I am trying to do (with a list view) is described in great detail in the following article: http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
Thanks #kaushal trivedi for the link
Details:
I have an android application I am working on that uses a list view with a custom adapter. The Listview Contains a Custom header of a non-fixed height. Also please note that the list items are also of variable height. My goal is to mimic the effect produced in the latest gmail app (as an example) where when you are viewing an email, and scroll past the header, it sticks to the top of the screen just under the action bar and the content continues to scroll under it. What I would like to do, is stick the bottom half of my header to the top of the screen.
My initial reasoning was to create an invisible view fixed in the desired location, and when the user scrolled to or past that location, make the view visible. The issue in this logic, is I need the exact pixel scroll height, which after many attempts I have determined very difficult to do. The exact issue I ran into is, it is not possible from what I can gather to retrieve the pixel level Y-scroll in an onScroll event, I have only been able to retrieve the value in the onScrollStateChanged event. Which as described above will not achieve the desired functionality.
Working with the onScroll event "int firstVisibleItem, int visibleItemCount, int totalItemCount" parameters is also not an option because of the fact that the content I want to "stick" is not the size of a list item, but a fraction of the size of the variable height header.
Is there a correct way to accomplish this effect? My current minSDK level is 10.
Update 10/10/13
I made some progress. The following code syncs the Y position floating view I have on the screen with the list view. b is the view I am setting just as an example.
NOTE: This is used in the onScroll event of the list view.
View c = view.getChildAt(0);
if (c != null) {
int currY = c.getTop();
int diffY = currY - lastY;
lastY = currY;
b.setTop(b.getTop() + diffY);
}
Now the issue is, the header of my List is a non fixed height as I said earlier. So I need to get the height of the header and apply an offset to "b" to place it at the bottom of the list header floating above the list.
This is the code I've tried so far.
header.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
The issue here us header.getMeasuredHeight(); always resolves to the same value no matter how tall the actual height is.
I understand I cannot get the height until after it is displayed. Is there a way I can get that value and set the offset after it is rendered?
Update 10/11/13
I Answered my last question as soon as I woke up this morning.
While the View.measure() code was returning a height. It appears to be the default height of the view, assuming there was no text (that would ultimately stretch the view). So I used the below event to listen for when the view is displayed, and then record its actual height (which works exactly as I had hoped :) )
ViewTreeObserver vto = header.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
b.setY(header.getMeasuredHeight() - 80); //80 is a temp offset
}
});
I have to go to work soon and being that I have still not fully achieved the desired effect, I will not mark this as answered yet. Hopefully I will be able to sit down and finish this in the next day or two. I am still open to suggestions on better ways of doing this.
Okay, so after a lot of time and research, I have found an answer to my question.
First off, Thank you #kaushal for this link: http://www.pushing-pixels.org/2011/07/18/android-tips-and-tricks-synchronized-scrolling.html
My solution ended up being somewhat complex. So instead of trying to describe it here, I made an example app and posted it here: https://github.com/gh123man/Partial-Header-ListView-Scroll-Sync
The specific file containing the code for the solution is here: https://github.com/gh123man/Partial-Header-ListView-Scroll-Sync/blob/master/src/com/example/partialheaderlistviewscrollsync/MainActivity.java
Does anyone know of a way to center a ListView based on its current selection or selection set with setSelection?
I did see this other StackOverflow question without any answers: Android ListView center selection
Thanks,
Kevin
First, get the height of the ListView using getHeight, which returns the height of the ListView in pixels.
Then, get the height of the row's View using the same method.
Then, use setSelectionFromTop and pass in half of the ListView's height minus half of the row's height.
Something like:
int h1 = mListView.getHeight();
int h2 = v.getHeight();
mListView.setSelectionFromTop(position, h1/2 - h2/2);
Or, instead of doing the math, you might just pick a constant for the offset from the top, but I would think it might be more fragile on different devices since the second argument for setSelectionFromTop appears to be in pixels rather than device independent pixels.
I haven't tested this code, but it should work as long as your rows are all roughly the same height.
You will need to have the scroll view and the view of the item selected. Then you can simply do:
scrollView.smoothScrollTo(0, selectedView.getTop() - (scrollView.getHeight() / 2) + (selectedView.getHeight() / 2), 0);
This will center the scroll view exactly on selectedView
I haven't tried any of this but based on the current selection could you use public void smoothScrollByOffset (int offset) to get the view to scroll to where you want so that your selection is in the middle of the view?