I'm creating an app, where after I've dragged'n'dropped a shadow the receiving fragment is supposed to create a RelativeLayout, at the absolute coordinates where the shadow was dropped.
My problem lies in the LayoutParams of the dynamically created RelativeLayout and it not responding as a subclass of MarginLayoutParams.
According to this: RelativeLayout.LayoutParams extends ViewGroup.MarginLayoutParams and should therefore be a subclass of the latter.
My Code:
RelativeLayout relLay = new RelativeLayout(getView().getContext());
if(relLay.getLayoutParams() instanceof ViewGroup.MarginLayoutParams){
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) relLay.getLayoutParams();
params.setMargins(Math.round(coordX), Math.round(coordY), 0, 0);
relLay.setLayoutParams(new ViewGroup.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,ActionBar.LayoutParams.WRAP_CONTENT));
RelativeLayout fragView = (RelativeLayout) getView().findViewById(R.id.RelativeStoryFaceFragment);
relLay.addView(iView);
fragView.addView(relLay);
}
The instanceof operator returns false, where I would expect it to return true. instanceof is not supposed to be there, but I'm using it to avoid the ugly crashes that will result until I've figured out why my code is wrong.
I can't set the margins unless I cast but the cast will crash if the instanceof returns false.
I've tried to focus on the problematic code, but will gladly include further context if necessary.
I'm probably missing something embarassing, any takers?
Related
I am not a native English user. Sorry for the bad grammar and spelling.
I have a Dialog that contains a View. The View is inflated by an XML file which contains a CustomView. The CustomView, which inherited from a LinearLayout, add views with addView() method.
My CustomView: https://gist.github.com/TomazWang/e1ddcc32fb2f733d874022ee9c4cffdd
Codes that generate the dialog: https://gist.github.com/TomazWang/61d9ad5df3c24bfaa06a78d8a1bcab13
The problems were:
When the layout_height of CustomView is set to match_parent, the child views in CustomView won't show.
After the layout_height of CustomView is set to wrap_content, the child views were shown, but the onMeasure() method takes too long.
I override the onMeasure() method. The method was not slowing my app. However, the child views are gone again.
Can't test it, but there seem to be a number of mistakes in your onMeasure() method. The first one is about missing parantheses.
int desiredHeight = mChildHeight * mEditors.size() - 1
+ (mViewOther == null ? 0 : mViewOther.getHeight())
+ (mViewTitle == null ? 0 : mViewTitle.getHeight());
should probably read
int desiredHeight = mChildHeight * (mEditors.size() - 1)
+ (mViewOther == null ? 0 : mViewOther.getHeight())
+ (mViewTitle == null ? 0 : mViewTitle.getHeight());
The second mistake is about your call to setMeasuredDimension(). It is not necessary since this is what the call to super.onMeasure(...) does. Go check the source.
The third mistake is about when to call super.onMeasure(...). You want to change how the superclass measures, so do not call super.onMeasure(...) at the beginning, but at the end of your custom onMeasure.
The fourth mistake is connected to the third; it is about the parameters you use when calling super.onMeasure(...). Do not use the regular LinearLayout parameters, but your computed values. In order to make a new MeasureSpec from your height variable, do:
int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, heightMode);
And then call super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);.
I guess that problem is that default orientation of LinearLayout is horizontal (doc), so if the first child's width of your custom view fits parent's width (match_parent) you will see only one item - first.
Try to modify init() method of your custom view with following changes:
private void init() {
this.mEditors = new HashMap<>();
mEditors.put("Other", new DataVO("other"));
setOrientation(VERTICAL);
}
I hope it will help.
I am adding views dynamically to a relative layout (let's say container) in a for loop. There is some thing strange I am noticing. When adding rows one below the other in a relative layout in a for loop, I see that the first time a few of the views are overlapping. But when I lock and unlock the screen, I can see that the views are placed correctly.
Should I be aware of something when adding views dynamically to a relative layout?
Edit
I have found a solution as to how to get rid of this (please check my answer). But I would be more than glad to accept an answer that analyses this problem and tells me why this happens.
I have simplified to code and the comments should give a good idea as to what I am doing.
int prev_id=ID_OF_THE_ELEMENT_ABOVE;
/*Empty RelativeView with width and height as MATCH_PARENT and WRAP_CONTENT respectively*/
RelativeLayout container=(RelativeLayout) findViewById(R.id.container);
while(ThereIsData){
/*GET THE DATA HERE THAT HAS TO BE ASSIGNED TO EACH TEXTVIEW*/
...
/* ADD TEXTVIEW #1 below prev_id/
...
...
/*ADD TEXTVIEW #2 (WITH BASELINE OF TEXTVIEW#
...
...
/*TEXTVIEW #3 (BELOW TEXTVIEW#1)*/
...
...
/*TEXTVIEW #4 (BELOW TEXTVIEW#2)*/
...
...
/*ASSIGN THE ID OF TEXTVIEW#3 TO prev_id SO THAT
IN THE NEXT ITERATION TEXTVIEW#1 CAN USE prev_id
*/
prev_id=ID(TEXTVIEW#2);
/*ADD TEXTVIEWS CREATED IN THIS ITERATION*/
container.addView(TEXTVIEW#1);
container.addView(TEXTVIEW#2);
container.addView(TEXTVIEW#3);
container.addView(TEXTVIEW#4);
}
It is due to the fact that you are having a RelativeLayout with height as WRAP_CONTENT, and adding a view doesn't refresh the whole container at that time.. so as you answered you can add a line to measure the dimensions explicitly or invalidate the view to recreate it completely.
In any case LinearLayout would be better to opt-for as it will automatically arrange the children in horizontal or vertical manner and you can even add the new view in any place other than last position and it will automatically be updated..
I used to struggle against common issues a year ago, when I was working on a library for dynamically creating layouts from XML files (as Android does not support this). So when you dynamically add views to a RelativeLayout you have to take in mind a few things:
Create the container View (in this case the RelativeLayout)
Create all views without assigning any layout parameters.
Add all child views to the container.
Iterate over the container's children and populate each child's layout parameters. This is needed because when the relational constraints are applied an Excpetion is thrown if the relative View is missing (was not previously added to the container).
This is an example code taken from the project I used to work on. Take in mind that it is just a single part so it contains references to classes that are not defined in the Android API. I am sure it will give you the basic idea of dynamically creating RelativeLayot:
private void setChildren(RelativeLayout layout, T widget,
InflaterContext inflaterContext, Context context,
Factory<Widget, View> factory) {
List<Widget> children = widget.getChildren();
if (Utils.isEmpty(children))) {
return;
}
// 1. create all children
for (Widget child : children) {
View view = factory.create(inflaterContext, context, child);
layout.addView(view);
}
// 2. Set layout parameters. This is done all children are created
// because there are relations between children.
for (Widget child : children) {
try {
View view = ViewIdManager.getInstance().findViewByName(layout, child.getId());
if (view != null) {
populateLayoutParmas(child, view);
}
} catch (IndexNotFoundException e) {
Log.e(LOG_TAG, "Cannot find a related view for " + child.getId(), e);
}
}
}
I have not yet found the answer to why this is happening. But I have found a solution. After adding each row in the loop, call container.measure(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
This seems to solve the problem. But I really think that container.addView() should also be calling measure().
/*ADD TEXTVIEWS CREATED IN THIS ITERATION*/
container.addView(TEXTVIEW#1);
container.addView(TEXTVIEW#2);
container.addView(TEXTVIEW#3);
container.addView(TEXTVIEW#4);
//---------------------------------------------------------------------
container.measure(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
//Declare globally
LinearLayout[] layout;
ImageView[] imageView1;
ImageView[] imageView2;
ImageView[] imageView3;
// Initialize your layout. It would be RelativeLayout too. Just reference to it.
LinearLayout ll = (LinearLayout) findViewById(R.id.mylinear);
// set listview row size as your demand
layout = new LinearLayout[200];
imageView1 = new ImageView[200];
imageView2 = new ImageView[200];
imageView3 = new ImageView[200];
for (int i = 0; i < 200; i++) {
layout[i] = new LinearLayout(this);
layout[i].setBackgroundResource(R.drawable.book_shelf);
// layout[i].setLayoutParams(new
// LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.FILL_PARENT,
// 120));
layout[i].setLayoutParams(new LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.FILL_PARENT, 220));
imageView1[i] = new ImageView(this);
imageView2[i] = new ImageView(this);
imageView3[i] = new ImageView(this);
imageView1[i].setLayoutParams(new LinearLayout.LayoutParams(0, 200,
0.33f));
imageView1[i].setPadding(0, 20, 0, 0);
imageView1[i].setImageResource(R.drawable.bibid_one_bankim);
imageView2[i].setLayoutParams(new LinearLayout.LayoutParams(0, 200,
0.33f));
imageView2[i].setPadding(0, 20, 0, 0);
imageView2[i].setImageResource(R.drawable.bibid_two_bankim);
imageView3[i].setLayoutParams(new LinearLayout.LayoutParams(0, 200,
0.33f));
imageView3[i].setImageResource(R.drawable.dena_pawna);
imageView3[i].setPadding(0, 20, 0, 0);
layout[i].setId(i);
layout[i].setClickable(true);
final int j = i;
layout[i].addView(imageView1[i]);
layout[i].addView(imageView2[i]);
layout[i].addView(imageView3[i]);
ll.addView(layout[i]);
}
}
Try adding your views in vertical Linear Layout.
Following link might help you
http://www.myandroidsolutions.com/2012/06/19/android-layoutinflater-turorial/
Inflate your layout in for loop.
I would like to have a ListView in which some items render on the left, and some on the right. I don't really know how to make this happen though. I was thinking of calling setGravity(Gravity.RIGHT) on the View my adapter's getView() method returns, but that method apparently exists only for ViewGroup, which makes me think it would actually change the gravity of the object's contents. It would look something like this:
getView(int position, View toReturn, ViewGroup parent) {
// Holder pattern, *yawn*
if (needsToBeOnRight) {
toReturn.setGravity(Gravity.RIGHT)
// or whatever it is I'm actually supposed to do
}
return toReturn;
}
The View represented by toReturn is expected to be a RelativeLayout, so I supppose in theory I could cast it to one and try the above, but as discussed above, I doubt that will work. How should I proceed?
Turns out I was almost there. In order to make it work, I had to wrap the view I want to right-or-left-orient in a FrameLayout. That would make toReturn in the above code a FrameLayout.
ViewHolder holder = (ViewHolder) toReturn.getTag();
// Get the view's LayoutParams. In this case, since it is wrapped by a FrameLayout,
// that is the type of LayoutParams necessary.
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) holder.viewThatMightNeedToBeOnRight.getLayoutParams();
// Set gravity to right or left as necessary within the LayoutParams.
if (params != null) {
if (needsToBeOnRight) {
params.gravity = Gravity.RIGHT;
} else {
params.gravity = Gravity.LEFT;
}
// Assign the newly edited LayoutParams to the view.
holder.viewThatMightNeedToBeOnRight.setLayoutParams(params);
}
LayoutParams lay = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
lay.gravity = Gravity.END;
mListView.setLayoutParams(lay);
I have a little issue on what sequence things are being called when adding stuff to a RelativeLayout. I have a class extending Activity (name it RelActivity) where I want to create a RelativeLayout and put several custom Views (name it cusView) into that RelativeLayout. The topMargin and leftMargin of a custom View is calculated by using the position of another custom View (i.e. the first custom View has to be positioned directly by setting a number to topMargin and leftMargin). Please note that the Rules of RelativeLayout is not sufficient in this case.
So, over to the problem. In my RelActivity I do this:
Create a RelativeLayout (name it relLayout)
Iterate a cursor with cusViews recieved from a database
For the first cusView -> Set position by topMargin and leftMargin using a LayoutParameter
For the other cusViews -> calculate their topMargin and leftMargin by using one of the other cusViews and a LayoutParameter
Set RelActivity's contentView to relLayout
What happens is that all cusViews but the first one are squeezed in the top left corner because both leftMargin and topMargin are always calculated to be zero. This happens because I use the width of the cusViews to calculate the topMargin and leftMargin, and the width of the cusView has not given a value yet.
Is the width first calculated in the cusView's overrided method onSizeChanged()? Is the onSizeChanged() method get called first when the layout is presented on the screen? If so, how do I work around this issue? Do I have to calculate the positionings after onSizeChanged() is done?
Edit: Here is a minimum working example:
Here is my onCreate in RelActivity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
relLayout = new RelativeLayout(this);
cusViews = new ArrayList<CusView>();
listParams = new ArrayList<RelativeLayout.LayoutParams>();
readDBandSetLayout();
setContentView(relLayout);
}
There is too much information in the readDBandSetLayout() method to present it all here. below are the most important details. If I create the LayoutParams in the following way it works fine, the cusViews are listed downwards and rightwards of eachother:
queryCursor = customApplication.customData.query( number); //Fetches cursor
for ( int i = 0; i < numberOfRows; i++ ){
if ( i == 0 ){
LayoutParams p = new LayoutParams(this.getResources().getDimensionPixelSize(R.dimen.small), this.getResources().getDimensionPixelSize(R.dimen.small));
p.topMargin = 50;
p.leftMargin = 50;
listParams.add(p);
}
else{
LayoutParams p = new LayoutParams(this.getResources().getDimensionPixelSize(R.dimen.large),this.getResources().getDimensionPixelSize(R.dimen.large));
p.addRule(RelativeLayout.BELOW, cusViews.get(i-1).getId());
p.addRule(RelativeLayout.RIGHT_OF, cusViews.get(i-1).getId());
listParams.add(p);
}
relLayout.addView(cusViews.get(i), listParams.get(i));
}
However, what I want to do in the else statement is something like:
else{
LayoutParams p = new LayoutParams(this.getResources().getDimensionPixelSize(R.dimen.large),this.getResources().getDimensionPixelSize(R.dimen.large));
//Here I want to calculate cusView2Topmargin and cusView2Leftmargin based on the widths of the first or previosly positioned cusViews. But here the widths are 0 since they haven't been calculated yet.
p.topMargin = cusView2Topmargin; //Always zero
p.leftMargin = cusView2Leftmargin; //Always zero
listParams.add(p);
}
So the problem lies in that the widths of the cusViews are zero at the point I need them to calculate the layout parameters topMargin and leftMargin.
Unfortunately I cannot use the RelativeLayout's Rules for what I want to achieve. If there were some way to create rules like RelativeLayout.RIGHT_OF and RelativeLayout.BELOW I could do it like that. Is this possible?
Its not very clear what your goal is for this layout. It might well be possible to use a simple LinearLayout to get what you want.
If you want to size these from a database lookup then try simply adding each of the views, using addView() first, storing a reference to each, then go back and sett the margins to place them in the proper positions.
Working with the XML file was easy as I could specify the parameters as
<android:layout_width="fill_parent" android:layout_height="wrap_content">
But I am confused while specifying it through code. For each view I specify the parameters using
view.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT));
I see that I have an option of specifying it as relative layout, frame layout etc.
As of now I am using linear layout for all views such as images, text and also gridview. Should the view parameters be defined based on the layout of the parent element? Or is it OK to specify it as linear layout even if the view is a child of, say, a framelayout? Sorry, but I couldn't find out the difference.
All layout classes (LinearLayout, RelativeLayout, etc.) extend ViewGroup.
The ViewGroup class has two static inner classes: LayoutParams and MarginLayoutParams. And ViewGroup.MarginLayoutParams actually extends ViewGroup.LayoutParams.
Sometimes layout classes need extra layout information to be associated with child view. For this they define their internal static LayoutParams class. For example, LinearLayout has:
public class LinearLayout extends ViewGroup {
...
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
...
}
}
Same thing for RelativeLayout:
public class RelativeLayout extends ViewGroup {
...
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
...
}
}
But LinearLayout.LayoutParams and RelativeLayout.LayoutParams are completely different independent classes. They store different additional information about child views.
For example, LinearLayout.LayoutParams can associate weight value with each view, while RelativeLayout.LayoutParams can't. Same thing with RelativeLayout.LayoutParams: it can associate values like above, below, alightWithParent with each view. And LinearLayout.LayoutParams simply don't have these capability.
So in general, you have to use LayoutParams from enclosing layout to make your view correctly positioned and rendered. But note that all LayoutParams have same parent class ViewGroup.LayoutParams. And if you only use functionality that is defined in that class (like in your case WRAP_CONTENT and FILL_PARENT) you can get working code, even though wrong LayoutParams class was used to specify layout params.
Depending on how many views you want to change the layouts on, I think it's better to create a helper method and pass whatever views you want to change to the method along with the height and width values you want them to change to:
public void setWidthHeight(View v, int width, int height){
LayoutParams lp;
lp = v.getLayoutParams();
lp.width = width;
lp.height = height;
v.setLayoutParams(lp);
}
Remember that setting the width and height here are not going to match the same values in your xml, i.e., android:layout_width="32dp" is not the same as lp.width = 32;
Also, the LayoutParams type variable called lp should be of the type returned by your view... Check what type is returned by the view and import that type in your import statements.