Skip to content

Commit 2de5d74

Browse files
committed
Improve image sizing
1 parent e131ec7 commit 2de5d74

File tree

5 files changed

+181
-80
lines changed

5 files changed

+181
-80
lines changed

libraries/sdk/src/main/java/com/fastcomments/sdk/FastCommentsFeedView.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ private void init(Context context, AttributeSet attrs, FastCommentsFeedSDK sdk)
121121
* Initialize the adapter with the SDK
122122
*/
123123
private void initAdapter(Context context) {
124+
// Configure RecyclerView for smoother scrolling with image preloading
125+
recyclerView.setItemViewCacheSize(20); // Cache more items
126+
recyclerView.setDrawingCacheEnabled(true);
127+
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
128+
129+
// Set larger prefetch to load images ahead of time
130+
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
131+
if (layoutManager != null) {
132+
layoutManager.setInitialPrefetchItemCount(5); // Prefetch 5 items
133+
}
134+
124135
// Initialize adapter
125136
adapter = new FeedPostsAdapter(context, feedPosts, sdk, new FeedPostsAdapter.OnFeedPostInteractionListener() {
126137
@Override
@@ -199,6 +210,12 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
199210
if ((visibleItemCount + firstVisibleItemPosition + 5) >= totalItemCount) {
200211
loadMore();
201212
}
213+
214+
// Preload next set of images
215+
int lastVisiblePosition = layoutManager.findLastVisibleItemPosition();
216+
if (adapter != null && lastVisiblePosition + 5 < totalItemCount) {
217+
adapter.preloadImages(lastVisiblePosition + 1, 5);
218+
}
202219
}
203220
}
204221
}

libraries/sdk/src/main/java/com/fastcomments/sdk/FeedPostsAdapter.java

Lines changed: 151 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import androidx.recyclerview.widget.RecyclerView;
1919

2020
import com.bumptech.glide.Glide;
21+
import com.bumptech.glide.load.engine.DiskCacheStrategy;
2122
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
2223
import com.fastcomments.model.FeedPost;
2324
import com.fastcomments.model.FeedPostLink;
@@ -62,6 +63,22 @@ public FeedPostsAdapter(Context context, List<FeedPost> feedPosts, FastCommentsF
6263
// Set date format based on SDK configuration
6364
this.useAbsoluteDates = Boolean.TRUE.equals(sdk.getConfig().absoluteDates);
6465
}
66+
67+
/**
68+
* Get the standard image height from resources
69+
* @return Standard image height in pixels
70+
*/
71+
private int getDefaultImageHeight() {
72+
return context.getResources().getDimensionPixelSize(R.dimen.feed_image_height);
73+
}
74+
75+
/**
76+
* Get the half-size image height from resources
77+
* @return Half image height in pixels
78+
*/
79+
private int getHalfImageHeight() {
80+
return context.getResources().getDimensionPixelSize(R.dimen.feed_image_half_height);
81+
}
6582

6683
@Override
6784
public int getItemViewType(int position) {
@@ -146,12 +163,49 @@ public void updatePosts(List<FeedPost> newPosts) {
146163
this.feedPosts.clear();
147164
this.feedPosts.addAll(newPosts);
148165
notifyDataSetChanged();
166+
167+
// Preload the first few images to reduce pop-in effect
168+
preloadImages(0, Math.min(5, newPosts.size()));
169+
}
170+
171+
/**
172+
* Preload images for a range of posts to prevent pop-in during scrolling
173+
*
174+
* @param startPosition Starting position to preload
175+
* @param count Number of posts to preload
176+
*/
177+
public void preloadImages(int startPosition, int count) {
178+
if (startPosition >= feedPosts.size() || count <= 0) {
179+
return;
180+
}
181+
182+
int endPosition = Math.min(startPosition + count, feedPosts.size());
183+
184+
for (int i = startPosition; i < endPosition; i++) {
185+
FeedPost post = feedPosts.get(i);
186+
if (post.getMedia() != null && !post.getMedia().isEmpty()) {
187+
FeedPostMediaItem mediaItem = post.getMedia().get(0);
188+
if (mediaItem.getSizes() != null && !mediaItem.getSizes().isEmpty()) {
189+
FeedPostMediaItemAsset bestAsset = selectBestImageSize(mediaItem.getSizes());
190+
if (bestAsset != null && bestAsset.getSrc() != null) {
191+
// Preload image into memory cache
192+
Glide.with(context)
193+
.load(bestAsset.getSrc())
194+
.diskCacheStrategy(DiskCacheStrategy.ALL)
195+
.preload();
196+
}
197+
}
198+
}
199+
}
149200
}
150201

151202
public void addPosts(List<FeedPost> morePosts) {
152203
int startPosition = this.feedPosts.size();
153204
this.feedPosts.addAll(morePosts);
154205
notifyItemRangeInserted(startPosition, morePosts.size());
206+
207+
// Preload the newly added images
208+
preloadImages(startPosition, Math.min(5, morePosts.size()));
155209
}
156210

157211
public void updatePost(int position, FeedPost updatedPost) {
@@ -160,6 +214,82 @@ public void updatePost(int position, FeedPost updatedPost) {
160214
notifyItemChanged(position);
161215
}
162216
}
217+
218+
/**
219+
* Select the best image size based on device display metrics
220+
* Prioritizes images that fit well on the screen while maintaining quality
221+
*
222+
* @param sizes List of available image sizes
223+
* @return The most appropriate FeedPostMediaItemAsset or the first one if no optimal size is found
224+
*/
225+
private FeedPostMediaItemAsset selectBestImageSize(List<FeedPostMediaItemAsset> sizes) {
226+
if (sizes == null || sizes.isEmpty()) {
227+
return null;
228+
}
229+
230+
// If there's only one size, use it
231+
if (sizes.size() == 1) {
232+
return sizes.get(0);
233+
}
234+
235+
// Get screen width for comparison
236+
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
237+
238+
// Target a size that's close to the screen width for optimal display
239+
// We'll tolerate images up to 1.5x screen width to maintain quality
240+
double optimalWidth = screenWidth;
241+
double maxAcceptableWidth = screenWidth * 1.5;
242+
243+
FeedPostMediaItemAsset bestMatch = null;
244+
double smallestDiff = Double.MAX_VALUE;
245+
246+
// First pass: find close matches to optimal width
247+
for (FeedPostMediaItemAsset asset : sizes) {
248+
if (asset == null || asset.getW() == null || asset.getSrc() == null) {
249+
continue;
250+
}
251+
252+
double width = asset.getW();
253+
double diff = Math.abs(width - optimalWidth);
254+
255+
// If width is within acceptable range and has smaller difference than current best match
256+
if (width <= maxAcceptableWidth && diff < smallestDiff) {
257+
bestMatch = asset;
258+
smallestDiff = diff;
259+
}
260+
}
261+
262+
// If no match found in optimal range, just use the largest that's not excessively large
263+
if (bestMatch == null) {
264+
double largestAcceptableWidth = 0;
265+
266+
for (FeedPostMediaItemAsset asset : sizes) {
267+
if (asset == null || asset.getW() == null || asset.getSrc() == null) {
268+
continue;
269+
}
270+
271+
double width = asset.getW();
272+
273+
// Find largest image that's not too oversized
274+
if (width > largestAcceptableWidth && width <= maxAcceptableWidth * 2) {
275+
bestMatch = asset;
276+
largestAcceptableWidth = width;
277+
}
278+
}
279+
280+
// If still no match, use the first valid asset
281+
if (bestMatch == null) {
282+
for (FeedPostMediaItemAsset asset : sizes) {
283+
if (asset != null && asset.getSrc() != null) {
284+
return asset;
285+
}
286+
}
287+
}
288+
}
289+
290+
// Return best match or first asset if no match found
291+
return bestMatch != null ? bestMatch : sizes.get(0);
292+
}
163293

164294
enum FeedPostType {
165295
TEXT_ONLY,
@@ -358,9 +488,15 @@ private void bindSingleImagePost(FeedPost post) {
358488
if (bestSizeAsset != null && bestSizeAsset.getSrc() != null) {
359489
mediaContainer.setVisibility(View.VISIBLE);
360490

491+
// Pre-set a placeholder with a fixed height before loading the image
492+
// This prevents the layout from jumping when the image loads
493+
mediaImageView.setMinimumHeight(getDefaultImageHeight()); // Match the 300dp in layout
494+
495+
// Use Glide's preload to cache the image before displaying it
361496
Glide.with(context)
362497
.load(bestSizeAsset.getSrc())
363-
.transition(DrawableTransitionOptions.withCrossFade())
498+
.centerCrop()
499+
.transition(DrawableTransitionOptions.withCrossFade(300))
364500
.error(R.drawable.image_placeholder)
365501
.into(mediaImageView);
366502

@@ -593,9 +729,13 @@ private void loadImageIntoView(FeedPostMediaItem mediaItem, ImageView imageView)
593729
if (mediaItem.getSizes() != null && !mediaItem.getSizes().isEmpty()) {
594730
FeedPostMediaItemAsset bestAsset = selectBestImageSize(mediaItem.getSizes());
595731
if (bestAsset != null && bestAsset.getSrc() != null) {
732+
// Set a minimum height to prevent layout jumps
733+
imageView.setMinimumHeight(getHalfImageHeight()); // Half height for smaller images
734+
596735
Glide.with(context)
597736
.load(bestAsset.getSrc())
598-
.transition(DrawableTransitionOptions.withCrossFade())
737+
.centerCrop()
738+
.transition(DrawableTransitionOptions.withCrossFade(300))
599739
.error(R.drawable.image_placeholder)
600740
.into(imageView);
601741
} else {
@@ -616,6 +756,7 @@ private ImageView createImageView(FeedPostMediaItem mediaItem) {
616756
ImageView imageView = new ImageView(context);
617757
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
618758
imageView.setBackgroundColor(context.getResources().getColor(android.R.color.darker_gray, null));
759+
imageView.setMinimumHeight(getDefaultImageHeight()); // Set a minimum height to prevent layout jumps
619760

620761
// Load image using Glide if media item has sizes
621762
if (!mediaItem.getSizes().isEmpty()) {
@@ -624,6 +765,7 @@ private ImageView createImageView(FeedPostMediaItem mediaItem) {
624765
if (bestSizeAsset != null) {
625766
Glide.with(context)
626767
.load(bestSizeAsset.getSrc())
768+
.centerCrop()
627769
.transition(DrawableTransitionOptions.withCrossFade())
628770
.error(R.drawable.image_placeholder)
629771
.into(imageView);
@@ -678,9 +820,15 @@ private void bindTaskPost(FeedPost post) {
678820

679821
if (bestSizeAsset != null && bestSizeAsset.getSrc() != null) {
680822
mediaContainer.setVisibility(View.VISIBLE);
823+
// Pre-set a placeholder with a fixed height before loading the image
824+
// This prevents the layout from jumping when the image loads
825+
mediaImageView.setMinimumHeight(getDefaultImageHeight()); // Match the 300dp in layout
826+
827+
// Use Glide's preload to cache the image before displaying it
681828
Glide.with(context)
682829
.load(bestSizeAsset.getSrc())
683-
.transition(DrawableTransitionOptions.withCrossFade())
830+
.centerCrop()
831+
.transition(DrawableTransitionOptions.withCrossFade(300))
684832
.error(R.drawable.image_placeholder)
685833
.into(mediaImageView);
686834

@@ -1013,81 +1161,5 @@ private String formatTimestamp(OffsetDateTime date) {
10131161
return relativeTime.toString();
10141162
}
10151163
}
1016-
1017-
/**
1018-
* Select the best image size based on device display metrics
1019-
* Prioritizes images that fit well on the screen while maintaining quality
1020-
*
1021-
* @param sizes List of available image sizes
1022-
* @return The most appropriate FeedPostMediaItemAsset or the first one if no optimal size is found
1023-
*/
1024-
private FeedPostMediaItemAsset selectBestImageSize(List<FeedPostMediaItemAsset> sizes) {
1025-
if (sizes == null || sizes.isEmpty()) {
1026-
return null;
1027-
}
1028-
1029-
// If there's only one size, use it
1030-
if (sizes.size() == 1) {
1031-
return sizes.get(0);
1032-
}
1033-
1034-
// Get screen width for comparison
1035-
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
1036-
1037-
// Target a size that's close to the screen width for optimal display
1038-
// We'll tolerate images up to 1.5x screen width to maintain quality
1039-
double optimalWidth = screenWidth;
1040-
double maxAcceptableWidth = screenWidth * 1.5;
1041-
1042-
FeedPostMediaItemAsset bestMatch = null;
1043-
double smallestDiff = Double.MAX_VALUE;
1044-
1045-
// First pass: find close matches to optimal width
1046-
for (FeedPostMediaItemAsset asset : sizes) {
1047-
if (asset == null || asset.getW() == null || asset.getSrc() == null) {
1048-
continue;
1049-
}
1050-
1051-
double width = asset.getW();
1052-
double diff = Math.abs(width - optimalWidth);
1053-
1054-
// If width is within acceptable range and has smaller difference than current best match
1055-
if (width <= maxAcceptableWidth && diff < smallestDiff) {
1056-
bestMatch = asset;
1057-
smallestDiff = diff;
1058-
}
1059-
}
1060-
1061-
// If no match found in optimal range, just use the largest that's not excessively large
1062-
if (bestMatch == null) {
1063-
double largestAcceptableWidth = 0;
1064-
1065-
for (FeedPostMediaItemAsset asset : sizes) {
1066-
if (asset == null || asset.getW() == null || asset.getSrc() == null) {
1067-
continue;
1068-
}
1069-
1070-
double width = asset.getW();
1071-
1072-
// Find largest image that's not too oversized
1073-
if (width > largestAcceptableWidth && width <= maxAcceptableWidth * 2) {
1074-
bestMatch = asset;
1075-
largestAcceptableWidth = width;
1076-
}
1077-
}
1078-
1079-
// If still no match, use the first valid asset
1080-
if (bestMatch == null) {
1081-
for (FeedPostMediaItemAsset asset : sizes) {
1082-
if (asset != null && asset.getSrc() != null) {
1083-
return asset;
1084-
}
1085-
}
1086-
}
1087-
}
1088-
1089-
// Return best match or first asset if no match found
1090-
return bestMatch != null ? bestMatch : sizes.get(0);
1091-
}
10921164
}
10931165
}

libraries/sdk/src/main/java/com/fastcomments/sdk/FullImageDialog.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ protected void onCreate(Bundle savedInstanceState) {
6464
.load(imageUrl)
6565
.error(R.drawable.image_placeholder)
6666
.into(photoView);
67+
// Note: we intentionally don't use centerCrop() here as this is a full-screen
68+
// image view with zoom capabilities via PhotoView
6769
}
6870
}
6971
}

libraries/sdk/src/main/java/com/fastcomments/sdk/PostImagesAdapter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,13 @@ void bind(FeedPostMediaItem mediaItem) {
9292
FeedPostMediaItemAsset bestSizeAsset = selectBestImageSize(mediaItem.getSizes());
9393

9494
if (bestSizeAsset != null && bestSizeAsset.getSrc() != null) {
95+
// Pre-set a minimum height for consistency
96+
imageView.setMinimumHeight(context.getResources().getDimensionPixelSize(R.dimen.feed_image_height));
97+
9598
Glide.with(context)
9699
.load(bestSizeAsset.getSrc())
97-
.transition(DrawableTransitionOptions.withCrossFade())
100+
.centerCrop()
101+
.transition(DrawableTransitionOptions.withCrossFade(300))
98102
.error(R.drawable.image_placeholder)
99103
.into(imageView);
100104
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- Standard image heights -->
4+
<dimen name="feed_image_height">300dp</dimen>
5+
<dimen name="feed_image_half_height">150dp</dimen>
6+
</resources>

0 commit comments

Comments
 (0)