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.
Related
I have a method creating a large grid of tiny little colored squares, and I noticed that this has a big impact on CPU usage.
Please note that this method is called only once, and that the problem continues and never ends even when the method has finished. If this method is not executed, cpu usage remains low.
// regionLayout is a vertical LinearLayout
// gridHorizontalSize and gridVerticalSize determines the size of the grid
regionLayout.removeAllViews();
regionLayout.setWeightSum(gridVerticalSize);
ImageView cell = null;
LinearLayout row;
int color;
for(int i=0; i<gridVerticalSize; i++) {
row = new LinearLayout(context);
row.setWeightSum(gridHorizontalSize);
regionLayout.addView(row, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f));
for(int j=0; j<gridHorizontalSize; j++) {
cell = new ImageView(context);
color = /* color is chosen somehow */
cell.setBackgroundColor(color);
row.addView(cell, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f));
}
}
I used two LinearLayout to make a grid because I'm unfamiliar with GridLayout and didn't really know how to set a weightSum for both horizontal and vertical sizes.
I used ImageViews for no particular reason, I just need something I can set a color on.
Grids are usually around 30x30 squares, so that makes 900 little colored squares and I know it's a lot, but there's no way around it, I need to have 900 of them on screen at the same time.
What can I change to have an improvement? Thanks.
You should use a custom view, not nested Linear Layouts. You've created 900 view objects on one screen. That is a huge performance hit.
I have a grid board view that I made for a project that you can look at to reference
https://github.com/shmuelr/ConwaysGame/blob/master/app/src/main/java/com/shmuelrosansky/conwaysgame/views/Board.java
I want to create a custom TextView, which draws a background behind my text, but it should be possible to have gaps between lines, if I use LineSpacingExtra. That's why I need a function which calculate the borders of each line in my TextView.
How is it possible to iterate through every line in the TextView? My first test was splitting the text after every space character, but after in a short test, I realized, that a - (minus) could also split lines. Now my idea was to go through every line, but I don't know how to get the from and to character position (or something else).
You can iterate through every line like this:
for (int i = 0; i < getLineCount(); ++i) {
}
See LinedEditText in https://github.com/chiuki/advanced-textview for a complete example.
I have a grid layout where the number of rows and columns in it varies depending on the value of an int that is parsed to the activity. I'm now trying to randomly put one of the 12 images I have into each of the grid spaces to get a random mix of images in a square on the screen. So how do I put drawables into grid spaces without using xml and would a table layout be better? I will also need to be able to detect what image is in the grid space when it is clicked on.
I had the same problem while implementing a memory game some time ago.
To get a drawable object you first need to get your app resources.
Resources res = getResources();
Then, what I do for generate the random position is to put all my images in an ArrayList that has the number of elements equal to the number of grids your have:
ArrayList<Drawable> imagesArray = new ArrayList<Drawable>();
imagesArray.add(res.getDrawable(R.drawable.yourdrawablename));
imagesArray.add(res.getDrawable(R.drawable.yourdrawable2name));
//and so on for the following drawables you have.
After that, you could generate a random number using your ArrayList's size, that is the same amount of images you have, and after that, you put the images on your grid and remove it from the array.
for(int i = 0 ; i < imagesArray.size() ; i++) {
int randomPosition = Random.nextInt(imagesArray.size());
//here's the code to put your drawable in your grid view, in my case, it was just a button
//background, so I didn't know exactly how to put it inside a position of the grid, but it
//should not be that difficult to get it from documentation
//In my case, I do:
button.setBackground(imagesArray.get(randomPosition));
//And then delete that position on the array...
imagesArray.remove(randomPosition);
//And repeat until array's length is null;
}
For the last question, for discovering witch drawable is in which grid, you could get the constantState of it like this:
gridViewElement.getBackground();
This will return the background as a drawable that is associated with that gridView.
Also, be aware that if you want to compare to drawables inside two differente grids, you should use this:
if(gridViewElement1.getBackground().getConstantState()
.equals(gridViewElement2.getBackground().getConstantState())) {
//code goes here.
}
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 have a long text so I've decided to divide it into pages, and I put a so each swipe scrolls to the next page.. The way I did it was:
NumberOfPages=text.length()/CharPerPage; //CharPerPage=500;
NumberOfPages++;
Chapters.add(text.indexOf(" ", CurrentPage*CharPerPage));
CurrentPage++;
int tmp=0;
for(int i =NumberOfPages;i>0;i--)
{
tmp =(CurrentPage*CharPerPage);
if(tmp>text.length())
{
Chapters.add(text.length());
break;
}
else
{
Chapters.add(text.indexOf(" ", tmp));
CurrentPage++;
}
}
So I divide the text into pages, and each page has 500 chars... But this isnt good since Android has different screen sizes and shapes, and line breaks arent counted so it may go beyond the screen...
So can anyone suggest a way to know how many chars are needed to fill the screen so i can make the rest of the text to another page? Thanks.
Ok here is a shot - and a rather clumsy one but in short:
*You need need to know if any give line of text will fit width-wise in your view
*You need to know how many lines you have
*You need to handle embedded newlines
so
will some text fit on any given line
private boolean isTooLarge (TextView text, String newText) {
float textWidth = text.getPaint().measureText(newText);
return (textWidth >= text.getMeasuredWidth ());
}
how many lines does your textview have:
numLinesPerPage=mTextView.getHeight()/mTextView.getLineHeight(); //not this doesn't handle special cases where you've changed the font, bolded it etc
With these two tools you could iterate through your text adding words keeping track of how many lines you have left to work with (and handle your newlines if your text contains them)
note: getHeight can't be called in constructor since it doesn't know the height yet - you could also do this in onDraw or maybe onMeasure if you have a custom control.