In this, this and this thread I tried to find an answer on how to set the margins on a single view. However, I was wondering if there isn't an easier way. I'll explain why I rather wouldn't want to use this approach:
I have a custom Button which extends Button. If the background is set to something else than the default background (by calling either setBackgroundResource(int id)
or setBackgroundDrawable(Drawable d)
), I want the margins to be 0. If I call this:
public void setBackgroundToDefault() {
backgroundIsDefault = true;
super.setBackgroundResource(android.R.drawable.btn_default);
// Set margins somehow
}
I want the margins to reset to -3dp (I already read here how to convert from pixels to dp, so once I know how to set margins in px, I can manage the conversion myself). But since this is called in the CustomButton
class, the parent can vary from LinearLayout to TableLayout, and I'd rather not have him get his parent and check the instanceof that parent. That'll also be quite inperformant, I imagine.
Also, when calling (using LayoutParams) parentLayout.addView(myCustomButton, newParams)
, I don't know if this adds it to the correct position (haven't tried however), say the middle button of a row of five.
Question: Is there any easier way to set the margin of a single Button programmatically besides using LayoutParams?
EDIT: I know of the LayoutParams way, but I'd like a solution that avoids handling each different container type:
ViewGroup.LayoutParams p = this.getLayoutParams();
if (p instanceof LinearLayout.LayoutParams) {
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)p;
if (_default) lp.setMargins(mc.oml, mc.omt, mc.omr, mc.omb);
else lp.setMargins(mc.ml, mc.mt, mc.mr, mc.mb);
this.setLayoutParams(lp);
}
else if (p instanceof RelativeLayout.LayoutParams) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams)p;
if (_default) lp.setMargins(mc.oml, mc.omt, mc.omr, mc.omb);
else lp.setMargins(mc.ml, mc.mt, mc.mr, mc.mb);
this.setLayoutParams(lp);
}
else if (p instanceof TableRow.LayoutParams) {
TableRow.LayoutParams lp = (TableRow.LayoutParams)p;
if (_default) lp.setMargins(mc.oml, mc.omt, mc.omr, mc.omb);
else lp.setMargins(mc.ml, mc.mt, mc.mr, mc.mb);
this.setLayoutParams(lp);
}
}
Because this.getLayoutParams();
returns a ViewGroup.LayoutParams
, which do not have the attributes topMargin
, bottomMargin
, leftMargin
, rightMargin
. The mc instance you see is just a MarginContainer
which contains offset (-3dp) margins and (oml, omr, omt, omb) and the original margins (ml, mr, mt, mb).
You should use LayoutParams
to set your button margins:
LayoutParams params = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
params.setMargins(left, top, right, bottom);
yourbutton.setLayoutParams(params);
Depending on what layout you're using you should use RelativeLayout.LayoutParams
or LinearLayout.LayoutParams
.
And to convert your dp measure to pixel, try this:
Resources r = mContext.getResources();
int px = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
yourdpmeasure,
r.getDisplayMetrics()
);
LayoutParams - NOT WORKING ! ! !
Need use type of: MarginLayoutParams
MarginLayoutParams params = (MarginLayoutParams) vector8.getLayoutParams();
params.width = 200; params.leftMargin = 100; params.topMargin = 200;
Code Example for MarginLayoutParams:
http://www.codota.com/android/classes/android.view.ViewGroup.MarginLayoutParams
Button
view: ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) button.getLayoutParams()
returns null
Best way ever:
private void setMargins (View view, int left, int top, int right, int bottom) {
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
p.setMargins(left, top, right, bottom);
view.requestLayout();
}
}
How to call method:
setMargins(mImageView, 50, 50, 50, 50);
Hope this will help you.
requestLayout()
?
int sizeInDP = 16;
int marginInDp = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInDP, getResources()
.getDisplayMetrics());
Then
layoutParams = myView.getLayoutParams()
layoutParams.setMargins(marginInDp, marginInDp, marginInDp, marginInDp);
myView.setLayoutParams(layoutParams);
Or
LayoutParams layoutParams = new LayoutParams...
layoutParams.setMargins(marginInDp, marginInDp, marginInDp, marginInDp);
myView.setLayoutParams(layoutParams);
Here is the all-in-one answer with recent updates:
Step 1, to update margin
The basic idea is to get margin out and then update it. The update will be applies automatically and you do not need to set it back. To get the layout parameters, simply call this method:
LayoutParams layoutParams = (LayoutParams) yourView.findViewById(R.id.THE_ID).getLayoutParams();
The LayoutParams
comes from the layout of your view. If the view is from a linear layout, you need to import LinearLayout.LayoutParams
. If you use relative layout, import LinearLayout.LayoutParams
, etc.
Now, if you set the margin using Layout_marginLeft
, Right
, etc, you need to update margin in this way
layoutParams.setMargins(left, top, right, bottom);
If you set margin using the new layout_marginStart
, you need to update margin in this way
layoutParams.setMarginStart(start);
layoutParams.setMarginEnd(end);
Step 2, to update margin in dp
All two ways of updating margin above are updating in pixels. You need to do a translation of dp to pixels.
float dpRatio = context.getResources().getDisplayMetrics().density;
int pixelForDp = (int)dpValue * dpRatio;
Now put the calculated value to the above margin update functions and you should be all set
With Android KTX, you can do something like that:
yourView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
setMargins(0, 0, 0, 0)
}
In Kotlin it will look like this:
val layoutParams = (yourView?.layoutParams as? MarginLayoutParams)
layoutParams?.setMargins(40, 40, 40, 40)
yourView?.layoutParams = layoutParams
This method will let you set the Margin in DP
public void setMargin(Context con,ViewGroup.LayoutParams params,int dp) {
final float scale = con.getResources().getDisplayMetrics().density;
// convert the DP into pixel
int pixel = (int)(dp * scale + 0.5f);
ViewGroup.MarginLayoutParams s =(ViewGroup.MarginLayoutParams)params;
s.setMargins(pixel,pixel,pixel,pixel);
yourView.setLayoutParams(params);
}
UPDATE
You can change the parameter that suits your need.
layout_margin is a constraint that a view child tell to its parent. However it is the parent's role to choose whether to allow margin or not. Basically by setting android:layout_margin="10dp", the child is pleading the parent view group to allocate space that is 10dp bigger than its actual size. (padding="10dp", on the other hand, means the child view will make its own content 10dp smaller.)
Consequently, not all ViewGroups respect margin. The most notorious example would be listview, where the margins of items are ignored. Before you call setMargin()
to a LayoutParam, you should always make sure that the current view is living in a ViewGroup that supports margin (e.g. LinearLayouot or RelativeLayout), and cast the result of getLayoutParams()
to the specific LayoutParams you want. (ViewGroup.LayoutParams
does not even have setMargins()
method!)
The function below should do the trick. However make sure you substitute RelativeLayout to the type of the parent view.
private void setMargin(int marginInPx) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
lp.setMargins(marginInPx,marginInPx, marginInPx, marginInPx);
setLayoutParams(lp);
}
You can use this method and put static dimen like 20 it converts according your device
public static int dpToPx(int dp)
{
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
Simple Kotlin Extension Solutions
Set all/any side independently:
fun View.setMargin(left: Int? = null, top: Int? = null, right: Int? = null, bottom: Int? = null) {
val params = (layoutParams as? MarginLayoutParams)
params?.setMargins(
left ?: params.leftMargin,
top ?: params.topMargin,
right ?: params.rightMargin,
bottom ?: params.bottomMargin)
layoutParams = params
}
myView.setMargin(10, 5, 10, 5)
// or just any subset
myView.setMargin(right = 10, bottom = 5)
Directly refer to a resource values:
fun View.setMarginRes(@DimenRes left: Int? = null, @DimenRes top: Int? = null, @DimenRes right: Int? = null, @DimenRes bottom: Int? = null) {
setMargin(
if (left == null) null else resources.getDimensionPixelSize(left),
if (top == null) null else resources.getDimensionPixelSize(top),
if (right == null) null else resources.getDimensionPixelSize(right),
if (bottom == null) null else resources.getDimensionPixelSize(bottom),
)
}
myView.setMarginRes(top = R.dimen.my_margin_res)
To directly set all sides equally as a property:
var View.margin: Int
get() = throw UnsupportedOperationException("No getter for property")
set(@Px margin) = setMargin(margin, margin, margin, margin)
myView.margin = 10 // px
// or as res
var View.marginRes: Int
get() = throw UnsupportedOperationException("No getter for property")
set(@DimenRes marginRes) {
margin = resources.getDimensionPixelSize(marginRes)
}
myView.marginRes = R.dimen.my_margin_res
To directly set a specific side, you can create a property extension like this:
var View.leftMargin
get() = marginLeft
set(@Px leftMargin) = setMargin(left = leftMargin)
var View.leftMarginRes: Int
get() = throw UnsupportedOperationException("No getter for property")
set(@DimenRes leftMarginRes) {
leftMargin = resources.getDimensionPixelSize(leftMarginRes)
}
This allows you to make horizontal
or vertical
variants as well:
var View.horizontalMargin
get() = throw UnsupportedOperationException("No getter for property")
set(@Px horizontalMargin) = setMargin(left = horizontalMargin, right = horizontalMargin)
var View.horizontalMarginRes: Int
get() = throw UnsupportedOperationException("No getter for property")
set(@DimenRes horizontalMarginRes) {
horizontalMargin = resources.getDimensionPixelSize(horizontalMarginRes)
}
NOTE: If margin is failing to set, you may too soon before render, meaning params == null. Try wrapping the modification with myView.post{ margin = 10 }
That how I have done in kotlin
fun View.setTopMargin(@DimenRes dimensionResId: Int) {
(layoutParams as ViewGroup.MarginLayoutParams).topMargin = resources.getDimension(dimensionResId).toInt()
}
If you want to add a margin to your TextView you will have to LayoutParams:
val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT)
params.setMargins(int left, int top, int right, int bottom)
your_view.layoutParams = params
LayoutParams can be any layouts like Relative, Linear, View or ViewGroups. Choose the LayoutParams as you need. Thanks
Use this method to set margin in dp
private void setMargins (View view, int left, int top, int right, int bottom) {
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
final float scale = getBaseContext().getResources().getDisplayMetrics().density;
// convert the DP into pixel
int l = (int)(left * scale + 0.5f);
int r = (int)(right * scale + 0.5f);
int t = (int)(top * scale + 0.5f);
int b = (int)(bottom * scale + 0.5f);
p.setMargins(l, t, r, b);
view.requestLayout();
}
}
call the method :
setMargins(linearLayout,5,0,5,0);
When you are in a custom View, you can use getDimensionPixelSize(R.dimen.dimen_value)
, in my case, I added the margin in LayoutParams created on init
method.
In Kotlin
init {
LayoutInflater.from(context).inflate(R.layout.my_layout, this, true)
layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
val margin = resources.getDimensionPixelSize(R.dimen.dimen_value)
setMargins(0, margin, 0, margin)
}
in Java:
public class CustomView extends LinearLayout {
//..other constructors
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
int margin = getResources().getDimensionPixelSize(R.dimen.spacing_dime);
params.setMargins(0, margin, 0, margin);
setLayoutParams(params);
}
}
Using Kotlin,
yourLayoutId.updateLayoutParams<ViewGroup.MarginLayoutParams> {
setMargins(15,15,15,15)
}
For a quick one-line setup use
((LayoutParams) cvHolder.getLayoutParams()).setMargins(0, 0, 0, 0);
but be carfull for any wrong use to LayoutParams, as this will have no if
statment instance chech
Created a Kotlin Extension function for those of you who might find it handy.
Make sure to pass in pixels not dp. Happy coding :)
fun View.addLayoutMargins(left: Int? = null, top: Int? = null,
right: Int? = null, bottom: Int? = null) {
this.layoutParams = ViewGroup.MarginLayoutParams(this.layoutParams)
.apply {
left?.let { leftMargin = it }
top?.let { topMargin = it }
right?.let { rightMargin = it }
bottom?.let { bottomMargin = it }
}
}
In my example i am adding an ImageView to a LinearLayout programatically. I have set top and bottom margins to ImagerView. Then adding the ImageView to the LinearLayout.
ImageView imageView = new ImageView(mContext);
imageView.setImageBitmap(bitmap);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
params.setMargins(0, 20, 0, 40);
imageView.setLayoutParams(params);
linearLayout.addView(imageView);
Working utils function using DP for those interested:
public static void setMargins(Context context, View view, int left, int top, int right, int bottom) {
int marginLeft = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, left, context.getResources().getDisplayMetrics());
int marginTop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, top, context.getResources().getDisplayMetrics());
int marginRight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, right, context.getResources().getDisplayMetrics());
int marginBottom = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, bottom, context.getResources().getDisplayMetrics());
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
p.setMargins(marginLeft, marginTop, marginRight, marginBottom);
view.requestLayout();
}
}
Based on other answers, i made a generic extension function, which identifies your parent and uses the params accordingly:
//takes margin values as integer , eg for 12dp top , you will pass 12
fun View?.setMarginFromConstant(mLeft:Int, mTop:Int, mRight:Int, mBottom:Int){
this?.apply {
val left = context?.dpToPixel(mLeft)?:0
val top = context?.dpToPixel(mTop)?:0
val right = context?.dpToPixel(mRight)?:0
val bottom = context?.dpToPixel(mBottom)?:0
when (val params = this.layoutParams) {
is ConstraintLayout.LayoutParams -> {
params.marginStart = left
params.marginEnd = right
params.topMargin = top
params.bottomMargin = bottom
}
is FrameLayout.LayoutParams -> {
params.marginStart = left
params.marginEnd = right
params.topMargin = top
params.bottomMargin = bottom
}
is RecyclerView.LayoutParams -> {
params.marginStart = left
params.marginEnd = right
params.topMargin = top
params.bottomMargin = bottom
}
}
}
}
and
fun Context.dpToPixel(dp: Int): Int =
(dp * applicationContext.resources.displayMetrics.density).toInt()
You can add support for other parent view groups too
Use this method to you can set dp correctly:
public int dpFormat(int dp) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
and then call
setMargins(dpFormat(15), dpFormat(15), dpFormat(15), dpFormat(15));
As today, the best is probably to use Paris, a library provided by AirBnB.
Styles can then be applied like this:
Paris.style(myView).apply(R.style.MyStyle);
it also support custom view (if you extend a view) using annotations:
@Styleable and @Style
For me I was using ViewGroup.LayoutParams in a themedbutton component. So using ViewGroup.LayoutParams unable to use setMargin.
Instead use MarginLayoutParams as below, it worked for me.
ViewGroup.MarginLayoutParams params =new ViewGroup.MarginLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
params.setMargins(0,8,0,0);
For me, the following code worked
buttonLinearLayout.layoutParams as MarginLayoutParams).apply
{
top.run {
topMargin = resources.getDimension(R.dimen.spacing).toInt()
}
}
val params = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
params.setMargins(0, 0, 0, 400)
binding.container.setLayoutParams(params)
((FrameLayout.LayoutParams) linearLayout.getLayoutParams()).setMargins(450, 20, 0, 250);
linearLayout.setBackgroundResource(R.drawable.smartlight_background);
I had to cast mine to FrameLayout
for a linearLayout as it inherits from it and set margins there so the activity appears only on part of the screen along with a different background than the original layout params in the setContentView
.
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.activity);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WindowManager.LayoutParams.FILL_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
linearLayout.setBackgroundColor(getResources().getColor(R.color.full_white));
setContentView(linearLayout,layoutParams);
None of the others worked for using the same activity and changing the margins based on opening the activity from a different menu! setLayoutParams never worked for me - the device would crash every single time. Even though these are hardcoded numbers - this is only an example of the code for demonstration purposes only.
You can use ViewGroup.MarginLayoutParams
to set the width, height and margins
ViewGroup.MarginLayoutParams marginLayoutParams = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
marginLayoutParams.setMargins(0,16,0,16);
linearLayout.setLayoutParams(marginLayoutParams);
Where the method setMargins();
takes in values for left, top, right, bottom respectively. Clockwise!, starting from the left.
Success story sharing