Улучшите большой адаптер ListView с плавным прокруткой, иногда отрывистым

Я пытаюсь понять, что делает мой пересмотр списка иногда при прокрутке, иногда это плохо, особенно когда приложение запускается.

Все условия, которые у меня есть, необходимы, если нет чего-то, чего я не знаю (весьма вероятно). Я не выполняю определенные задачи в отдельном потоке, потому что они зависят от данных, которые я получаю от бэкэнд (я кодирую оба, поэтому предложения по бэкэнд также приветствуются). Продукт находится в стадии бета-тестирования, но на самом деле нужно сделать его немного более гладким. Я сжимаю изображения, и они немного длинны, но это не проблема, потому что, когда я загружаю изображения с устройства, я также указываю ширину и высоту изображения и отправляю их вместе с бэкэнд. Эти размеры возвращаются при загрузке списка.

Интересно, стоит ли вычислять/преобразовывать размеры экрана конкретного устройства, вызывая небольшое отставание. Не уверен, насколько ресурсоемкой эта задача, но без нее (не зная размеров, каждая строка будет начинаться с нуля, а затем расширяться до фактического размера изображения, что приведет к переходу списка, поэтому я не могу выполнить этот расчет на фон).

В принципе прокрутка неплохая, но мне нужно как-то это улучшить.

Вот мой адаптер:

public class VListAdapter extends BaseAdapter {

    ViewHolder viewHolder;
    private boolean isItFromProfile;

    /**
     * fields For number formating, ex. 1000
     * would return 1k in the format method
     */
    private static final NavigableMap<Long, String> suffixes = new TreeMap<>();

    static {
        suffixes.put(1_000L, "k");
        suffixes.put(1_000_000L, "M");
        suffixes.put(1_000_000_000L, "G");
        suffixes.put(1_000_000_000_000L, "T");
        suffixes.put(1_000_000_000_000_000L, "P");
        suffixes.put(1_000_000_000_000_000_000L, "E");
    }

    private Context mContext;
    private LayoutInflater mInflater;
    private ArrayList<Post> mDataSource;
    private static double lat;
    private static double lon;

    public VListAdapter(Context context, ArrayList<Post> items) {
        mContext = context;
        mDataSource = items;
        //mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        isItFromProfile = false;
        mInflater = LayoutInflater.from(context);
    }

    public VListAdapter() {

    }

    public VListAdapter(Context baseContext, ArrayList<Post> posts, boolean b) {
        mContext = baseContext;
        mDataSource = posts;
        //mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        isItFromProfile = b;
        mInflater = LayoutInflater.from(baseContext);
    }


    public void addElement(Post post) {
        mDataSource.add(0, post);
        this.notifyDataSetChanged();
    }

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

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

    @Override
    public long getItemId(int position) {
        return position;
    }


    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        int limit = Math.min(position + 4, getCount());
        for (int i = position; i < limit; i++) {
            Glide.with(mContext).load(((Post) getItem(i)).getFilename().toString()).preload();
        }


//        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
//                .detectDiskWrites().detectNetwork()
//                .penaltyLog().build());



        View rowView = convertView;

        if (rowView == null) {
            viewHolder = new ViewHolder();
            rowView = mInflater.inflate(R.layout.mylist, parent, false);

            viewHolder.titleTextView = (TextView) rowView.findViewById(R.id.usernameinlist);
            viewHolder.timeago = (TextView) rowView.findViewById(R.id.timeago);
            //viewHolder.sharebutton = (ImageView) rowView.findViewById(R.id.sharebutton);
            viewHolder.likesTextView = (TextView) rowView.findViewById(R.id.likestext);
            viewHolder.viewcount = (TextView) rowView.findViewById(R.id.viewcount);
            viewHolder.distance = (TextView) rowView.findViewById(R.id.distance);
            viewHolder.footprints = (TextView) rowView.findViewById(R.id.footprintcount);
            viewHolder.postText = (TextView) rowView.findViewById(R.id.posttext);
            viewHolder.profilePic = (ImageView) rowView.findViewById(R.id.profilethumb);
            viewHolder.caption = (TextView) rowView.findViewById(R.id.captiontext);
            viewHolder.moremenu = (ImageView) rowView.findViewById(R.id.dots);
            viewHolder.likesPic = (ImageView) rowView.findViewById(R.id.likeimage);
            viewHolder.mapitPic = (ImageView) rowView.findViewById(R.id.mapimage);
            viewHolder.playbutton = (ImageView) rowView.findViewById(R.id.playbutton);
            viewHolder.videoThumb = (ImageView) rowView.findViewById(R.id.videothumb);
            viewHolder.listphoto = (ImageView) rowView.findViewById(R.id.listphoto);
            viewHolder.rainbow = (ImageView) rowView.findViewById(R.id.rainbow);
            rowView.setTag(viewHolder);

        } else {
            viewHolder = (ViewHolder) rowView.getTag();
        }


        final Post post = (Post) getItem(position);
        int color = Color.parseColor("#dddddd");
        viewHolder.likesPic.setColorFilter(color);
        viewHolder.mapitPic.setColorFilter(color);
        viewHolder.moremenu.setColorFilter(color);


        if (Hawk.count() == 0)
            initHawkWithDataFromServer();

        if (isItFromProfile) {
            viewHolder.profilePic.setVisibility(View.GONE);
            viewHolder.titleTextView.setVisibility(View.GONE);
            viewHolder.distance.setVisibility(View.GONE);
        }

        viewHolder.titleTextView.setText(post.getUsername());
        PrettyTime prettyTime = new PrettyTime();
        DateTime dateTime = new DateTime(post.getUploadDate().get$date());
        viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
        viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
        viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size() - 1)));

        //don't display 0 if there are no likes, just show heart icon
        if (viewHolder.likesTextView.getText().equals("0"))
            viewHolder.likesTextView.setVisibility(View.GONE);
        else
            viewHolder.likesTextView.setVisibility(View.VISIBLE);



        //don't display 0 if there are no footprints
        if (viewHolder.footprints.getText().equals("0"))
            viewHolder.footprints.setVisibility(View.GONE);
        else
            viewHolder.footprints.setVisibility(View.VISIBLE);

        double[] loc = post.getLocation().get(0);
        viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
        if (post.getViews() != null)
            viewHolder.viewcount.setText(format(post.getViews()) + (post.getViews() == 1 ? " View" : " Views"));

        String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername()
                + ".jpg";

        String filename = post.getS3link();
        final String videoThumbURL = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + filename;


        Glide.with(mContext).load(profilePictureS3Url).asBitmap().centerCrop().into(new BitmapImageViewTarget(viewHolder.profilePic) {
            @Override
            protected void setResource(Bitmap resource) {
                RoundedBitmapDrawable circularBitmapDrawable =
                        RoundedBitmapDrawableFactory.create(mContext.getResources(), resource);
                circularBitmapDrawable.setCircular(true);
                viewHolder.profilePic.setImageDrawable(circularBitmapDrawable);
            }
        });

        int height = ((Post) getItem(position)).getHeight();
        int width = ((Post) getItem(position)).getWidth();

        if (height != 0 && width != 0) {
            ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
            Resources r = mContext.getResources();
            height = (int) getHeight(height, width);
            params.height = height;
            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
            viewHolder.listphoto.setLayoutParams(params);

        } else {
            ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
            params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
            viewHolder.listphoto.setLayoutParams(params);
        }

        if (post.getType() == null) {
            Glide.clear(viewHolder.listphoto);
            viewHolder.listphoto.setVisibility(View.GONE);
            //Glide.clear(viewHolder.listphoto);
            viewHolder.videoThumb.setVisibility(View.VISIBLE);
            viewHolder.rainbow.setVisibility(View.VISIBLE);
            Glide.with(mContext).load(videoThumbURL).fitCenter()
                    .diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);
            viewHolder.playbutton.setVisibility(View.VISIBLE);

        }

        if (post.getType() != null) {
            if (post.getType().equals("video")) {
                viewHolder.playbutton.setVisibility(View.VISIBLE);
                Glide.clear(viewHolder.listphoto);
                viewHolder.listphoto.setVisibility(View.GONE);
                Glide.clear(viewHolder.postText);
                viewHolder.postText.setVisibility(View.GONE);
                viewHolder.videoThumb.setVisibility(View.VISIBLE);
                viewHolder.rainbow.setVisibility(View.VISIBLE);

                Glide.with(mContext).load(videoThumbURL).fitCenter()
                        .diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate().into(viewHolder.videoThumb);

            }

            if (post.getType().equals("image")) {
                Glide.clear(viewHolder.videoThumb);
                viewHolder.videoThumb.setVisibility(View.GONE);
                viewHolder.rainbow.setVisibility(View.GONE);
                Glide.clear(viewHolder.playbutton);
                viewHolder.playbutton.setVisibility(View.GONE);
                Glide.clear(viewHolder.postText);
                viewHolder.postText.setVisibility(View.GONE);
                viewHolder.listphoto.setVisibility(View.VISIBLE);
                viewHolder.listphoto.setBottom(0);

                Glide.with(mContext).load(post.getFilename().toString())
                        .diskCacheStrategy(DiskCacheStrategy.ALL).dontAnimate()
                        .into(viewHolder.listphoto);
            }

            if (post.getType().equals("text")) {
                Glide.clear(viewHolder.videoThumb);
                viewHolder.videoThumb.setVisibility(View.GONE);
                viewHolder.rainbow.setVisibility(View.GONE);
                Glide.clear(viewHolder.playbutton);
                viewHolder.playbutton.setVisibility(View.GONE);
                Glide.clear(viewHolder.listphoto);
                viewHolder.listphoto.setVisibility(View.GONE);
                viewHolder.postText.setVisibility(View.VISIBLE);
                viewHolder.postText.setText(post.getText());
            }

        }

        if (Hawk.contains("liked" + post.getId().get$oid())) {
            viewHolder.likesPic.clearColorFilter();
            Glide.with(mContext).load(R.drawable.heartroundorange).into(viewHolder.likesPic);
            ((ImageView) viewHolder.likesPic).setColorFilter(Color.parseColor("#ff3a6f"));
        } else {

            Glide.with(mContext).load(R.drawable.heartroundgray).diskCacheStrategy(DiskCacheStrategy.ALL)
                    .into(viewHolder.likesPic);
        }


        if (Hawk.contains("mapped" + post.getId().get$oid())) {
            viewHolder.mapitPic.clearColorFilter();
            ((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
            ((ImageView) viewHolder.mapitPic).setColorFilter(Color.parseColor("#444444"));
        } else {
            Glide.with(mContext).load(R.drawable.dropdarkgray).diskCacheStrategy(DiskCacheStrategy.ALL)
                    .into(viewHolder.mapitPic);
        }


        if (!Hawk.contains("mapped" + post.getId().get$oid())) {
            viewHolder.mapitPic.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Hawk.put("mapped" + post.getId().get$oid(), 1);
                    ((ImageView) viewHolder.mapitPic).setImageResource(R.drawable.dropmaincolororange);
                    viewHolder.footprints.setText(String.valueOf(post.getLocation().size() + 1));
                    post.getLocation().add(new double[]{PostListFragment.lon, PostListFragment.lat});
                    notifyDataSetChanged();
                    Thread t = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            postMappedToServer(post.getId().get$oid());
                        }
                    });
                    t.start();
                    TastyToast.makeText(mContext, "Post dropped off here.", TastyToast.LENGTH_SHORT, TastyToast.CONFUSING);
                }
            });
        } else {
            viewHolder.mapitPic.setClickable(false);
        }

        if (!Hawk.contains("liked" + post.getId().get$oid())) {
            viewHolder.likesPic.setOnClickListener(new View.OnClickListener() {

                @Override
                public void onClick(View v) {
                    Hawk.put("liked" + post.getId().get$oid(), 1);
                    viewHolder.likesPic.setClickable(false);
                    ((ImageView) viewHolder.likesPic).setImageResource(R.drawable.heartroundorange);
                    viewHolder.likesTextView.setText(String.valueOf(post.getLikes() + 1));
                    post.setLikes(post.getLikes() + 1);
                    notifyDataSetChanged();
                    Thread t = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            postLikeToServer(post);
                        }
                    });
                    t.start();
                }
            });
        } else {
            viewHolder.likesPic.setClickable(false);
        }


        if (post.getType() == null || post.getType().equals("video"))
            viewHolder.videoThumb.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                    if (VListAdapter.this.mContext instanceof ProfileFeed) {
                        ((ProfileFeed) VListAdapter.this.mContext).closeActivity();
                    }

                    Intent broadcast = new Intent();
                    broadcast.setAction("com.molehead.openout.POST");

                    broadcast.putExtra("postId", post.getFilename().toString());
                    broadcast.putExtra("hawkId", post.getId().get$oid());
                    broadcast.putExtra("s3link", post.getS3link());
                    broadcast.putExtra("username", post.getUsername());

                    if (Hawk.contains("liked" + post.getId().get$oid()))
                        broadcast.putExtra("liked", "yes");
                    else
                        broadcast.putExtra("liked", "no");

                    broadcast.putExtra("likecount", post.getLikes().toString());

                    App.post = post;
                    LocalBroadcastManager.getInstance(mContext.getApplicationContext()).sendBroadcast(broadcast);
                }
            });

        viewHolder.moremenu.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                PopupMenu popup = new PopupMenu(mContext.getApplicationContext(), viewHolder.moremenu, Gravity.CENTER);
                //Inflating the Popup using xml file
                popup.getMenuInflater().inflate(R.menu.menu_main, popup.getMenu());

                //registering popup with OnMenuItemClickListener
                popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                    public boolean onMenuItemClick(MenuItem item) {
                        switch (item.getItemId()) {
                            case R.id.action_share:
                                String postId = post.getId().get$oid();
                                Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
                                sharingIntent.setType("text/plain");
                                String shareBody = postId + ".jpg"; //https://openout.herokuapp.com/posts/" + postId;
                                String shareSub = "Shared via Molehead";
                                sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, shareSub);
                                sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
                                sharingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                Intent new_intent = Intent.createChooser(sharingIntent, "Share");
                                new_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                mContext.getApplicationContext().startActivity(new_intent);
                                break;
                        }
                        return true;
                    }
                });

                popup.show();
            }
        });
        return rowView;
    }


    private void initHawkWithDataFromServer() {
        SharedPreferences settings = mContext.getApplicationContext().getSharedPreferences("userinfo", 0);
        String username = settings.getString("username", "ok");
        String password = settings.getString("password", "ok");


        LoginService loginService =
                ServiceGenerator.createService(LoginService.class, username, password);
        final Call<List<Post>> call = loginService.getLikes(username);
        Log.i("lonlat", String.valueOf(lon) + " and  " + String.valueOf(lat));


        call.enqueue(new Callback<List<Post>>() {
            @Override
            public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {

                ArrayList<Post> posts = new ArrayList<>();
                posts = (ArrayList<Post>) response.body();
                if (!posts.isEmpty())
                    for (Post p : posts) {
                        Hawk.put("liked" + p.getId().get$oid(), 1);
                    }
            }

            @Override
            public void onFailure(Call<List<Post>> call, Throwable t) {
            }
        });
    }


    private void postMappedToServer(String oid) {
        SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
        String username = settings.getString("username", "ok");
        String password = settings.getString("password", "ok");
        LoginService loginService =
                ServiceGenerator.createService(LoginService.class, username, password);

        Log.i("postlistfraglat", String.valueOf(PostListFragment.lat));
        Call<ResponseBody> call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat);

        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful())
                    Log.i("mapped", "success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {

            }
        });
    }


    public void postLikeToServer(Post post) {

        SharedPreferences settings = mContext.getSharedPreferences("userinfo", 0);
        String username = settings.getString("username", "ok");
        String password = settings.getString("password", "ok");

        LoginService loginService =
                ServiceGenerator.createService(LoginService.class, username, password);

        Call<ResponseBody> call = loginService.like(post, 1, username);


        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {

                if (response.isSuccessful()) {
                    try {
                        Log.i("call", response.body().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.i("MFEED", "like request failed");
            }
        });
    }


    public static String format(long value) {
        //Long.MIN_VALUE == -Long.MIN_VALUE so we need an adjustment here
        if (value == Long.MIN_VALUE) return format(Long.MIN_VALUE + 1);
        if (value < 0) return "-" + format(-value);
        if (value < 1000) return Long.toString(value); //deal with easy case

        Map.Entry<Long, String> e = suffixes.floorEntry(value);
        Long divideBy = e.getKey();
        String suffix = e.getValue();

        long truncated = value / (divideBy / 10); //the number part of the output times 10
        boolean hasDecimal = truncated < 100 && (truncated / 10d) != (truncated / 10);

        return hasDecimal ? (truncated / 10d) + suffix : (truncated / 10) + suffix;
    }


    static class ViewHolder {
        private TextView titleTextView;
        private TextView timeago;
        private TextView likesTextView;
        private TextView viewcount;
        private TextView distance;
        private TextView footprints;
        private ImageView profilePic;
        private ImageView moremenu;
        private ImageView likesPic;
        private ImageView mapitPic;
        private ImageView rainbow;
        //private ImageView sharebutton;
        private TextView caption;
        private ImageView listphoto;
        private ImageView videoThumb;
        private ImageView playbutton;
        private TextView postText;
        private Post post;

    }


    private float getHeight(float height, float width) {
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return (height * size.x / width);

    }
}

Ответ 1

  • Измените свою реализацию на RecyclerView, которая более эффективна с точки зрения поиска или переработки.
  • Мы также можем включить оптимизацию, если элементы статичны и не будут меняться для более плавной прокрутки:

    recyclerView.setHasFixedSize(true);
    
  • Создайте службу намерения и зарегистрируйте BroadcastReceiver как обратный вызов возврата данных или обратный вызов ошибки, когда запрос api, бизнес-правило, изменение данных завершено. Используйте синхронный вызов для выполнения initHawkWithDataFromServer() заранее, и после получения результата от api продолжайте изменять или применять бизнес-логику. После этого создайте новый адаптер или обновите существующий набор данных адаптера.

  • Переместите все приведенные ниже данные или логику форматирования значений данных из адаптера getView() в службу вышеописанного намерения.

    Вы можете добавить больше геттеров и сеттеров в существующее Post pojo.

    DateTime dateTime = new DateTime(post.getUploadDate().get$date()); 
    viewHolder.timeago.setText(prettyTime.format(dateTime.toDate()));
    viewHolder.likesTextView.setText(String.valueOf(format(post.getLikes())));
    viewHolder.footprints.setText(String.valueOf(format(post.getLocation().size)) - 1)));
    
    Post{
        //Your existing property
        @Expose(serialize = false, deserialize = false)
        //equals neither serialize nor deserialize or
        private DateTime uploadedDateTime;
        //etc. prettyTime.format, String.valueOf
    }
    
  • Удаляет ненужное отражение:

    GsonBuilder builder = new GsonBuilder();
    builder.excludeFieldsWithoutExposeAnnotation();
    Gson gson = builder.create();
    new  Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).build();
    

    и это относится к вашему классу создания службы обновления. Вы также можете использовать переходный режим (private transient DateTime uploadedDateTime;)

  • Удалить public void addElement(Post post) { mDataSource.add(0, post); this.notifyDataSetChanged();} и всякий раз, когда вам нужно уведомить, что один или несколько элементов вставлены, удалены и т.д. Используйте ниже:

    notifyItemChanged(int)
    notifyItemInserted(int)
    notifyItemRemoved(int)
    notifyItemRangeChanged(int, int)
    notifyItemRangeInserted(int, int)
    notifyItemRangeRemoved(int, int)
    

Мы можем использовать их из действия или фрагмента:

//Add a new contact
items.add(0, new Post("Barney"));
//Notify the adapter that an item was inserted at position 0
adapter.notifyItemInserted(0);

Выше методы более эффективны. Каждый раз, когда мы хотим добавлять или удалять элементы из RecyclerView, нам нужно будет явно сообщить об этом адаптеру события. В отличие от адаптера ListView адаптер RecyclerView не должен полагаться на notifyDataSetChanged(), поскольку следует использовать более подробные действия. Подробнее см. документацию по API

Кроме того, если вы планируете обновлять существующий список, перед внесением любых изменений обязательно получите текущее количество элементов. Например, a getItemCount() на адаптере должен быть вызван для записи первого индекса, который будет изменен.

// record this value before making any changes to the existing list
int curSize = adapter.getItemCount(); // replace this line with wherever you get new records
ArrayList<Post> newItems = Post.createPostsList(20);
// update the existing list
items.addAll(newItems);
// curSize should represent the first element that got added
// newItems.size() represents the itemCount
adapter.notifyItemRangeInserted(curSize, newItems.size());

Дифференциальные изменения

В библиотеке поддержки v24.2.0 добавлен новый класс DiffUtil, который поможет рассчитать разницу между старым и новым списком. Подробности

  • Не предварительно загружайте изображения с помощью скольжения, если ваши размеры изображения различны. Попробуйте создать свой собственный. Также попробуйте посмотреть.
  • Создайте цвет как член класса

    int color = Color.parseColor("#dddddd");
    
  • Запишите View.GONE или View.VISIBLE в поле Post pojo, которое будет выполнено в фоновом потоке из Retrofit if IntentService. Попробуйте api return boolean в json вместо "0" в качестве строки.

  • Переместите все ниже в IntentService // не показывать 0, если нет, просто показать значок сердца   if (viewHolder.likesTextView.getText(). equals ( "0" ))       viewHolder.likesTextView.setVisibility(View.GONE);   еще       viewHolder.likesTextView.setVisibility(View.VISIBLE);

    //don't display 0 if there are no footprints
    if (viewHolder.footprints.getText().equals("0"))
        viewHolder.footprints.setVisibility(View.GONE);
    else
        viewHolder.footprints.setVisibility(View.VISIBLE);
    
    double[] loc = post.getLocation().get(0);
    viewHolder.distance.setText("~" + PostListFragment.distance(loc[0], loc[1], 'M') + " Miles");
    
  • Конкатенация всех строк также в Post или IntnetService, например:

    String profilePictureS3Url = "https://s3-us-west-2.amazonaws.com/moleheadphotos/" + post.getUsername() + ".jpg";
    

Также вы можете создать цветной фильтр заранее и только один раз. Удалите полосу прокрутки из списка, поскольку она вычисляет высоту для отображения полосы прокрутки.

Ответ 2

Невозможно указать на конкретную проблему, потому что в вашем адаптере столько кода. Одно можно сказать наверняка, но переход на RecyclerView не поможет вам в этом случае.

Адаптеры не должны содержать бизнес-логику - они должны только "адаптировать" входные объекты к основным представлениям. В вашем случае, похоже, адаптер выполняет вычисления, порождает новые потоки, выполняет сетевые запросы и т.д.

Вам нужно реорганизовать свой код таким образом, чтобы адаптер был подобен этому:

public class PostsListAdapter extends ArrayAdapter<Post> {


    private Context mContext;

    public PostsListAdapter(Context context, int resource) {
        super(context, resource);
        mContext = context;
    }

    public void bindPosts(List<Post> posts) {
        clear();
        addAll(posts);
        notifyDataSetChanged();
    }


    @NonNull
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            // assign new View to convertView
            // create new ViewHolder
            // set ViewHolder as tag of convertView
            // set listeners
        } else {
            // get a reference to existing ViewHolder
        }

        // populate ViewHolder elements with data from getItem(position)
        // kick off asynchronous loading of images
        // NOTE: no calculations allowed here - just simple bidding of data to Views

        return convertView;
    }

}

Ваш код должен быть структурирован таким образом, что бизнес-логика, которая включает в себя вычисления и преобразование данных, выполняемых до того, как вы привяжете новые данные к объектам ListView и Post, которые вы передаете методу bindPosts() уже содержат результаты вышеупомянутых вычислений и преобразований.

Адаптер просто "адаптирует" конечные данные от Posts до Views - ничего более.

Если у вас короткое время, и вам просто нужно "заставить его работать", я бы начал с удаления логики, которая порождает новые потоки и делает сетевые запросы. Смотрите, улучшает ли производительность.

Ответ 3

Слишком много вещей, чтобы улучшить здесь. Вот несколько примеров.

Я вижу это

if (Hawk.count() == 0)
        initHawkWithDataFromServer();

Я считаю, что метод initHawkWithDataFromServer будет вызываться много раз за время появления списка.

Этот вызов может быть выполнен только один раз, когда была создана активность.

Glide.with(mContext).load(videoThumbURL).fitCenter()

Но сначала нужно переформатировать свой код, переместив логику в другой класс. Попробуйте удалить какой-то код (это должно быть сделано с использованием некоторых атрибутов компоновки)

ViewGroup.LayoutParams params = viewHolder.listphoto.getLayoutParams();
        Resources r = mContext.getResources();
        height = (int) getHeight(height, width);
        params.height = height;
        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        viewHolder.listphoto.setLayoutParams(params);