Have a fragment with a listView that should be inflated as follows :
one with a different layout (custom row),position two with (slider) and from position 3 (item_layout) have gone through quite a lot of resources on the web and stack overflow which didn't solve my purpose looking for a sample or tutorial of this kind.
It would be better to use header instead of different types of items, because you will not recycle first two rows.
Note: When first introduced, this method could only be called before
setting the adapter with setAdapter(ListAdapter).
Example:
ListView listView = view.findViewById(R.id.list);
View header = LayoutInflater.from(getActivity()).inflate(R.layout.your_first_two_rows, listView, false);
listView.addHeaderView(header);
listView.setAdapter(new YourCustomAdapter(...));
private static final int TYPE_ITEM0 = 0;
private static final int TYPE_ITEM1 = 1;
private static final int TYPE_ITEM2 = 2;
#Override
public int getViewTypeCount() {
return 3;
}
#Override
public int getItemViewType(int position) {
if(getItem(position) instanceof Type1List){
type = TYPE_ITEM0;
}
else if(getItem(position) instanceof BingImageBean){
type = TYPE_ITEM1;
}
else if(getItem(position) instanceof InuvoSearchType3Bean){
type = TYPE_ITEM2;
}
return type;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final int viewType = getItemViewType(position);
if(viewType == 0){
// InuvoSearch Type 1
InuvoSearchType_1_ViewHolder holder;
if(convertView == null || (Integer)convertView.getTag(R.id.view_type) != 0){
holder = new InuvoSearchType_1_ViewHolder();
convertView = mInflater.inflate(R.layout.inuvosearch_list_type1,parent,false);
holder.txtInuvoTitle = (TextView) convertView.findViewById(R.id.txtInuvoTitle);
convertView.setTag(holder);
convertView.setTag(R.id.view_type, 0);
}
else {
holder = (InuvoSearchType_1_ViewHolder) convertView.getTag();
}
return convertView;
}
else if(viewType == 1)
{
// Bing Related Images
ImageViewHolder holder;
if(convertView == null || (Integer)convertView.getTag(R.id.view_type) != 1){
holder = new ImageViewHolder();
convertView = mInflater.inflate(R.layout.imagesearch_list,parent,false);
holder.imageView1 = (ImageView)convertView.findViewById(R.id.image1);
convertView.setTag(holder);
convertView.setTag(R.id.view_type, 1);
}
else{
holder = (ImageViewHolder) convertView.getTag();
}
return convertView;
}
else if(viewType ==2)
{
// InuvoSearch Type 3
InuvoSearchType_3_ViewHolder holder;
if(convertView == null || (Integer)convertView.getTag(R.id.view_type) != 2){
holder = new InuvoSearchType_3_ViewHolder();
convertView = mInflater.inflate(R.layout.inuvosearch_list_type3,parent,false);
convertView.setTag(holder);
convertView.setTag(R.id.view_type, 2);
}
else{
holder = (InuvoSearchType_3_ViewHolder) convertView.getTag();
}
holder.baseLayout.setId(position);
return convertView;
}
return null;
}
static class ImageViewHolder{
//Bing Related Images Holder:
private ImageView imageView1,imageView2,imageView3;
private ProgressBar progressBariamgeView1,progressBariamgeView2,progressBariamgeView3;
private RelativeLayout baseLayout;
}
static class InuvoSearchType_1_ViewHolder{
//InuvoSearchType_1_ViewHolder:
private TextView txtInuvoTitle,txtInuvoDescription,txtInuvoLink;
private ImageView icon;
private RelativeLayout baseLayout;
}
static class InuvoSearchType_3_ViewHolder{
//InuvoSearchType_3_ViewHolder:
private TextView txtInuvoTitle,txtInuvoDescription,txtInuvoLink;
private ImageView icon;
private RelativeLayout baseLayout;
}
Like I shown in the above code, we can create different layout for listview using getItemViewType in getView method. Based on the type value, set the xml layout. The above code must be used in Adapterclass of the listview
Related
I have a conversation mode in my application where I wish to load one layout for one user and another layout for the other. It need not always be alternating hence I cannot use a simple "%2" to achieve it.
Based on a flag I am assigning a dynamic layout, which works. My problem is that as I scroll the layouts get distorted as in, conversation of user_1 will get layout_2 or conversation of user_2 will get layout_1, absolutely random.
I did something similar to an answer I saw here:
https://stackoverflow.com/a/16774696/4810718
There were a few posts about randomized data. That is not my issue, the order of the list items does not change however the layout get's randomly applied. I read that the items in view + 1 are kept in temporary memory, regarding this another thing I noticed was: as I keep adding items such that the scrollbar comes into picture when I add a second item outside the visibility it tends to get the layout of the topmost item (first item) visible. Scrolling would later give me seemingly randomized results.
public class ConversationAdapter extends BaseAdapter
{
private LayoutInflater inflater;
private ArrayList<ConversationContent> objects;
ImageView user;
static int ind = 0;
private class ViewHolder
{
TextView textView1;
TextView textView2;
TextView textView3;
}
public ConversationAdapter(Context context, ArrayList<ConversationContent> objects)
{
inflater = LayoutInflater.from(context);
this.objects = objects;
}
public int getCount()
{
return objects.size();
}
public ConversationContent getItem(int position)
{
return objects.get(position);
}
public long getItemId(int position)
{
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if(convertView == null)
{
holder = new ViewHolder();
if (Main_Page3.convFlag == 1)
{
convertView = inflater.inflate(R.layout.conversation_item_1, null);
}
else
{
convertView = inflater.inflate(R.layout.conversation_item_2, null);
}
holder.textView1 = (TextView) convertView.findViewById(R.id.trans);
holder.textView1.setTypeface(Main_Activity.fontC);
holder.textView2 = (TextView) convertView.findViewById(R.id.lang);
holder.textView2.setTypeface(Main_Activity.fontC);
holder.textView3 = (TextView) convertView.findViewById(R.id.user);
holder.textView3.setTypeface(Main_Activity.fontC);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.textView1.setText(objects.get(position).getTranslatedText());
holder.textView2.setText(objects.get(position).getTranslationString());
SpannableString originalTextString = new SpannableString("\n" + objects.get(position).getOriginalText());
originalTextString.setSpan(new RelativeSizeSpan(0.5f), 0, originalTextString.length(), 0);
holder.textView1.append(originalTextString);
holder.textView3.setText(objects.get(position).getUser());
return convertView;
}
}
So, that's the code I've written. A possible solution I thought of was if I used an array of views and loaded them accordingly, it may work? I'm really not really sure how I should be going about doing this - I'm still pretty new to Android.
I've searched a bit but could not get a helpful solution. Please direct me to a helpful solution you find or, a working answer would be most appreciable. Thank you.
I think the best way to achieve what you want is to put the flag to determine which layout to use on your ConversationContent object, then override getViewTypeCount() and getItemViewType(int position) something like this:
#Override
int getViewTypeCount() {
return 2;
}
#Override
int getItemViewType(int position) {
if (objects.get(position).isReply()) { //isReply can be whatever you want to determine whether to change layout
return 1;
}
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if(convertView == null)
{
holder = new ViewHolder();
if (getItemViewType(position) == 1)
{
convertView = inflater.inflate(R.layout.conversation_item_1, null);
}
else
{
convertView = inflater.inflate(R.layout.conversation_item_2, null);
}
holder.textView1 = (TextView) convertView.findViewById(R.id.trans);
holder.textView1.setTypeface(Main_Activity.fontC);
holder.textView2 = (TextView) convertView.findViewById(R.id.lang);
holder.textView2.setTypeface(Main_Activity.fontC);
holder.textView3 = (TextView) convertView.findViewById(R.id.user);
holder.textView3.setTypeface(Main_Activity.fontC);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
holder.textView1.setText(objects.get(position).getTranslatedText());
holder.textView2.setText(objects.get(position).getTranslationString());
SpannableString originalTextString = new SpannableString("\n" + objects.get(position).getOriginalText());
originalTextString.setSpan(new RelativeSizeSpan(0.5f), 0, originalTextString.length(), 0);
holder.textView1.append(originalTextString);
holder.textView3.setText(objects.get(position).getUser());
return convertView;
}
For listview adapter, if you want to show different layout,
like conversion mode. you would better override the following two methods:
//set your layout type here
public int getItemViewType(int position)
{
return 0;
}
//the layout count in your adapter
public int getViewTypeCount()
{
return 0;
}
Here is an example you can refer to:
public class ChatMessageAdapter extends BaseAdapter
{
private LayoutInflater mInflater;
private List<ChatMessage> mDatas;
public ChatMessageAdapter(Context context, List<ChatMessage> mDatas)
{
mInflater = LayoutInflater.from(context);
this.mDatas = mDatas;
}
#Override
public int getCount()
{
return mDatas.size();
}
#Override
public Object getItem(int position)
{
return mDatas.get(position);
}
#Override
public long getItemId(int position)
{
return position;
}
#Override
public int getItemViewType(int position)
{
ChatMessage chatMessage = mDatas.get(position);
if (chatMessage.getType() == Type.INCOMING)
{
return 0;
}
return 1;
}
#Override
public int getViewTypeCount()
{
return 2;
}
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
ChatMessage chatMessage = mDatas.get(position);
ViewHolder viewHolder = null;
if (convertView == null)
{
if (getItemViewType(position) == 0)
{
convertView = mInflater.inflate(R.layout.item_from_msg, parent,
false);
viewHolder = new ViewHolder();
viewHolder.mDate = (TextView) convertView
.findViewById(R.id.id_form_msg_date);
viewHolder.mMsg = (TextView) convertView
.findViewById(R.id.id_from_msg_info);
} else
{
convertView = mInflater.inflate(R.layout.item_to_msg, parent,
false);
viewHolder = new ViewHolder();
viewHolder.mDate = (TextView) convertView
.findViewById(R.id.id_to_msg_date);
viewHolder.mMsg = (TextView) convertView
.findViewById(R.id.id_to_msg_info);
}
convertView.setTag(viewHolder);
} else
{
viewHolder = (ViewHolder) convertView.getTag();
}
//set data here
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
viewHolder.mDate.setText(df.format(chatMessage.getDate()));
viewHolder.mMsg.setText(chatMessage.getMsg());
return convertView;
}
private final class ViewHolder
{
TextView mDate;
TextView mMsg;
}
}
I use an efficientAdapter to populate mylistview as shown below:
What you can do is use setTag() and getTag() to maintain the scrolling position and have a check that your ImageView of ListView row don't inter-change the resource.
Inside your Adapter's getView() class use
vi.setTag(R.id.btOnOFF, holder.btOnOFF);
And then fetch it inside onItemClick() using getTag()
ImageView imgview = (ImageView) view.getTag(R.id.btOnOFF);
imgview.setBackgroundResource(R.drawable.air_radio_button_rouge);
For further reference you can check my blog post
You can try something like this. I cleaned up and simplified your code.
public class EfficientAdapter extends BaseAdapter {
private Activity mActivity;
private ArrayList<SearchTracks> mSearchTracks;
private ResultatMultiple mResultatMultiple;
private int mSelectedPosition;
public EfficientAdapter(Activity activity, ArrayList<SearchTracks> searchTracks) {
mActivity = activity;
mSearchTracks = searchTracks;
}
public EfficientAdapter(ResultatMultiple resultatMultiple){
mResultatMultiple = resultatMultiple ;
}
public void setSelectedPosition(int position) {
mSelectedPosition = position;
}
#Override
public int getCount() {
return mSearchTracks.size();
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
ViewHolder holder = null;
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.ecran_multiple_row, null);
holder = new ViewHolder();
holder.indexTextView = (TextView)view.findViewById(R.id.txIndex);
holder.titleTextView = (TextView)view.findViewById(R.id.txSTitle);
holder.buttonOnOffImageView = (ImageView)view.findViewById(R.id.btOnOFF);
view.setTag(holder);
}else {
holder = (ViewHolder)view.getTag();
}
int index = position + 1;
holder.indexTextView.setText((index <= 9 ? "0" : "") + Integer.toString(index));
holder.titleTextView.setText(mSearchTracks.get(position).getTitle());
if(position % 2 == 0) {
view.setBackgroundResource(R.drawable.listview_selector_odd);
}else {
view.setBackgroundResource(R.drawable.listview_selector_even);
}
if(mSearchTracks.size() == 1 || position == mSelectedPosition) {
holder.buttonOnOffImageView.setBackgroundResource(R.drawable.air_radio_button_rouge);
}else {
holder.buttonOnOffImageView.setBackgroundResource(R.drawable.air_deezer_check);
}
return view;
}
private class ViewHolder {
public TextView indexTextView;
public TextView titleTextView;
public ImageView buttonOnOffImageView;
}
}
Now in your onClick listener you can call listAdapter.setSelectedPosition(position); and then listAdapter.notifyDataSetChanged(); to reload your table.
I was using standart android ListView with simle_list_item_multiple_choice item layout and custom adapter. It saved it's stated on pause and restored on resume. But after few days of working on I've implemented SectionIndexer, StickyListHeadersAdapter interface within my adapter and changed ListView to StickyListHeadersListView from this library. Also there were other changes in application but these are only attached to ListView. However now when my application resumes working listview is coming scrolled to begining and with all items unchecked. I've tryed to remove SectionIndexer interface and sticky headers support but it had no effect. Maybe there is some hidden options of listview which I need to enable?
(tried saveState property - no effect)
public class WordAdapter extends ArrayAdapter<Word> implements SectionIndexer, StickyListHeadersAdapter{
// -----------------------------------------------------------------------
//
// Fields
//
// -----------------------------------------------------------------------
private int mResID;
private List<Word> mList;
private String[] mSectionNames;
private StickyListHeadersListView mListView;
private int[] mSectionIndexes;
private boolean mHeadersEnabled;
// -----------------------------------------------------------------------
//
// Constructor
//
// -----------------------------------------------------------------------
public WordAdapter(Context context, StickyListHeadersListView listView, int resID, List<Word> list, String[] sectionNames, int[] sectionIndexes, boolean enableHeaders) {
super(context, resID, list);
mResID = resID;
mList = list;
mListView = listView;
mSectionIndexes = sectionIndexes;
mSectionNames = sectionNames;
mHeadersEnabled = enableHeaders;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(mResID, parent, false);
holder = new ViewHolder();
holder.root = convertView;
holder.textView = (TextView) convertView.findViewById(android.R.id.text1);
holder.checkBox = (CheckBox) convertView.findViewById(android.R.id.checkbox);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
holder.textView.setText(getItem(position).getValue());
if(mListView.isItemChecked(position))
holder.root.setBackgroundColor(getContext().getResources().getColor(R.color.highlight));
else
holder.root.setBackgroundColor(getContext().getResources().getColor(android.R.color.transparent));
return convertView;
}
#Override
public View getHeaderView(int position, View convertView, ViewGroup parent) {
if(!mHeadersEnabled)
return new View(getContext());
HeaderViewHolder holder;
if (convertView == null) {
holder = new HeaderViewHolder();
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item_header, parent, false);
holder.labelView = (TextView) convertView.findViewById(R.id.list_item_header_label);
convertView.setTag(holder);
} else {
holder = (HeaderViewHolder) convertView.getTag();
}
String label;
if(mSectionNames != null){
label = mSectionNames[getSectionForPosition(position)];
holder.labelView.setText(label);
}
return convertView;
}
#Override
public long getHeaderId(int position) {
long result = getSectionForPosition(position);
return result;
}
#Override
public int getPositionForSection(int section) {
return (mSectionIndexes == null || section < 0 ) ? 0 : (section == mSectionIndexes.length ? mList.size():mSectionIndexes[section]);
}
#Override
public int getSectionForPosition(int position) {
if(mSectionIndexes != null && position >= mSectionIndexes[0]) {
for(int i = 0; i < mSectionIndexes.length; ++i)
if(position < mSectionIndexes[i])
return i-1;
return (mSectionIndexes.length - 1);
}
return 0;
}
#Override
public Object[] getSections() {
return mSectionNames == null ? new Object[0] : mSectionNames;
}
// -------------------------------------------------------------------------------
//
// Internal classes
//
// -------------------------------------------------------------------------------
private static class ViewHolder {
public View root;
public TextView textView;
public CheckBox checkBox;
}
private static class HeaderViewHolder {
public TextView labelView;
}
Here is adapter code.
I Have a 3 holders. 1 holder for 1 item.
Method getView looks:
public View getView(int position, View convertView, ViewGroup parent) {
mCursor.moveToPosition(position);
int type = checkDialogType(mCursor);
Holder holder = null;
if (convertView != null){
holder = (Holder)convertView.getTag(type);
if (holder == null){
holder = createHolderByType(type, parent);
}
} else {
holder = createHolderByType(type, parent);
}
return holder.fillView(mCursor, position); //convertView
}
Where content of createHolderByType():
public Holder createHolderByType(int type, ViewGroup parent){
View v;
if (type == IN_TYPE){
v = mInflater.inflate(R.layout.dialog_incoming_item, parent, false);
return new InHolder(v, mCursor, mContext, this, IN_TYPE);
} else if (type == OUT_TYPE){
v = mInflater.inflate(R.layout.dialogs_outcoming_item, parent,false);
return new OutHolder(v, mCursor, mContext, this, OUT_TYPE);
} else {
v = mInflater.inflate(R.layout.dialogs_chat_item, parent, false);
return new ChatHolder(v, mCursor, mContext, this, CHAT_TYPE);
}
}
It works fine, But for 39 items in listView method createHolderByType was called 19 times. For create a holder I need inflate xml. It's very expensive. If i trying use same view for holder or saving holder in memory - it doesn't work. Can i increase performance with some tricks?
UPD:
There is one holder:
public class InHolder extends Holder {
private View baseView;
private TextView fio;
private TextView message;
private TextView date;
private ImageView isOnline;
private int type;
private ImageView senderIMG;
public InHolder(View v, Cursor dialogCursor, Context context, DialogCurAdapter adapter, int type){
super(dialogCursor, context, adapter);
fio = (TextView)v.findViewById(R.id.fio);
senderIMG = (ImageView)v.findViewById(R.id.sender_image);
message = (TextView)v.findViewById(R.id.message_preview);
date = (TextView)v.findViewById(R.id.date);
isOnline = (ImageView)v.findViewById(R.id.isonline);
this.type= type;
baseView = v;
baseView.setTag(type, this);
}
#Override
public View fillView(Cursor dialogCursor, final int position) {
try{
int ownerID = dialogCursor.getInt(ownerIndex);
User usr = mDButils.getUserByID(ownerID);
String messageText = Html.fromHtml(dialogCursor.getString(bodyIndex)).toString();
date.setText(DateUtils.getTime(dialogCursor.getInt(dateIndex), mContext));
message.setText(messageText);
if (messageText == null || messageText.equals("")){
if (dialogCursor.getInt(hasAttIndex) == 1){
message.setText(mContext.getResources().getString(R.string.attachment));
message.setTextColor(getColor(R.color.date_blue_to_white_selector));
}
}
if (dialogCursor.getInt(hasAttIndex) == 1){
String[] attaches = dialogCursor.getString(attTypeIndex).split(",");
}
if (dialogCursor.getInt(readstateIndex) == 0){
baseView.setBackgroundDrawable(getDrawable(R.drawable.lightblue_to_transparent_selector));
} else baseView.setBackgroundDrawable(getDrawable(R.drawable.white_to_blue_selector));
if (usr != null){
mImageLoader.displayImage(usr.getImageURL(), senderIMG);
fio.setText(usr.getFirstName() + " " + usr.getLastName());
isOnline.setVisibility(usr.isOnLine() == 1 ? View.VISIBLE : View.INVISIBLE);
} else {
AsyncUserLoader userLoader = new AsyncUserLoader(mContext) {
#Override
protected void onPostExecute(User user) {
if (user != null){
mDialogAdapter.updateItem(position);
}
}
};
userLoader.execute(Integer.parseInt(ownerID + ""));
}
}catch (Exception e){
e.printStackTrace();
}
return baseView;
}
#Override
public int getType() {
return type;
}
}
You could have multiple layouts implementation for BaseAdapter as given in this post and this article
Note: in getItemViewType(int position) you can use checkDialogType(mCursor); to find what layout type to use. But getItemViewType(int position) should return value between 0 - 2 (when count is 3).
I found a solution. I made a complex container when all views which i need. And depend of type of view i make they visible or invisible. So i don't need inflate xml when convertView doesn't match with type.
I want to build a dynamic ListView (like a chat list with different layouts/bubbles).
My problem is, that each row has an individual height. My code below works,
but every time I scroll down or receive a new message,
the row with a different height gets heigher.
private class dialogAdapter extends BaseAdapter {
private LayoutInflater mInflater;
public dialogAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
#Override
public boolean hasStableIds() {
return true;
}
public int getCount() {
return dialog.size();
}
public int getViewTypeCount() {
return 999999;
}
public Object getItem(int position) {
return dialog.get(position);
}
public int getItemViewType(int position) {
return position;
}
public String getType(int position) {
return dialogType.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
Log.w("DRAGII", "POS: "+position);
if (convertView == null) {
convertView = mInflater.inflate(R.layout.bubble, null);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.parser = new URLImageParser(holder.text);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if (position <= dialogCache.size())
dialogCache.add(position, Html.fromHtml((String)getItem(position),
holder.parser, null));
holder.text.setText(dialogCache.get(position));
holder.type = getType(position);
int bubble = R.drawable.bubble;
if (holder.type.equals("R")) bubble = R.drawable.bubble_right;
else if (holder.type.equals("L")) bubble = R.drawable.bubble_left;
holder.text.setBackgroundResource(bubble);
return convertView;
}
class ViewHolder {
TextView text;
String type = "B";
URLImageParser parser;
}
}
What should I do?
Solved this problem by using TableLayout instead of ListView.
if u are adding tableRow programmatically to tableLayout, you will have performance issues. Think it again and find a way by using listView