改进大型 ListView 适配器平滑滚动,有时生涩

Posted

技术标签:

【中文标题】改进大型 ListView 适配器平滑滚动,有时生涩【英文标题】:Improve Large ListView Adapter smooth scroll, sometimes jerky 【发布时间】:2017-05-28 16:26:36 【问题描述】:

我想看看是什么让我的列表视图在滚动时有时会抖动,有时会很糟糕,尤其是在应用程序首次启动时。

我拥有的所有条件都是必要的,除非有我不知道的事情(很有可能)。 我没有在单独的线程上运行某些任务,因为它们依赖于我从后端收到的数据(我正在编写两者,因此也欢迎后端建议)。产品处于测试阶段,但确实需要让它稍微平滑一点。我正在压缩图像,它们有点长,但这不是问题,因为当我从设备上传图像时,我还包括图像的宽度和高度并将其发送到后端。加载列表时这些尺寸会返回。

我想知道的一件事是计算/转换特定设备屏幕的尺寸是否会导致轻微滞后。不确定该任务的资源密集程度如何,但没有它(不知道尺寸,每一行都会开始平坦,然后扩展到实际的图片大小,这会导致列表跳转,所以我无法在背景。)

基本上滚动还不错,但我需要以某种方式改进它。

这是我的适配器:

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);

    

【问题讨论】:

我建议您改用 RecyclerView。 Recycler View 专门用于此目的Recycler View 据我所知,当你写 if 条件时,你也应该写 else 条件。还有一个更重要的想法是,当您从服务器解析或获取数据时,您存储在其他变量(布尔值)中的每个条件。 @GaneshKatikar 是的,但是如果将其转换为 recyclerview,我基本上不是从头开始吗? 使用recycle-view 将有助于使代码更干净。我认为通过使用多种视图替换此“if (post.getType().equals("xxxx")) ”。 【参考方案1】:

不可能指出具体问题,因为您的适配器中有太多代码。不过有一件事是肯定的——在这种情况下,切换到 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's 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 之前执行的涉及数据计算和转换的业务逻辑,以及您已经传递给 bindPosts() 方法的 Post 对象包含上述计算和转换的结果。

适配器只是将最终数据从Posts“调整”到Views——仅此而已。

如果您现在时间不多,只需要“让它工作”,那么我将首先删除产生新线程并发出网络请求的逻辑。看看这是否能提高性能。

【讨论】:

您阅读过developer.android.com/training/displaying-bitmaps/index.html 培训文档序列吗?使用缓存对我的图像 GridView 的滚动平滑度产生了巨大影响。 他有很多命令可以根据数据调整视图,如setVisible、setLayout。所以使用recycle-view会帮助他把代码做得更好,性能也会提高一点。 @VõQuangHòa,我在 OP 代码中看到的两个最“渴望性能”的部分是线程和网络相关逻辑。使用RecyclerView 而不是ListViewViewHolder 模式不能保证更快(惊喜,惊喜),即使在这种情况下它会更快,由于这种变化而带来的性能提升将可以忽略不计与线程和网络逻辑的性能低效相比,因此并不明显。 OP 绝对应该从重构网络和线程出适配器开始。 @Vasiliy 我同意,“性能饥渴”的主要原因是线程和网络相关逻辑过多。 谢谢大家,是的,你是对的,我正在从适配器中移走一些逻辑【参考方案2】: 将您的实现更改为 RecyclerView,这在报废视图或回收方面更有效。

如果项目是静态的并且不会更改以显着平滑滚动,我们还可以启用优化:

recyclerView.setHasFixedSize(true);

创建一个意图服务,注册BroadcastReceiver作为api请求、业务规则、数据修改完成时的数据返回回调或错误回调。使用同步调用提前执行initHawkWithDataFromServer(),从api获取结果后继续修改或应用业务逻辑。之后创建新的适配器或更新现有的适配器数据集。

将以下所有数据计算或数据值格式化逻辑从适配器的getView() 移至上述意图服务。

您可以向现有的 Post pojo 添加更多的 getter 和 setter。

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(),因为应该使用更精细的操作。 See the API documentation for more details

此外,如果您打算更新现有列表,请确保在进行任何更改之前获取当前的项目计数。例如,应调用适配器上的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 类,以帮助计算新旧列表之间的差异。 Details

如果您的图片尺寸不同,请勿通过 Glide 预加载图片。 Try creating your own。 Also try looking at。

创建颜色作为类成员

int color = Color.parseColor("#dddddd");

在 Post pojo 本身中写入 View.GONEView.VISIBLE,如果 IntentService 将在 Retrofit 的后台线程中执行。尝试 api 在 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";

您也可以提前创建一次颜色过滤器。从列表视图中删除滚动条,因为它计算高度以显示滚动条。

【讨论】:

【参考方案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);

【讨论】:

Hawk.count() 应该只被调用一次,第一次调用后,数据保存在设备上,所以第二次调用计数将 != 0 @franklinexpress 这不正确。我可以看到函数异步调用web api,所以条件不够。它将被调用一次或多次,具体取决于互联网连接(慢或快)。我建议您在该函数中放置一些 Log.i(...) 以查看它是如何工作的。

以上是关于改进大型 ListView 适配器平滑滚动,有时生涩的主要内容,如果未能解决你的问题,请参考以下文章

Flutter ListView.Builder 导致滚动不连贯。如何使滚动平滑?

Android 在加载联系人照片时平滑 ListView 滚动

提高 Android ListView 中的滚动平滑度

在“ListView”中拖放

Android中的Listview延迟加载平滑[重复]

滚动时 ListView 自定义适配器错误