How to add subView to ViewGroup using RelativeLayout.LayoutParams - android

I wrote a class that extends ViewGroup and override the method as follow:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
for(int index = 0; index < getChildCount(); index++){
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
View child = getChildAt(index);
child.measure(View.MeasureSpec.makeMeasureSpec(width,
View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
.makeMeasureSpec(height, View.MeasureSpec.UNSPECIFIED));
}
}
When I add subView to this viewGroup as follow:
String word = words[random.nextInt(words.length)];
RelativeLayout layout = new RelativeLayout(DraggableGridViewSampleActivity.this);
TextView text = new TextView(DraggableGridViewSampleActivity.this);
text.setText(word);
ImageView view = new ImageView(DraggableGridViewSampleActivity.this);
view.setImageBitmap(getThumb(word));
layout.addView(view);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
layout.addView(text, params);
dgv.addView(layout);
The effect is like: The textView can't be displayed completely. It seems that the parent view doesn't provide enough space for the textview to display.
How to have the textView displayed completely?

The textView can't be displayed completely. It seems that the parent
view doesn't provide enough space for the textview to display.
What exactly are you trying to do with the onMeasure method? Right now you're letting the super class handle an initial measure of the children(super.onMeasure) and then you remeasure the children again, this time giving them a MeasureSpec.UNSPECIFIED, basically telling the children they could be as big as they want.
You should always respect the MeasureSpec when you're measuring the children(or the parent should allow scrolling). If the parent says that the children should be AT_MOST 500 px then don't potentially step over this value when measuring the children by giving them a MeasureSpec of UNSPECIFIED.

Related

Android: get parent layout width in custom view to set child width

I have made class called ProgressButton that extended RelativeLayout.Now in main xml i added this class:
<com.tazik.progressbutton.ProgressButton
android:id="#+id/pb_button"
android:layout_width="200dp"
android:layout_height="wrap_content"/>
As you can see i added android:layout_width="200dp", now in ProgressButton class i want to get this size to create a button with this size:
public class ProgressButton extends RelativeLayout {
private AppCompatButton button;
public ProgressButton(Context context) {
super(context);
initView();
}
private void initView() {
initButton();
}
private void initButton() {
button = new AppCompatButton(getContext());
LayoutParams button_params = new LayoutParams(????, ViewGroup.LayoutParams.WRAP_CONTENT);
button_params.addRule(RelativeLayout.CENTER_IN_PARENT,RelativeLayout.TRUE);
button.setLayoutParams(button_params);
button.setText("click");
addView(button);
}
I want to create button exactly to size of relativeLayout, so how can i get layout_width in my custom view to set button_params width?
now in ProgressButton class i want to get this size to create a button with this size
As #MikeM. suggested in a comment. It could be as easy as giving that child view a width of MATCH_PARENT. See below...
LayoutParams button_params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
With that in place you don't need to worry about the actual size because MATCH_PARENT will stretch your child view to occupy the whole parent's width...obviosuly respecting margins and paddings.
However, if you do need to know the parent's width, you should query that in onMeasure. I strongly suggest you to stay away from onMeasure whenever possible because it is a bit complex and it might take a lot of your development time.
Either way, in onMeasure you can know what measurements the parent view wants to give to its child views, this is based on the space available to render inside the parent and the layout params specified...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int childWidth = 0;
if(widthSpecMode == MeasureSpec.AT_MOST){
//The parent doesn't want the child to exceed "childWidth", it doesn't care if it smaller than that, just not bigger/wider
childWidth = MeasureSpec.getSize(widthMeasureSpec);
}
else if(widthSpecMode == MeasureSpec.EXACTLY){
//The parent wants the child to be exactly "childWidth"
childWidth = MeasureSpec.getSize(widthMeasureSpec);
}
else {
//The parent doesn't know yet what its children's width will be, probably
//because it's still taking measurements
}
//IMPORTANT!!! set your desired measurements (width and height) or call the base class's onMeasure method. Do one or the other, NOT BOTH
setMeasuredDimension(dimens, dimens);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Add a few Log.d calls inside onMeasure for a better understanding of what's happening. Be aware that this method will be called multiple times.
Again, this is an unnecessary overkill for your case scenario. Setting MATCH_PARENT to the button should produce the results you want

child views losing layout_margin

I'm adding a LinearLayout as a child of a custom layout. The LinearLayout has margins, but the margins never get into the LayoutParams.
Here's the xml for the LinearLayout
<com.perinote.widgets.LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="#dimen/popup_margin_LR"
android:layout_marginRight="#dimen/popup_margin_LR"
android:layout_marginTop="#dimen/popup_margin_TB"
android:layout_marginBottom="#dimen/popup_margin_TB"
>
Here's the code that inflates and adds the LinearLayout to the custom layout, where "this" is the custom layout, which is an extension of ViewGroup:
public View setContentView (int layoutId)
{
LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater();
ViewGroup v = (ViewGroup) inflater.inflate (layoutId, this, false);
contentView = v;
ViewGroup.LayoutParams params = v.getLayoutParams ();
this.addView (contentView, params);
return contentView;
}
And, finally, here's a piece of onMeasure in the custom layout.
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) contentView.getLayoutParams();
int contentWidth = MeasureSpec.getSize (widthMeasureSpec);
int contentHeight = MeasureSpec.getSize (heightMeasureSpec);
... some other stuff ...
// measure contentView.
int contentWidthSpec = MeasureSpec.makeMeasureSpec (contentWidth, MeasureSpec.AT_MOST);
int contentHeightSpec = MeasureSpec.makeMeasureSpec (contentHeight, MeasureSpec.AT_MOST);
contentView.measure (contentWidthSpec, contentHeightSpec);
setMeasuredDimension (MeasureSpec.getSize (widthMeasureSpec), MeasureSpec.getSize (heightMeasureSpec));
}
The app crashes on the first line of onMeasure() -- params =. The error is:
java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
which indicates that, during inflation, the inflator created LayoutParams instead of MarginLayoutParams.
What can I do to get MarginLayoutParms or some other variant that includes the layout_margin values?
The class extending ViewGroup must also override generateLayoutParams(). In my case:
#Override
public LayoutParams generateLayoutParams (AttributeSet attrs)
{
return new ViewGroup.MarginLayoutParams (getContext(), attrs);
}
but you can extend LayoutParams to contain whatever you need instead of using MarginLayoutParams. During inflation of a child view, generateLayoutParams() gets called. This (aha) is why inflate needs to know the parent view group, even if you aren't adding the new child to the parent at this time.
Credit really goes to #Mike M. Thanks!

Views will not align or center vertically

I have a class to which I need to add one or more Views. In this example, a single ImageView.
I can add views without a problem and align them using LayoutParameters, but when I try to align or center them somewhere along the vertical axis, they either stick to the top or don't appear at all (they are likely just out of view).
In the constructor I call a method fillView(), which happens after all dimensions and such are set.
fillView()
public void fillView(){
img = new ImageView(context);
rl = new RelativeLayout(context);
img.setImageResource(R.drawable.device_access_not_secure);
rl.addView(img, setCenter());
this.addView(rl, matchParent());
}
matchParent()
public LayoutParams matchParent(){
lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
lp.setMargins(0, 0, 0, 0);
return lp;
}
setCenter()
public LayoutParams setCenter(){
lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); //This puts the view horizontally at the center, but vertically at the top
return lp;
}
Similarly, adding rules such as ALIGN_RIGHT or BELOW will work fine, but ALIGN_BOTTOM or CENTER_VERTICALLY will not.
I tried using both this method and the setGravity() a LinearLayout offers, with the same results.
You're adding your ImageView before you've added the RelativeLayout
While I still don't know why my method worked horizontally, but not vertically, I did solve the problem. The posted methods worked, the problem was hidden in onMeasure().
I previously set the dimensions by simply passing them to setMeasuredDimension(). I fixed the issue by also passing them to the layoutParams(). I also changed the integers I used to MeasureSpecs while I was at it.
I changed this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(this.getT_Width(), this.getT_Heigth());
this.setMeasuredDimension(desiredHSpec, desiredWSpec);
}
to this:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
final int desiredHSpec = MeasureSpec.makeMeasureSpec(this.getT_heigth(), MeasureSpec.EXACTLY);
final int desiredWSpec = MeasureSpec.makeMeasureSpec(this.getT_width(), MeasureSpec.EXACTLY);
this.getLayoutParams().height = this.getT_heigth();
this.getLayoutParams().width = this.getT_width();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(desiredWSpec);
int height = MeasureSpec.getSize(desiredHSpec);
setMeasuredDimension(width, height);
}
getT_Width() and getT_Heigth() are methods I used to get some custom dimensions I set elsewhere.
I hope this helps somebody.

Can onMeasure be skipped when adding a View to a ViewGroup?

Answered
I have a RelativeLayout where I am adding views dynamically as the user scrolls vertically or horizontally. I have rolled my own ViewRecycler since there is potentially thousands of views that could compose the whole of what can be scrolled, but I only show 30 or so at any time. Think a zoomed in view of a calendar.
I am running into performance problems when I add the views that are about to be seen, onMeasure is called on the RelativeLayout cascading down to onMeasure getting called on all of it's child views. I already have the calculated size of how big the RelativeLayout will ever be and have set that on it's LayoutParameters, so measuring the ViewGroup isn't necessary, nor is re-measuring the Views that have already been added with their final size and the newly added view has no bearing on those view.
The simple example to demonstrate the problem is adding/removing a View to a RelativeLayout and watching the onMeasure get called despite the fact that it doesn't affect the RelativeLayout's size or the position of other Views.
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/shell"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
MyActivity.java
public class MyActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ViewGroup shell = (ViewGroup) findViewById(R.id.shell);
final RelativeLayout container = new RelativeLayout(this) {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d("MyActvity", "onMeasure called on map");
}
};
container.setBackgroundColor(Color.rgb(255, 0, 0));
ViewGroup.LayoutParams containerParams = new ViewGroup.LayoutParams(300, 300);
final TextView childView = new TextView(this);
childView.setBackgroundColor(Color.rgb(0, 255, 0));
childView.setText("Child View");
Button viewToggle = (Button) findViewById(R.id.button);
viewToggle.setText("Add/Remove Child View");
viewToggle.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
if (childView.getParent() == null) {
container.addView(childView, 400, 30);
} else {
container.removeView(childView);
}
}
});
shell.addView(container, containerParams);
}
}
Running this, you would see 2 initial (an expected) calls to onMeasure, then one for each time that you add/remove the view by clicking the button. This obviously runs fine, but you can see where constant calls to onMeasure when you have a complex layout of nested views can get problematic.
Is there a recommended way to bypass these onMeasure calls or at least onMeasure calling measureChildren?
Instead of rolling my own Layout Manager (which I may still do in the future), I changed the onMeasure to:
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; count > i; i++) {
View v = getChildAt(i);
if (v.getVisibility() != GONE) {
if (v.getMeasuredWidth() <= 0 || v.getMeasuredHeight() <= 0) {
measureChild(v,
MeasureSpec.makeMeasureSpec(v.getLayoutParams().width,
MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(v.getLayoutParams().height,
MeasureSpec.AT_MOST));
}
}
}
setMeasuredDimension(resolveSize(staticContainerWidth, widthMeasureSpec),
resolveSize(staticContainerHeight, heightMeasureSpec));
}
... and added a sudo-hard coded height and width for the container as a variable. Setting these to what you expect is out of the scope of this solution.
int staticContainerHeight = 300;
int staticContainerWidth = 300;
I ran into a similar problem when animation occurs on the size of viewgroup, whose onMeasure() gets called very frequently. Because parent view contains numerous child views, the frequent cascaded onMeasure() calls caused animation performance hiccups. I have another dirty solution but much simpler than rolling out my own layoutManager.
long mLastOnMeasurTimestamp;
...
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
long currentTimestamp = System.currentTimeMillis();
if(currentTimestamp - mLastOnMeasureTimestamp < SKIP_PERIOD_IN_MILL){
return;
}
mLastOnMeasureTimestamp = currentTimestamp;
...
I have encountered a similar problem and my solution was to check if the dimensions have changed:
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(parentWidth, parentHeight);
if ( mClientWidth == parentWidth && mClientHeight == parentHeight ) {
return;
}
mClientWidth = parentWidth;
mClientHeight = parentHeight;
So, if the parent's dimensions don't really change, it won't be cascaded down to its children.

Is it possible to add views to layout in onLayout event?

Is it possible to add views to a layout during the onLayout event of one of its Childs?
i.e.
FrameLayout contains View, in View.onLayout() I want to add views to the parent FrameLayout.
This is because the views I need to draw on the FrameLayout needs the child View dimensions (width, height) to assign them to particular positions on the FrameLayout.
I already try to do so, but nothing is getting drawn. Do you know how can I accomplish the same effect? or if I'm doing something wrong. Don't know why I'm unable to draw the views, event if I call invalidate.
Thanks.
Yes, it's possible. I have solved similar problem (placing a checkpoint Button into FrameLayout over SeekBar) using the following code (overriden methods from SeekBar):
#Override
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) {
super.onLayout(changed, left, top, right, bottom);
View child = new Button(getContext());
//child measuring
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, 0, LayoutParams.WRAP_CONTENT); //mWidthMeasureSpec is defined in onMeasure() method below
int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);//we let child view to be as tall as it wants to be
child.measure(childWidthSpec, childHeightSpec);
//find were to place checkpoint Button in FrameLayout over SeekBar
int childLeft = (getWidth() * checkpointProgress) / getMax() - child.getMeasuredWidth();
LayoutParams param = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
param.gravity = Gravity.TOP;
param.setMargins(childLeft, 0, 0, 0);
//specifying 'param' doesn't work and is unnecessary for 1.6-2.1, but it does the work for 2.3
parent.addView(child, firstCheckpointViewIndex + i, param);
//this call does the work for 1.6-2.1, but does not and even is redundant for 2.3
child.layout(childLeft, 0, childLeft + child.getMeasuredWidth(), child.getMeasuredHeight());
}
#Override
protected synchronized void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//we save widthMeasureSpec in private field to use it for our child measurment in onLayout()
mWidthMeasureSpec = widthMeasureSpec;
}
There is also ViewGroup.addViewInLayout() method (it's protected, so you can use it only if you override onLayout method of your Layout) which javadoc says its purpose is exactly what we discuss here, but I haven't understood why is it better than addView(). You can find it's usage in ListView.

Categories

Resources