Android: FrameLayout not respecting draw order - android

I have a FrameLayout with four SurfaceViews arranged in a 2x2 grid. The user can resize each of the views.
I'd like the views to be drawn in order of their area, with the largest view drawn first and so on. Each time a view is resized, I order the views by their area, and update the FrameLayout:
public void reorderViews() {
PlotView child1;
PlotView child2;
boolean swap = false;
for(int i = 0; i < layout.getChildCount(); i++) {
child1 = (PlotView) layout.getChildAt(i);
for(int j = i + 1; j < layout.getChildCount(); j++) {
child2 = (PlotView) layout.getChildAt(j);
if(child1.area < child2.area) {
layout.removeViewAt(j);
layout.addView(child2, i);
}
}
}
}
This works, in the sense that the FrameLayout's children array holds the views in the correct order. But after re-ordering, the views continue to draw in their original order (i.e. the order in which they were originally added).
I've tried requesting layout on the FrameLayout, on the individual child views, on the FrameLayout's parent view. I've also tried invalidating everything. I've overridden the FrameLayout's onDraw hoping to force the correct draw order. No go.
A few points: I'm using FrameLayout rather than Grid because I'd like larger views to obscure smaller views, instead of having a larger view push a smaller view aside, as is the behaviour in GridView.
In order to position the child views properly, I adjust their margins and set their gravity to 'top' (it doesn't seem to matter what value I use for gravity, just so long as gravity is set to something, margins work). Might the gravity be the issue?
Also, I'm sure there's a more efficient way to reorder the views, but there's only four total, so this works ok.

SurfaceView widgets are not drawn using the normal Android layout mechanism. Instead, each SurfaceView gets its own, separate window. I suspect those windows are not being reordered when the views are reordered. What you're trying to do would probably work for any other kind of widget.

Related

Smooth scrolling of expandablelistview containing listviews

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.

Set padding postInvalidate not removing padding on the bottom part on left side of puzzle

Hi currently I'm working on a puzzle that slice and randomize one image. Upon setting up I have manually set the padding into 2 for all corners then after the puzzle is finished padding should be set to 0 on all corners as well to display the image in full. Problem is there is an odd thing that happen wherein only the right side of the puzzle removes the padding completely while the other side still has it's padding on the bottom part. This only happens on devices with lower specs. (Nexus One and alike)
Here's my code for the setting of padding after completing the puzzle:
if(true){
enable_puzzle = false;
for (int i=0; i < parent.getChildCount(); i++){
LinearLayout cont = (LinearLayout) parent.getChildAt(i);
for (int c = 0; c < cont.getChildCount(); c++){
View v = cont.getChildAt(c);
v.setClickable(false);
v.setPadding(0,0,0,0);
v.postInvalidate();
}
cont.postInvalidate(); //Tried this didn't work
}
parent.postInvalidate(); //Tried this didn't work
}
for better understanding of how I placed my layout it's pretty simple. I have a linearLayout that in horizontal orientation which holds the imageViews which will be places into the parent container in vertical orientation.
Here's the screenshot for sample.
The puzzle on create (see odd padding on the bottom part on left side)
And upon completing:
This is the screenshot on my Nexus S emulator. Same thing happen on real device with lower specs.
UPDATE:
Upon checking it appears that the main cause was the width of the container for the images. Since I have a 4x4 puzzle and uses weight instead of fixed sizes the width for each images is not evenly distributed causing a 1px/dp difference. Now the only problem is on how can I get the width of the parent horizontal layout to be divided and round up to get an even result for each imageviews?
There is a workaround for this, you can create a listener something like onPuzzelCompleteListener(), something that you are doing even now to remove the padding. Here you can show an ImageView with the complete image. This should solve your padding issue as for now.

Optimizing GridLayout's memory usage

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.

How to speed up for loop

I dynamically add textviews to a relative layout based on user response to create a coloured grid pattern. Typically this can contain 5000+ textviews which have different background colors based on the value in textview tag.
I have this method where I iterate through all the textviews to show only those that have the same color and set the rest to gray. This works well when there are say 500 textviews but when the number is higher, say 5000 it take 13 seconds to complete.
if (code.equals("all")) {
for (int i = 0; i < textViewIDs.size(); i++) {
TextView tv = (TextView) findViewById(textViewIDs.get(i));
if (!tv.getTag().toString().equals("header")) {
tv.setBackgroundColor(Color.parseColor("#" + tv.getTag().toString()));
}
}
} else {
for (int i = 0; i < textViewIDs.size(); i++) {
TextView tv = (TextView) findViewById(textViewIDs.get(i));
if (!tv.getTag().equals(code)) {
if (!tv.getTag().toString().equals("header")) {
tv.setBackgroundColor(Color.LTGRAY);
}
}else{
tv.setBackgroundColor(Color.parseColor("#" + tv.getTag().toString()));
}
}
}
textViewIDs is the array holding all the textview ids.
What if anything can be done to speed this up?
Update:
I understand that having that number of widgets is not ideal however I could not come up with a better solution.
As well as each grid cells, in this case each texview, having different colors I also need to be able to manage onclick event for the cell so that I can add text. That was the reasoning for the textviews. Prior to using textviews I drew all the elements but that's when I couldn't find a way to add onclick event to each cell.
I'd better detail the concept to help you guys with what I'm trying to achieve and if I've gone down the wrong road.
This is part of a much larger app where I'm converting images into stitch charts.
Based on user input a grid of colored cells is drawn where each cell is a solid color that has been calculated from the original image most dominant color.
The grid will be larger than the screen so my views are placed in both horizontal and scroll views so they can be panned and zoomed. (This is all working well).
The grid cells have to click-able so I can turn on or off the background colors and also add a single text "X" character to mark stitch (cell) as completed.(This is to slow when the number of textview (cells) are > 500)
Hope there is enough detail there...
findViewById() seems to be your pressure point.
Instead of keeping a list of the ids, I'd a keep a list of references to the Views themselves (WeakReferences if leaks are a possibility)!
1 - for (int i = 0; i < textViewIDs.size(); i++) { ...
It's not optimized: precalculate your limit in a variable before starting the cycle:
int len = textViewIDs.size(); and use len in your cycle.
2 - i-- (I call it "reverse loop") seems to be faster than i++. See a nice loop comparison here
It's a bad practice to have that many TextView's, low-end devices will not be able to load it.
Try making one TextView with multiple styles in it, you can use HTML tags for background colors.
Or even better is to create just one ListView, this will recycle the views for you.

Make INVISIBLE View handle touches as ACTION_OUTSIDE

I want to create transparent system overlay window, which has several round views:
Green view is VISIBLE, red one is INVISIBLE. The thing is that I need red view to pass touches to the underlying window, but by default it doesn't.
I tried setting visibility of red view to GONE, but then overall size of containing view changes. Since I need the containing view to be snapped to the right edge of the screen, this means that position of green view changes, and I don't want this.
Is there any way to override default touch handling in INVISIBLE state?
Here's an actual screenshot to make my question more clear:
Also, I had to override dispatchTouchEvent() to dispatch touch events correctly:
#Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Circle target=null;
int targetIndex=-1;
for (int i=0; i<getChildCount(); i++) {
Circle child=(Circle) getChildAt(i);
if (child.viewMetrics.containsPoint(ev.getX(), ev.getY()) && (ev.getAction()==MotionEvent.ACTION_MOVE || child.isVisible())) {
target=child;
targetIndex=i;
break;
}
}
onMotionEvent(ev, target, targetIndex);
Log.d(">| CircularLayout", "action: "+ev.getAction()+", target: "+target);
return target!=null && target.dispatchTouchEvent(ev);
}
Create custom view to do this. In such case you do not need any trickery related to visible invisible - it's you who decide what is your clickable area and also you who decies what area will alter the state (like is drawn as pressed on touch event). So your custom view should be of the size of desired area but in your onDraw() you shall only draw the change on that smaller inner area.
Here are basics on how to create custom views: http://developer.android.com/training/custom-views/index.html
What I ended up doing is calculating two sets of view dimensions: maximum possible (size of the red area) and current (green area). When child view is hidden or shown I set width and height of window LayoutParams to current dimensions and then set difference between maximum and current dimensions to y and x parameters of LayoutParams.
For some reason applying modified LayoutParams triggers measure/layout pass twice (which in turn causes the view to "bounce"), but that seems to be the whole new story.

Categories

Resources