Set Android navigation drawer menu footer at bottom of screen - android

I'm trying to do something that from the beginning I already know it's quite difficult to achieve, and it's to place a footer for a navigation drawer menu at the bottom of the screen.
The fact is that I need the footer to be exactly at the bottom of the screen when the list view items of the drawer are all visible on the screen and the footer should be just below the last item when elements go off the screen and scrollbars appear (normal behaviour).
For that I'm using the addFooterView method in the next way
ViewGroup footer = (ViewGroup)inflater.inflate(R.layout.testme_drawer_footer, mDrawerList, false);
mDrawerList.addFooterView(footer, null, false);
Where testme_drawer_footer is the next layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/footer_menu_facebook"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="start"
android:orientation="horizontal">
<TextView
android:id="#+id/drawer_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#8d3169"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:textColor="#fff"
android:textSize="12sp"/>
</LinearLayout>
Without doing anything the addFooterView just behaves the normal way and if elements are all visible in screen and there is much blank space left at bottom the footer just places below the last element (bad for what I'm trying to achieve).
I've tried many suggestions in different StackOverflow posts with no avail and after struggling my head for a while I was able to get something very close to what I need, and it's the next:
I have given all the list view elements a fixed height and the same for header so in the end I calculate footer height with screenHeight - statusBarHeight - actionBarHeight - drawerHeaderHeight - listViewElementHeight * numberOfElements in the next way:
ViewGroup footer = (ViewGroup)inflater.inflate(R.layout.testme_drawer_footer, mDrawerList, false);
int screenHeight = GeneralUtils.getScreenHeight(oActivity);
int statusBarHeight = GeneralUtils.getStatusBarHeight(oActivity);
int actionBarHeight = GeneralUtils.getActionBarHeight(oActivity);
int drawerHeaderHeight = GeneralUtils.dp2px(60, oActivity);
int menuItemHeight = drawerHeaderHeight;
int totalItemsHeight = menuItemHeight*(endItem-startItem+1);
int footerHeight = screenHeight - statusBarHeight - actionBarHeight - drawerHeaderHeight - totalItemsHeight;
footer.setMinimumHeight(footerHeight);
mDrawerList.setFooterDividersEnabled(true);
mDrawerList.addFooterView(footer, null, false);
But it's most likely some of the height measurement methods are not being quite exact and there is a difference of some pixels that it's not equal in all tested devices.
I know this is not the best way to do it, in fact I don't like it, I don't like to set a fixed height for drawer header and elements insted of wrap_content and I don't like calculating overall height this way but cannot find any other working way to achieve this.
Any help?
Thanks in advance!
This is the way I set the ListView in all activities:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llMainMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="horizontal">
//MAIN CONTENT
<ListView
android:id="#+id/left_drawer"
android:layout_width="#dimen/navigation_drawer_width_phone"
android:layout_height="match_parent"
android:layout_gravity="left|start"
android:headerDividersEnabled="true"
android:background="#ffeeeeee"/>
</android.support.v4.widget.DrawerLayout>

After struggling my head for the whole day I've finally found a great and accurate solution and I'll answer my own question in order to help anyone who might be facing the same problem.
The key was to set fixed heights to ListView items (in my case I've set a fixed height of 60dp) and calculate ListView height before it gets drawn with a TreeObserver, then you just multiply fixed item height by number of items and substract this value to ListView height. In my case I had to add header height (also fixed) and item dividers size to the total ListView height, so in the end my piece of code looks like follows:
final int numberOfItems = endItem - startItem + 1;
final ViewTreeObserver treeObserver = mDrawerList.getViewTreeObserver();
treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mDrawerList.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int mDrawerListHeight = mDrawerList.getHeight();
int dividerHeight = mDrawerList.getDividerHeight() * numberOfItems;
int footerHeight = calculateFooterHeight(oActivity, mDrawerListHeight, numberOfItems, dividerHeight);
ViewGroup footer = (ViewGroup)inflater.inflate(R.layout.testme_drawer_footer, mDrawerList, false);
footer.setMinimumHeight(footerHeight);
mDrawerList.setFooterDividersEnabled(true);
mDrawerList.addFooterView(footer, null, false);
UserSession us = new UserSession(oActivity);
String footerText = us.getUserSession().getAlias();
TextView tvDrawerFooter = (TextView) oActivity.findViewById(R.id.drawer_footer);
tvDrawerFooter.setText(footerText);
// Set the list's click listener
DrawerItemClickListener drawer = new DrawerItemClickListener();
drawer.oActivity = oActivity;
mDrawerList.setOnItemClickListener(drawer);
}
});
private static int calculateFooterHeight(Activity oActivity, int listViewHeight, int numberOfItems, int dividerHeight){
int drawerHeaderHeight = GeneralUtils.dp2px(60, oActivity);
int menuItemHeight = drawerHeaderHeight;
int totalItemsHeight = menuItemHeight * numberOfItems;
return listViewHeight - drawerHeaderHeight - totalItemsHeight - dividerHeight;
}
I truly hope I can help anyone else with the same problem.

Related

Spinner dropdown items: how is width determined?

I have a Spinner in my app, with customized dropdown views. This is what the layout of the dropdown items look like:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/leadingButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_weight="0" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_gravity="center_vertical"
android:layout_weight="1">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/dropdown_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/dropdown_text_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/dropdown_text" />
</RelativeLayout>
</FrameLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/trailingButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_weight="0" />
</LinearLayout>
Android Studio warns me that my FrameLayout is useless. But when I take out the FrameLayout the dropdown views become narrow, and don't align with the spinner itself anymore. I have had the same problem when I tried to rewrite the drop-down items with a ConstraintLayout: the dropdown list became narrow, about half of the Spinner's size, and could not display all text, even though the ConstraintLayout had android:layout_width="match_parent".
A sketch to illustrate what I mean:
Why does this happen? How can I predict what the width of the dropdown menu will be based on the layout?
I find this dropdown view sizing quite magical
Did you look at the source code of the Spinner class? I just did. Here's what I found (API 27 Sources):
The spinner uses a ListView internally (first LOL), backed by DropdownPopup (private class):
private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
Before looking at it, look at ListPopupWindow because has a lot of info about the problems it has to deal with. It's a big class but among these things, you can see:
private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
private int mDropDownHorizontalOffset;
private int mDropDownVerticalOffset;
It appears the DropDown is -by default- WRAPPING the content based upon the base class, however, the DropDownPopup that drives (and contains the adapter with all the items in the spinner) also has a void computeContentWidth() { method.
This method is called from the show() method, so before showing the popup, this computation happens every time.
I think here's part of the answer you're looking for:
void computeContentWidth() {
final Drawable background = getBackground();
int hOffset = 0;
if (background != null) {
background.getPadding(mTempRect);
hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
} else {
mTempRect.left = mTempRect.right = 0;
}
final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
final int spinnerPaddingRight = Spinner.this.getPaddingRight();
final int spinnerWidth = Spinner.this.getWidth();
if (mDropDownWidth == WRAP_CONTENT) {
int contentWidth = measureContentWidth(
(SpinnerAdapter) mAdapter, getBackground());
final int contentWidthLimit = mContext.getResources()
.getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
if (contentWidth > contentWidthLimit) {
contentWidth = contentWidthLimit;
}
setContentWidth(Math.max(
contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
} else if (mDropDownWidth == MATCH_PARENT) {
setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
} else {
setContentWidth(mDropDownWidth);
}
if (isLayoutRtl()) {
hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
} else {
hOffset += spinnerPaddingLeft;
}
setHorizontalOffset(hOffset);
}
You may want to DEBUG and set breakpoints here to observe what these values are and what they mean.
The other piece there is the setContentWidth() method. This method is from the ListPopupWindow, and looks like:
/**
* Sets the width of the popup window by the size of its content. The final width may be
* larger to accommodate styled window dressing.
*
* #param width Desired width of content in pixels.
*/
public void setContentWidth(int width) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
mDropDownWidth = mTempRect.left + mTempRect.right + width;
} else {
setWidth(width);
}
}
And setWidth (also in that class) all it does is:
/**
* Sets the width of the popup window in pixels. Can also be {#link #MATCH_PARENT}
* or {#link #WRAP_CONTENT}.
*
* #param width Width of the popup window.
*/
public void setWidth(int width) {
mDropDownWidth = width;
}
This mDropDownWidth seems used all over the place, but also made me found this other method in ListPopupWindow...
/**
* Sets the width of the popup window by the size of its content. The final width may be
* larger to accommodate styled window dressing.
*
* #param width Desired width of content in pixels.
*/
public void setContentWidth(int width) {
Drawable popupBackground = mPopup.getBackground();
if (popupBackground != null) {
popupBackground.getPadding(mTempRect);
mDropDownWidth = mTempRect.left + mTempRect.right + width;
} else {
setWidth(width);
}
}
So there you have it, more logic needed including the "window dressing" (?)
I agree the Spinner is a badly designed class (or rather, with outdated design) and even more so with the name (at this Google I/O in 2019, they actually explained in one of the sessions why the name "Spinner" hint: it comes from the 1st android prototypes). By looking at all this code, it would take a few hours to figure out what the spinner is trying to do and how it works, but the trip won't be pleasant.
Good luck.
I will reiterate my advice to use ConstraintLayout which you said you were familiar with; at the very least, discard weights.
By looking at how this works (ListView!!!) the weight calculation warrants a 2nd measure/layout pass, which is not only extremely inefficient and not needed, but also may be causing issues with the internal data adapter this DropDown thing manages so the "list" is displayed.
Ultimately, another class is also involved, this is all presented in a PopupView. PopupViews are what you see when you open a Menu item for example, and are very hard to customize sometimes, depending what you want to do.
Why Google chose this approach at the time, I don't know, but it certainly warrants an update and Material Design hasn't brought much to the table in this regard yet, as it will always be incomplete or in alpha state a year behind anything else.
It is telling you the FrameLayout is useless because it has a single child view ( the Relative Layout).
Your Framelayout has width defined as so:
<FrameLayout
android:layout_width="0dp"
android:layout_weight="1"
Your relative layout has its width defined as:
<RelativeLayout
android:layout_width="match_parent"
So just removing the FrameLayout means that a different "rule" is in place for the width.
To truly replace the FrameLayout with the RelativeLayout it should look like this:
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"

Make last Item / ViewHolder in RecyclerView fill rest of the screen or have a min height

I'm struggeling with the RecyclerView. I use a recycler view to display the details of my model class.
//My model class
MyModel {
String name;
Double latitude;
Double longitude;
Boolean isOnline;
...
}
Since some of the values might not be present, I use the RecyclerView with custom view types (one representing each value of my model).
//Inside my custom adapter
public void setModel(T model) {
//Reset values
itemCount = 0;
deviceOfflineViewPosition = -1;
mapViewPosition = -1;
//If device is offline, add device offline item
if (device.isOnline() == null || !device.isOnline()) {
deviceOfflineViewPosition = itemCount;
itemCount++;
}
//Add additional items if necessary
...
//Always add the map as the last item
mapViewPosition = itemCount;
itemCount++;
notifyDataSetChanged();
}
#Override
public int getItemViewType(int position) {
if (position == deviceOfflineViewPosition) {
return ITEM_VIEW_TYPE_OFFLINE;
} else if (position == mapViewPosition) {
return ITEM_VIEW_TYPE_MAP;
} else if (...) {
//Check for other view types
}
}
With the RecyclerView I can easily determine at runtime which values are available and add corresponding items to the RecyclerView datasource. I simplyfied the code but my model has a lot more values and I have a lot more view types.
The last item in the RecyclerView is always a map and it is always present. Even if there is no value at all in my model, there will at least be one item, the map.
PROBLEM: How can I make the last item in RecyclerView fill the remaining space on screen and also have a min heigh. The size shall be what ever value is lager: the remaining space or the min height. For example:
Model has a few values, which in sum take up 100dp of a 600dp screen -> map heigh should be 500dp
Model has a lot of values, which in sum take up 500dp of a 600dp screen -> map heigh should be a min value of 200dp
Model has no values -> map fills whole screen
You can find the remaining space in RecyclerView after laying out the last item and add that remaining space to the minHeight of the last item.
val isLastItem = getItemCount() - 1 == position
if (isLastItem) {
val lastItemView = holder.itemView
lastItemView.doOnLayout {
val recyclerViewHeight = recyclerView.height
val lastItemBottom = lastItemView.bottom
val heightDifference = recyclerViewHeight - lastItemBottom
if (heightDifference > 0) {
lastItemView.minimumHeight = lastItemView.height + heightDifference
}
}
}
In onBindHolder check if the item is last item using getItemCount() - 1 == position. If it is the last item, find the height difference by subtracting recyclerView height with lastItem bottom (getBottom() gives you the bottom most pixel of the view relative to it's parent. In this case, our parent is RecyclerView).
If the difference is greater than 0, then add that to the current height of the last view and set it as minHeight. We are setting this as minHeight instead of setting directly as height to support dynamic content change for the last view.
Note: This code is Kotlin, and doOnLayout function is from Android KTx. Also your RecyclerView height should be match_parent for this to work (I guess that's obvious).
You can extend LinearLayoutManager to layout the last item yourself.
This is a FooterLinearLayoutManager that will move the last item of the list to the bottom of the screen (if it isn't already there). By overriding layoutDecoratedWithMargins the LinearLayouyManager calls us with where the item should go, but we can match this against the parent height.
Note: This will not "resize" the view, so fancy backgrounds or similar probably won't work, it will just move the last item to the bottom of the screen.
/**
* Moves the last list item to the bottom of the screen.
*/
class FooterLinearLayoutManager(context: Context) : LinearLayoutManager(context) {
override fun layoutDecoratedWithMargins(child: View, left: Int, top: Int, right: Int, bottom: Int) {
val lp = child.layoutParams as RecyclerView.LayoutParams
if (lp.viewAdapterPosition < itemCount - 1)
return super.layoutDecoratedWithMargins(child, left, top, right, bottom)
val parentBottom = height - paddingBottom
return if (bottom < parentBottom) {
val offset = parentBottom - bottom
super.layoutDecoratedWithMargins(child, left, top + offset, right, bottom + offset)
} else {
super.layoutDecoratedWithMargins(child, left, top, right, bottom)
}
}
}
I used muthuraj solution to solve a similar problem, I wanted the last item to be shown at the last normally if previous items fill up the height of the page or more height but in case the previous item doesn't fill up the height, I wanted last item to be shown on the bottom of the page.
When previous item + specialItem take all the place.
--------------
item
item
item
specialItem
--------------
When previous item + specialItem take more than the height
--------------
item
item
item
item
item
item
specialItem
--------------
When previous item + specialItem take less than the height
--------------
specialItem
--------------
or
--------------
item
specialItem
--------------
to archive this I use this code
val lastItemView = holder.itemView
//TODO use a better option instead of waiting for 200ms
Handler().postDelayed({
val lastItemTop = lastItemView.top
val remainingSpace = recyclerViewHeight() - lastItemTop
val heightToSet = Math.max(remainingSpace, minHeight)
if (lastItemView.height != heightToSet) {
val layoutParams = lastItemView.layoutParams
layoutParams.height = heightToSet
lastItemView.layoutParams = layoutParams
}
}, 200)
the reason I use Handler().postDelayed is that doOnLayout never gets called for me and I couldn't figure out why so instead run the code with 200ms delay until I found something better to work with.
This worked for me, let the recycler view with layout_height="0dp", and put the top constraint to the bottom of the corresponding above item, and the bottom constraint to the parent, then it will be sized with the remaining space:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ToggleButton
android:id="#+id/toggleUpcomingButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:gravity="top"
android:text="#string/upcoming"
app:layout_constraintEnd_toStartOf="#+id/togglePupularButton"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ToggleButton
android:id="#+id/togglePupularButton"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="top"
android:text="#string/popular"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/toggleUpcomingButton"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="#string/popular_movies"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toggleUpcomingButton" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/text_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

change the Listview dimensions dynamically with respect to the phone screen

How can change the position of the list view dynamically.For example my list view initially covers 2/4 of the screen and on scroll down ,I want the list will size to expand and cove 3/4 of the screen.
below code is giving me whether user is scrolling up or down in the list view
#Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
final ListView lw = lv;
if (view.getId() == lw.getId()) {
final int currentFirstVisibleItem = lw.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
mIsScrollingUp = false;
}
else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
mIsScrollingUp = true;
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
thanks in advance
The idea is to use a layout (a LinearLayout or RelativeLayout) and a transparent view whose visibility may change to GONE.
Something like:
<LinearLayout ...>
<ListView android:layout_height="0px" android:layout_weight="1" .../>
<View android:background="#null" android:layout_weight="0.5" android:visibility="gone" ...>
<View android:background="#null" android:layout_weight="0.5" android:visibility="visible" ...>
</LinearLayout>
If you change the visibility of the 2nd layout item from gone to visible, the ListView will change its height from 2/3 to 1/2 of the LinearLayout height.
Disclaimer: I myself find the idea of changing the ListView height very counter-intuitive.
Just in case: View.getMeasuredHeight() gives the real height but you may have to define a ViewTreeObserver.OnGlobalLayoutListener to get notified when it becomes calculated. When you react on input events, the measured height is already calculated.
That is achieved by using DisplayManager class. Sorry I haven't tested the following code, but it should work, although you might need to fix some errors and constraints, the logic is fine:
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Display mainDisplay = ((DisplayManager)Context.getSystemService(DISPLAY_SERVICE)).getDisplay(0);
Point screenSize = new Point();
mainDisplay.getSize(screenSize);
view.setLayoutParams(new LayoutParams(screenSize.x * 3 / 4, screenSize.y * 3 / 4));
//rest of your code
}
However, the problem you'll be facing afterwards is that your ListView won't revert back once you stop scrolling, you need to create an additional method (and Event handler) that would accomplish that.

how to display footer view to the end of the screen in the case when the list has very few items?

I want to add a footer to the listview. When the number of list items are more,the footer works fine.
But when listview has very few items,the footer gets displayed in the middle of the screen,just below the listview .which looks shabby.In such case i want the footer to align parent bottom.
Thankyou in anticipation.
it is a simplest example of what you want. you can customize it:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/footer"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</ListView>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="90dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:id="#+id/footer" >
</RelativeLayout>
</RelativeLayout>
if you want do that you said in comment you must set layout param's of footer in code, you must get the size of your list, then get the number of row that shows in screen, then
if (listSize < numRow)
//set footer to bottom of your list
else
// android:layout_alignParentBottom="true"
Maybe your listview's height is set to wrap_content?
As far as I know the footer is added at the bottom of the listview. If you set the listview's height to match_parent it should be aligned to the bottom and also the footer should be displayed there.
(If you use a relative layout simply set listview's attribute alignParentBottom="true" in your .xml file)
You can use RecyclerView with RecyclerView.ItemDecoration to implement this behavior.
public class StickyFooterItemDecoration extends RecyclerView.ItemDecoration {
/**
* Top offset to completely hide footer from the screen and therefore avoid noticeable blink during changing position of the footer.
*/
private static final int OFF_SCREEN_OFFSET = 5000;
#Override
public void getItemOffsets(Rect outRect, final View view, final RecyclerView parent, RecyclerView.State state) {
int adapterItemCount = parent.getAdapter().getItemCount();
if (isFooter(parent, view, adapterItemCount)) {
//For the first time, each view doesn't contain any parameters related to its size,
//hence we can't calculate the appropriate offset.
//In this case, set a big top offset and notify adapter to update footer one more time.
//Also, we shouldn't do it if footer became visible after scrolling.
if (view.getHeight() == 0 && state.didStructureChange()) {
hideFooterAndUpdate(outRect, view, parent);
} else {
outRect.set(0, calculateTopOffset(parent, view, adapterItemCount), 0, 0);
}
}
}
private void hideFooterAndUpdate(Rect outRect, final View footerView, final RecyclerView parent) {
outRect.set(0, OFF_SCREEN_OFFSET, 0, 0);
footerView.post(new Runnable() {
#Override
public void run() {
parent.getAdapter().notifyDataSetChanged();
}
});
}
private int calculateTopOffset(RecyclerView parent, View footerView, int itemCount) {
int topOffset = parent.getHeight() - visibleChildsHeightWithFooter(parent, footerView, itemCount);
return topOffset < 0 ? 0 : topOffset;
}
private int visibleChildsHeightWithFooter(RecyclerView parent, View footerView, int itemCount) {
int totalHeight = 0;
//In the case of dynamic content when adding or removing are possible itemCount from the adapter is reliable,
//but when the screen can fit fewer items than in adapter, getChildCount() from RecyclerView should be used.
int onScreenItemCount = Math.min(parent.getChildCount(), itemCount);
for (int i = 0; i < onScreenItemCount - 1; i++) {
totalHeight += parent.getChildAt(i).getHeight();
}
return totalHeight + footerView.getHeight();
}
private boolean isFooter(RecyclerView parent, View view, int itemCount) {
return parent.getChildAdapterPosition(view) == itemCount - 1;
}
}
Make sure to set match_parent for the RecyclerView height.
Please have a look at the sample application https://github.com/JohnKuper/recyclerview-sticky-footer and how it works http://sendvid.com/nbpj0806
A Huge drawback of this solution is it works correctly only after notifyDataSetChanged() throughout an application(not inside decoration). With more specific notifications it won't work properly and to support them, it requires a way more logic. Also, you can get insights from the library recyclerview-stickyheaders by eowise and improve this solution.

Choose wrapping view when layout is wider than screen

I have a horizontal linear layout with an image and a couple text views. In some languages (German...) the text is so long that to fit everything on one line, the layout would have to be wider than the screen. To prevent this, android automatically makes the text views wrap on to the next line.
Is there any way to choose which of the text views will end up wrapping? At the moment it appears that the last view added to the layout is the one that wraps. However I'd really like to have one of the earlier text views wrap and have the last text view always display on a single line. Is this possible? I've already subclassed most of the views involved so I can override protected methods.
Heres a rough outline of my code:
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/some_icon" />
<TextView
android:id="#+id/can_wrap_if_neccessary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/some_really_long_text" />
<TextView
android:id="#+id/shouldnt_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/some_more_really_long_text" />
</LinearLayout
We can know which Text will wrap.
Logic:
First calculate width of Text If width of Text is greater than width of the screen, that text will be wrapped
The following method returns true if text will be wrapped else returns false
Source code
boolean isTextWrapped(String text) {
boolean isWrapped = false;
int widthOfText = 0;
int deviceWidth = 0;
// calculate widthOftext
TextView textView = new TextView(this);
textView.setVisibility(View.GONE);
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
widthOfText = bounds.width();
System.out.println("...text view width..."+widthOfText);
// calculate width of screen
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
deviceWidth = displaymetrics.widthPixels;
System.out.println("...text view width..."+widthOfText+"...screen width..."+deviceWidth);
isWrapped = widthOfText > deviceWidth ? true : false;
return isWrapped;
}
I figured it out: the solution is to set the layout_weight attribute on the view(s) that I want to wrap, and not set it at all on the views I don't want to wrap. Any view with a layout_weight will wrap in preference over those without.

Categories

Resources