I'm trying to see what is making my listview jerk sometimes when scroll, at times it's bad especially when the application first launches.
All the conditions I have are necessary, unless there is something I don't know(highly likely). I'm not running certain tasks on a seperate thread because they are dependent on the data I receive from the backend(I'm coding both, so backend suggestions are welcome as well). Product is in beta but really need to make this a slightly bit smoother. I'm compressing the images, and they are a bit long but it's not the problem because when I upload the images from the device, I also include the width and height of the image and send that along to the backend. These dimensions come back when loading the list.
One thing I wonder is if calculating/converting the dimensions for the specific device's screen is causing the slight lag. Not sure how resource intensive that task is, but without it(without knowing the dimensions, each row would start out flat and then expand to the actual picture size which would cause the list to jump, so I can't run that calculation on the background either.)
Basically the scrolling isn't bad, but I need to improve this somehow.
Here is my Adapter:
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 NavigableMapsuffixes = 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 mDataSource; private static double lat; private static double lon; public VListAdapter(Context context, ArrayList 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 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 > call = loginService.getLikes(username); Log.i("lonlat", String.valueOf(lon) + " and " + String.valueOf(lat)); call.enqueue(new Callback
>() { @Override public void onResponse(Call
> call, Response
> response) { ArrayList
posts = new ArrayList<>(); posts = (ArrayList ) response.body(); if (!posts.isEmpty()) for (Post p : posts) { Hawk.put("liked" + p.getId().get$oid(), 1); } } @Override public void onFailure(Call > 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
call = loginService.addLocation(oid, PostListFragment.lon, PostListFragment.lat); call.enqueue(new Callback () { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) Log.i("mapped", "success"); } @Override public void onFailure(Call 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 call = loginService.like(post, 1, username); call.enqueue(new Callback () { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { try { Log.i("call", response.body().string()); } catch (IOException e) { e.printStackTrace(); } } } @Override public void onFailure(Call 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 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); } }
Vasiliy.. 6
指向特定问题是不可能的,因为适配器中有太多代码.但有一件事是肯定的 - RecyclerView
在这种情况下切换到不会帮助你.
适配器不应包含业务逻辑 - 它们只应将输入对象"调整"到基础视图.在您的情况下,似乎适配器执行计算,生成新线程,执行网络请求等.
您需要重构代码,以使适配器与此类似:
public class PostsListAdapter extends ArrayAdapter{ private Context mContext; public PostsListAdapter(Context context, int resource) { super(context, resource); mContext = context; } public void bindPosts(List 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
,以及Post
传递给bindPosts()
方法的对象已经包含上述计算和转换的结果.
适配器刚刚从"适应"的最终数据Posts
以Views
仅此而已- .
如果你现在时间很短,只需要"让它工作",那么我将首先删除产生新线程并发出网络请求的逻辑.看看这是否会提高性能.
指向特定问题是不可能的,因为适配器中有太多代码.但有一件事是肯定的 - RecyclerView
在这种情况下切换到不会帮助你.
适配器不应包含业务逻辑 - 它们只应将输入对象"调整"到基础视图.在您的情况下,似乎适配器执行计算,生成新线程,执行网络请求等.
您需要重构代码,以使适配器与此类似:
public class PostsListAdapter extends ArrayAdapter{ private Context mContext; public PostsListAdapter(Context context, int resource) { super(context, resource); mContext = context; } public void bindPosts(List 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
,以及Post
传递给bindPosts()
方法的对象已经包含上述计算和转换的结果.
适配器刚刚从"适应"的最终数据Posts
以Views
仅此而已- .
如果你现在时间很短,只需要"让它工作",那么我将首先删除产生新线程并发出网络请求的逻辑.看看这是否会提高性能.