I have a problem about listview. Each item of listview have an imageview and a textview. I extended BaseAdapter class for Listview adapter and overrided some methods that I must override. By the way I want to shrink the size of text in the textview if greater than 25. For this reason I created a method whose name is "shrinkText()". When I execute the application first time, this method works correctly.So the textviews whose size of text grater than 25 have been shrinked and other textviews keep their size. However, when I scrool down the listview, textviews that their text size less than 25 have been shrinked too. What should I do to fix this? Thanks
My listview adapter...
public class ListAdapter extends BaseAdapter{
private final ProgramInfo values;
private LayoutInflater mInflater;
public ListAdapter(Context context, ProgramInfo values) {
this.values = values;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount(){
return values.getSize();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.brd_stream_list_item,parent,false);
viewHolder = new ViewHolder();
viewHolder.p_Name = (TextView)convertView.findViewById(R.id.prgName);
viewHolder.p_Image = (ImageView)convertView.findViewById(R.id.prgImage);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.p_Name.setText(values.programNames.get(position));
viewHolder.p_Image.setImageResource(R.drawable.alarm_clock_ed);
CharSequence text = viewHolder.p_Name.getText();
shrinkText(text,viewHolder.p_Name); //Call for shrink
}
My shrinkText() method..
private void shrinkText(CharSequence text, TextView v){
if(text.length()>25){
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
}
}
You need to add the else statement :
private void shrinkText(CharSequence text, TextView v){
if(text.length()>25){
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11);
}
else {
v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25); //the default text size
}
}
A tips : to avoid bug, always use if-else statement in getView (not only if).
Related
I'm coding a scrollable listview in android with textview and edittext in each row. Because there's the issue when you scroll, that the new data gets lost i update my ArrayList in an TextChangedListener like this:
TextWatcher txtwatch = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() > 0) {
if (!scrolling) {
InhalteArr.set(position, s.toString());
}
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
My problem is now, that this method is working, but only for the seven first rows. The following rows are showing the right information, but they don't set the updated data at the right position in the ArrayList.
I can't understand why the first seven rows are doing what I want and the rest seems to begin setting the updated data upcounting from zero again and not going on with eight.
It's very creepy because all rows also the last rows seems to get the right data from ArrayList, but the last view rows are setting the data at wrong place in ArrayList.
In getView() Method I'm setting the data like this
viewHolder.Inhalt.setText(InhalteArr.get(position).toString());
but that works.
Has anyone any idea?
Thank you!
EDIT: Here's my adapter:
public class SyAdapter extends ArrayAdapter<ArrayList> {
Typeface bahn = Typeface.createFromAsset(getContext().getAssets(), "fonts/bahnschrift.ttf");
Typeface ih = Typeface.createFromAsset(getContext().getAssets(), "fonts/corbel.ttf");
private static class ViewHolder {
TextView Merkmal;
TextView Inhalt;
}
ArrayList MerkmaleArr;
ArrayList InhalteArr;
public SyAdapter (Context context, ArrayList Merkmale, ArrayList Inhalte) {
super(context, R.layout.listanzeige, R.id.MerkmalT, Merkmale);
this.MerkmaleArr = Merkmale;
this.InhalteArr = Inhalte;
}
#NonNull
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.listanzeige, parent, false);
viewHolder.Merkmal = (TextView) convertView.findViewById(R.id.MerkmalT);
viewHolder.Merkmal.setTypeface(bahn);
viewHolder.Inhalt = (TextView) convertView.findViewById(R.id.InhaltT);
viewHolder.Inhalt.setTypeface(ih);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
System.out.println("Aktuelle Position: " + position);
viewHolder.Inhalt.setText(InhalteArr.get(position).toString());
viewHolder.Merkmal.setText(MerkmaleArr.get(position).toString());
return convertView;
}}
EDIT2: Now i figured out that all rows with position >7 have at the beginning already convertView != null, what means, that they skip the if (convertView == null) code. But why?
Assuming size of InhalteArr and MerkmaleArr is same
Add this method to your adapter class
#Override
public int getCount() {
return InhalteArr.size();
}
Adapter Class refinement Example:
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
convertView = layoutInflater.inflate(R.layout.listanzeige, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.setTag(viewHolder);
}else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.Inhalt.setText(InhalteArr.get(position).toString());
viewHolder.Merkmal.setText(MerkmaleArr.get(position).toString());
return convertView;
}
private class ViewHolder {
final EditText Inhalt;
final TextView Merkmal;
public ViewHolder(View v) {
Inhalt = v.findViewById(R.id.InhaltT);
Merkmal = v.findViewById(R.id.MerkmalT);
}
}
For those who have the same problem:
I solved my issue setting a tag out of the if(convertview == null), at first i was trying to set the tags into that ifmethod, but that won't work at all rows.
To get the edittext'stag into the onTextChangeListeneri found some good code here at stackoverflow:
View focView = activity.getCurrentFocus();
if (focView != null) {
EditText edit = (EditText) focView.findViewById(R.id.tvinhalt);
if (edit != null && edit.getText().toString().equals(s.toString())) {
int positionof = Integer.parseInt(edit.getTag().toString());
System.out.println("Tag= " + positionof);
YouArray.set(positionof, s.toString());
}
Hope with this answer you don't have to invest as much time as me to understand, that the tag must been set outside of that iffunction :D
I am making an Custom listview with Custom Adapter extending BaseAdapter. Now I want to set a different layout only for first row and another layout for all other rows. I am using this code but it sets the special layout for 1st row and also repeats it after every 5/6 rows. How can I fix it and can set it only for 1st row and another layout for all other rows.
public class NewsAdapter extends BaseAdapter {
private Context context;
List<News> newsList;
private Typeface customBanglaFont;
private LayoutInflater inflater;
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
public NewsAdapter(Context context, List<News> newsList){
this.context = context;
this.newsList = newsList;
}
#Override
public int getCount() {
return newsList.size();
}
#Override
public Object getItem(int arg0) {
return newsList.get(arg0);
}
#Override
public long getItemId(int arg0) {
return arg0;
}
#Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
View row;
row = convertView;
ViewHolder holder=null;
if(row == null){
if(position == 0){
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row =inflater.inflate(R.layout.row_single_big_view,viewGroup,false);
}
else{
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row =inflater.inflate(R.layout.row_single_default,viewGroup,false);
}
holder = new ViewHolder(row);
row.setTag(holder);
}
else{
holder = (ViewHolder) row.getTag();
}
if (imageLoader == null){
imageLoader = AppController.getInstance().getImageLoader();
}
final News news = newsList.get(position);
customBanglaFont = Typeface.createFromAsset(viewGroup.getContext().getAssets(), "fonts/SolaimanLipi.ttf");
holder.newsTitleView.setTypeface(customBanglaFont);
holder.newsTitleView.setText(news.getTitle());
holder.thumbNail.setImageUrl(news.getFeaturedImgSrc(), imageLoader);
return row;
}
}
/**
* Custom View Holder Class
* #author Tonmoy
*
*/
class ViewHolder{
TextView newsTitleView;
NetworkImageView thumbNail;
public ViewHolder(View v) {
// TODO Auto-generated constructor stub
newsTitleView = (TextView) v.findViewById(R.id.news_title);
thumbNail = (NetworkImageView) v.findViewById(R.id.news_image);
}
}
Why don't you use the Header functionality already built into ListView for this specific thing.
The docs:
http://developer.android.com/reference/android/widget/ListView.html#addHeaderView%28android.view.View,%20java.lang.Object,%20boolean%29
or a SO question:
Using ListView : How to add a header view?
your attempting is wrong. Your way you will be able to inflate just one layout. In fact, after the method returns the first time, convertView is not null. To fix quickly you could override getViewTypeCount and getItemViewType. This way you will get convertViews eqaul to the return value of getViewTypeCount, or you could just add the first item as header view for the ListView
I have made an Listview populated with list_row_layout.xml(which is populated with json serializable class), i have clickable textview and onclick changing text from "Accept" to "Accepted". But when i click it on first listview item, another textview listview items below are changing.
Here's some photos to descibe you better
this is the adapter class
public class CustomListAdapter extends BaseAdapter {
private ArrayList<FeedItem> listData;
private LayoutInflater layoutInflater;
private Context mContext;
public CustomListAdapter(Context context, ArrayList<FeedItem> listData) {
this.listData = listData;
layoutInflater = LayoutInflater.from(context);
mContext = context;
}
#Override
public int getCount() {
return listData.size();
}
#Override
public Object getItem(int position) {
return listData.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.list_row_layout, null);
holder = new ViewHolder();
holder.headlineView = (TextView)convertView.findViewById(R.id.sex);
holder.reportedDateView = (TextView) convertView.findViewById(R.id.confid);
holder.approve = (TextView) convertView.findViewById(R.id.approveTV);
holder.approve.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View argView)
{
holder.approve.setText("Accepted");
}
}
);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
FeedItem newsItem = (FeedItem) listData.get(position);
holder.headlineView.setText(Html.fromHtml(newsItem.getTitle()));
holder.reportedDateView.setText(Html.fromHtml(newsItem.getContent()));
return convertView;
}
static class ViewHolder {
TextView approve;
TextView headlineView;
TextView reportedDateView;
ImageView imageView;
}
}
Remember that views can be recycled via convertView.
In your onClick method you set the approve text to "Accepted" but when the view is recycled, you never set it back to "Accept"
Actually you need to update (something in) the list in response to an click and have the Accept/Accepted value toggle based on that value rather than simply updating what is currently visible on the screen.
-- to answer the "how" question (asked below)--
Add a new field to ViewHolder
static class ViewHolder {
TextView approve;
TextView headlineView;
TextView reportedDateView;
ImageView imageView;
FeedItem newsItem;
}
Change the onClick method:
public void onClick(View argView)
{
// note that holder no longer needs to be final in the parent class
// because it is not used here.
View parent = (View)argView.getParent();
ViewHolder clickedHolder = (ViewHolder)parent.getTag();
clickedHolder .newsItem.setAccepted(true); /// a new method
clickedHolder .approve.setText ("Accepted");
Log.d(TAG, "Accepted item #" + position);
}
After you have convertView created (if necessary)
FeedItem newsItem = (FeedItem) listData.get(position);
holder.newsItem = newsItem; // populate the new field.
holder.headlineView.setText(Html.fromHtml(newsItem.getTitle()));
holder.reportedDateView.setText(Html.fromHtml(newsItem.getContent()));
if(newsItem.isAccepted ()){ // another new method!
holder.approve.setText ("Accepted");
Log.d(TAG, "Set text to Accepted for item #" + position);
}else{
holder.approve.setText("Accept");
Log.d(TAG, "Set text to Accept for item #" + position);
}
Once it is working you should consider removing the Log.d() lines to cut down on the noise in LogCat.
I have listview which contains textview and buttons. When i delete listview item and i try to scroll down, i get exception on this:
BuildQueue eile = countryList.get(position);
Exception:
02-08 19:11:04.279: E/AndroidRuntime(10509): java.lang.IndexOutOfBoundsException: Invalid index 15, size is 15
Seems i do not updating something when i delete item from listview. I think i have problem with ViewHolder, but i do not know what kind of...
My ArrayAdapter code:
public class MyCustomAdapter extends ArrayAdapter<BuildQueue> {
private ArrayList<BuildQueue> countryList;
public MyCustomAdapter(Context context, int textViewResourceId,ArrayList<BuildQueue> countryList) {
super(context, textViewResourceId, countryList);
this.countryList = new ArrayList<BuildQueue>();
this.countryList.addAll(countryList);
}
private class ViewHolder {
TextView code;
TextView field;
Button del;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.queue_buildings, null);
holder = new ViewHolder();
holder.code = (TextView) convertView.findViewById(R.id.code);
holder.field = (TextView) convertView.findViewById(R.id.field_text);
holder.del = (Button) convertView.findViewById(R.id.del_button);
convertView.setTag(holder);
holder.del.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button del_button = (Button) v;
BuildQueue building = (BuildQueue) del_button.getTag();
countryList.remove(building);
dataAdapter.notifyDataSetChanged();
}
});
} else {
holder = (ViewHolder) convertView.getTag();
}
BuildQueue eile = countryList.get(position);
holder.code.setText(" ( Level: " + eile.getOld_level() + " to "+eile.getNew_level()+")");
holder.field.setText(eile.getNameSort());
holder.field.setTag(eile);
holder.del.setText("Delete");
holder.del.setTag(eile);
return convertView;
}
}
You are using a two arrays in your Adapter, but only changing one of them.
Every Adapter uses getCount() to determine how many row should be drawn. ArrayAdapter's getCount() simply asks for the size of the array that you pass to the super constructor here: super(context, textViewResourceId, countryList);. But you are also using a second, local array and when you delete a value from this countryList getCount() has no idea this happened which results in getView() throwing an IndexOutOfBoundsException...
Either extend BaseAdapter, or use ArrrayAdapter's methods like getItem(), add(), and remove() and remove your local data set.
How do I refresh the content of a ListActivity using the custom ListAdapter that I created? I have in the arrayadapter a method that calls "notifyDataSetChanged();". That does not work. Neither have any of the related solutions on this site. Here's the code thus far:
private final Activity context;
private Message[] messages;
public RamRSSAdapter(Activity context, Message[] messages) {
super(context, R.layout.ram_rss_row);
this.context = context;
this.messages = messages;
}
// static to save the reference to the outer class and to avoid access to
// any members of the containing class
static class ViewHolder {
public ImageView imageView;
public TextView textView;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// ViewHolder will buffer the assess to the individual fields of the row
// layout
ViewHolder holder;
// Recycle existing view if passed as parameter
// This will save memory and time on Android
// This only works if the base layout for all classes are the same
View rowView = convertView;
//string code goes here
if (rowView == null) {
LayoutInflater inflater = context.getLayoutInflater();
rowView = inflater.inflate(R.layout.ram_rss_row, null, true);
holder = new ViewHolder();
holder.textView = (TextView) rowView.findViewById(R.id.label);
holder.imageView = (ImageView) rowView.findViewById(R.id.icon);
rowView.setTag(holder);
} else {
holder = (ViewHolder) rowView.getTag();
}
holder.textView.setText(messages[position].getTitle());
//code for image here
holder.imageView.setImageResource(getImageResID(getType(messages[position].getTitle())));
return rowView;
}
private String getType(String title){
int i1 = title.indexOf("[");
int i2 = title.indexOf("]");
if((i1==-1)||(i2==-1)){
return "";
}else{
return title.substring(i1+1, i2);
}
}
public void changeData(Message[] newData){
messages = newData;
notifyDataSetChanged();
}
/*
private int getImageResID(String type){
}
}
I have yet to see a case where notifyDataSetChanged() does anything. I've just gotten a new adapter based on the latest information, and changed the scroll position on the ListView to make it appear it's simply updated.
You should not overwrite the array of data directly- instead use the clear/insert/add/addAll/remove methods that are provided by the ArrayAdapter.