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));
Related
I have been trying to make a layout for my app, where I set the android:layout_height="wrap_content".
This is my code ::
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="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="com.example.hp.money.DashBoard">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:id="#+id/cv_amount_display"
android:layout_width="match_parent"
android:layout_height="200dp">
.....
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardCornerRadius="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/last_10_transaction" />
<ListView
android:id="#+id/last_transactions_lv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>
I am using a ListView inside a ScrollView, which is a bad idea, I know! It leads to some scrolling issues, I believe. But I have to dynamically update data on my screen during runtime, and I think only the listview can handle it. If there's any other View to do it, please suggest.
Now, the problem is that even if the Data source of the ListView has 10 items, the height of the ListView always remains equal to the size of one listView element, but the height of the listview is set to wrap_content, so probably it is supposed to resize accordingly. But it doesn't! How to fix this?
This is how it looks, even though the Listview has 10 items!
The ListView is the one which has a heading as LAST 10 TRANSACTIONS
Why I used a ScrollView??
I put a scrollview, because, the number of enteries in the listview can be many, so I want that when the user scrolls to see the entries not currently visible int the listview, the whole page gets scrolled, and not only the listview.
What do you mean by " dynamically insert data into the Listview during runtime". If you are modifying list data dynamically then ListView can handle it internally. You no need to do anything in that case. So you can remove the ScrollView so it works fine.
You can not add a ListView inside ScrollView .
For considering your needs you can set ListView height dynamically to fulfill your needs ...
After setting up your list adapter simply call this method...
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
As others pointed, you can't put a ListView inside a ScrollView (anyways it doesn't make sense, ListView has its own scroll)
But I have to dynamically insert data into the Listview during runtime, if there's any other way to do it, please suggest.
I can't see why you think you can't, and want to know why you think a ScrollView will do it.
If you have created your own adapter to fill that ListView you should have something like this:
class listViewAdapter extends BaseAdapter
{
public View getView(int position, View convertView, ViewGroup parent)
{
}
public int getCount()
{
}
public Object getItem(int position) {
}
public long getItemId(int position){
}
}
Then you should create a data field and a setter
private Arraylist<DataType> data;
public void setData(Arraylist<DataType> data)
{
data = data;
}
Then you can use it like this:
ListView lv = findViewById...
ListViewAdapter adapter = new ListViewAdapter();
adapter.setData(data);
lv.setAdapter(new ListViewAdapter());
When you have added or deleted data you can reload it by calling
adapter.notifySetDataChanged();
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>
I have an expandablelistview which contains multiple viewpagers, and each viewpager contains multiple pages which each have a linearlayout of data. The data in the linearlayout is dynamic (there could be 5 rows or there could be 20), and each row has a dynamic height (it could have 1 line of text or 2-3).
As the viewpager requires a hard-coded height to be set for it to display, I need to display all of the content in each page's linearlayout, and I can't allow the content within the viewpager to scroll, I'm in the difficult situation of trying to calculate the height of the viewpager's active page view and then dynamically update the height of the viewpager so that the whole linearlayout content is visible. As a new page is swiped to, it should then update the viewpager height accordingly.
I'm attempting to do this within the pager adapter's SetPrimaryItem method, however the height that is coming back is close but not accurate. In my pager page view I have an outer relativelayout with a blue background, and then an inner one with a red background. For some reason the outer (blue) layout is going maybe 70dp further than the bottom of the data, I'm guessing the pager is manipulating its height behind-the-scenes. The inner one (red) behaves correctly and wraps the data within it, so this is the one I'm trying to calculate the height of so I can update the viewpager's height, however its height is incorrectly being calculated as bigger than it is, so after I update the viewpager height there is empty space at the bottom. See the screenshot, as it probably makes more sense than what I just typed:
Another acceptable solution would be to calculate the longest page's height and set the viewpager to that once, and then don't update it as the page changes, but I haven't gone that route yet and would still run into the same issue calculating the correct height.
I've spent a day on this already, any ideas / suggestions for how I can accurately update the viewpager height to fit its contents would be appreciated. I've tried a lot of other solutions on SO and haven't found one that actually worked. Here is where I've gotten so far with it:
Pager view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v4.view.ViewPager
android:id="#+id/property_details_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
Pager adapter: (SetPrimaryItem method is where I try to update the pager's height dynamically)
public class PropertyDetailsPagerAdapter : PagerAdapter {
private ViewPager pager;
private List<List<KeyValuePair<string, string>>> lists;
public PropertyDetailsPagerAdapter (View view, List<List<KeyValuePair<string, string>>> lists) {
this.pager = view.FindViewById<ViewPager> (Resource.Id.property_details_pager);
this.pager.OffscreenPageLimit = lists.Count;
this.lists = lists;
}
public void Update (List<List<KeyValuePair<string, string>>> lists) {
this.pager.Adapter = null;
this.lists = lists;
this.pager.Adapter = this;
this.pager.Adapter.NotifyDataSetChanged ();
}
#region Setup Views
public override Java.Lang.Object InstantiateItem (View collection, int position) {
var layoutInflater = (LayoutInflater)ApplicationContext.Activity.GetSystemService (Context.LayoutInflaterService);
var view = layoutInflater.Inflate (Resource.Layout.PropertyDetailsPagerItem, null);
var pagerList = view.FindViewById<LinearLayout> (Resource.Id.property_details_pager_list);
var list = this.lists [position];
new PropertyDetailsPagerListAdapter (pagerList, Resource.Layout.PropertyDetailsPagerKeyValueRow, list);
var listViewSwipe = view.FindViewById<LinearLayout> (Resource.Id.property_details_pager_swipe);
listViewSwipe.Visibility = this.lists.Count > 1 ? ViewStates.Visible : ViewStates.Gone;
this.pager.AddView(view);
return view;
}
public override void SetPrimaryItem (View container, int position, Java.Lang.Object #object) {
base.SetPrimaryItem (container, position, #object);
var view = this.pager.GetChildAt (position);
var innerView = view.FindViewById<RelativeLayout> (Resource.Id.property_details_pager_item);
innerView.Measure (0, 0);
var parameters = this.pager.LayoutParameters as RelativeLayout.LayoutParams;
parameters.Height = innerView.MeasuredHeight;// TODO: Why isn't this height accurate?!?!
this.pager.LayoutParameters = parameters;
}
#endregion
#region Infrastructure
// Other irrelevant methods
public override int Count {
get {
return this.lists.Count;
}
}
#endregion
public List<List<KeyValuePair<string, string>>> Lists { get { return this.lists; } }
}
View for each page within the pager:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/blue">
<RelativeLayout
android:id="#+id/property_details_pager_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/red">
<LinearLayout
android:id="#+id/property_details_pager_swipe"
android:layout_below="#+id/dotted"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:visibility="invisible" />
<RelativeLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="#color/light_gray">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Swipe for\nmore records"
android:textColor="#color/gray"
android:gravity="right"
android:padding="#dimen/padding" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="#+id/property_details_pager_list"
android:layout_below="#+id/property_details_pager_swipe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</RelativeLayout>
</RelativeLayout>
Adapter for each page within the pager:
public class PropertyDetailsPagerListAdapter
{
private int resourceId;
private List<KeyValuePair<string, string>> keyValuePairs;
public PropertyDetailsPagerListAdapter (LinearLayout pagerList, int resourceId, List<KeyValuePair<string, string>> keyValuePairs) {
this.resourceId = resourceId;
this.keyValuePairs = keyValuePairs;
pagerList.RemoveAllViews ();
foreach (var keyValuePair in this.keyValuePairs) {
pagerList.AddView (GetView (keyValuePair));
}
}
public View GetView (KeyValuePair<string, string> keyValuePair) {
var layoutInflater = (LayoutInflater)ApplicationContext.Activity.GetSystemService (Context.LayoutInflaterService);
var view = layoutInflater.Inflate(resourceId, null);
// Assumes Resource.Layout.PropertyDetailsPagerKeyValueRow
view.FindViewById<TextView> (Android.Resource.Id.Text1).Text = keyValuePair.Key;
view.FindViewById<TextView> (Android.Resource.Id.Text2).Text = keyValuePair.Value;
return view;
}
}
I was able to figure it out. The issue was that I was using innerView.Measure (0,0) and then getting the MeasuredHeight, however that was giving a bogus height. After calling Measure correctly, which entails passing a real Width into it, the correct Height is returned!!
public override void SetPrimaryItem (View container, int position, Java.Lang.Object #object) {
base.SetPrimaryItem (container, position, #object);
var view = this.pager.GetChildAt (position);
var innerView = view.FindViewById<RelativeLayout> (Resource.Id.property_details_pager_item);
var desiredWidth = MeasureSpec.MakeMeasureSpec (this.pager.Width, MeasureSpecMode.AtMost);
innerView.LayoutParameters = new RelativeLayout.LayoutParams (RelativeLayout.LayoutParams.WrapContent, RelativeLayout.LayoutParams.WrapContent);
innerView.Measure (desiredWidth, (int)MeasureSpecMode.Unspecified);
var parameters = this.pager.LayoutParameters as RelativeLayout.LayoutParams;
parameters.Height = innerView.MeasuredHeight;
this.pager.LayoutParameters = parameters;
}
I am getting this error but I am not sure exactly why:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
The part where I add the View is the line that the error is pointing to. I am providing my Adapter code in order for you guys to get a better picture of what I am doing and why I am getting this error. Please let me know if anymore info is needed. Thanks in advance.
Adapter
private class InnerAdapter extends BaseAdapter{
String[] array = new String[] {"12\nAM","1\nAM", "2\nAM", "3\nAM", "4\nAM", "5\nAM",
"6\nAM", "7\nAM", "8\nAM", "9\nAM", "10\nAM", "11\nAM",
"12\nPM", "1\nPM", "2\nPM", "3\nPM", "4\nPM", "5\nPM",
"6\nPM", "7\nPM", "8\nPM", "9\nPM", "10\nPM", "11\nPM"};
TextView[] views = new TextView[24];
public InnerAdapter() {
TextView create = new TextView(DayViewActivity.this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
create.setLayoutParams(params);
create.setBackgroundColor(Color.BLUE);
create.setText("Test");
views[0] = create;
for(int i = 1; i < views.length; i++) {
views[i] = null;
}
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return array.length;
}
#Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}
#Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
convertView = inflater.inflate(R.layout.day_view_item, parent, false);
}
((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);
if(views[position] != null) {
layout.addView((TextView)views[position], position);
}
return convertView;
}
}
XML
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="61dp"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="61dp"
android:orientation="vertical">
<TextView
android:id="#+id/day_hour_side"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="12AM"
android:background="#bebebe"
android:layout_weight="0"
android:textSize="10dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_weight="0"
android:background="#000000"
android:id="#+id/hour_side_divider"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="61dp"
android:orientation="vertical"
android:layout_weight="1">
<LinearLayout
android:id="#+id/day_event_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal" ></LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#000000"
android:id="#+id/event_side_divider" />
</LinearLayout>
</LinearLayout>
You didn't say when you get that exception(when the application starts or when you scroll the GridView up and down) but it's normal. The views array has one value that is not null(the first entry in that array in set to the TextView that you create) and most likely you'll be trying to re-add that TextView at some point. Also the parent AdapterView may call the getView method several times to get some children to measure.
Anyway you don't know exactly what you're trying to do but the current approach is wrong.
First, you create an array with one TextView and the rest of the values set to null and you basically don't do anything else with it(but maybe this isn't the full code?!). Second, you shouldn't store an array of Views especially within a child of AdapterView(like GridView, ListView etc) which has a mechanism to recycle its children. Third, you didn't take in consideration the recycling mechanism of the GridView. For example, you add the TextView for the first element but you don't revert this changes in the getView so if this first row's View(which contains the added TextView) gets recycled you'll end up with a row View containing the previously added TextView at rows where you don't want it.
I already accepted an answer, but thought I'd add this as it helps explain some stuff about ListView (and by definition, GridView as well), that someone learning about it can understand. I was confused about recycling Views in ListView, and this article I found is great. Explains it well. Hopefully it helps anyone not fully understanding how ListView and Adapters work, which was an obvious problem for me.
I encountered a problem when embedding a ListView inside a ScrollView, or at least that's where I guess the problem comes from. The ListView element is a fairly simple one:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/item_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/general_background_list_middle"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:paddingRight="0dp"
android:paddingLeft="0dp">
<ImageView
android:id="#+id/chat_friends_avatar"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_marginLeft="7dp"
android:layout_marginRight="8dp"
android:paddingRight="0dp"
android:paddingLeft="0dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="#drawable/friends_icon_avatar_default"/>
<TextView
android:id="#+id/chat_message_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/chat_friends_avatar"
android:layout_alignParentTop="true"
android:layout_marginRight="35dp"
android:maxLines="10"
android:textSize="12dp"/>
<TextView
android:id="#+id/chat_friend_name"
android:layout_width="140dp"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
style="#style/SubText"
android:layout_toRightOf="#id/chat_friends_avatar"
android:layout_below="#id/chat_message_text" />
<TextView
android:id="#+id/chat_message_time"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_marginTop="3dp"
style="#style/SubText"
android:layout_alignParentRight="true"
android:layout_below="#id/chat_message_text" />
</RelativeLayout>
However, when I embed a list of such elements in a ScrollView, in between some other elements, the rows are not fully displayed, they are clipped (see image below) if the text is wrapped. The ListView is instantiated as follows in the ScrollView:
<ListView
android:id="#+id/info_chat_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:cacheColorHint="#color/frame_background_color"
android:clickable="false"
android:divider="#null"
android:footerDividersEnabled="false"
android:focusable="false" >
</ListView>
If the height of the ListView is set to "wrap_content" only the first element is shown. That's why I'm using a method to calculate the height of the rows of the list:
private int getCommentsListHeight() {
if (mChatAdapter != null && mChatAdapter.getCount() != 0) {
if (mChatList != null) {// && mCommentsListItemHeight == 0) {
mCommentsListItemHeight = 0;
for (int i = 0; i < mChatAdapter.getCount(); i++) {
// Get view item height
View viewItem = mChatAdapter
.getView(i, new View(OnAirActivity.this), mChatList);
viewItem.measure(0, 0);
Logger.d(LOGTAG, "View " + i + " measured height = " + viewItem.getMeasuredHeight());
mCommentsListItemHeight += viewItem.getMeasuredHeight();
}
}
//return mChatAdapter.getCount() * mCommentsListItemHeight;
return mCommentsListItemHeight;
} else {
return 0;
}
}
Unfortunately, in case when the text inside the TextView is wrapped, even over several lines, the height of the row element returned by the getMeasuredHeight() method is constant. Also the getLineCount() called on the TextView inside the row element returns 1 even if the text is wrapped.
On the other hand, if this ListView is embedded in a LinearLayout, everything works fine and the full list is displayed with no clipping.
Do you have any suggestions as to what might be wrong here? I really don't like the idea of manually measuring the height of the list elements and it apparently doesn't work but why can't android nicely stretch the ListView inside the ScrollView to fit it all in there?
Clipped list:
Use this method created by https://stackoverflow.com/users/205192/dougw
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
// pre-condition
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
}
It's a BAD practice to encapsulate ListView within a ScrollView because ListView itself contains scrolling capabilities. You should implement a solution that does not contain such hierarchy of views and I hope it will do the magic :)
Here resource of main layout with ScrollView:
<ScrollView android:layout_height="fill_parent" android:layout_width="fill_parent">
<LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="#+id/parentLayout"/>
</ScrollView>
Here the code to insert items:
parentLayout.removeAllViews();
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = comments.size() - 1; i >= 0; i--) {
CommentInfo comment = comments.get(i);
View view = inflater.inflate(your_resource_id, null, false);
TextView commentsContent =(TextView)view.findViewById(R.id.commentContent);
if (commentsContent != null) {
String data = String.format("%s (by %s, %s)", comment.getCommentText(), comment.getUserName(),
commentsContent.setTextSize(st.getTextSize());
commentsContent.setText(data);
}
parentLayout.addView(view, 0);
}
I had the same problem in my project.You need to create simple LinearLayout inside ScrollView. After that you need create new View with your listview item xml using LayoutInflater. After creation put all data in new View and add to LinearLayout as child view:
linearLayot.addView(newView, position_you_need).
Hope it would help you!
I took the recommendation of not using a ListView element inside a ScrollView to heart and decided to use a slightly brute force method to achieve what I need. Since there is a constant number of up to five list rows that need to be displayed I removed the ListView instantiation from the xml file and replaced it with five instances of rows:
<include android:id="#+id/info_comment_1" layout="#layout/chat_single_message" />
<include android:id="#+id/info_comment_2" layout="#layout/chat_single_message" />
<include android:id="#+id/info_comment_3" layout="#layout/chat_single_message" />
<include android:id="#+id/info_comment_4" layout="#layout/chat_single_message" />
<include android:id="#+id/info_comment_5" layout="#layout/chat_single_message" />
In the Activity class I declare five placeholders for these views:
private RelativeLayout mChatMessages[] = new RelativeLayout[COMMENTS_NUMBER];
and initialize them with:
mChatMessages[0] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_1);
mChatMessages[1] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_2);
mChatMessages[2] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_3);
mChatMessages[3] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_4);
mChatMessages[4] = (RelativeLayout) mMoreInfoLayout.findViewById(R.id.info_comment_5);
Then, whenever a new message is received I use the ChatAdapter (the same I used for the ListView previously) and call its getView() method:
protected void updateChatMessages() {
int msgCount = mChatAdapter.getCount();
for (int i = 0; i < COMMENTS_NUMBER; i++) {
if (msgCount <= i) {
mChatMessages[i].setVisibility(View.GONE);
} else {
mChatMessages[i] = (RelativeLayout) mChatAdapter.getView(i, mChatMessages[i], null);
mChatMessages[i].setVisibility(View.VISIBLE);
}
}
}
I don't inflate the pariculat views ever again since the only thing that changes is the content of each row, not the layout. This means there is no performance penalty here.
This is basically a manual implementation of a ListView with a limited maximum number of elements. This time, however, ScrollView is able to fit them nicely and nothing gets clipped.
For a dynamic number of rows the approach suggested by Layko could be employed with the views being instantiated programatically and added to the LinearLayout inside the ScrollView.
I can see the ListView is inside a ViewPager; one other simple approach to resolving this issue is to add
app:layout_behavior="#string/appbar_scrolling_view_behavior" to the ViewPager in your layout xml as seen below.
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
To prevent the same behavior at the bottom of the list, you can also add android:layout_marginBottom="?attr/actionBarSize" to the ViewPager like so
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_marginBottom="?attr/actionBarSize"/>
This is coming late, but I hope it helps any other person.
try it..
after create all view add bellow line for ScrollView location on screen (x,y)
ScrollView scrollView = (ScrollView)findViewById(R.id.scrollView);
scrollView.smoothScrollTo(0,0);// top location zero index
I had a similar problem. I have a
RelativeLayout
listView
includeLayout
where I include some bottom nav beneath the listView with this
<include
android:id="#+id/includeLayout"
layout="#layout/bottom_nav_bar"
and my listView was clipped - not taking the full height available between the header and bottom nav. I tried various xml settings suggested in this and other threads, but what worked for me was to add
android:layout_alignBottom="#+id/includeLayout"
to my listView. That seemed to pull the listView down to the top of the bottom nav, so that the listView is now using the full available height (and it scrolls as needed).
This works for me
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/activity_vertical_margin">
<TextView
android:id="#+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="17sp"
android:text="#string/text_list_devices" />
<ListView
android:id="#+id/lv_paired"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:cacheColorHint="#00000000"
android:layout_above="#+id/signup_t"
android:layout_below="#id/tv_status"
/>
<Button
android:id="#+id/signup_t"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textColor="#android:color/white"
android:layout_alignParentBottom="true"
android:text="Print All Records"
android:typeface="sans"
android:layout_marginLeft="45dp"
android:layout_marginRight="45dp"
android:textSize="24sp"
android:background="#drawable/selector_for_button"
android:textStyle="bold" />
</RelativeLayout>
This is BAD practice. But there are some situations we can not avoid using that. For example dynamic e-commerce layouts we may put multiple lists or recycle views but you don't want to scroll inside a single item height (if accidentally wanted!!). I faced this kind of problem. I fixed using a simple way. I don't tell this is the correct way but it may help some.
!! I used to recycle the view.
(01) Create an Interface to return view height.
public interface AfterViewLoadListener {
void onViewHeightMeasured(int height, String mode);
}
(02) implement with your activity
public class *Activity extends AppCompatActivity implements AfterViewLoadListener{
/** your codes **/
final SimpleListRecycleAdapter order_adapter = new SimpleListRecycleAdapter(this,"ORDER");
}
#Override
public void onViewHeightMeasured(int height, String mode) {
if(mode.equals("ORDER") && height > 0){
recycleView.setMinimumHeight(height);
}
}
(03) inside the recycle view custom adapter
AfterViewLoadListener viewLoadListener = null;
public SimpleListRecycleAdapter(AfterViewLoadListener listener, String mode) {
if(listener instanceof AfterViewLoadListener){
viewLoadListener = listener;
}
this.mode = mode;
}
(04) override the onViewAttachedToWindow method
#Override
public void onViewAttachedToWindow(#NonNull SimpleListViewHolder holder) {
super.onViewAttachedToWindow(holder);
View view = holder.itemView;
view.measure(0, 0);
this.viewMinHeight = view.getMeasuredHeight();
if(!firstFlag){
firstFlag = true;
viewLoadListener.onViewHeightMeasured(this.viewMinHeight*filtered.length(),mode);
}
}
(05) That's it. It worked for me.