Search code examples
androidlistviewandroid-arrayadapter

ListView changing views during scrolling after adjusting them programmatically


I spent all last evening to find any solution about my problem but I didn't, so it's not duplicate.

I have a listView and simple ArrayAdapter. I want each time when I click on item its textView field becomes bold, and previous if it exists becomes normal. It's very simple problem, but I have caught a lot of troubles.

My device can show 13 items at once. So, if I don't scroll everything works. But if I try to scroll, I find some next items become also bold and the click event works unproperly. In debug I see listView has only first 13 elements. I understand that adapter loads others asyncronically but how to solve my problem in these conditions?

Here is my code:

ListView listLibrary;
int posPrev;
ArrayAdapter<String> adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_about_info);

    listLibrary = findViewById(R.id.listSongs);
    listLibrary.setOnItemClickListener(this);

    fillList();
    posPrev = -1;
}

private void fillList() {
    String[] arrList = {"New York", "San Francisco", "Los Angeles", "Dallas", "Houston", "Tucson", "Phoenix", "Denver", "Las Vegas", "Kansas City", "Chicago", "Detroit", "Atlanta", "Miami", "Philadelphia", "Baltimore", "Washington", "Boston", "Pittsburg", "Columbus", "Cincinnati", "Indianapolis", "St. Louis", "Seattle", "Portland", "San Jose", "San Diego", "San Antonio"};
    ArrayList<String> cities = new ArrayList<String>();
    for (String s : arrList)
        cities.add(s);

    if (listLibrary != null) {
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, cities);
        listLibrary.setAdapter(adapter);
    }
}

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

    if (posPrev != -1) {
        ((TextView)listLibrary.getChildAt(posPrev)).setTypeface(null, Typeface.NORMAL);
    }

    ((TextView)listLibrary.getChildAt(i)).setTypeface(null, Typeface.BOLD);
    posPrev = i;
}

EDIT So as you had suggested I added custom adapter. If tvCaption.setTypeface(null, Typeface.NORMAL) is removed I get randomly bold items next of list. Actually I understand the problem I don't know how to solve it.

 ArrayList<String> cities;
Context context;
int resource;
TextView tvCaption;
int posClicked;

public CustomArrayAdapter(@NonNull Context context, int resource, @NonNull List<String> objects) {
    super(context, resource, objects);

    this.context = context;
    this.resource = resource;
    cities = (ArrayList) objects;

    posClicked = -1;
}

@NonNull
@Override
public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
    if (view == null) {
        LayoutInflater layoutInflater = LayoutInflater.from(context);
        view = layoutInflater.inflate(resource, null);
    }

    tvCaption = view.findViewById(R.id.tvCaption);
    tvCaption.setText(cities.get(position));

    if (posClicked != -1) {
        if (posClicked == position)
            tvCaption.setTypeface(null, Typeface.BOLD);
    } else
        tvCaption.setTypeface(null, Typeface.NORMAL);

    LinearLayout llCity = view.findViewById(R.id.llCity);
    llCity.setTag(position);
    llCity.setOnClickListener(this);

    return view;
}

@Override
public void onClick(View view) {
    posClicked = (int)view.getTag();
    notifyDataSetChanged();
}

EDIT LAST: THERE IS WORKING CODE!

After all editing I got this code:

Activity:

ListView listLibrary;
CustomArrayAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_about_info);

    listLibrary = findViewById(R.id.listSongs);
    listLibrary.setOnItemClickListener(this);

    fillList();
}

private void fillList() {
    String[] arrList = {"New York", "San Francisco", "Los Angeles", "Dallas", "Houston", "Tucson", "Phoenix", "Denver", "Las Vegas", "Kansas City", "Chicago", "Detroit", "Atlanta", "Miami", "Philadelphia", "Baltimore", "Washington", "Boston", "Pittsburg", "Columbus", "Cincinnati", "Indianapolis", "St. Louis", "Seattle", "Portland", "San Jose", "San Diego", "San Antonio"};
    ArrayList<String> cities = new ArrayList<String>();
    for (String s : arrList)
        cities.add(s);

    if (listLibrary != null) {
        adapter = new CustomArrayAdapter(this, R.layout.list_custom_item, cities);
        listLibrary.setAdapter(adapter);
    }
}

@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {

    adapter.setCurClickedPos(i);
    adapter.notifyDataSetChanged();

}

And CustomAdapter:

ArrayList<String> cities;
Context context;
int resource;
int curClickedPos;

public CustomArrayAdapter(@NonNull Context context, int resource, @NonNull List<String> objects) {
    super(context, resource, objects);

    this.context = context;
    this.resource = resource;
    cities = (ArrayList) objects;

    curClickedPos = -1;
}

@NonNull
@Override
public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
    if (view == null) {
        LayoutInflater layoutInflater = LayoutInflater.from(context);
        view = layoutInflater.inflate(resource, null);
    }

    TextView tvCaption = view.findViewById(R.id.tvCaption);
    tvCaption.setText(cities.get(position));

    if (curClickedPos != -1) {
        if (curClickedPos == position)
            tvCaption.setTypeface(null, Typeface.BOLD);
        else
            tvCaption.setTypeface(null, Typeface.NORMAL);
    } else
        tvCaption.setTypeface(null, Typeface.NORMAL);

    LinearLayout llCity = view.findViewById(R.id.llCity);
    llCity.setTag(position);

    return view;
}

public void setCurClickedPos(int curClickedPos) {
    this.curClickedPos = curClickedPos;
}

Solution

  • if your getView() function, you are reusing your views. So while creating views you will have to check if the item was clicked or not. When you check if posClicked is not equal to -1, in that if block you need to add an else statement as well like below:

    public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
        // your logic here
        if (posClicked != -1) {
            if (posClicked == position)
                tvCaption.setTypeface(null, Typeface.BOLD);
            else
                tvCaption.setTypeface(null, Typeface.NORMAL);
        } else
            tvCaption.setTypeface(null, Typeface.NORMAL);
        }
    }