Is there a way to have a ListView with the with equal to the longest row? Setting wrap_content for the ListView 's width has no effect. The ListView covers the whole screen horizontally.
This is the activity layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ListView android:id="#+id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/country_picker_bg" />
and this is the row xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginBottom="#dimen/padding"
android:padding="#dimen/padding"
android:background="#drawable/white_round_rect" >
<TextView android:id="#+id/title"
style="#style/GreyTextView"
android:layout_marginLeft="#dimen/padding"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/orange_arrow_selector"/>
</LinearLayout>
Thank you,
Gratzi
Sometimes, you know there will always be a limited number of items, maybe 5, maybe 40. In those times you still want to use a ListView and you still want to wrap content.
For those times there is this method:
/**
* Computes the widest view in an adapter, best used when you need to wrap_content on a ListView, please be careful
* and don't use it on an adapter that is extremely numerous in items or it will take a long time.
*
* #param context Some context
* #param adapter The adapter to process
* #return The pixel width of the widest View
*/
public static int getWidestView(Context context, Adapter adapter) {
int maxWidth = 0;
View view = null;
FrameLayout fakeParent = new FrameLayout(context);
for (int i=0, count=adapter.getCount(); i<count; i++) {
view = adapter.getView(i, view, fakeParent);
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int width = view.getMeasuredWidth();
if (width > maxWidth) {
maxWidth = width;
}
}
return maxWidth;
}
Use it like so (notice I added some extra space to the width just in case):
listView.getLayoutParams().width = getWidestView(mContext, adapter)*1.05;
Is there a way to have a ListView with the with equal to the longest row?
No, in part because we have no way to know what the longest row is.
Let's say that you attach a ListAdapter to the ListView, where getCount() on the ListAdapter returns a value of 1,234,567. To determine the width of the longest row, we would have to create each row and examine its width. This would take hours, and the user will not be happy during those hours.
Related
I am currently trying to use a preference fragment within a Navigation Drawer. There are multiple preference fragments that may populate a frame layout, all of which could be of a different size and may not be the full height of the parent.
For this reason I was wanting to use wrap_content for the frame layout height, but as preference fragment extends from ListView this causes problems (reference ListView Wrap Content). wrap_content does provide the desired result, although I can see that OnBindView is being called continuously, which highly inefficient and causes my data binding methods to be called all the time.
Has anyone got any simple out the box solutions I could try? Or would this be a case for a custom View where I would measure the children of the fragment and set the height at run time? Any help on this would be much appreciated.
The layout below shows the drawer layout which is included in the main layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="horizontal"
android:padding="20dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RadioGroup
android:id="#+id/drawer_radio"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RadioButton
android:id="#+id/drawer_radio_button_0"
style="#style/ButtonDrawer"
android:background="#drawable/fft_button"
android:button="#null"
android:contentDescription="#string/image_button"/>
<RadioButton
android:id="#+id/drawer_radio_button_1"
style="#style/ButtonDrawer"
android:background="#drawable/trigger_button"
android:button="#null"
android:contentDescription="#string/image_button"/>
</RadioGroup>
</LinearLayout>
<FrameLayout
android:id="#+id/drawer_preference_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
tools:layout="#android:layout/simple_list_item_1"
android:background="#drawable/floating_preference_background_shape"/>
</LinearLayout>
Ok, so I think I found a good solution so I thought I'd post it for others to use.
The way I had to do this was to extend from PreferenceFragment and do some measurements at run time, in which I can use to resize the listView.
I did this with the following.
public class PreferenceFragmentHeightWrap extends PreferenceFragment {
/**
* We can guarantee that all children will be in the adaptor list by this point
*/
#Override
public void onResume() {
super.onResume();
setListViewHeightBasedOnItems(getView());
}
/**
* Sets ListView height dynamically based on the height of the items.
*
* #return true if the listView is successfully resized, false otherwise
*/
public boolean setListViewHeightBasedOnItems(View view) {
ListView listView = (ListView) view.findViewById(android.R.id.list);
ListAdapter listAdapter = listView.getAdapter();
if(listAdapter != null) {
int numberOfItems = listAdapter.getCount();
// Get total height of all items.
int totalItemsHeight = 0;
for(int itemPos = 0; itemPos < numberOfItems; itemPos++) {
View item = listAdapter.getView(itemPos, null, listView);
item.measure(0, 0);
totalItemsHeight += item.getMeasuredHeight();
}
// Get total height of all item dividers.
int totalDividersHeight = listView.getDividerHeight() *
(numberOfItems - 1);
// Set list height.
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = totalItemsHeight + totalDividersHeight + listView.getPaddingBottom()
+ listView.getPaddingTop();
view.setLayoutParams(params);
return true;
} else {
return false;
}
}
Hopefully this will make sense and feel free to comment if you see anything untoward.
I would like to have a ScrollView that contains a ListView, which is obviously undesirable since a ListView has its own scroll bar.
Essentially I would like to have a RelativeLayout with a number of views forming the header of the page, followed by a ListView, similar to Facebook's profile page with the images followed by the feed.
If I simply put a ListView inside a RelativeLayout then only the ListView area is scrollable instead of the whole page.
Is there a way to do this without including the header (RelativeLayout) as the first element in the ListView?
It looks like there is a method on ListView called addHeaderView. I can use this to add the RelativeLayout at the top. Thanks for your input.
Actually I haven't done this myself, but I kinda know the trick to achieve your goal and give a sexy look to your layout.
First get rid of that ScrollView.
Then put a monitor(listener) on your ListView to detect how much your ListView has scrolled and consider a reasonable threshold to detect *.
Now what is * ? it is the time you should "Hide" or "slowly fade" your header items. when user scrolls down the ListView and reaches that threshold slowly fade or hide the header. Then again when user scrolls up use a threshold to decide when to make your header items visible.
As far as I know this is the idea. Also try checking github and android-arsenal I guess there should be a library for this that will ease your work.
Use this class
public class Helper {
public static void getListViewSize(ListView myListView) {
ListAdapter myListAdapter = myListView.getAdapter();
if (myListAdapter == null) {
// do nothing return null
return;
}
//set listAdapter in loop for getting final size
int totalHeight = 0;
for (int size = 0; size < myListAdapter.getCount(); size++) {
View listItem = myListAdapter.getView(size, null, myListView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
//setting listview item in adapter
ViewGroup.LayoutParams params = myListView.getLayoutParams();
params.height = totalHeight + (myListView.getDividerHeight() * (myListAdapter.getCount() - 1));
myListView.setLayoutParams(params);
// print height of adapter on log
Log.i("height of listItem:", String.valueOf(totalHeight));
}
}
and in the activity class, use,
Helper.getListViewSize(ur listview name);
Try this Code.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp" >
<LinearLayout
android:id="#+id/headerLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="TextView" />
</LinearLayout>
<ListView
android:id="#+id/listView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/headerLayout" >
</ListView>
</RelativeLayout>
Currently, I have a layout which looks like this. It contains.
Title text view, and Price text view, which is visible always.
Description Text View, which can be visible or gone, depending expanding or collapsing.
Collapsing (During app startup)
Expanding (When user taps on it)
I want to have some nice animation around it. So, I referred to https://stackoverflow.com/a/13381228/72437
One of the key element, is to know the exact height of Description Text View, even before it is visible.
However, I realize a few type of code. They are't accurate
// v is description text view.
v.measure(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
final int targtetHeight = v.getMeasuredHeight();
// v is description text view.
v.measure(MeasureSpec.makeMeasureSpec(LayoutParams.MATCH_PARENT, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.EXACTLY));
final int targtetHeight = v.getMeasuredHeight();
This will return value 32. (The correct measured height suppose to be 92). This is the height for first line of text. This ends up my animation is ended at
May I know, what is the correct way to determine the measured height of a view, even before it changed from GONE to VISIBLE?
My layout code is as followed :
<LinearLayout
android:clickable="true"
android:id="#+id/chart_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#drawable/dummy"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:width="0dp"
android:layout_weight="0.6"
android:layout_height="match_parent"
android:gravity="left"
android:textSize="20sp"
android:textColor="#ff000000"
android:text="Summary chart" />
<TextView
android:id="#+id/chart_price_text_view"
android:layout_width="0dp"
android:width="0dp"
android:layout_weight="0.4"
android:layout_height="match_parent"
android:gravity="right"
android:textSize="20sp"
android:textColor="#ffF76D3C"
android:text="$2.99" />
</LinearLayout>
<TextView
android:visibility="gone"
android:id="#+id/chart_description_text_view"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/currency_exchange_description"
android:textColor="#ff626262"
android:textSize="15sp" />
</LinearLayout>
Your 2nd code snippet is almost correct, but you need to specify pixel sizes - not FILL_PARENT/MATCH_PARENT. This should work:
v.measure(MeasureSpec.makeMeasureSpec(parentView.getWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(MAX_HEIGHT, MeasureSpec.AT_MOST));
final int targetHeight = v.getMeasuredHeight();
You'll need to have a reference to the ViewGroup that v is a child of to get its width, and define MAX_HEIGHT (or perhaps use the parent View's height?).
Also, you should change the height parameters of the two TextViews that are within the horizontal LinearLayout to wrap_content, as using match_parent here may cause problems. The LinearLayout is set to wrap_content, but the two children don't specify a height.
I accomplished this for my accordion component. I was facing the exact same issue, of expanding my accordion which required the 'destination' height, that is, the height that it would take after the expansion.
This returns the correct height:
/***
* This function returns the actual height the layout. The getHeight() function returns the current height which might be zero if
* the layout's visibility is GONE
* #param layout
* #return
*/
public static int getFullHeight(ViewGroup layout) {
int specWidth = View.MeasureSpec.makeMeasureSpec(0 /* any */, View.MeasureSpec.UNSPECIFIED);
int specHeight = View.MeasureSpec.makeMeasureSpec(0 /* any */, View.MeasureSpec.UNSPECIFIED);
layout.measure(specWidth,specHeight);
int totalHeight = 0;//layout.getMeasuredHeight();
int initialVisibility = layout.getVisibility();
layout.setVisibility(View.VISIBLE);
int numberOfChildren = layout.getChildCount();
for(int i = 0;i<numberOfChildren;i++) {
View child = layout.getChildAt(i);
if(child instanceof ViewGroup) {
totalHeight+=getFullHeight((ViewGroup)child);
}else {
int desiredWidth = View.MeasureSpec.makeMeasureSpec(layout.getWidth(),
View.MeasureSpec.AT_MOST);
child.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight+=child.getMeasuredHeight();
}
}
layout.setVisibility(initialVisibility);
return totalHeight;
}
You can take a look at the accordion component and the animation on GitHub: Android Accordion View
This a ListView screenshot of my problem:
This is the layout XML:
<LinearLayout
android:id="#+id/viewer_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/background_dark"
android:orientation="vertical" >
<EditText
android:id="#+id/viewer_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableRight="#android:drawable/ic_menu_search"
android:hint="#string/hint_filter"
android:background="#android:color/white"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="3dp"
android:inputType="text"
android:paddingLeft="4dp"
android:selectAllOnFocus="true" >
</EditText>
<EditText
android:id="#+id/viewer_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableRight="#android:drawable/ic_menu_search"
android:hint="#string/hint_search"
android:background="#android:color/white"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="3dp"
android:layout_marginBottom="5dp"
android:inputType="text"
android:paddingLeft="4dp"
android:selectAllOnFocus="true" >
</EditText>
</LinearLayout>
<HorizontalScrollView
android:id="#+id/viewer_hscroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/viewer_top" >
<ListView
android:id="#+id/viewer_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ListView>
</HorizontalScrollView>
There are 3 problems in this scenario:
The Horizontal scrollview does not cover the full screen width (I drew a thick red line to mark the end)
The Horizontal scrollview does not scroll horizontally
The ListView rows are not of uniform width (this can be seen by the background color ending) (see the getView code below for details)
private static final int listRowLayout = android.R.layout.activity_list_item;
private Map<String, Integer> mColors = new HashMap<String, Integer>();
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// No logs here to keep ListView performance good
ViewHolder holder;
int color;
if( convertView == null ) {
convertView = mInflater.inflate(listRowLayout, parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(android.R.id.text1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
String data = mData.get(position);
// A compiled regex is faster than String.Contains()
Matcher m = ViewHolder.regex.matcher(data);
if( m.find() ) {
color = mColors.get(m.group(1));
} else {
color = mColors.get("V");
}
holder.text.setText(data);
holder.text.setBackgroundColor(color);
return convertView;
}
private static class ViewHolder {
TextView text;
static Pattern regex = Pattern.compile(" ([VIDWEF])/");
}
}
I encountered the exact same issue in trying to display a log file. I have a dedicated activity to display the log file:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_log);
// Read in lines from the log file
File clientLogFile = new File(LOG_FILE);
ArrayList<String> lines = new ArrayList<String>();
try
{
Scanner scanner = new Scanner((Readable) new BufferedReader(new FileReader(clientLogFile)));
try
{
while(scanner.hasNextLine())
{
lines.add(scanner.nextLine());
}
}
finally
{
scanner.close();
}
}
catch (FileNotFoundException e)
{
lines.add("No log file");
}
// Create a simple adaptor that wraps the lines for the ListView
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.list_item,lines);
// Create a ListView dynamically to overide onMeasure()
final ListView listView = new ListView(this)
{
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Override onMeasure so we can set the width of the view to the widest line in the log file
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Find maximum width of item in list and set scroll width equal to that
int maxWidth = 0;
for(int i=0; i<getAdapter().getCount(); i++)
{
View listItem = getAdapter().getView(i, null, this);
listItem.measure(0, 0);
int width = listItem.getMeasuredWidth();
if(width > maxWidth)
{
maxWidth = width;
}
}
// Set width of measured dimension
setMeasuredDimension(maxWidth, getMeasuredHeight());
}
};
// Add to scroll view
HorizontalScrollView horizontalScrollView = (HorizontalScrollView)findViewById(R.id.logScrollView);
horizontalScrollView.addView(listView);
// Set adaptor
listView.setAdapter(adapter);
// Enable fast scroll
listView.setFastScrollEnabled(true);
// Scroll to end
listView.post(new Runnable(){
public void run() {
listView.setSelection(listView.getCount() - 1);
}});
}
The onCreate method reads the log file and then dynamically adds a ListView to a HorizontalScrollView with onMeasure() overridden. The onMeasure() code determines the maximum width of the views in the adaptor and sets the ListView width to be that.
My activity_view_log.xml layout file is therefore very simple:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="5dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingTop="5dp"
>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/logScrollView">
</HorizontalScrollView>
</RelativeLayout>
In order to have finer grained control of the lines in the ListView I give my adapter my own layout file in list_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/text1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:inputType="text|none"
/>
At the end of by onCreate() I enable fast scroll and also scroll to the end of the lines in the log file.
I would probably reverse what you are doing. Create a ListView and make each item in the listview horizontally scrollable. This way items only scroll when they need to, and it does not scroll the entire screen. And you get complete control over the dimensions of each list item. To do this use a custom listview adapter as mentioned in the comments. There is also a possible duplicate of your question here: Android horizontal scroll list
In order to solve the 3 problems I had to make all the components (the horizontal scroll view, the list view and it's items) have a "fill_parent" width (I think it's the same as "match_parent"). In addition I had the listview's onMeasure(...) overridden to calculate the max width of it's items and set it via setMeasuredDimension(...). This will measure the view by it's widest item, not by it's first, as it is implemented now.
This is the solution I found.
The root of all evil :-) is that ListView is not designed to efficiently deal with rows of different length. To determine the ListView width, instead of looking at all the rows, only 3 rows are taken as average.
So, if the 3 rows are by chance short rows, the width will be clipped for the longer rows, it explains the problems I experienced.
To bypass this I calculated the maximum row length for all data, and I padded shorter rows with spaces, it solved all 3 problems I described in the question.
The code for padding (executed inside getView() )
holder.text.setText(String.format("%1$-" + mLen + "s", data));
I have a GridView inside of a LinearLayout inside of a ScrollView that pages in data from the server. Beneath the GridView is a button to load more data. My GridView will have an ultimate height that is larger than the screen. If I set the height of my GridView to either wrap_content or parent_fill, it sizes itself to the exact available on-screen height and does not scroll at all, cropping out the extra rows. If I explicitly set the layout_height to something large, like 1000dip, scrolling behaves properly, however I cannot predict the final height of my scroll view apriori.
How do I programmatically determine the necessary height of a GridView to get the desired behaviour?
Here is my layout below. As you can see I set the height to 1000dip, but that is bogus, I need that value to get set automatically/programmatically:
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
android:layout_weight="1"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/grid"
android:layout_width="fill_parent"
android:layout_height="1000dip"
android:columnWidth="70dp"
android:numColumns="auto_fit"
android:verticalSpacing="0dp"
android:horizontalSpacing="0dp"
android:stretchMode="columnWidth"
android:gravity="center"
android:background="#000000"
android:layout_weight="1"
/>
<Button
android:id="#+id/load_more"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Load More Foo"
/>
</LinearLayout>
</ScrollView>
Here is one way to do this, if someone needs it. A bit of a hack but does the trick. You have to set GridView initially big enough for all the views (e.g. 10000dip)
final GridView imageContainer = // your GridView
imageContainer.getViewTreeObserver().addOnGlobalLayoutListener( new OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
imageContainer.getViewTreeObserver().removeGlobalOnLayoutListener( this );
View lastChild = imageContainer.getChildAt( imageContainer.getChildCount() - 1 );
imageContainer.setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, lastChild.getBottom() ) );
}
});
I know it's an old case, but I had a similar problem where my ScrollView contained multiple LinearLayouts, which in their turn contained a header and a GridView.
Basically I made categorised sections with headers containing images belonging to that category.
The GridView had to have a flexible height.
I found a lot of answers about overriding onMeasure(), but it worked only on some devices, not all. The height would eventually be 1, or 3 or just 0, displaying only a few pixels of the image.
StretchingGridView class
I overrode the drawableStateChanged() method with this code, inspired by #Karitsa's solution:
#Override
public void drawableStateChanged() {
getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
getViewTreeObserver().removeOnGlobalLayoutListener( this );
View lastChild = getChildAt( getChildCount() - 1 );
if (lastChild != null) {
int height = Math.max(lastChild.getBottom(), getColumnWidth());
float child = getAdapter().getCount();
float col = getNumColumns();
int rows = (int) Math.ceil(child / col);
height = rows * getColumnWidth() + (getHorizontalSpacing() * rows-1);
setLayoutParams( new LinearLayout.LayoutParams( LayoutParams.MATCH_PARENT, height ) );
}
}
});
}
Note: My GridView uses square images, so I base the height on their width. I don't think it works well with flexible grid item heights.
Apparently GridViews inside ScrollViews are not kosher in Android-land. Switching to ListView with custom-made rows. That seems to behave better.