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
Related
I have a statically defined relative layout with height set to wrap_content.
I add several children to it dynamically. This appears just fine.
I now have to add a few more children and line them up above previously added children. This sort of works. (Note: I can only do this once those previously added children have actually been added, as my new children depend on their width.)
I can only see the changes if I change layout height to say 50dp vs wrap_content.
I tried calling invalidate(), postInvalidate() and requestLayout() on the holding layout, but that didn't work. What am I not doing?
public void layOutExtras(CustomView section) {
if (section.isUnderlined()) {
int labelOrientation = section.getLabelOrientation();
View line = createLine(labelOrientation, section.getMeasuredWidth(), section.getId());
addView(line);
}
}
private View createLine(int orientation, int width, int viewId) {
RelativeLayout.LayoutParams params = new LayoutParams(width, 4);
params.addRule(ABOVE, viewId);
params.addRule(ALIGN_RIGHT, viewId);
View line = new ImageView(context);
line.setBackgroundColor(Color.RED);
line.setLayoutParams(params);
return line;
}
Sounds like the issue I had a few days ago. The issue is probably due to the height not getting set right. Try setting the minimum height of both the new view and the container you're modifying.
I have a layout as follows
Horizontal Scroll View: id - container
Scroll View: width & height - match_parent
Relative Layout: id - wrapper, width & height - match_parent
View
LinearLayout: id - timeline, width & height - match_parent
LinearLayout: id - body, width & height - match_parent
The "container" has an initial width. I want to add a LinearLayout inside "body" which has the width equal to the container. This linear layout has multiple text views inside it. But I want to increase the width of the "container" and also the linear layout when the text views exceed the initial width so that text views are not clipped when added. Here's how I'm doing it:
This is the LinearLayout im trying to add into the "body"
public class Layer extends LinearLayout {
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), 100);
};
public void addTextView(CharSequence text) {
TextView tv = new TextView(text);
addView(tv);
if(this.getWidth() < tv.getX() + tv.getWidth()) {
RelativeLayout wrapper = (RelativeLayout) findViewById(R.id.wrapper);
wrapper.setMinimumWidth(size);
wrapper.invalidate();
}
}
I'm getting a NullPointerException at this line 'wrapper.setMinimumWidth(size);'. Is there any other better way to do it? I'm kind of stuck. Any help would be appreciated!
Ok so I found the solution. I had a fragment which contained my custom view as a private member variable. onCreateView() method of the fragment returned the custom view variable. Inside my custom view class, findViewById() was returning null. So I passed the context by getActivity() from my fragment to my custom view class and casted it into Activity to make it work!!
((Activity)context).findViewById()
Hope this helps!!
I have the a LinearLayout with width set in xml as fill_parent , now i need its width at runtime programatically. So i did this:
LinearLayout layoutGet=(LinearLayout) findViewById(R.id.GameField1);
LayoutParams layParamsGet= layoutGet.getLayoutParams();
int width=layParamsGet.width;
But width value on debugging was found to be -1, anybody with idea why can't i get the exact width at runtime for LinearLayout with fill_parent set in layout xml.
I got a simple approach working.
myLayout = (RelativeLayout) findViewById(R.id.my_layout);
myLayout.post(new Runnable()
{
#Override
public void run()
{
Log.i("TEST", "Layout width : "+ myLayout.getWidth());
}
});
Jens Vossnack's approach mentioned below works fine. However, I found that the onGlobalLayout() method of GlobalLayoutListener is called repeatedly, which may not be appropriate in certain cases.
You can try to listen to the globalLayout event, and get the width in there. You probably get the -1 because you are trying to get the width before the views are layed-out.
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//Do it here
LinearLayout layoutGet=(LinearLayout) findViewById(R.id.GameField1);
LayoutParams layParamsGet= layoutGet.getLayoutParams();
int width=layParamsGet.width;
removeOnGlobalLayoutListener(layoutGet, this); // Assuming layoutGet is the View which you got the ViewTreeObserver from
}
});
#SuppressLint("NewApi")
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
if (Build.VERSION.SDK_INT < 16) v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
else v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
(vto is the view you want to get the width of)
If you look at the value for FILL_PARENT (you should be using MATCH_PARENT since FILL_PARENT is now deprecated) you will notice the value for it is -1. LayoutParams are simply attributes for how your view should look. Once the view is inflated and looks the way the params specify, the view does not go back and change those Params to reflect the actual values of the view (width/height/etc).
If you wanted to get the actual width of your view you would have to call getWidth() on your view once the layout has been inflated and displayed. Calling getWidth() before your layout has been displayed will result in 0.
LinearLayout layoutGet=(LinearLayout) findViewById(R.id.GameField1);
int width = layoutGet.getWidth();
First of all, you should use match_parent instead of fill_parent (deprecated).
When you do int width=layParamsGet.width;, you take the right result which is -1 corresponding at fill_parent.
If you wan't to get the real width, you need to wait onMesure() call and use
LinearLayout layoutGet=(LinearLayout) findViewById(R.id.GameField1);
int width = layoutGet. getMeasuredHeight();
layoutGet.getWidth() should work out
I want to have an ImageView with width=fill_parent, and the height should be whatever the width is. I'm not sure if there's a way to specify that in xml, is the only option to create my own ImageView-derived class?:
public class MyImageView extends ImageView {
// Just return whatever the current width is?
private int measureHeight(int measureSpec) {
return getWidth();
}
}
Is this the way to go, any other options? (I'm not sure if the above is even correct, not sure if the width measurement even takes place before the height measurement for example)
Thanks
You can get the width with the method
imageView.getMeasuredWidth();
So, you can set it's height
imageView.setLayoutParams(new LayoutParams(imageView.getMeasuredWidth(), imageView.getMeasuredWidth()));
Make layout height equal to the width:
The problem with digulino's answer (in my case at least) is that if you want to change the dimensions in the start, getMeasuredWidth() will return 0 because the views haven't been drawn yet.
You can still do it by using a Runnable() thread like this:
FrameLayout frame = (FrameLayout)findViewById(R.id.myFrame);
frame.post(new Runnable() {
#Override
public void run() {
RelativeLayout.LayoutParams lparams;
lparams = (RelativeLayout.LayoutParams) frame.getLayoutParams();
lparams.height = frame.getWidth();
frame.setLayoutParams(lparams);
frame.postInvalidate();
}
});
Important Note:
This example assumes your view is a FrameLayout inside a RelativeLayout. Change it accordingly for other layouts.
I wanted to create a custom LinearLayout (and later a custom ImageButton) that could take percentage values for both dimensions of size based on its parent's size regardless of the parent type (Relative or Linear). I was following this post: How to size an Android view based on its parent's dimensions, and it was very helpful, but I have a problem that those answers don't address.
When I place my Custom LinearLayout inside another LinearLayout, everything works as expected. My Custom LinearLayout covers the expected space (80% of the parent's width in the example below).
However if I place it inside a RelativeLayout, my screen always shows empty, I am not sure why this happens.
Here is my class:
public class ButtonPanel extends LinearLayout {
public ButtonPanel(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int newWidth = (int) Math.ceil(parentWidth * 0.8);
this.setMeasuredDimension(newWidth, parentHeight);
this.setLayoutParams(new RelativeLayout.LayoutParams(newWidth,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
And here is my testing layout for the activity.
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.android.tests.views.ButtonPanel
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/inner_panel"
android:gravity="center_horizontal"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true">
</com.android.tests.views.ButtonPanel>
</RelativeLayout>
In my activity all I do is set the Content View to the above layout.
(Incidentally, does anybody now how I could get the type of the parent dynamically for setting the new LayoutParameters? Above you'll see the parent type (RelativeLayout) hard-coded into the Custom View onMeasure function)
Thanks in advance for any help!
Is this exposed to be a problem?
this.setLayoutParams(new RelativeLayout.LayoutParams(newWidth,parentHeight)); // <-- a RelativeLayout params?
In the onMeasure function you could use something like this to know what class is the parent of the view.
this.getParent().getClass().getName()
This should also work
a instanceof B
or
B.class.isAssignableFrom(a.getClass())
When using "instanceof", you need to know the class of "B" at compile time. When using "isAssignableFrom" it can be dynamic and change during runtime.
If you are not compfortable with string comparison, you could also use enums.
Turns out my two inquiries in this post were more related than expected.
I realized that by setting my view's LayoutParams to a completely new instance, I was overwriting the layout positioning information needed by the Relative Layout to position my view.
By 'zeroing out' that information, my view has the right dimensions, but the layout doesn't know where to place it, so it simply doesn't.
The following code for the new onMeasure shows how just directly modifying the height and width of the LayoutParams already attached to my view I avoid both overwriting the layout position information and having to create new LayoutParams based on the parent's type.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specHeight = MeasureSpec.getSize(heightMeasureSpec);
int newWidth = (int) Math.ceil(parentWidth * 0.8);
int newHeight = (int) Math.ceil(parentHeight * 0.8);
this.setMeasuredDimension(newWidth, newHeight);
this.getLayoutParams().height = newHeight;
this.getLayoutParams().width = newWidth;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
Now, I'll be honest and say that this code is still not bug-free. Bringing the activity to the foreground and background multiple times constantly reduces the size of this custom view. The 0.8 reduction factor gets applied over and over each time the activity is brought up (I suspect the setting of the LayoutParams has to do with it, it might actually be unnecessary, but I haven't has time to test).
BUT, this still answered the question concerning this post, namely, why was my view not appearing at all despite having the right dimensions.