I am adding a View to a RelativeLayout at runtime, which is working fine, but now I would like to display a TextView just to left of this View but I am unable to retrieve the Top or Left of this View in order to calculate the TextView's position.
The code I have is
private void displayGuitar() {
for (GuitarString guitarString: guitar.getGuitarStrings()) {
GuitarStringView guitarStringView = new GuitarStringView(context, attributeSet, guitarString);
this.addView(guitarStringView);
// Add Note name
TextView noteNameView = new TextView(context);
RelativeLayout.LayoutParams noteParams =
new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
noteParams.addRule(LEFT_OF, guitarStringView.getId());
Log.w("string top", String.valueOf(guitarStringView.getTop()));
Log.w("string left", String.valueOf(guitarStringView.getLeft()));
noteParams.setMargins(0, 0, 2, 0);
noteNameView.setLayoutParams(noteParams);
noteNameView.setText(guitarString.getTunedNote().getNoteName(0));
this.addView(noteNameView);
}
}
This results in the TextViews all appearing on top of each other (in the correct X position) but I can't set the Top margin as both of the Log statements return 0. How can I determine what the Top position of the GuitarStringView is?
EDIT
Following advice from laalto I have now generated ID for the guitarStringView and confirmed it's being assigned. However, if I use LEFT_OF, RIGHT_OF etc the TextViews are not displayed, but if I use ALIGN_PARENT_RIGHT they're displayed fine? Code is now as follows
private void displayGuitar() {
for (GuitarString guitarString: guitar.getGuitarStrings()) {
GuitarStringView guitarStringView = new GuitarStringView(context, attributeSet, guitarString);
int viewId = guitarStringView.generateViewId();
guitarStringView.setId(viewId);
this.addView(guitarStringView);
// Add Note name
TextView noteNameView = new TextView(context);
RelativeLayout.LayoutParams noteParams =
new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
noteParams.addRule(RelativeLayout.LEFT_OF, viewId);
noteNameView.setLayoutParams(noteParams);
noteNameView.setText(guitarString.getTunedNote().getNoteName(0));
this.addView(noteNameView);
}
}
EDIT 2
The accepted fix didn't work at first as I hadn't override the onMeasure() of my GuitarStringView which according to the documentation defaults to 100x100.
First, your guitarStringView doesn't have an id and getId() will return -1, that is, NO_ID. The LEFT_OF rule therefore doesn't work as it doesn't refer to any actual id. To fix that, just set some id to the view.
Second, the view has not been measured nor laid out yet so that's why size and position come out as zeros.
using addRule with any of LEFT_OF, RIGHT_OF or BELOW the TextView is no longer displayed at all
Possibly the string view is already taking up all horizontal space. Add another constraint such as ALIGN_PARENT_LEFT to make the text view left edge align with parent relative layout's left edge.
You said "at runtime", which means you're potentially invoking this method in onCreate, or onResume. These methods should start behaving as expected in onWindowFocusChanged(boolean).
See this other question:
getRight, getLeft, getTop returning zero
Related
I've been trying to write some dynamically generated layout code for a simple app I came up with. I want to display a vertical row of cards, each on containing an undefined number of vertically aligned text boxes.
I wrote the code to generate these and populate the text, but it doesn't appear to be working and I can't figure it out for the life of me.
I'm new to Android Studio, and Java is still relatively fresh to me as well, so I could well be missing something quite obvious here.
I've tried using a few different types of View in A. Studio, and so far most work by themselves, but none can be contained within a card which would be ideal for me. Dynamically creating and editing properties of textViews works fine, but once I include the card view they no longer appear using the exact same code.
//Define Params
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(left,top,right,bottom);
//Add a card for each ingredient
for (Ingredient ing : ingredients)
{
CardView card = new CardView(this);
CardView.LayoutParams cardParams = new CardView.LayoutParams(CardView.LayoutParams.WRAP_CONTENT, 200);
card.setLayoutParams(cardParams);
card.setRadius(15);
card.setPadding(25,25,25,25);
card.setElevation(10);
card.setMaxCardElevation(30);
card.setBackgroundColor(Color.DKGRAY);
//Make a grid for each card, text on the left, image on the right
LinearLayout linearLayoutInCard = new LinearLayout(card.getContext());
LinearLayout.LayoutParams layoutParamsInCard = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
linearLayoutInCard.setLayoutParams(layoutParamsInCard);
card.addView(linearLayoutInCard);
for(int x = 0; x < 3; x++)
{
TextView textView = new TextView(this);
textView.setLayoutParams(params);
textView.setPadding(left, top, right, bottom);
textView.setTextSize(15);
textView.setElevation(11);
textView.setTextColor(Color.WHITE);
linearLayoutInCard.addView(textView);
switch (x)
{
case 0:
textView.setText(ing.name);
break;
case 1:
textView.setText(ing.price);
break;
case 2:
//textView.setText(ing.calories);
break;
}
}
I'm expecting a vertical row of cards with text boxes vertically aligned withing them, each with their own content (this whole script will only make one card for now, but that's a data driven thing) yet when I run the application, I get nothing but a blank screen.
Before venturing much further...this could be an example of the XY Problem.
Maybe have a look at the RecyclerView option?
RecyclerViews are entirely designed to manage the UI look and responsiveness of changing/scrolling data sets.
They can be a bit "what the heck" at first...but once writing them a few times, your UI look and code base is a lot more efficient and clean.
RecyclerView example Youtube
So I'm trying to add a RelativeLayout to a RelativeLayout however, when I run my app, it's an IllegalStateException that shows this error: The specified child already has a parent. You must call removeView() on the child's parent first. I'm sure you guys have seen this before. My question is how do I properly nest two relative layouts together?
Here is the code snippet that produces the exception:
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
RelativeLayout newFrame = new RelativeLayout(vidRipperService.getBaseContext());
newFrame.setLayoutParams(layoutParams);
// configure image view constraints...
// have the frame be right in the center of the layout.
ImageView editedFrame = new ImageView(vidRipperService.getBaseContext());
// Note: when doing padding the height and the width must be a multiple of two. A nice example is 70+30 = 100/2 = 50, but 80+30 = 110/2 = 55 <- not a multiple of two. Keep this in mind.
editedFrame.setId(View.generateViewId());
editedFrame.setPadding(30,30,30,0); // padding of 30 around the whole view.
editedFrame.setImageBitmap(frame); // set the frame to be that of the actual background.
RelativeLayout.LayoutParams frameLayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
frameLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL); // place frames in center of the view.
editedFrame.setLayoutParams(frameLayoutParams);
newFrame.addView(breakingNewsLayout); // add the breaking news layout to this view!
newFrame.addView(editedFrame);
The problem is specifically triggered from the second to last line newFrame.addView(breakingNewsLayout) That line triggers the exception. The breakingNewsLayout is another relative layout that I would like to add to newFrame. I would appreciate any knowledge on how to get this to work. I never ran into a problem when nesting layouts before, but for some reason, this is really not playing nice.
Here is the code that creates the breakingNewsLayout:
private void createBreakingNewsLayout()
{
breakingNewsLayout = new RelativeLayout(vidRipperService.getBaseContext()); // create the new breaking new layout.
breakingNewsLayout.setElevation(5);
breakingNewsLayout.setPadding(0,0,0,15);
breakingNewsLayout.setBackgroundColor(ContextCompat.getColor(vidRipperService, R.color.transparent)); // ensure that the background is transparent.
// MATCH_PARENT for both width and height so that banner is shown on the frame for the video.
RelativeLayout.LayoutParams breakingNewsParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
breakingNewsParams.setMargins(0,50,0,0); // todo: ensure that the margin is 50dp not pixels!
breakingNewsParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
breakingNewsLayout.setLayoutParams(breakingNewsParams); // set the layout params for the breaking news layout.
// set all of the text view attributes.
TextView liveBannerText = getLiveBannerText();
TextView breakingNewsTime = getBreakingNewsTime();
TextView breakingNewsHeadline = getBreakingNewsHeadline(breakingNewsTime.getId()); // headline goes about the breaking news time.
TextView breakingNewsBanner = getBreakingNewsBanner(breakingNewsHeadline.getId()); // banner goes above the breaking news headline
TextView viddyWatermarkText = getViddyWatermarkText(breakingNewsHeadline.getId()); // viddy watermark goes above the breaking news headline.
TextView breakingNewsDescription = getBreakingNewsDescription(breakingNewsTime.getId()); // breaking news description goes to the end of the breaking news time
// Add all of the views for the breaking news layout.
breakingNewsLayout.addView(liveBannerText);
breakingNewsLayout.addView(breakingNewsBanner);
breakingNewsLayout.addView(viddyWatermarkText);
breakingNewsLayout.addView(breakingNewsHeadline);
breakingNewsLayout.addView(breakingNewsTime);
breakingNewsLayout.addView(breakingNewsDescription);
}
maybe you should remove breakingNewsLayout before you add it:
((ViewGroup) breakingNewsLayout.getParent()).removeView(breakingNewsLayout);
I figured it out! After extensive researching and testing the reason I was getting this issue was because I was attempting to reuse the breakingNewsLayout when creating a new frame. I have x amount of frames and I needed to generate the breakingNewsLayout every single time I wanted to apply the layout to the frame. Since I was trying to reuse the layout that I have already added, the layout already has a parent and thus the exception above was called.
Apologies for the question and any lack of details.
I am developing an app where the user has to match the image and corresponding name of it correctly.
My problem is when the user selects the image first and selects the wrong name it will display wrong answer and if he selects the answer it will be displayed correct answer.
The user should not have to re-select the image again
I have made the onClickListerner's null but it wont work some of my code is as follows,
txt_tag[0] = (TextView) findViewById(R.id.txt_tag1);
txt_tag[0].setOnClickListener(this);
txt_tag[0].setTypeface(tf);
txt_tag[1] = (TextView) findViewById(R.id.txt_tag2);
txt_tag[1].setOnClickListener(this);
txt_tag[1].setTypeface(tf);
txt_tag[2] = (TextView) findViewById(R.id.txt_tag3);
txt_tag[2].setOnClickListener(this);
txt_tag[2].setTypeface(tf);
txt_tag[3] = (TextView) findViewById(R.id.txt_tag4);
txt_tag[3].setOnClickListener(this);
txt_tag[3].setTypeface(tf);
img[0] = (ImageButton) findViewById(R.id.img1);
img[0].setOnClickListener(this);
img[1] = (ImageButton) findViewById(R.id.img2);
img[1].setOnClickListener(this);
img[2] = (ImageButton) findViewById(R.id.img3);
img[2].setOnClickListener(this);
img[3] = (ImageButton) findViewById(R.id.img4);
img[3].setOnClickListener(this);
btn_nxt = (Button) findViewById(R.id.btn_next);
btn_nxt.setOnClickListener(this);
and I have called an method inside that method where I have made all onClickListerner's null
txt_tag[0].setOnClickListener(null);
txt_tag[1].setOnClickListener(null);
txt_tag[2].setOnClickListener(null);
txt_tag[3].setOnClickListener(null);
img[0].setOnClickListener(null);
img[1].setOnClickListener(null);
img[2].setOnClickListener(null);
img[3].setOnClickListener(null);
Can anyone tell me where I am going wrong or any modifications I can do to it.
Thanks in advance
Try using
txt_tag[0].setClickable(false);
txt_tag[1].setClickable(false);
..
img[0].setClickable(false);
img[1].setClickable(false);
..
Your question is not that clear.. but if you want your image and text tag not clickable.. make them android:clickable="false" in xml or setClickable(false);
If I were you I would be checking that logic in a listener. So if the quiestion (if it's a quiz) is in the state "ANSWERED", don't react to event.
Your question is unclear, but I understand it as follows:
You have a bunch of ImageViews and a bunch of TextViews and a Mapping between them.
You want to be able to first select an ImageView, then a TextView. If they match, "correct answer" will be displayed somewhere, if not, "wrong answer" will be displayed
If you click on a TextView before an ImageView is selected, nothing happens
If you click on a Textview and another TextView is already selected, nothing happens
If that is correct, you can do this like this: You keep two variables
int selectedImage = -1;
int selectedText = -1;
In your OnClickListener you update their values like this:
if (source instanceof ImageViews) {
selectedImage = getArrayIndex(source); // I guess you already have a method to retrieve the index
selectedText = -1; // reset textSelection
} else {
if (selectedText < 0) {
selectedText = getArrayIndex(source);
}
}
updateAnswerTextView(); // here you check if the two selections (selectedText and selectedImage) match and display the corresponding string.
Instead, you could just iterate through the TextView array and call
setClickable(false);
on every element as soon as one is clicked. If a new image is selected, you will have to set them to clickable again.
EDIT: And I agree with Rob, you should not remove your Listeners to achieve this behaviour.
Here's my suggestion, if you want you code up specific behaviour you can use the onClickListener callback to achieve what you want.
In the listener, check the state of the image; if it is already selected and you want to ignore the event then you just exit from your callback.
I think setting the onClickListener to null is the wrong thing to do.
I am doing an pageindex, and the current page should be highlighted with a arrow image (imageview).
The index is a RelativeLayout with 25 textviews added to it:
for (int i = 0; i < 25; i++) {
TextView tv = new TextView(this);
tv.setText(Integer.toString(i+1));
tv.setGravity(Gravity.CENTER);
int id = 2000+i;
tv.setId(id);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.FILL_PARENT);
if(i==0)
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
else
params.addRule(RelativeLayout.RIGHT_OF, prevViewId);
prevViewId = id;
rl.addView(tv,params);
}
And when the page changes I do something like this:
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_BOTTOM, 2000+i);
params.addRule(RelativeLayout.ALIGN_RIGHT, 2000+i);
params.addRule(RelativeLayout.ALIGN_LEFT, 2000+i);
arrowImageView.setLayoutParams(params);
rl.requestLayout();
rl.invalidate();
Everything looks correct, I can place the arrow at arbitrary page at "start up", but the arrow wont move when page is changed. I have debuged and verified that the code is run and everything looks correct, but still the arrow is stuck in the first position. If I force delete and add a new imageview instead of updating the LayoutParams the arrow will disappear totally.
I have the same problem.. I also want to move my views around at run-time inside a reference layout. So if anyone ca help that would be awesome.
What i believe is happening above is that the arrow IS having its location changed, however it doesn't get updated to the screen. Correct me if I'm wrong, please, this is just my guess as I am too having the same problem.
-edit-
After some messing around I've found what works for me is to remove first then add.
rl.removeView(tv);
rl.addView(tv,params);
-edit-
also, u can save the params for the moving view into a unique variable so that way all ude have to do is change the margins...
for example: instead of params, have it be its own name "arrowParams"
then to move it ude just need: arrowParams.leftMargin = 2000+i; and so on...
Basically, I have a LinearLayout that holds a random amount of horizontal LinearLayouts, and in each of the horizontal LinearLayouts there's a TextView and an EditText. I want to be able to get the value of each EditText children of the master LinearLayout.
Sorry if it's confusing, I'm no good at explaining things!
Could I just set the same ID for each of the EditTexts then use findViewById, or would that only return the first instance of an EditText?
Thanks,
Alex.
LinearLayout ll = //Your Layout this can be any Linear or Relative layout
//in which you added your spinners at runtime ;
int count = ll.getChildCount();
for(int i =0;i<count;i++)
{
View v = ll.getChildAt(i);
if(v instanceof Spinner)
{
// you got the spinner
EditText s = (EditText) v;
Log.i("Item selected",s.getText().toString());
}
}
findViewById returns only the first view with the given id. You're going to have to traverse the view hierarchy yourself, at least until you get down to each horizontal linear layout. You'll find the methods ViewGroup.getChildCount() and ViewGroup.getChildAt(int) useful for this.
You would need to call findViewById on each of the LinearLayouts. If you do this, you can set the same ID for each EditText.