I have been trying to inflate views from xml to their corresponding view objects, then add these view objects to a programmed viewgroup. However, whenever I call myviewgroup.addView(childview), the childview seems to lose all the buttons and things the xml code should have given it. However, the one thing the childview does have is the background color which I programmed in to ensure the childview was given the correct dimensions. The childview is given the correct dimensions, but none of its components are visible
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
viewchanger= new ViewChanger(this); //my viewgroup
View mainmenu= inflater.inflate(R.layout.main, null); //the xml code
mainmenu.setBackgroundColor(Color.rgb(0, 0, 255));
viewchanger.addView(mainmenu);
setContentView(viewchanger);
the above code simply sets the background of my screen to blue, but none of the buttons show up. also, when I setContentView(mainmenu) everything shows up from that xml code perfectly as well as the background color. The point of this viewgroup is so I can switch between any of multiple views regardless of their order as childviews.
Here is the code for viewchanger. I want it to have 7 child views, and when I call a method
setView(int v) i want all the other views to be gone, and I want only that one view to be visible.
public class ViewChanger extends ViewGroup{
/**Represent which view to show, only one can show at a time*/
public static final int MainMenu=0, ChooseMap=1, MapOptions=2,
SetLocation=3, view=4, EditMap=5, CreateMap=6;
private int current; //the current view
private View[] views= new View[7];
public ViewChanger(Context context) {
super(context);
//load 7 views into view array here
//LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
/*views[0]= inflater.inflate(R.layout.main, null);
* views[1]= inflater.inflate(R.layout.choose, null);
* views[2]= inflater.inflate(R.layout.mapoptions, null);
* views[0].setBackgroundColor(Color.rgb(255, 0, 0));
* views[1].setBackgroundColor(Color.rgb(255, 255, 0));
* views[2].setBackgroundColor(Color.rgb(0, 255, 0));*/
* addView(views[0]);
* addView(views[1]);
* addView(views[2]);
* this is how i want to load the childviews*/
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for(int i = 0; i < getChildCount(); i++){
getChildAt(i).layout(0, 0, getWidth(), getHeight());
}
}
/**Sets the view of the view group, only one view can be viewed at a time*/
public void setView(int v){
current=v;
for (int i=0; i<views.length; i++){
if (v==i){
views[i].setVisibility(View.VISIBLE);
} else{
views[i].setVisibility(View.GONE);
}
}
}
When I load the child views in the ViewChanger constructor, the viewgroup only shows me the background colors of whichever view I set through setView(int v), and not the buttons.
Here is the xml code for main, but all the others look very similar to this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="The title"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/welcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome [...]"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/load"
android:layout_width="122dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Load Map" />
<Button
android:id="#+id/create"
android:layout_width="122dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Create Map" />
<Button
android:id="#+id/users"
android:layout_width="122dp"
android:layout_height="wrap_content"
android:text="Other User" />
</LinearLayout>
After running mentally through everything I could think of, I created a dummy project identical to this to check out a couple of possibilities. For some reason, having ViewChanger extend from FrameLayout instead of ViewGroup makes this work - most likely because ViewGroup lacks something needed to render it's child views (I tried overriding onDraw, but it still didn't work). Since you're only displaying one view at a time - the usual use of FrameLayout - I'd recommend just making this change; it's likely a lot easier than isolating and fixing the missing draw functionality in ViewGroup.
public class ViewChanger extends FrameLayout {
should be the only change you need to make to get the original design (adding views from the constructor) to work properly.
I had the same problem, created a custom viewGroup and inside it i added a linear layout viewGroup and i couldnt see the linear layout contents.
I solved the problem adding following code in my custom ViewGroup:-
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
final int count = getChildCount();
for (int i = 0; i < count; i++) {
childView = getChildAt(i);
childView.measure(widthMeasureSpec, heightMeasureSpec);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Check the FrameLayout statement. Hope it works.
LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
viewchanger= new ViewChanger(this); //my viewgroup
View mainmenu= inflater.inflate(R.layout.main, null); //the xml code
mainmenu.setBackgroundColor(Color.rgb(0, 0, 255));
viewchanger.addView(mainmenu);
FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView().findViewById(android.R.id.content);
frameLayout.addView(viewchanger);
Related
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 custom aggregate View that I'm trying to derive from RelativeLayout like so:
public class CheckableView extends RelativeLayout implements Checkable {
TextView mTextView;
boolean mIsChecked = false;
int mId = 0;
public CheckableView(
Context context,
AttributeSet attrs,
int defStyle,
int resource,
int textViewResourceId) throws Exception {
super(context, attrs, defStyle);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
inflater.inflate(resource, this);
this.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mTextView = (TextView) findViewById(textViewResourceId);
if (mTextView == null) {
throw new Exception("The specified TextView resource Id was not found.");
}
}
}
}
The resource param is the id of a layout I want this CheckableView to aggregate and textViewResourceId is the id of the TextView that I was to graphically indicate checked status on (by drawing a checkmark against it)
This xml is defined as:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/itemslist_categoryName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:text="#string/app_name" />
<LinearLayout
android:id="#+id/category_details_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/addedit_category_check"
android:orientation="vertical" >
<TextView
android:id="#+id/addedit_category_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right|center_vertical"
android:text="#string/app_name" />
<TextView
android:id="#+id/addedit_category_price_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right|center_vertical"
android:text="#string/app_name" />
</LinearLayout>
</RelativeLayout>
I want to use this custom view as each row of a particular ListView so elsewhere in a ListView adapter's getView, I do the following:
public View getView(int position, View convertView, ViewGroup parent) {
CheckableView view = null;
if (convertView == null) {
try {
view = new CheckableView(getContext(),
null,
0,
R.layout.addedit_group_budget,
R.id.addedit_category_check);
} catch (Exception e) {
e.printStackTrace();
}
} else {
view = (CheckableView) convertView;
}
...
return view;
}
Every row of the ListView has texts of differing lengths. When the view is first created, every thing seems to be fine, but when views start to get recycled (i.e. when I scroll up/down). The TextViews don't seem to resize to fit the size of the text even though I have wrap_content specified for the width.
What could I be missing? Example screen shot below. Notice that some of the text is ellipsized, even though there's clearly room for more text.
If I delete implements Checkable from the class definition, things work as expected indicating this has something to do with it.
Figured out that the actual culprit is the setCompoundDrawablesWithIntrinsicBounds API which I was making on one of TextView's. Calling this API seems to wreck havoc on the measurements of the View's within the parent layout. This issue is articulated beautifully # calling setCompoundDrawablesWithIntrinsicBounds on multiple views with the same background gives inconsistent sizes
I was able to work around the problem by using an ImageView in which I set the appropriate icon to indicate "checked" state.
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.
In my xml layouts I have a custom view in which i will put some children like:
<com.proj.layouts.components.ScrollLayout
android:id="#+id/slBody"
android:layout_width="700dp"
android:layout_height="400dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="child1"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="child2"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="child3"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="child4"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="child5"/>
</com.proj.layouts.components.ScrollLayout>
Let me explain a bit more. I wrote a custom ScrollView in which I already have a container defined, for the children. So I just want to put them there.
public class ScrollLayout extends LinearLayout {
// View responsible for the scrolling
private FrameLayout svContainer;
// View holding all of the children
private LinearLayout llContainer;
public ScrollLayout(Context context) {
super(context);
init();
}
public ScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
super.removeAllViews(); // kill old containers
svContainer = new HorizontalScroll(getContext());
llContainer = new LinearLayout(getContext());
llContainer.setOrientation(orientation);
svContainer.addView(llContainer);
svContainer.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
llContainer.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
addView(svContainer);
}
... I left out the part which takes care of the scroll event ...
}
What is the way to add Child* to llContainer?
Why don't you just add all the children to the LinearLayout from your ScrollLayout? This should be done in the onFinishInflate() method.
for (int i = 0; i<getChildCount(); i++)
{
View v = getChildAt(i);
removeViewAt(i);
llContainer.addView(v);
}
When you write your structure in the XML file - all inner views are children of your custom layout. Just replace it to LinearLayout.
Jin35's answer has a serious problem: getChildCount() changes value over the iterations because we are removing the children.
This should be a better solution:
while (getChildCount() > 0) {
View v = getChildAt(0);
removeViewAt(0);
llContainer.addView(v);
}
I agree Jin35's answer is flawed. Also, the svContainer is added, so we cannot continue until getChildCount() == 0.
By the end of init(), getChildCount() == 1, since svContainer has been added but the TextViews have not.
By the end of onFinishInflate(), the TextViews have been added and should be at positions 1, 2, 3, 4 and 5. But if you then remove the View at position 1, the indices of the others will all decrease by 1 (standard List behaviour).
I would suggest:
#Override
protected void onFinishInflate() {
super.onFinishInflate();
View v;
while ((v = getChildAt(1)) != null) {
removeView(v);
llContainer.addView(v);
}
}
I'm back at trying out some Android dev again. I have an "old" HTC Hero phone lying around, so I booted that one up, did some updates and are now up n running again with Eclipse and the rest.
I have Android 2.1 running on the device.
I have made a very simple test app that doesn't do anything at all except for showing some Activities and such. Even though there is no database connection, no data fetched from any network the app is very slow.
For example, I have a ListView with some custom layout items. When adding only 6-7 items (so that I get the scrolling) it is very slow when scrolling. Also, I have some buttons that changes the Activity and also that is very very slow:
mButtonListenerUPP = new OnClickListener() {
#Override
public void onClick(View v)
{
Intent myIntent = new Intent(BaseActivity.this, ActivityMain.class);
BaseActivity.this.startActivity(myIntent);
}
};
I cannot figure out why.
The Adapter, NodeRowAdapter
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.view.*;
import android.widget.ArrayAdapter;
import android.widget.TextView;
public class NodeRowAdapter extends ArrayAdapter
{
private Activity context;
private ArrayList<Node> mList;
private LayoutInflater inflater;
NodeRowAdapter(Activity context, ArrayList<Node> objects)
{
super(context, R.layout.nodepickup, objects);
this.context=context;
mList = objects;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
class ViewHolder
{
TextView name;
TextView time;
TextView road;
Node node;
}
public Node getNode(int position)
{
if (mList != null && mList.size() > position)
return mList.get(position);
else
return null;
}
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
ViewHolder holder;
if (view == null)
{
view = inflater.inflate(R.layout.nodepickup, parent, false);
holder = new ViewHolder();
holder.name =(TextView)view.findViewById(R.id.name);
holder.time =(TextView)view.findViewById(R.id.time);
holder.road =(TextView)view.findViewById(R.id.road);
view.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
Node node = mList.get(position);
holder.name.setText(node.name);
holder.time.setText(node.time);
holder.road.setText(node.road);
return(view);
}
}
The main activity, ActivityMain
public class ActivityMain extends BaseActivity
{
private NodeRowAdapter _nodeRowAdapter;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
SICApplication._myContext = this;
SICApplication._myContext = this;
_nodeRowAdapter = new NodeRowAdapter(this, SICApplication.dataHolder_activityMain._nodes);
ListView listView1 = (ListView) findViewById(R.id.ListViewNodes);
listView1.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Node node = _nodeRowAdapter.getNode(position);
Log.v("MyApp", "Node=" + node.name);
}
});
listView1.setAdapter(_nodeRowAdapter);
}
/* Handles item selections */
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case R.id.add_item:
addNodeItem();
return true;
}
return false;
}
private void addNodeItem()
{
_nodeRowAdapter.add(new Node("Test", "asd asd ", "14:00", 1));
}
}
The custom list item, NodePickup
public class NodePickup extends LinearLayout
{
public NodePickup(Context context, AttributeSet attributeSet)
{
super(context, attributeSet);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.nodepickup, this);
this.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setMessage("Ajabaja!")
.setCancelable(true)
.setPositiveButton("JA!", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
dialog.cancel();
}
});
builder.show();
}
});
}
}
And lastly, the NodePickup XML layout
<LinearLayout
android:id="#+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="64dip"
android:orientation="horizontal"
android:background="#drawable/stateful_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="#+id/ImageView01"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="#drawable/arrow_up_green"
android:background="#android:color/transparent">
</ImageView>
<LinearLayout
android:id="#+id/LinearLayout02"
android:background="#android:color/transparent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:text="14:46 (15 min)"
android:id="#+id/time"
android:textSize="12dip"
android:textColor = "#000000"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent">
</TextView>
<TextView
android:text="test"
android:id="#+id/road"
android:textSize="12dip"
android:textColor = "#000000"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent">
</TextView>
<TextView
android:text="test test"
android:id="#+id/name"
android:textSize="12dip"
android:textColor = "#000000"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent">
</TextView>
</LinearLayout>
</LinearLayout>
Update
If I remove the image in the NodePickup, the lagginess is gone. The question is - why?
UPDATE 2011-08-29 If I remove the image in the NodePickup, the lagginess is gone.
The view has a hard time figuring how the layout should be rendered. The xml you posted don't help much. If you remove the ImageView then the LinearLayout02 will take all the width of the parent. But having the imageView with standar dimentions and the layout to the right will fill_parent confuses the view a lot. Requests the size of the imageView again to "push the margins to the right" (kind of). Take a look at my suggestions below
Tip1
use the LinearLayout property weight. Make the imageView fill_parent and the LinearLayout too (for the width) and play with the weight properties.
Do that also for the vertical layout with the TextViews. The best Solution whould be to put a fixed size to the height of the TextViews thought.
Also consider to change the top view to RelativeLayout. Make the image with standar dimentions , AlignParentLeft and put the LinearLayout02 toRightOf imageView. You will relief the onMeasure of the ViewGroup a lot.
Tip2
It seems like when the text changes height the whole view need to be reinflated.A common technic to avoid that it to make list Item fixed in height. So the listview can reuse the recycled views without reinflating.
Tip3
Give your LinearLayout02 a fixed height of 64dp or Fill_Parent since you don't have any left space, but the Layout don't know that and try to rearrange it self every time since the text is also Wrap_content.
Also you said that if you remove the ImageView everything is fast again.If the above don't have any effect can you please try this? Since you know that imageView size is fixed.
Extend your imageView and override requestLayout() method.
public class MyImageView extends ImageView {
public PImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public PImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PImageView(Context context) {
super(context);
}
#Override
public void requestLayout() {
/*
* Do nothing here
*/
}
}
Now include the MyImageView widget to your XML like that.
<com.package_name.MyImageView
android:id="#+id/ImageView01"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="#drawable/arrow_up_green"
android:background="#android:color/transparent">
</com.package_name.MyImageView >
After optimizing my getView() method to use a holder and to reuse convertView if it's not null, my listview was still pretty laggy.
Then, I've tried
listView.setScrollingCacheEnabled(false);
and it solved it at once.
Hope this helps someone.
I just discovered this and I wanna share it with you guys.
I tried ALL the solutions provided but nothing worked. What was causing the problem is the length of the text I am feeding one of my TextView views because i'm using
mTextView.SetText(theLongString)
in my adapter in my getView method. Now that I shrinked my String, the listview is VERY smooth :)
It took a while! I tried everything. Disabling the scroll cache, viewHolder, cacheColorHint ... but nothing worked!
After searching many hours I found the root of all evil!
In my themes.xml I had a scaling background image:
<item name="android:windowBackground">#drawable/window_bg</item>
After removing the beackground everything was butter smooth.
I hope this helps someone!
To improve performance of listview use both or any one of the two - (Simple implementation)
ViewHolder
AsyncTask (a separate thread)
If you have moderately long lists I recommend ViewHolder otherwise for large lists (like in the case of a music player) I recommend using ViewHolder along with AsyncTask. The key to smooth ListView scrolling is to reduce memory consumption as much as possible.
To implement ViewHolder, is simple. Just create a static class in your custom Adapter that holds all the views that you find via findViewById. So this is how it should look -
static class ViewHolder
{
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
Then, the next time you need to findViewById any of these views, just reference the ViewHolder like this-
ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) yourView.findViewById(R.id.listitem_image);
holder.text = (TextView) yourView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) yourView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) yourView.findViewById(R.id.progress_spinner);
This is tested code and taken from one of my projects. However, the original source is here - http://developer.android.com/training/improving-layouts/smooth-scrolling.html
The lists become smoother using ViewHolder. Using AsyncTask is a little more complex, but do check out the link if you wish to implement the AsyncTask along with ViewHolder for a much smoother scrolling experience. Good Luck!
Load the image once as Bitmap and apply it to the ImageView programmatically after inflating it. If you need different images per item you should create the Bitmaps asynchronously like described in Android: How to optimize ListView with ImageView + 3 TextViews?
Try using android:cacheColorHint="#00000000" for your listview. To improve drawing performance during scrolling operations, the Android framework reuses the cache color hint.
Reference: developer.android.com article.
This Might help some one
If you have an image in your list Item, you have to remember to reduce the quality of that Image. It's allot faster to load in a few Kb's than a few megabytes.
This helped me
public Bitmap MakeFileSmaller_ToBitmap(File f) {
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// The new size we want to scale to
final int REQUIRED_SIZE=200;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while(o.outWidth / scale / 2 >= REQUIRED_SIZE &&
o.outHeight / scale / 2 >= REQUIRED_SIZE) {
scale *= 2;
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {
Log.d(TAG, "FILE NOT FOUND " );
}
Log.d(TAG, "OTHER EXCEPTION");
return null;
}
I had the same issue before while i was using different layout like LinearLayout, RelativeLayout, CardView as parent of different child in same list view item. I solved that issue by changing all view inside RelativeLayout.
Using RelativeLayout as main and it's child layout may increase the speed of loading each item. So scrolling will be smooth.
<RelativeLayout
android:id="#+id/LinearLayout01"
android:layout_width="fill_parent"
android:layout_height="64dip"
android:orientation="horizontal"
android:background="#drawable/stateful_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="40dip"
android:layout_height="40dip"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:id="#+id/relativeLayout">
<ImageView
android:id="#+id/ImageView01"
android:layout_width="40dip"
android:layout_height="40dip"
android:src="#drawable/arrow_up_green"
android:background="#android:color/transparent">
</ImageView>
</RelativeLayout>
<TextView
android:text="14:46 (15 min)"
android:id="#+id/time"
android:textSize="12dip"
android:textColor = "#000000"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:layout_alignParentTop="true"
android:layout_toRightOf="#+id/relativeLayout"
android:layout_toEndOf="#+id/relativeLayout" />
<TextView
android:text="test"
android:id="#+id/road"
android:textSize="12dip"
android:textColor = "#000000"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:layout_below="#+id/time"
android:layout_toRightOf="#+id/relativeLayout"
android:layout_toEndOf="#+id/relativeLayout" />
<TextView
android:text="test test"
android:id="#+id/name"
android:textSize="12dip"
android:textColor = "#000000"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:layout_below="#+id/road"
android:layout_toRightOf="#+id/relativeLayout"
android:layout_toEndOf="#+id/relativeLayout"/></RelativeLayout>
You can use
listview.setScrollingCacheEnabled(false).When scrolling listview application hold area then throwing Out Of Memory(OOM) exception.My solution is worked for me.