After numerous questions about this topic I still haven't fully solved the problem.
In the end the first imageview always gets "overwritten" or better said overlapped by another view which should go right of that view.
After extending RelativeLayout is it enough to only override onLayout() method for children view placement? Does onLayout method place all children in one pass or is it called for every specific child? How should that onLayout method implement child placement if I want to use RelativeLayouts specific placemnts (RIGHT_OF, BELOW, etc ...)
In view creation, how can I create a view without layoutparams, is it even possible?
EDIT: Ok I avoided using getWidth in any form and still get bad layout. Icons get first row mixed but full (5 icons), next row has 1 icon only and 2 are missing. At this point I'm quite frustrated because it's a stupid issue and I can't seem to find what's wrong, why doesn't official developer tutorial have more help on dynamic layouts and views?
Log.e from down says:
2: RIGHT OF 1
3: RIGHT OF 2
4: RIGHT OF 3
5: RIGHT OF 4
6: BELOW 1
7: RIGHT OF 6
8: RIGHT OF 7
9: RIGHT OF 8
That's the way it's supposed to be yet it doesn't work, I can't set relative position to layouts that should have been layed out?
private void loadResources() {
cursor = managedQuery(Browser.BOOKMARKS_URI, projection, selection,
null, SORT_BY_COLUMN + " " + SORT_ORDER);
this.startManagingCursor(cursor);
ImageView previousBookmark;
int idOfViewToTheLeft = 1;
if(cursor.moveToFirst()) {
bookmarkCounter = 1;
ByteArrayInputStream blobImage;
int size = (int) scale * FAVICON_SIZE;
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int rowBookmarkCount = (int) (screenWidth/(size + scale*leftMargin));
do{
bookmark = new ImageView(this);
bookmark.setId(bookmarkCounter++);
bookmark.setBackgroundColor(Color.WHITE);
blobImage = new ByteArrayInputStream(
cursor.getBlob(cursor.getColumnIndex(BookmarkColumns.FAVICON)));
bookmark.setImageDrawable(
Drawable.createFromStream(blobImage, "" + bookmark.getId()));
urls.put(bookmark.getId(),
cursor.getString(
cursor.getColumnIndex(BookmarkColumns.URL)));
bookmark.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent browserIntent = new Intent(
Intent.ACTION_VIEW, Uri.parse(urls.get(v.getId())));
startActivity(browserIntent);
}
});
lp = new RelativeLayout.LayoutParams(size, size);
lp.topMargin = (int) (scale * topMargin);
lp.leftMargin = (int) (scale * leftMargin);
if(bookmark.getId() > 1) {
previousBookmark = (ImageView) findViewById(bookmark.getId() - 1);
if((bookmark.getId() % (rowBookmarkCount + 1)) != 0)
{
Log.e("" + bookmark.getId(), "RIGHT OF " + previousBookmark.getId());
lp.addRule(RelativeLayout.RIGHT_OF, previousBookmark.getId());
} else {
Log.e("" + bookmark.getId(), "BELOW " + idOfViewToTheLeft);
lp.addRule(RelativeLayout.BELOW, idOfViewToTheLeft);
idOfViewToTheLeft = bookmark.getId();
}
}
bookmarkLayout.addView(bookmark, lp);
} while (cursor.moveToNext());
}
}
For standard components you don't really need to override onLayout, if you want for them to layout components as usual that is. If you do override onLayout and don't call super.onLayout then you must position all your children manually.
Otherwise, just use RelativeLayout.LayoutParams with added rules (BELOW, ABOVE, etc..) and set it when adding a view addView(youView, yourLayoutParamsInstance)
Most of the logic in RelativeLayout actually happens during the measurement phase, so no, just overriding onLayout is most likely not enough.
If you want to extend RelativeLayout, I'm afraid you'll have to get accustomed to it's source code, otherwise it'll be quite hard to understand how to influence it's behavior. You should really consider finding another solution though, since you really picked one of the more complex classes with RelativeLayout.
What I did in the end is quit using relative parameter to describe view's position relative to other view. Instead I did all the math myself and put everything in setMargins, hope that's is equally if not faster than using Relativelayouts methods to figure it out.
private void loadResources() {
cursor = managedQuery(Browser.BOOKMARKS_URI, projection, selection,
null, SORT_BY_COLUMN + " " + SORT_ORDER);
this.startManagingCursor(cursor);
if(cursor.moveToFirst()) {
bookmarkCounter = 0;
ByteArrayInputStream blobImage;
int leftMargin = (int) (scale * this.leftMargin);
int topMargin = (int) (scale * this.topMargin);
int size = (int) scale * FAVICON_SIZE;
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int rowBookmarkCount = (int) (screenWidth/(size + leftMargin));
do{
bookmark = new ImageView(this);
bookmark.setId(bookmarkCounter++);
bookmark.setBackgroundColor(Color.WHITE);
blobImage = new ByteArrayInputStream(
cursor.getBlob(cursor.getColumnIndex(BookmarkColumns.FAVICON)));
bookmark.setImageDrawable(
Drawable.createFromStream(blobImage, "" + bookmark.getId()));
urls.put(bookmark.getId(),
cursor.getString(
cursor.getColumnIndex(BookmarkColumns.URL)));
bookmark.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent browserIntent = new Intent(
Intent.ACTION_VIEW, Uri.parse(urls.get(v.getId())));
startActivity(browserIntent);
}
});
lp = new RelativeLayout.LayoutParams(size, size);
lp.setMargins(
(int) (leftMargin + (bookmark.getId() % rowBookmarkCount) * (size + leftMargin)),
(int) (topMargin + (bookmark.getId() / rowBookmarkCount) * (size + topMargin)),
0, 0);
bookmarkLayout.addView(bookmark, lp);
} while (cursor.moveToNext());
setContentView(scrollView);
}
}
Related
Subject of interest : Android
After hours of searching I couldn't find or understand how to create ImageView dynamically to store associative data and perform an action depending on the image view clicked.
Let me elaborate ,
I am creating a grid of ImageViews using GridLayout (as gridview examples are too complex for me). I am able to create the grid but still struggling with alignment issues.
So now I have this Grid of ImageViews, but from what I learned, the Id's of these ImageViews are converted to integers at run time. But I need these IDs as I use them to fetch data from server depending on their uniqueness. Is there a way to store other data than the Ids in ImageView Tags?. I come from a web development background where I could use JavaScript easily to create elements with dynamic Ids to refer later and use in queries. So please guide me.
UPDATE:
I tried using the SetId method, and that is when I got this doubt. Because SetId only lets me set IntegerIDs. What if I needed alphanumeric ID to be used later for querying purposes?.
Exam_Screen.Java
public class Exam_Screen extends AppCompatActivity {
GridLayout gridLayout;
public int[] QArray;
private int[] GetQuestionsFromServer() {
return new int[50];
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exam__screen);
QArray = GetQuestionsFromServer();
AddQuestionsToScrollArea(QArray);
}
public void AddQuestionsToScrollArea(int[] QArray)
{
gridLayout = findViewById(R.id.gridlayout);
gridLayout.removeAllViews();
int total = QArray.length;
int column = 4 ;
int row = total / column;
gridLayout.setColumnCount(column);
gridLayout.setRowCount(row + 1);
//gridLayout
for (int i = 0, c = 0, r = 0; i < total; i++, c++) {
if (c == column) {
c = 0;
r++;
}
ImageView oImageView = new ImageView(this);
oImageView.setScaleX(0.3f);
oImageView.setScaleY(0.3f);
oImageView.setId(i); // I want to set the Ids I get from Server, and use them later
oImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
oImageView.setAdjustViewBounds(true);
oImageView.setImageResource(R.drawable.new_candidate_image);
GridLayout.LayoutParams lp = new GridLayout.LayoutParams();
lp.width = 30;
lp.height = 30;
lp.topMargin = 5;
lp.bottomMargin = 5;
lp.leftMargin = 5;
lp.rightMargin = 5;
oImageView.setLayoutParams(lp);
GridLayout.Spec rowSpan = GridLayout.spec(GridLayout.UNDEFINED, 1);
GridLayout.Spec colspan = GridLayout.spec(GridLayout.UNDEFINED, 1);
if (r == 0 && c == 0) {
Log.e("", "spec");
Log.d("Column", "value: " + column);
Log.d("rows", "value: " + row);
colspan = GridLayout.spec(GridLayout.UNDEFINED, 1);
rowSpan = GridLayout.spec(GridLayout.UNDEFINED, 1);
}
GridLayout.LayoutParams gridParam = new GridLayout.LayoutParams(
rowSpan, colspan);
gridLayout.addView(oImageView, gridParam);
}
}
}
Relevant Exam Screen Activity XML
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/border">
<GridLayout
android:id="#+id/gridlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ScrollView>
When I use Java code to dynamically add a button to an Android layout I first have to declare a local copy of the button similar to this:
Button btn = new Button(this);
Then there is some typical code as follows (but varies with the application):
// sets button width and height
RelativeLayout.LayoutParams bparms = new RelativeLayout.LayoutParams(w, h);
// sets button left and top position inside layout
bparms.setMargins(l, t, 0, 0);
This may be followed by setting other button properties such as text, background etc. Finally the button gets added to the parent layout as follows:
// add the dynamic button to the keypad view
kpad.addView(btn, bparms);
Here is my question. Does the layout receiving the button make a copy of the locally created dynamic button? Or does it just take a reference to the that created button leaving the originally created object intact?
Looking at the source code, there is an addInArray method of ViewGroup class, and it just takes a reference of the child view and adds it to children array:
private void addInArray(View child, int index) {
View[] children = mChildren;
final int count = mChildrenCount;
final int size = children.length;
if (index == count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, size);
children = mChildren;
}
children[mChildrenCount++] = child;
} else if (index < count) {
if (size == count) {
mChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
children = mChildren;
} else {
System.arraycopy(children, index, children, index + 1, count - index);
}
children[index] = child;
mChildrenCount++;
if (mLastTouchDownIndex >= index) {
mLastTouchDownIndex++;
}
} else {
throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
}
}
Generally, java is working with references. So your object will not be cloned, you just give a reference of the object to the layout. Also, whenever you receive the object, e.g. by iteration over all views in the layout programmatically, you will also only receive a reference to the object. Cloning may be possible, when you e.g. assign a new variable (with a new memory position).
I am creating a Table of buttons, to control a LED Matrix via Bluetooth.
I have found on the web Brian's Video Tutorials and followed his Dynamic Buttons and Images video to implement this.
Here is the code:
public class DrawerMode extends Activity {
private static final int NUMOFCOL = 15;
private static final int NUMOFROW = 8;
Button buttons[][] = new Button[NUMOFROW][NUMOFCOL];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Assign content
setContentView(R.layout.activity_draw_mod);
fillTable();
}
private void fillTable() {
TableLayout tableLayout = (TableLayout) findViewById(R.id.drawer_table);
for( int iter_R = 0; iter_R!= NUMOFROW; iter_R++){
TableRow tableRow = new TableRow(this);
tableRow.setLayoutParams(new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT,TableLayout.LayoutParams.WRAP_CONTENT,1.0f));
tableLayout.addView(tableRow);
for(int iter_C = 0; iter_C != NUMOFCOL; iter_C++){
final int FINAL_COL = iter_C;
final int FINAL_ROW = iter_R;
Button button = new Button(this);
button.setLayoutParams(new TableRow.LayoutParams( TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT, 1.0f));
button.setText("" + iter_C + "," + iter_R);
button.setPadding(0, 0, 0, 0);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
whenBtnClicked(FINAL_COL, FINAL_ROW);
}
});
tableRow.addView(button);
buttons[iter_R][iter_C] = button;
}
}
}
private void whenBtnClicked(int col, int row) {
//Toast.makeText(this, "Button clicked: " + FINAL_COL + "," + FINAL_ROW, Toast.LENGTH_SHORT).show();
Button button = buttons[row][col];
// Lock Button Sizes:
lockButtonSizes();
int newWidth = button.getWidth();
int newHeight = button.getHeight();
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_dark_blue);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, true);
Resources resource = getResources();
button.setBackground(new BitmapDrawable(resource, scaledBitmap)); // Change text on button:
button.setText(" ");
}
private void lockButtonSizes(){
for (int row = 0; row < NUMOFROW; row++){
for (int col = 0; col < NUMOFCOL; col++){
Button button = buttons[row][col];
int width = button.getWidth();
button.setMinWidth(width);
button.setMaxWidth(width);
int height = button.getHeight();
button.setMinHeight(height);
button.setMaxHeight(height);
}
}
}
}
It works great, but while testing I have found the following issue.
When I click random buttons it works great:
[img]http://i.imgur.com/OYFJ6zJ.png?1[/img]
But when I complete a row (all elements on row are clicked), and I mean any row it starts to rescale the buttons in the whole table:
[img]http://i.imgur.com/ttAz4U0.png?1[/img]
I was thinking that maybe the LayoutParams of the TableRow should be changed, but not sure about that. What am I missing here?
I think you're right about the layout parameters needing to change. This line
tableRow.setLayoutParams(new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT,TableLayout.LayoutParams.WRAP_CONTENT,1.0f));
would cause the row's height to shrink in size if no button had displayed text, which seems to be what's happening. The TableLayout.LayoutParams does support setting fixed width/height, which you could sensibly calculate by first getting the device's screen width/height and dividing accordingly.
Or, if that gets to cumbersome, you could set -- though this may be too much of a hack -- the default text in the TextViews in the "unset" buttons with some transparent text (e.g., "1,1") so that the height is the same as a set button. This SO answer answer shows how to make transparent text.
I am certain that this is not a good solution for all cases. But just as I thought, the problem was with
button.setLayoutParams(new TableRow.LayoutParams( TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT, 1.0f));
If I understand correctly after the entire row was clicked, since the LayoutParams change according to the MATCH_PARENT value, the TableRow rescales the whole row to meet this criteria,since the height of the entire row is the same now. Not sure if it happens exactly this way, but I think this is the case because of my solution.
My work around is to add specific values for the LayoutParams, instead of leaving it the system to figure it out:
button.setLayoutParams(new TableRow.LayoutParams( 75, 50, 1.0f));
I am aware this is not how it should be done. But since I have a deadline to met soon, I can't spend any more time with it. Most likely the correct way to do this is Jason's suggestion to get the screen size and calculate it. You can do this with:
Display display = getWindowManager().getDefaultDisplay(); Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
The problem is to come up with a correct formula to calculate this values you can pass to the LayoutParam. If anyone can figure this out please do post your solution and I will accept that answer. At this point I accept Jason's suggestion.
Can anyone tell me why this is happening, please?
What I'm trying to do is get them to all align centered, regardless of how many there are. It works lovely for quite a few, and even works with only 1 more, but 4 seems to do this. I'm adding them in code, here:
LinearLayout guessHolders = (LinearLayout)findViewById(R.id.guessHolders);
guessHolders.removeAllViews();
currentLetterPosition = 0;
final Bitmap emptyLetterHolder = BitmapFactory.decodeResource(getResources(), R.drawable.letter_holder);
for(int i=0; i<Globals.mUser.getLevel().getSolution().length(); i++)
{
final EmptyLetter tmp = new EmptyLetter(this, i);
tmp.setImageBitmap(emptyLetterHolder);
LinearLayout.LayoutParams parms = new LinearLayout.LayoutParams(Globals.defaultLetterSizes[mBlockSize],
Globals.defaultLetterSizes[mBlockSize], 1);
parms.weight = 1;
parms.gravity = Gravity.CENTER_HORIZONTAL;
tmp.setLayoutParams(parms);
tmp.setAdjustViewBounds(false);
tmp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i("PP2", tmp.getId() + " << the clicked empty letter holder id");
currentLetterPosition = tmp.getId();
tmp.setImageBitmap(emptyLetterHolder);
setChosenLetter(tmp.getId(), '\u0000');
}
});
currentEmpties.add(tmp);
guessHolders.addView(tmp);
}
I've tried setting gravity, weightsum, width and height of the box views as well as the parent container LinearLayout, but nothing seems to shift them.
Any help is greatly appreciated!
Fixed it. With the following:
LinearLayout.LayoutParams parms = new LinearLayout.LayoutParams(Globals.defaultLetterSizes[mBlockSize],
Globals.defaultLetterSizes[mBlockSize], 0);
parms.gravity = Gravity.CENTER_HORIZONTAL;
tmp.setLayoutParams(parms);
So, I set the weighting to zero and leave the rest to the OS. Works for all of them now. Marvelous.
Cheers
I'm trying to make a dynamic grid layout, it being API 10+ is the part that's been making it slow going. I tried to make it wrap automatically.. but in the end found it easier just to try to force it into a grid pattern using coordinates. This script was working by itself when I did the positioning at time of creation, but now I am trying to loop through each item as a sort. So if one item is deleted, they all float back into a grid without a hole in the middle.
Problem is, it seems the layout parameters are only applying to the last object.
Here's some base variables and onCreate setup:
int screenWidth;
int screenHeight;
int distStep = 130;
int leftPad = 20;
int numCols;
int baseID = 0;
android.util.DisplayMetrics metrics = this.getResources().getDisplayMetrics();
screenWidth = metrics.widthPixels;
screenHeight = metrics.heightPixels;
numCols = (int) (screenWidth - leftPad) / distStep;
int scrRemain = screenWidth - ((numCols * distStep) + leftPad);
distStep += (int) scrRemain / numCols;
Then on to the main function for adding:
public void addObjToLayout() {
RelativeLayout relLay = (RelativeLayout) this.findViewById(R.id.mainWindow);
for(int i = 1; i <= currQuantity; i++){
TextView tv=new TextView(this);
tv.setTextSize(40);
tv.setId(baseID + i);
tv.setPadding(24, 4, 24, 4);
tv.setBackgroundColor(0x110000FF);
tv.setText(String.valueOf(baseID + i)); //Val for debugging
tv.setTextColor(0xFFFFFFFF);
relLay.addView(tv);
}
baseID += currQuantity;
sortLayout();
}
Then the sorting:
public void sortLayout() {
int leftNum = 20;
int topNum = 0;
for(int i = 1; i <= baseID; i++){
TextView tv= (TextView) this.findViewById(baseID);
MarginLayoutParams mp = new MarginLayoutParams(tv.getLayoutParams());
mp.setMargins(leftNum, topNum, 0, 0);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(mp);
tv.setLayoutParams(lp);
leftNum += distStep;
if(leftNum >= distStep * numCols){
leftNum = leftPad;
topNum += distStep;
}
}
}
What I am getting is all the textViews pile up in the top left corner, except the last one which is positioned exactly where it should be. So it seems in my head, the params object isn't applying until the loop ends or something.. but logically I don't see why.
As I said, this worked when I set the params at the get go, problem is mass updating them all at once. I am pretty new to android, so I hope I'm not just doing something stupid.
Thanks for your time
Margin means it will set a gap between the previous view and current view.
When you add view1, view2 and view3 to grid layout and if you remove view2 at some point of time, then the margin for view3 is set according to view1. So, it won't leave empty space in place of view2. Instead of removing view2 at run time, set the background for view2 as null and set the text as empty as below.
textView.setBackground(null);
textView.setText("");
So that the view is still available but looks as deleted.
Started looking into GridView using an extended baseAdapter. Looks promising:
For more (see #2):
http://www.mkyong.com/android/android-gridview-example/