I have to display posts from a json feed inside a RecyclerView and I have a layout for how a single row looks inside the RecyclerView as follows
I have not yet implemented the bottom part in my actual layout which contains the red and green boxes from the figure above and my layout looks like this
I am also implementing Swipe to delete with undo which as you know requires a FrameLayout as the root , and 2 nodes under it, one showing the normal area and one showing the layout which is revealed on swipe. In my case, when I swipe the item, this is what you will see.
Now the problem is, there are 13 views per row and I don't like the odds of that, I will be displaying a maximum of 100 items in the RecyclerView at a given time and as you see it would lead to a large number of Views.
I have certain approaches in mind to make a custom View to reduce the number of Views in the row. What would be the best way according to you to optimise this View or should I say, reduce the number of Views per row in the RecyclerView. I get all the data from JSON and the central text area needs to be expandable in nature with a Read More or Read Less depending on its state.
Approach 1
Slight simplification
In this approach, I will combine the person's profile picture at the top left, the TextView with the name and updated time into a single Custom View, in other words, it will extend from View, have its own canvas for drawing the Bitmap, the Strings but I'll have to manually code RTL and LTR and other items using a StaticLayout in Android. I need the central text area to be expandable in nature so I will stick with one of the Expandable Views everyone keeps mentioning on stackoverflow.
Approach 2
Highly modular Custom UI component.
The entire diagram composed of the user's image, text with name, time, central text and image can be made into a single CustomView, I am not sure how I can make this expandable yet because a StaticLayout once initialised in Android cannot be modified. What do you think? Is this the correct approach to go? I will end up having only 4 children per row if I make the entire thing a single View. Is that a valid use case for custom Views?
Well, I found the answer myself. There are two types of post items I am dealing with, ones that contain an ImageView to display a 16:9 post image and the ones that don't. My RecyclerView was lagging heavily in the scroll operation till now since I had a single layout file which had an ImageView with its width set to match_parent and height set to wrap_content. When I was scrolling, the posts with Images were using Glide to load images and were calling requestLayout() since the size of those items were changing. This was resulting in too many requestLayout() calls causing the heavy lag while scrolling.
How did I fix this?
I made an Adapter which was sectioned having 2 types of rows.
#Override
public int getItemViewType(int position) {
if (mResults != null) {
return mResults.get(position).getPicture() != null ? IMAGE : NO_IMAGE;
}
return NO_IMAGE;
}
Based on whether the result at the current position contains an Image in the post or not, I had my onCreateViewHolder method modified to set a predefined size for the ImageView incase my item had a post image.
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == IMAGE) {
view = mLayoutInflater.inflate(R.layout.row_post_image, parent, false);
RowImageHolder holder = new RowImageHolder(view);
//To set the width and height of the ImageView of our image in the post, we get its LayoutParams and adjust its width and height to maintain 16:9 ratio
ViewGroup.LayoutParams params = holder.mPostPicture.getLayoutParams();
params.width = mPostImageWidth;
params.height = mPostImageHeight;
holder.mPostPicture.setLayoutParams(params);
return holder;
} else {
view = mLayoutInflater.inflate(R.layout.row_post_image, parent, false);
RowNoImageHolder holder = new RowNoImageHolder(view);
return holder;
}
}
And thats all which needs to be done. The onBindViewHolder sets an image if the type of the item supports an image. The benefit is that space is reserved in advance for those items that have images and the performance of the RecyclerView on scroll improves tremendously. I achieved this without using any custom views so far :)
Related
It seems that every angle I manage to find doesn't end up working in the way I need it to. My goal is to be able to customize the positioning and size of any scrollbar on any view, be it a recyclerview, gridview, or listview. I've tried using layer-list xmls to adjust the height and positioning, a Seekbar turned vertically, as well as trying to create my own scrollbar thumb and track using imageviews.
In terms of the layer-list, it just didn't have an effect on the scrollbar at all. The other two attempts at a solution (using a Seekbar, using individual imageviews) were nearly effective, except I needed the current scrolled position (getScrollY()) to be able to make the scrollbars I made actually accurate instead of just visually being a scrollbar. However, even though getScrollY() is defined for recyclerview, gridview and more, it always returns a 0, so I am unable to get that information (except for scrollviews, perhaps; I believe that's the only view type that properly returns a getScrollY() value).
Is it even possible to customize the scrollbar in this manner? I'd be keen to see references or documentation that can point me in the right direction. It feels like this is generally a non-issue for most developers on Android, or at least in general isn't something many people have asked for.
Edit
To assist in visualizing what I have and what I desire, here's a screenshot of the scrollbar as it is right now:
The following image is marked up to show what my intended outcome for this scrollbar would be:
Views have the capability for a scrollbar but a lot don't show them by default.
So any View has a whole load of XML attributes to customise the appearance, size and position.
But these are useless if not shown.
A lot of ViewGroups sub classes setWillNotDraw to be true and this removes the capability to draw the built in scrollbars of the View.
So to get any view to show it's built in scrollbars you need to the setWillNotDraw(false)
Getting any View to show it's built in scrollbars is Step 1 but again not all Views Calculate automatically the length and position of scroll hence they return 0 for the scroll position.
The View has to implement the following methods and return the appropriate numbers for the scroll position to be correct and things like getScrollY to return more than 0
// Length of scrollbar track
#Override
protected int computeHorizontalScrollRange() {
return (int) value;
}
// Position from thumb from the left of view
#Override
protected int computeHorizontalScrollOffset() {
return (int) value;
}
#Override
protected int computeVerticalScrollRange() {
return (int) value;
}
#Override
protected int computeVerticalScrollOffset() {
return (int) value;
}
Off Course some View sub classes don't use the built in ones but draw there own.
What I'm trying to do:
I'm trying to create a staggered grid layout that takes items with varying width and height. The layout dynamically moves items around so they don't overlap.
What I have tried:
I looked into using the default StaggeredGridLayoutManager, but it appears to only work in one orientation (i.e. Vertical or Horizontal). Furthermore, every library or tutorial I've come across also only deals with items containing either a dynamic height or dynamic width, but not both.
Is there a way to make the StaggeredGridLayoutManager dynamically span both vertically and horizontally? Or will I have to create my own custom layout manager? If the latter, could someone point me in the right direction to learn about how one would create such a layout manager?
Instead of StaggeredGridLayoutManager, GridLayoutManagercan be used with spanSizeLookup. Using spanSizeLookup, we can specify the column span.
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
item[position].columnSpan
}
}
While creating the GridLayoutManager , specify the max no of columns like this
layoutManager = GridLayoutManager(context, MAX_NUM_COL)
The row span can be applied by calculating the height using the spanning factor and height of the parent
My app has a screen with an expandablelistview. Within the expandablelistview are collapsible rows that contain listviews. These listviews contain a dynamic number of items and with dynamic heights on each item. As listviews are meant to be set to a hard-coded height and then to have scrolling within them, and I don't want to have scrolling within scrolling, I've had to get around that by calculating the actual height of the listview's items then set the height of the listview to that height so that all of its contents will fit within the exandablelistview row.
Here is my extension method that calculates the listview's height:
public static void SetHeightBasedOnChildren(this ListView listView) {
var listAdapter = listView.Adapter;
var totalHeight = listView.PaddingTop + listView.PaddingBottom;
if (listView.DividerHeight > 0)
totalHeight += (listView.DividerHeight * (listAdapter.Count - 1));
var desiredWidth = DeviceHelper.GetDimensions ().X;// Add parameter if listview doesn't always span entire width of screen
var listViewWidth = MeasureSpec.MakeMeasureSpec (desiredWidth, MeasureSpecMode.AtMost);
View listItem;
for (int i = 0; i < listAdapter.Count; i++) {
listItem = listAdapter.GetView (i, null, listView);
if (listItem is ViewGroup && listItem.LayoutParameters == null)
listItem.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
listItem.Measure (listViewWidth, (int)MeasureSpecMode.Unspecified);
totalHeight += listItem.MeasuredHeight;
}
var layoutParams = listView.LayoutParameters;
layoutParams.Height = totalHeight;
listView.LayoutParameters = layoutParams;
}
The issue with this is that when scrolling down the screen and reaching each listview, as it calculates the dynamic height of the listview's items and sets its height to that, it causes the scrolling to get really choppy.
As I mentioned that listviews are meant to be a hard-coded height, I tried swapping out our listviews for linearlayouts with vertical orientation, which are meant to have an adjustable height that fit their content so don't need any fancy height calculations on the fly. Unfortunately this approach didn't work, as linearlayouts don't cache their rows like listviews do, so the screen was completely unusable from trying to manage hundreds -> thousands of rows without any sort of caching. Note: the linearlayouts performed fine on the emulator but not on devices.
I'm wondering if anyone has ideas of how to get smooth scrolling in this situation. Some ideas I had...
Keep using listviews but set a hard-coded height on each item in the listview. Then I can calculate the height of the listview much faster, as I know the height based on the total number of rows. I would still have to set the height on each listview though, which makes it still not smooth but better than before. The downside is that I'd either need to make all rows tall enough to fit longer content (waste of vertical space), or truncate content that doesn't fit.
Look into if anyone has developed a hybrid widget, that is a linearlayout with vertical orientation that utilizes row caching.
Figure out how to force the expandablelistview to load the listview sections immediately on screen load rather than waiting until they're just about to come into view. The downside is we're holding a lot more in memory so this might render the screen unusable.
Thoughts??
EDIT: I should probably mention that the listviews are also within viewpagers, as the user can swipe between lists from different years (think tax records or mortgage records for a property). I bring this up so someone won't say, just don't have listviews at all, just only use the expandablelistview.
I'm simulating a tile-based map in Android using android-support-v7-gridlayout.
I implement it smoothly and fast during the debugging times until I test it on a huge data scale. The actual data would be about ~700x400 (row-column) and I just tested it in 400x100 but the application has just crashed and throws an OutOfMemoryException. I then reduced the data until it actually runs on 300x100. It's not lagging or I don't have any CPU performance issue, the only issue is inadequate memory.
This is how I add ImageViews in the grid layout:
public boolean genMap(GridLayout gl) {
gl.removeAllViews();
gl.setRowCount( mapFile.getRowCount() );
gl.setColumnCount( mapFile.getColCount() );
try {
for (int row = 0; row < mapFile.getRowCount(); ++row) {
for (int col = 0; col < mapFile.getColCount(); ++col) {
com.gridlayout.GridLayout.Spec rowspan = GridLayout.spec(row, 1);
com.gridlayout.GridLayout.Spec colspan = GridLayout.spec(col, 1);
GridLayout.LayoutParams lp = new GridLayout.LayoutParams(rowspan, colspan);
lp.width = Cell.SIZE;
lp.height = Cell.SIZE;
Cell cell = genNewCell( ctx.getApplicationContext(), row, col );
gl.addView( cell, lp );
}
}
return true;
} catch (Exception e) {
return false;
}
}
Where Cell is a subclass of ImageView
Perhaps, I also think of lazyload pattern where the only visible view will be loaded out but, I'd like to know if GridLayout already implements it.
Update 1
GridView seems is not what I'm looking for. It cannot scroll diagonally plus it can't have a scrollbar both horizontal and vertical at the same time. What I want to achieve is something like GoogleMaps' ViewGroup layout wherein you can scroll in a 2 dimensional way and each cells memory allocation/deallocation are managed automatically. I think GridLayout is my last hope as I cannot see any ViewGroup w/c implements what I wanted to.
So my question is, how can I able to recycle those cells that aren't visible yet in the screen while keeping layout as they are present?
GridLayout does not do any dynamic memory management and simply creates everything regardless if it is actually on the screen or not. You need to use something like the GridView. With a GridView, you could theoretically support an infinite amount of items. Now because you want to do horizontal scrolling you will need a custom GridView implementation. Something like https://github.com/jess-anders/two-way-gridview.
If you take away nothing from my post, DO NOT DO THIS WITH A GRID LAYOUT. You could test all day but every phones different and some have more or less memory.
EDIT 1:
This also might be easier with the release of the new RecyclerView in android L, but I haven’t look into it that much.
In my opinion you should use Webview for this. Has scroll, zoom, memory issues already addressed.
I used Horizontal scroll view in which I add 'Child View'. But on HZ scroll view I want to arrange child some think like, whole screen will show 3.5 child at a time or 4.5 , 5.5 depends on screen size. The half of child indicate there is more child on Scroller.
For that I used different dimensions for child view depends on density. But still their is some device which show whole child at the end of screen.
So how I can mange this on scroll view. Please guide me in right direction.
For that I used different dimensions for child view depends on
density. But still their is some device which show whole child at the
end of screen.
That will not work well in a lot of devices.
Assuming that those orange child views are all of the same width(although not that important) and you want to show half of one only when the HorizontallScrollView is first laid out, you could simply post a Runnable(in the onCreate method) on a view to set their widths to a proper dimension so you make the proper appearance:
public void onCreate(Bundle saved) {
// ... work
horizontalScrollView.post(new Runnable() {
#Override
public void run() {
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenWidth = dm.widthPixels;
// based on this width, the space between the children and their
// widths calculate new widths so you get a half clipped
// children on the right side.
}
});
}
But, I would avoid this and simply set a much more powerful indicator on the HorizontalScrollView(overriding its dispatchDraw method and drawing something there(maybe also hiding it a bit later with a Runnable)) if you really want to make sure that the user sees the extra content.