ListView very slow when scrolling (using ViewHolder/recycling) - android

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.

Related

How to scroll two-columns buttons in Activity

I can include six buttons and displays correctly as .
Now I want to include more than six buttons in scrollable view, but I can't handle it for matching available space, creating 2 rows (or columns if portrait).
Can you provide some way to achieve this?
References:
I have a composed button created using the following code:
public class ImageButtonText extends RelativeLayout {
ImageButton button;
TextView label;
Holder holder;
private void init() {
LayoutInflater li = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
li.inflate(R.layout.big_button, this, true);
button = (ImageButton) findViewById(R.id.button);
label = (TextView) findViewById(R.id.label);
/*initialise button and label*/
}
}
and the following xml:
<?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="match_parent"
android:layout_margin="#dimen/component_margin">
<ImageButton
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/text_box"
android:scaleType="fitCenter"/>
<TextView
android:id="#+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
android:layout_margin="#dimen/component_margin"
android:gravity="center"
android:text="Sample"
android:textSize="#dimen/text_size"/>
</RelativeLayout>
finally add to the main layout using:
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center">
<ImageButtonText
android:id="#+id/button1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
custom:buttonBackground="#drawable/states_green"
android:src="#drawable/ic_safe_call"
android:contentDescription="#string/btn1_info"
android:text="#string/btn1text"
android:textColor="#color/text_green" />
<ImageButtonText
android:id="#+id/button2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
custom:buttonBackground="#drawable/states_red"
android:src="#drawable/ic_private_call"
android:contentDescription="#string/btn2_info"
android:text="#string/btn2text"
android:textColor="#color/text_red" />
</LinearLayout>
NOTES
May be a different approach also. My final goal is to display multiple buttons (each containing a stretched image and bottom aligned text) in two rows if portrait or three columns if landscape. All this wrapped in scrollable view.
UPDATE:
I solved using RecyclerView :D Thanks everyone
An easy and dynamic approach would be to create a custom List of buttons and put it into a GridView with 2 Colums resp. Rows.
This adapter i did for my navigation has an icon and a text, maybe it helps you. The icon is on the Left side as you can read in this line: holder.textView.setCompoundDrawablesWithIntrinsicBounds(item.get_icon(), 0, 0, 0); Te second one would be the icon above text.
Anyway, I think you will need a custom layout, wich you can easily create doing a new xml file with a Relative or LinearLayout and put an ImageView and a TextView into it and give as a parameter in constructor layoutResourceId.
The height of a listItem you can define in this layout xml file.
The GridView you can Configure different for Landscape and Portrait
In landscape mode the layout from the layout-land/ will be used
In portrait mode the layout from the layout-port/ will be used
public class NavigationAdapter extends ArrayAdapter<NavItem> {
List<NavItem> data;
Context context;
int layoutResourceId;
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
NavHolder holder;
if(row == null)
{
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
row = inflater.inflate(layoutResourceId, parent, false);
holder = new NavHolder();
holder.textView = (TextView)row.findViewById(android.R.id.text1);
row.setTag(holder);
}
else
{
holder = (NavHolder) row.getTag();
}
NavItem item = data.get(position);
holder.textView.setText(item.get_title());
holder.textView.setCompoundDrawablesWithIntrinsicBounds(item.get_icon(), 0, 0, 0);
//holder.textView.setCompoundDrawablePadding(10);
return row;
}
public NavigationAdapter(Context context, int layoutResourceId, List<NavItem> data) {
super(context, layoutResourceId, data);
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
static class NavHolder
{
TextView textView;
}
}

GridView isn't being displayed correctly

I took a screenshot of it. Its displaying some weird way:
This is the code.
The GridAdapter:
public class GridViewAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<Uri> mUrls;
// references to our images
public GridViewAdapter(Context c, ArrayList<Uri> images) {
mContext = c;
this.mUrls = images;
}
public int getCount() {
return mUrls.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater mInflater = (LayoutInflater) mContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
ImageView inflatedImageView = (ImageView) mInflater.inflate(R.layout.imageview_amb_background, null);
//ImageView imageView = new ImageView(mContext);
inflatedImageView.setImageURI(mUrls.get(position));
inflatedImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
return inflatedImageView;
}
And the inflatedImageView is a layout inflate, like this:
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="#drawable/bgimages"
android:maxWidth="120dp"
android:padding="5dp">
</ImageView>
On the other hand, I've a gridView in a xml file:
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="250dp"
android:horizontalSpacing="5dp"
android:numColumns="3"
android:verticalSpacing="5dp" >
</GridView>
So, I inflate this gridView, I add several URI's (3 exactly) in a loop. Once added, I set the adapter to the gridview:
ArrayList<Uri> uriFotos = new ArrayList<Uri>();
HashMap<Integer, ListItem> items = xmlList.getItems();
for (int i = 0; i<items.size();i++){
ListItem itemActual = items.get(i);
itemActual.getLogoSrc();
uriFotos.add(Uri.parse(Environment.getExternalStorageDirectory().toString()+rutaFinsCarpetaClient+itemActual.getLogoSrc()));
}
gridViewInflated.setAdapter(new GridViewAdapter(this,uriFotos));
variableContent.addView(gridViewInflated);
The images are "linked" correctly.
variableContent is a LinearLayout inside a ScrollView, so the grid should be scrollable.
But as you can see few things are happening:
Height is soo big. Shouldn't it be like the inflatedImageView says?
Scroll isnt working. Well, its working but i've to move the finger around and tap several times until it works. If i stop scrolling I've to repeat the same proces until it reacts again. (SOLVED)
Hope you guys can help me. I've changed lots of layouts, changed widths, heights, and the same thing is happening.
Note that the image you see that is in the gridView, is something like 1200x800px.
Edit
The same code with smaller images:
I would try to set the Image view size to wrap_content :
android:layout_height="wrap_content"
and if you really need the 120dp height, try to reset this size after having set the image src:
inflatedImageView.setImageURI(mUrls.get(position));
inflatedImageView.setLayoutParams(new GridView.LayoutParams(120, 120));
also set the scaleType before adding the picture (preferably in the xml) :
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="120dp"
android:scaleType="centerInside"
android:background="#drawable/bgimages"
android:maxWidth="120dp"
android:padding="5dp">
</ImageView>

Android Why the Horizontal scroll does not fit the whole width

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));

Custom Layout doesn't resize children when used as ListView items

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.

Android - how to align list view items to be nicely spaced left and right align?

I am trying to add an image to my ListView to make it look more like a button. I would like the images to be a little smaller, maybe 60% of current. And the images to lign up nicely on the right in a column. Here is a screen of what I currently have:
and here is my list view xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:padding="10dp"
android:textSize="16sp"
android:layout_width="match_parent"
android:drawableRight="#drawable/arrow_button"
>
</TextView>
any idea what I am doing incorrectly?
The ListView that contains this TextView is defined like this:
One note, the way I create and work with my Lists is with the ListAdapter, using code like this:
Question q = new Question ();
q.setQuestion( "This is a test question and there are more than one" );
questions.add(q);
adapter = new ArrayAdapter<Question>( this, R.layout.questions_list, questions);
setListAdapter(adapter);
Thanks!
Ahh. You are doing the correct thing using a compound drawable. Not sure if there is a better way to maybe have the spacing in your compound drawable expand, but I know this'll work.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<TextView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="10dp"
android:textSize="16sp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true" />
<View
android:layout_height="64dip"
android:layout_width="64dip"
android:background="#drawable/arrow_button"
android:layout_centerVertical="true"
android:layout_alignParentRight="true" />
</RelativeLayout>
Basically just pointing out using the align parent right and left. You may want to add some margins or padding to them. Also make sure to vertically center your elements.
With the comment and advice that Frank Sposaro gave, you will be able to position your views correctly.
For your next problem, I advice you to make your own adapter similar to this:
private class CustomAdapter extends ArrayAdapter<Question> {
private LayoutInflater mInflater;
public CustomAdapter(Context context) {
super(context, R.layout.row);
mInflater = LayoutInflater.from(context);
}
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.row, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.mTextView);
holder.image = (ImageView) convertView.findViewById(R.id.mImage);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//Fill the views in your row
holder.text.setText(questions.get(position).getText());
holder.image.setBackground... (questions.get(position).getImage()));
return convertView;
}
}
static class ViewHolder {
TextView text;
ImageView image;
}
In your onCreate:
ListView mListView = (ListView) findViewById(R.id.mListView);
mListView.setAdapter(new CustomAdapter(getApplicationContext(), questions));
Another example for a ListView with an Adapter can be found here

Categories

Resources