Its my first post here but i am reading this Q&A for years and I always find an answer, but this time i cannot find it, or i cannot combine multiple answers with my problem. I hope you can help.
So I have fragment which overrides onCreateView
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO Auto-generated method stub
db = new WineryDatabaseAdapter(getActivity());
bacveList = db.getBacve();
v = inflater.inflate(R.layout.sve_bacve, container, false);
Log.v("onCreateView", "oncreateview");
return v;
}
Then I have method where I set my data
public void getBacveItems(){
ArrayAdapter<Bacve> ad = new BacveListAdapter(getActivity(), bacveList);
lv = (ListView) v.findViewById(R.id.listSveBacve);
lv.setAdapter(ad);
lv.setOnItemClickListener(this);
Log.v("getBacveItems", "getBacveItems");
}
In that method I am calling my Adapter so i can use my listview layout
public class BacveListAdapter extends ArrayAdapter<Bacve>{
List<Bacve> bacve;
Context c;
String[] values = new String[] { "prva","druga" };
public BacveListAdapter(Context c,List<Bacve> l){
//super(c,R.layout.sve_bacve_item,l);
super(c,R.layout.sve_bacve_item,l);
Log.v("BacveListAdapter", "BacveListAdapter");
this.bacve = l;
this.c = c;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View iv = convertView;
TextView tv;
if (iv == null){
iv = (View)((LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.sve_bacve,parent,false);
tv = new TextView(c);
}
else{
Bacve bacva = bacve.get(position);
tv = (TextView) iv.findViewById(R.id.textNazivBacve);
tv.setText(bacva.getIme());
}
return iv;
}
}
But for some reason i am getting errors when trying to access tv. It is always null.
I think its something with views that are created/getted, and I am creating/getting some parent view in higher hierarchy. I have tried to debug it but dont know how to use those IDs in debug mode. Well not how to use it, but how to compare it so I can see is it right view.
Please help :)
I did it like this in this edit but nothing. Is it because i am using swipe views and I am getting wrong view in the first?
This is my sve_bacve_item and it has textview.
I am kind 1 step forward with #Raghunandan solution but now I have problem with convertin String to holder. I am trying to resolve that.
<?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">
<TextView
android:id="#+id/textNazivBacve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="15dp"
android:layout_marginTop="15dp"
android:layout_toRightOf="#+id/bacveIcon"
android:text="#string/BacveNaziv"
android:textAppearance="?android:attr/textAppearanceLarge" />
<ImageView
android:id="#+id/bacveIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignTop="#+id/textNazivBacve"
android:adjustViewBounds="true"
android:contentDescription="#string/Bacve"
android:maxHeight="120dp"
android:maxWidth="120dp"
android:src="#drawable/bacve_item" />
</RelativeLayout>
BR
Use a ViewHolder for perofrmance.
public static ViewHolder
{
TextView tv;
}
Change to
LayoutInflater mInflater;
public BacveListAdapter(Context c,List<Bacve> l){
//super(c,R.layout.sve_bacve_item,l);
super(c,R.layout.sve_bacve_item,l);
mInflater = LayoutInflater.from(c);
this.bacve = l;
this.c = c;
}
Change getView to
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null){
convertView = (View) mInflater.inflate(R.layout.sve_bacve,parent,false);
holder = new ViewHolder();
holder.tv = (TextView) convertView.findViewById(R.id.textNazivBacve);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
Bacve bacva = bacve.get(position);
holder.tv.setText(bacva.getIme());
return convertView;
}
Also you mention
This is my sve_bacve_item and it has textview
So change
convertView = (View) mInflater.inflate(R.layout.sve_bacve,parent,false);
to
convertView = (View) mInflater.inflate(R.layout.sve_bacve_item,parent,false);
Edit:
Your NPE was because you inflated the wrong layout. I got confused the first time.
Also you have commented this
public BacveListAdapter(Context c,List<Bacve> l){
//super(c,R.layout.sve_bacve_item,l);
remove the comment
public BacveListAdapter(Context c,List<Bacve> l){
super(c,R.layout.sve_bacve_item,l);
And do check this to understand ViewHolder pattern to understand why you need it.
A ViewHolder object stores each of the component views inside the tag
field of the Layout, so you can immediately access them without the
need to look them up repeatedly.
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
and also check
How ListView's recycling mechanism works
I'm guessing your layout doesn't have the TextView defined because your code should look like the following:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = (View)((LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.sve_bacve_item,parent,false);
}
TextView tv = (TextView)convertView.findViewById(R.id.textNazivBacve);
Bacve bacva = bacve.get(position);
tv.setText(bacva.getIme());
return iv;
}
And your custom layout should have a text view defined somewhere in your layout file like so.
<TextView android:id="#+id/textNazivBacve"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, I am a TextView" />
UPDATE
Ok it looks like your layout file looks fine. You still do not need to create a TextView the way you were trying. You can simply findView. One thing I noticed, your inflate call is using the wrong layout (sve_bacve not sve_bacve_item), that is why it is unable to find the text view.
This line:
convertView = (View) mInflater.inflate(R.layout.sve_bacve,parent,false);
Should be:
convertView = (View) mInflater.inflate(R.layout.sve_bacve_item,parent,false);
Related
Am getting the following warning in Eclipse:
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
The code which i had used is:
class myadapter extends ArrayAdapter<String>
{
Context context;
int[] images;
String[] mytitle;
String[] mydescp;
myadapter(Context c, String[] tittle, int[] imgs, String[] desc)
{
super(c, R.layout.single_row, R.id.listView1, tittle);
this.context=c;
this.images=imgs;
this.mytitle= tittle;
this.mydescp=desc;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflator.inflate(R.layout.single_row, parent, false);
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
}
Am getting warning in the line : View row = inflator.inflate(R.layout.single_row, parent, false);
And it causes my android application to Force Close... What can i do it now??
Any Suggestions???
You need to recycle your views.What android as a system cares about is only the items that are visible.So you have to recycle the row items which are out of focus to be re-used for the newitems.
Or else imagine the amount of caching involved.
#Override
public View getView(int position, View row, ViewGroup parent) {
// TODO Auto-generated method stub
if(row==null){
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflator.inflate(R.layout.single_row, parent, false);
}
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
You should re-use the view instead of inflating again and again. This brings down performance.
From your code,
View row = inflator.inflate(R.layout.single_row, parent, false);
this will inflate everytime when you scroll. To maximize the performance, use it like
//re-use
if (row == null)
{
inflate code here
}
else
{
you already have a view `row`, just use it.
}
You can see, for the first time row will be null & it will inflate and store it in View row. But, from the next time, it's not going to inflate again and again instead it will use the View row. (Re-use)
"Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling"
It's not the error it's just the warning for asking you to use ViewHolder Pattern. Let me explain you why it's important.
Without ViewHolder Pattern :
The first time it was loaded, convertView is null. We’ll have to inflate our list item layout and find the TextView via findViewById().
The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. But we’ll use findViewById() again.
The following times it was loaded, convertView is definitely not null. But findViewById() is constantly called, it will work but, it slows down the performance especially if you have lots of items and Views in your ListView.
With the ViewHolder Design Pattern :
The first time it was loaded, convertView is null. We’ll have to inflate our list item layout, instantiate the ViewHolder, find the TextView via findViewById() and assign it to the ViewHolder, and set the ViewHolder as tag of convertView.
The second time it was loaded, convertView is not null, good! We don’t have to inflate it again. And here’s the sweet thing, we won’t have to call findViewById() since we can now access the TextView via its ViewHolder.
The following time it was loaded, convertView is definitely not null. The findViewById() is never called again, and that makes our smooth ListView scrolling.
Why to use?
Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the view holder design pattern.
So, what is ViewHolder?
A ViewHolder object stores each of the component views inside the tag field of the Layout, so you can immediately access them without the need to look them up repeatedly. First, you need to create a class to hold your exact set of views.
How to use?
Make a separate class as ViewHolder & declare what you use like EditText,TextView etc..
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
Then populate the ViewHolder and store it inside the layout.
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) { // if convertView is null
convertView = mInflater.inflate(R.layout.mylayout,
parent, false);
holder = new ViewHolder();
// initialize views
convertView.setTag(holder); // set tag on view
} else {
holder = (ViewHolder) convertView.getTag();
// if not null get tag
// no need to initialize
}
//update views here
return convertView;
}
Source :
Making ListView Scrolling Smooth from Android documentation
Android ViewHolder Pattern example
Hi Vinesh Senthilvel ,
Don't worry
Use my code below , It will definetely solve your problem,
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View row = inflator.inflate(R.layout.single_row, null, false);
ImageView myImage = (ImageView) row.findViewById(R.id.imageView1);
TextView myText = (TextView) row.findViewById(R.id.textView1);
TextView mydesc = (TextView) row.findViewById(R.id.textView2);
myImage.setImageResource(images[position]);
myText.setText(mytitle[position]);
mydesc.setText(mydescp[position]);
return row;
}
If still problem persists then post logcat exception stack trace ,I will help you
There is a another approach , You just have to import android.view.LayoutInflater; and take the context of parent (ViewGroup) - parent.getContext() ,It will work
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.single_row, parent, false);
You have to use this code
View row = convertView;
before this line,
View row = inflator.inflate(R.layout.single_row, parent, false);
Hope it works..
I have a ListView using a custom cursoradapter to fill the ListView.
The row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<LinearLayout
android:orientation="vertical"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent">
<TextView
android:id="#+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center_vertical"
android:ellipsize="marquee"
android:textSize="24dp" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/artist"
android:singleLine="true"
android:ellipsize="marquee"
android:textSize="14dp" />
</LinearLayout>
<ImageView
android:id="#+id/currentplaying"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginLeft="1dip"
android:src="#android:drawable/ic_media_play"
android:contentDescription="#string/now_playing"
android:visibility="gone" />
</LinearLayout>
As you can see, the ImageView's visibility is gone. I want to make it
visible for one particular row. Here is the code I tried but it is not
working...
View view = getListView().getAdapter().getView(0, null, null);
ImageView iv = (ImageView)view.findViewById(R.id.currentplaying);
iv.setVisibility(ImageView.VISIBLE);
Thanks in advance.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView==null) {
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.yourlayout, null);
holder.imgViewLogo = (ImageView) convertView.findViewById(R.id.imgViewLogo);
convertView.setTag(holder);
}
else {
holder=(ViewHolder)convertView.getTag();
}
if(position==0) {
holder.imgViewLogo.setVisiblity(View.VISIBLE);
}
return convertView;
}
EDIT:
I got it working. I used this to start the ListView activity.
intent.putExtra("id", c.getInt(c.getColumnIndex(DatabaseHelper._ID)));
startActivity(intent);
In the ListView activity,
currentplayingid = getIntent().getExtras().getInt("id");
Then I added this in bindview()
ImageView imgview = (ImageView)view.findViewById(R.id.currentplaying);
int id = c.getInt(c.getColumnIndex(DatabaseHelper._ID));
if (id == SongsListActivity.this.currentplayingid)
imgview.setVisibility(View.VISIBLE);
else
imgview.setVisibility(View.GONE);
I got it working. I used this to start the list view activity.
intent.putExtra("id", c.getInt(c.getColumnIndex(DatabaseHelper._ID)));
startActivity(intent);
In the listview activity,
currentplayingid = getIntent().getExtras().getInt("id");
Then i added this in bindview()
ImageView imgview = (ImageView)view.findViewById(R.id.currentplaying);
int id = c.getInt(c.getColumnIndex(DatabaseHelper._ID));
if ( id == SongsListActivity.this.currentplayingid )
imgview.setVisibility(View.VISIBLE);
else
imgview.setVisibility(View.GONE);
perhaps you should do it in getView() of your adapter
EDIT:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
// codes...
if (position == 0)
{
holder.imgViewLogo.setVisibility(ImageView.VISIBLE);
}
else
{
holder.imgViewLogo.setVisibility(ImageView.GONE);
}
// codes...
}
You have to do it like this
iv.setVisibility(View.VISIBLE);
If it is working some time then may be i can help you. What happen that whenever you move you listview it recreates all views again in this case it never save the last state of view. So what you need to do is to save state of your each imageview and in getView() you have to set accordingly.I am posting one of my answer it may help you.
Here is a little code for your help: I will create a boolean arraylist.
private ArrayList imageview_visible = null;
Then I will set states of all imageview as false in my constructor:
for (int i=0; i < no_of_elements.size(); i++) {
imageview_visible.add(i, false);
}
In your getView write this code:
public View getView (int position, View convertView, ViewGroup parent)
{
//WRITE YOUR CODE
if (imageview_visible.get(position) == true)
{
//SET YOUR IMAGE VIEW AS VISIBLE
} else {
// SET IMAGEVIEW AS GONE
}
}
Whenever you unhide or hide your view just save it into imageview_visible.set(true or false) this will save state of you all imageview and set every image view accordingly
Use LayoutInflater to get view object
LayoutInflater inflater = this.getLayoutInflater();
View rowView = inflater.inflate(R.layout.row, null, true);
ImageView iv = (ImageView)view.findViewById(R.id.currentplaying);
iv.setVisibility(ImageView.VISIBLE);
Try the following code as follows,
private class ViewHolder
{
ImageView imgViewLogo;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
if(convertView==null)
{
holder = new ViewHolder();
convertView = inflater.inflate(R.layout.yourlayout, null);
holder.imgViewLogo = (ImageView) convertView.findViewById(R.id.imgViewLogo);
convertView.setTag(holder);
}
else
{
holder=(ViewHolder)convertView.getTag();
}
if(position==0)
{
holder.imgViewLogo.setVisiblity(View.VISIBLE);
}
return convertView;
}
It Works for me...It may help you.
I ran into similar problems where several widgets would appear for some rows but not for others. The problems were due to view recycling. I'm not exactly sure if that's your issue here, but you should handle it anyway. The trick is to set visibility for every row; instead of just for the row that you want to appear/disappear.
So:
if (position == 0)
{
iv.setVisibility(ImageView.VISIBLE);
}
else
{
iv.setVisibility(ImageView.GONE);
}
Otherwise, you're assuming that for positions other than 0 the visibility is GONE but that might not be the case with view recycling. I do this work in bindView, by the way. Not sure if that's technically correct.
I have the same issue... i solved with a non standar solution, but worked for me...
v.setImageResource(R.color.transparent);
importing R from android
import android.R;
Both iv.setVisibility(View.VISIBLE); and iv.setVisibility(ImageView.VISIBLE); are correcte but It's better to use View instead of ImageView because VISIBLE & GONE are defined in View class.
You most change Both Visibility (VISIBLE or GONE) in that if. like:
if(?)
iv.setVisibility(View.VISIBLE);
else iv.setVisibility(View.GONE);
You can hide or show views using setVisibility(int) .
use iv.setVisibility(View.VISIBLE);
I have a Gridview filled by an Adapter which returns LinearLayouts each contains an ImageButton and TextView.
In the adapter I am binding an onClick and onLongClick event to the ImageButton.
I am trying to bind OnItemClickListener to the gridview but I don't know why that the onItemclicked never fired up.
It's my 6th hour without anything.
By the way;
OnItemSelectListener working perfectly on the Grid.
I am checking if some piece of code accidentally handles the onItemClicked but couldn't catch yet.
I need help guys.
gridView = (GridView) layoutInflater.inflate(R.layout.gridview, null);
gridView.setOnItemClickListener(new ItemClickListener());
.
.
.
//inner handler class
class ItemClickListener implements AdapterView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(mainActivity.getApplicationContext(),view + " clicked at pos " +
i,Toast.LENGTH_SHORT).show();
}
}
Do not use clickable objects in the grid. In that case Android cannot handle the click event of GridView.
Instead, use something to show a similar user interface view. Then handle that object's click actions.
Don't: put Button in the GridView to perform some click actions.
Do: put an ImageView instead of ImageButton and handle ImageView's click events.
If you wants to use Button or ImageButton then you need to write these attributes in your xml code of the widgets.
android:focusable="false"
android:focusableInTouchMode="false"
Its works for me.
But in GridView, Try to avoid use of these widgets. You can use any other widgets in place of these (Like ImageView or any other).
Also make sure, that your ListAdpter returns true for
public boolean isEnabled(int _position)
for the position you want to click.
Hey guyz finally got a solution...
what we were doing is directly accessing the Layout inside the GridView, so the onItemClickListener finds it confusing to access the item.
So the solution is to apply the onClickListener inside the Adapter (i.e. normally ArrayAdapter)
so what i m trying to say is:
public View getView(int position, View convertView, ViewGroup parent) {
//Here row is a view and we can set OnClickListener on this
final View row;
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
//Here we inflate the layout to view (linear in my case)
row = inflater.inflate(layoutResourceId, parent, false);
holder = new ViewHolder();
holder.imageTitle = (TextView) row.findViewById(R.id.text);
holder.image = (ImageView) row.findViewById(R.id.image);
row.setTag(holder);
} else {
row = convertView;
holder = (ViewHolder) row.getTag();
}
ImageItem item = data.get(position);
holder.imageTitle.setText(item.getTitle());
holder.image.setImageBitmap(item.getImage());
//Now get the id or whatever needed
row.setId(position);
// Now set the onClickListener
row.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(context, "Clicked" + row.getId() + "!!",
Toast.LENGTH_SHORT).show();
}
});
return row;
}
Try to set
android:clickable="false"
android:focusable="false"
I meet same problem too, because of several reasons.
So, here's my tips:
Extend BaseAdapter for your adapter;
Use OnClickListener inside the getView in adapter instead setting OnItemClickListener for GridView;
Avoid setting LayoutParams multiple times;
Check if position = 0, don't use convertView, inflate new View;
Set OnClickListener not only for parent View, but for any child View, if any;
Make all your Views clickable.
I just tested it on 4 devices, and this solution works as expected. Hope, it will help in your case.
Correct me, if I made something wrong.
Layout code XML:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#273238"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:padding="1dp">
<ImageView
android:id="#+id/open_image_item_imageview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/loh"
android:clickable="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:scaleType="centerCrop"/>
<TextView
android:id="#+id/open_image_item_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_gravity="bottom"
android:padding="4dp"
android:textSize="10sp"
android:ellipsize="start"
android:background="#55000000"
android:textColor="#FFFFFF"
android:text="image name"/>
</FrameLayout>
Adapter code Java:
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = null;
if(convertView != null && position != 0)
view = convertView;
else{
view = LayoutInflater.from(getContext()).inflate(R.layout.open_image_item_layout, null, false);
view.setLayoutParams(new GridView.LayoutParams(GridView.AUTO_FIT, size));
}
TextView textView = (TextView)view.findViewById(R.id.open_image_item_textview);
ImageView imageView = (ImageView)view.findViewById(R.id.open_image_item_imageview);
...
View.OnClickListener onClickListener = getOnClickListener(files[position]);
view.setOnClickListener(onClickListener);
textView.setOnClickListener(onClickListener);
imageView.setOnClickListener(onClickListener);
return view;
}
I have the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="#+id/ListView01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1.0"
android:textColor="#android:color/white"
android:dividerHeight="1px"
android:listSelector="#drawable/highlight_sel"
/>
</LinearLayout>
And the code:
private ListView lv1;
private String lv_arr[]={"Item 1","Item 2","Item 3","Item 4"};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.newsmenu);
lv1=(ListView)findViewById(R.id.ListView01);
// By using setAdpater method in listview we an add string array in list.
lv1.setAdapter(
new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
lv_arr));
}
I want the text color of Item 2 (or 1 or 3 or 4) to appear dynamically as red (denoting a new item) or white (default). Is there a way to do this?
I already have a selector present, which is why I used ListView. I've search the Internet and this site, and I have not seen this question broached.
So is it possible?
Yes everything is possible. you need to write your own adapter implementation basically overriding the getView Method in the adapter. search google and stack you will find many tutorials on how to write an adapter.
Writing a special adapter to override getView in simple adapter is the way to change the text color alternating on the lines of your choice in a listview. I took the example which has been repeated many times on this website and added a way to change the text color. position mod length to select the color position can be replaced with any scheme you like. The text view "business" can be the first line of your layout like mine--or use the android.R.id.text1.
public class SpecialAdapter extends SimpleAdapter {
private int[] colors = new int[] { 0x30FF0000, 0x300000FF };
public SpecialAdapter(Context context, List<HashMap<String, String>> items, int resource, String[] from, int[] to) {
super(context, items, resource, from, to);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
int colorPos = position % colors.length;
//view.setBackgroundColor(colors[colorPos]); //old example
TextView tv1 = (TextView)view.findViewById(R.id.business); //new
tv1.setTextColor(colors[colorPos]); //new
return view;
}
}
Just use SpecialAdapter instead of SimpleAdapter in your app.
Here's an example of a getView method. Note that it's using a viewholder for efficiency. If you want to know more about that, let me know.
public View getView(int position, View convertView, ViewGroup parent) {
tempDeal = exampleBoxArrayList.get(position);
ViewHolder holder;
if (convertView == null) {
convertView = inflator.inflate(R.layout.list_item_example_box, null);
holder = new ViewHolder();
holder.divider = (RelativeLayout) convertView.findViewById(R.id.example_box_divider);
holder.merchantName = (TextView) convertView.findViewById(R.id.example_box_merchant_name);
holder.expireDate = (TextView) convertView.findViewById(R.id.example_box_expire_date);
holder.description = (TextView) convertView.findViewById(R.id.example_box_description);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (tempDeal.isDivider()) {
holder.divider.setVisibility(View.VISIBLE);
} else {
holder.divider.setVisibility(View.GONE);
}
holder.merchantName.setText(tempDeal.getMerchantName());
holder.expireDate.setText(tempDeal.getExpiryDateString());
holder.description.setText(tempDeal.getPriceOption().getDescription());
return convertView;
}
As you can see, I call the isDivider() method on my custom object (this method looks at a boolean set on data load). This method is used to turn the visibility of part of the layout on or off.
Alternatively, you could load a completely new layout based on this same concept.
I have a ListView, and I have added a header (with getListView().addHeaderView) that simply contains a TextEdit widget.
Then when I tap the TextEdit to start writting, the keyboard appears and it messes up the list!
If I tap everywhere else to hide the keyboard, the list messes up again!
I don't know why is this happening. I thought it was something related with the onConfigurationChanged method, but after implementing it (and adding the corresponding attribute in the manifest file) the problem persists.
How could I fix it? Why is Android messing up my list?
EDIT:
My list uses a custom adapter, this is the getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v != null) {
return v;
}
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.list_row, null);
ListTask list_item = items.get(position);
if (list_item != null) {
TextView item_name = (TextView) v.findViewById(R.id.item_name);
item_name.setText(list_item.getTitle());
}
return v;
}
The problem is not the value of my items, but their order. They are displayed in a different order when the keyboard appears, but the values are correct.
EDIT2:
Ok, I have changed my getView method with rekaszeru's suggestion and now it works as expected. But now I'm facing another problem: what if my items have two textviews?
Let's say the second textview is optional, and "Item 1" and "Item 3" have it, but "Item 2" does not, so it's initialized as a void String (length == 0).
The first time the list is displayed, it shows "Item1" and "Item 3" with their second textview, and "Item 2" without it. That's correct. But when the keyboard appears, the "Item 2" takes the second textview of another item and displays it!
This is the modified code I have right now:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
TextView item_optional_text = (TextView) convertView.findViewById(R.id.item_optional_text);
item_name.setText(list_item.getTitle());
// if the item has defined the optional text, make some room and display it
if (item_optional_text.isNotEmpty()) {
LayoutParams layout_params = (LayoutParams) item_name.getLayoutParams();
layout_params.topMargin = 10;
layout_params.height = -2; // -2: wrap_content
item_name.setLayoutParams(layout_params);
item_optional_text.setText(list_item.getOptionalText());
}
return convertView;
}
The isNotEmpty() does this in the Item class:
public boolean isNotEmpty() {
return this.optional_text.length() > 0;
}
Maybe it's too complex to understand in a written question. If so, I can make a short video showing the problem and my source code. Thanks in advance for your help guys.
Your row recycling is messed up. Android is not changing the order of the items, you are.
Right now, if you are passed a row to recycle, you return it without modification. This is a mistake. You are supposed to modify the contents of the row to reflect the data at the supplied position. The only piece of logic you can skip in this case is inflating a brand-new row.
Here is a free excerpt from one of my books that goes through all of this.
You should override the getView method in your ListAdapter implementation, and make sure that you always assign a new value to the view that you are returning (or at least always update it to contain the proper data).
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = inflater.inflate(R.layout.list_row, parent, false);
//set the necessary data in your TextViews, Checkboxes, etc...
return convertView;
}
If you don't inflate your item renderer, then you can instantiate it from code, like:
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
convertView = new TextView([...]);
convertView.setText(textBasedOnYourData);
return convertView;
}
Edit
As #CommonsWare noted, attention should be payed to the recycling of your list item renderer. So instead of instantiating it every time, you should check whether it already exists or not, and update the underlying TextView afterwards.
So I'd suggest give a try to this slightly modified getView implementation:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getContext().
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.list_row, null);
}
ListTask list_item = items.get(position);
TextView item_name = (TextView) convertView.findViewById(R.id.item_name);
//the item should never be null, but just in case:
item_name.setText((list_item == null) ? "" : list_item.getTitle());
return convertView;
}