2

I've got a problem with ListView. When app starts it loads 10 posts from net and show them. When you get to bottom of ListView I start load next 10 post and add them at the end of list. And when I call notifyDataSetChanged() ListView will redraw elements which doesn't change. And that cause strange blinking of images.

I've read that hasStableIds() could help, and tried to set it in both values, but it doesn't help.

Here's my Adapter code:

    public class PostListAdapter extends BaseAdapter {
    Context context;
    ArrayList<Post> posts;
    LayoutInflater inflater;
    View.OnClickListener onClickListener;

    public PostListAdapter(Context context, ArrayList<Post> posts, View.OnClickListener onClickListener){
        this.posts = posts; this.context = context; inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.onClickListener = onClickListener;
    }

    @Override
    public int getCount() {
        return posts.size();
    }

    @Override
    public Object getItem(int position) {
        return posts.get(position);
    }

    @Override
    public long getItemId(int position) {
        return (long)posts.get(position).id;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        System.out.println("draw " + posts.get(position).id);
        LinearLayout mainLayout;
        if (convertView == null) {
            mainLayout = (LinearLayout) inflater.inflate(R.layout.post, null, false);
        }else {
            mainLayout = (LinearLayout) convertView;
            LinearLayout cont = (LinearLayout) mainLayout.findViewById(R.id.image_container);
            while (cont.getChildCount() != 0){
                cont.removeViewAt(0);
            }

        }


        LinearLayout imageContainer = (LinearLayout) mainLayout.findViewById(R.id.image_container);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        Post post = posts.get(position);
        TextView textView = (TextView)mainLayout.findViewById(R.id.postLink);

        textView.setText("http://joyreactor.cc/post/" + Integer.toString(post.id));
        textView.setPaintFlags(textView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
        textView.setOnClickListener(onClickListener);
        ArrayList<ImageView> images = new ArrayList<>();
        for (int i=0; i<post.images.size(); i++){
            ScaleImageView image = new ScaleImageView(context);
            image.setPadding(0, 10, 0, 0);
            image.setImageBitmap(Bitmap.createBitmap(post.sizes.get(i)[0], post.sizes.get(i)[1], Bitmap.Config.RGB_565));
            //image.setImageResource(R.drawable.grill);
            image.setLayoutParams(params);
            ImageLoader loader = new ImageLoader(image, post, i, context);
            loader.execute(post.images.get(i));
            post.loaders.add(loader);
            imageContainer.addView(image);
        }


        if (post.images.size() == 0){
            ScaleImageView image = new ScaleImageView(context);

            image.setLayoutParams(params);

            image.setImageBitmap(MainActivity.bmpMissPicture);
            imageContainer.addView(image);
        }
        mainLayout.setTag(post);
        return mainLayout;

    }
}

There's log where you can see that Adapter reload views again just after notifyDataSetChanged(), even if i doesn't scroll it.

05-11 21:00:46.406 4391-4391/com.olleggerr.joyrector I/System.out: draw 3098998
05-11 21:00:46.414 4391-4391/com.olleggerr.joyrector I/System.out: draw 3099753
05-11 21:00:46.416 4391-4391/com.olleggerr.joyrector I/System.out: draw 3098998
05-11 21:00:46.417 4391-4391/com.olleggerr.joyrector I/System.out: draw 3099753
05-11 21:00:49.039 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100202
05-11 21:00:49.172 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100315
05-11 21:00:49.304 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100328
05-11 21:00:49.455 4391-4391/com.olleggerr.joyrector I/System.out: draw 3098651
05-11 21:00:49.755 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100191
05-11 21:00:50.038 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100287
05-11 21:00:51.754 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100348
05-11 21:00:51.838 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100386
05-11 21:00:51.938 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100348
05-11 21:00:52.411 4391-4691/com.olleggerr.joyrector I/System.out: 10 posts load started!
05-11 21:00:56.806 4391-4391/com.olleggerr.joyrector I/System.out: calling notifyDataSetChanged()
05-11 21:00:56.807 4391-4391/com.olleggerr.joyrector I/System.out: Size after load more posts 20
05-11 21:00:56.821 4391-4391/com.olleggerr.joyrector I/System.out: draw 3098998
05-11 21:00:56.821 4391-4391/com.olleggerr.joyrector I/System.out: draw 3099753
05-11 21:00:56.822 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100348
05-11 21:00:56.822 4391-4391/com.olleggerr.joyrector I/System.out: draw 3100386

Also notifyDataSetChanged() i call from handler in MainActivity thread:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    bmpMissPicture = Bitmap.createBitmap(500, 500, Bitmap.Config.RGB_565);
    Canvas c = new Canvas(bmpMissPicture);
    Paint p = new Paint();
    ListView listView = (ListView) findViewById(R.id.postList);
    View.OnClickListener onClickListener = new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(((TextView) v).getText().toString()));
            startActivity(browserIntent);
        }
    };
    adapter = new PostListAdapter(getApplicationContext(), posts, onClickListener);
    listView.setAdapter(adapter);
    p.setColor(getResources().getColor(R.color.missPicColor));
    c.drawRect(0, 0, 500, 500, p);
    handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            System.out.println("calling notifyDataSetChanged()");
            if (msg.what == 0) {
                ((LinearLayout) findViewById(R.id.mainContainer)).removeViewAt(0);
                findViewById(R.id.mainContainer).invalidate();
                findViewById(R.id.postList).invalidate();
                adapter.notifyDataSetChanged();
                //System.out.println(posts.size());
                final ListView listView = (ListView) findViewById(R.id.postList);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (!stopAskUpdate){
                            int pos = listView.getLastVisiblePosition();
                            //Log.d("Inf Scroll", "Pos: " + (pos+1) + " of " + posts.size());
                            if (pos >= posts.size()-1 && !loadNewPosts){
                                MainActivity.loadNewPosts = true;
                                new PageParser("http://joyreactor.cc/" + nextPage, posts, MainActivity.handler, false).start();
                            }
                            try{Thread.sleep(1500);}catch (InterruptedException e){}
                        }
                    }
                }).start();
            }else {
                adapter.notifyDataSetChanged();
                System.out.println("Size after load more posts " + posts.size());
            }
        }
    };
}

So can you describe me how to fix this and why it doesn't work.

Thank you in advance for you help.

Olleggerr
  • 68
  • 1
  • 8
  • Have you considered using RecyclerView which gives you finer-grained control over which items in your dataset have changed? – Michael Krause May 11 '17 at 22:06
  • Yes, but RecyclerView was added only in Android Lolipop. I want to do it with ListView. – Olleggerr May 11 '17 at 22:13
  • It's available in the android support library so you should be able to use with older versions of Android as well. – Michael Krause May 11 '17 at 22:21
  • Really?! I will try, but this question is like a challenge for me now. – Olleggerr May 11 '17 at 22:37
  • Out of the box, I don't think you'll be able to mitigate the problem you are seeing with ListView. notifyDataSetChanged is equivalent to saying "something, I don't know what, changed in the dataset, so ListView, because you don't know either, redraw all of the visible items." – Michael Krause May 11 '17 at 22:48
  • I did find this, which might be helpful: http://stackoverflow.com/questions/3724874/how-can-i-update-a-single-row-in-a-listview – Michael Krause May 11 '17 at 22:49

3 Answers3

1

I found the way to do it. You should do the work which BasicAdapter should, but in some reasons it doesn't do it. Before fill convertView with new data in getView() you should check is convertView already contain it. For my situation solution looks like:

@Override
public long getItemId(int position) {
    return (long)posts.get(position).id;
}

public View getView(int position, View convertView, ViewGroup parent) {
    boolean flag = false;

    LinearLayout mainLayout;
    if (convertView == null) {
        flag = true;
        mainLayout = (LinearLayout) inflater.inflate(R.layout.post, null, false);
    }else {
        mainLayout = (LinearLayout) convertView;
        if (((Post)convertView.getTag()).id != getItemId(position)){ //View tag contain link to the post.
            flag = true;
            LinearLayout cont = (LinearLayout) mainLayout.findViewById(R.id.image_container);
            while (cont.getChildCount() != 0){
                cont.removeViewAt(0);
            }
        }
    }

    if (flag) {
        //do some stuff with mainLayout....
        mainLayout.setTag(posts.get(position));
    }
    return mainLayout;

So if convertView represent post which getView() requests, we just return it without any changes, else we do our changes to represent new post.

Because of method hasStableIds() i thought that Adapter do it by himself and prevent useless calls of getView(), but he doesn't.

Hope it will help someone, and thank you for answers and comments.

Olleggerr
  • 68
  • 1
  • 8
  • This helped me a lot! Please guys check convertview if its null before inflating it. Thanks a lot again. You rock. – Kl3jvi Apr 06 '21 at 14:01
0

I guess the problem you are facing is because maybe you are initializing your "Adapter" in your "Activity" class again and again. It is possible that every time when you are adding new items in your "Array", you are initializing the adapter and then calling the "notifyDataSetChanged()" method after setting the adapter. Please make you are initializing your adapter just once.

One more suggestion try "Picasso" library for image loading.

Zohaib Hassan
  • 984
  • 2
  • 7
  • 11
  • No. I am 100% sure that i init Adapter only once in `onCreate()`. About Picasso: For the beginning i want to do all by myself, and don't want to use any external libs. But thanks for recommendation. – Olleggerr May 11 '17 at 21:50
  • That's good if you want to make your own custom modules, try to modify your module and optimize it's performance by removing unnecessary stuff. When you are done with all your efforts and you still think that your code still needs some modification, then find some image loading library code like "Picasso" or "Universal Image Loader" and compare your cache process code with the library code. – Zohaib Hassan May 11 '17 at 21:59
0

You can add and remove items in your array and can call notifyItemInserted or notifyitemRemoved this will lead you to much better performance. Also, Using Picasso would be a nice option.