Strange behavior of setMarginStart with supportsRTL false in Manifest - android

Consider the following snippets:
<TextView
android:id="#+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:text="StrangerThings"/>
Java side,
TextView tv =findViewById(R.id.tv);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) tv.getLayoutParams();
params.setMarginStart(0);
with supportsRTL flag set to true in Android Manifest, above code produces text with 0 margin as expected. However, with supportsRTL false, setMarginStart has no effect.
Moreover, with SupportsRTL false, no matter how you set left margin (in xml or programatically using setMargins), once you set left margin, setMarginStart has no effect on it.
Is it the intended android behavior or a bug? Can someone explain this behavior?

tl;dr - android:supportsRtl="false" puts the app into "RTL compatibility mode", which causes startMargin to be ignored in any case where leftMargin is defined. Removing the android:layout_marginLeft="50dp" attribute allows startMargin to take effect.
Inside the MarginLayoutParams class, two different fields track "left" margin and "start" margin (these have very creative names: leftMargin and startMargin). Similarly, two different fields track "right" margin and "end" margin.
The class pretty much does all of its work using "left" and "right" margins; it just goes through a process that resolves the "start" and "end" values (based on layout direction) to "left" or "right". Here is the source code for that method:
private void doResolveMargins() {
if ((mMarginFlags & RTL_COMPATIBILITY_MODE_MASK) == RTL_COMPATIBILITY_MODE_MASK) {
// if left or right margins are not defined and if we have some start or end margin
// defined then use those start and end margins.
if ((mMarginFlags & LEFT_MARGIN_UNDEFINED_MASK) == LEFT_MARGIN_UNDEFINED_MASK
&& startMargin > DEFAULT_MARGIN_RELATIVE) {
leftMargin = startMargin;
}
if ((mMarginFlags & RIGHT_MARGIN_UNDEFINED_MASK) == RIGHT_MARGIN_UNDEFINED_MASK
&& endMargin > DEFAULT_MARGIN_RELATIVE) {
rightMargin = endMargin;
}
} else {
// We have some relative margins (either the start one or the end one or both). So use
// them and override what has been defined for left and right margins. If either start
// or end margin is not defined, just set it to default "0".
switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
case View.LAYOUT_DIRECTION_RTL:
leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
endMargin : DEFAULT_MARGIN_RESOLVED;
rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
startMargin : DEFAULT_MARGIN_RESOLVED;
break;
case View.LAYOUT_DIRECTION_LTR:
default:
leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
startMargin : DEFAULT_MARGIN_RESOLVED;
rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
endMargin : DEFAULT_MARGIN_RESOLVED;
break;
}
}
mMarginFlags &= ~NEED_RESOLUTION_MASK;
}
When the manifest is set with android:supportsRtl="false", we go into the first branch of the top if statement. So now the question is just whether or not the left margin is "undefined"... and we know that it is not, since the view tag specified android:layout_marginLeft="50dp". As such, the value passed to setMarginStart() is ignored.

Related

Programmatically set margins in a MotionLayout

I have some views that need some margins set programmatically (from an applyWindowInsets listener), but the views seem to be ignoring any margins I set with my code, even though I am not animating the margins.
I'm able to set padding just fine, but I cannot accomplish what I need using only padding.
The issue seems to be related to MotionLayout since it works fine if it is a ConstraintLayout.
I've been using this util method.
public static void addTopMargin(View v, int margin) {
((ViewGroup.MarginLayoutParams) v.getLayoutParams()).topMargin += margin;
}
The issue you're having is that MotionLayout derives its margins from the assigned ConstraintSets, so changing the base margin isn't actually doing anything. To get this to work, you need to target one or both of the ConstraintSets that define the MotionScene:
val motionLayout = findViewById(R.id.motionLayoutId)
motionLayout.getConstraintSet(R.id.startingConstraintSet)?
.setMargin(targetView.id, anchorId, value)
You could also do this for more than one view with a let:
val motionLayout = findViewById(R.id.motionLayoutId)
motionLayout.getConstraintSet(R.id.startingConstraintSet)?.let {
setMargin(firstView.id, anchorId, value)
setMargin(secondView.id, anchorId, value)
}
For the top margin, use ConstraintSet.TOP, etc.
Remember that if you're not wanting to animate that margin, you'll have to assign to both the start and end ConstraintSet.
Just adding a simple note to UnOrthodox solution that in case of you don't need to animate that margin you will need to keep the base margin with adding the ConstraintSets margin:
public static void addTopMargin(View v, int margin) {
((ViewGroup.MarginLayoutParams) v.getLayoutParams()).topMargin += margin;
MotionLayout root = findViewById(R.id.motion_layout);
root.getConstraintSet(R.id.start).setMargin(firstView.id, anchorId, margin);
root.getConstraintSet(R.id.end).setMargin(firstView.id, anchorId, margin);
}

Update layout_margin inside kotlin class for specific elements

So, this should be pretty basic, but I am new to Kotlin. So I basically have the following ImageView inside a RelativeLayout view.
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srcCompat="#drawable/moon_0"
android:id="#+id/imagen_luna"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true" />
The thing is that under certain condition this ImageView can collide visually with another element, so I have to modify the marginStart and the marginEnd properties. I saw in some tutorials that first I must obtain the current layout margins or something like that, but I am not sure why it's so complicated, in Swift I just modify the properties of each margin.
Is there an easy way to achieve this or in case there isn't what would be the easiest way to do this?
UPDATE - Added code in the Adapter
This is the code I have in my adapter for this specific ImageView:
override fun onBindViewHolder(holder: AnoViewHolder, position: Int) {
...
if(respec.nombre_icono_signo != "" && respec.imagen_luna != "") {
val layoutParamsLuna = holder.view?.imagen_luna.layoutParams as RelativeLayout.LayoutParams
layoutParamsLuna.setMargins(1, 8, 15, 8)
holder.view?.imagen_luna.layoutParams = layoutParamsLuna
}
...
}
The thing is that this does nothing and my theory is that the setMargins function accepts left and right margins, not start and end margins.
You can update margin in layoutParams of view
Ex:
val layoutParams= imagen_luna.layoutParams as RelativeLayout.LayoutParams
layoutParams.marginEnd=resources.getDimension(your_dimension).toInt()
layoutParams.marginStart=resources.getDimension(your_dimension).toInt()
layoutParams.bottomMargin=resources.getDimension(your_dimension).toInt()
layoutParams.topMargin=resources.getDimension(your_dimension).toInt()
//or
layoutParams.setMargins(start,top,end,bottom)
imagen_luna.layoutParams=layoutParams
You have to add value for margins in dimension file.because if you set setMargins(1, 8, 15, 8) like this,It's will margin in pixel not in dp.
So use margin values like below.
<dimen name="spacing_normal">16dp</dimen>
<dimen name="spacing_small">8dp</dimen>
<dimen name="spacing_tiny">4dp</dimen>
Update your dapter class like belo
override fun onBindViewHolder(holder: AnoViewHolder, position: Int) {
...
if(respec.nombre_icono_signo != "" && respec.imagen_luna != "") {
val layoutParamsLuna = holder.view?.imagen_luna.layoutParams as RelativeLayout.LayoutParams
layoutParamsLuna.setMargins(context.resources.getDimension(R.dimen.spacing_normal).toInt(),
context.resources.getDimension(R.dimen.spacing_normal).toInt(),
context.resources.getDimension(R.dimen.spacing_normal).toInt(),
context.resources.getDimension(R.dimen.spacing_normal).toInt())
holder.view?.imagen_luna.layoutParams = layoutParamsLuna
}else{
//reset margin for else case because recylerview reuse views so it's will keep previous states
}
...
}

View.setX prevents adjustPan from working

I am programmatically setting the x value of an edittext. However, in doing so the adjust pan function does not work. Any ideas on how to make this work. The activity is not in full-screen mode and this only happens in APIs > 23.
Ex.
Edittext editext = findViewById(R.id.edittext);
edittext.setX(200);
//Clicking on edittext now, gets covered by the soft keyboard if it low enough on screen
Note: this also happens with setY()
Here is an example to reproduce the error:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context context = this;
setContentView(R.layout.activity_main);
//Get edittext from layout file and set X and Y to the bottom
//left of screen
EditText edittext = findViewById(R.id.editText);
edittext.setX(0);
//Getting device frame to make sure its below keyboard, Not
//necessary can just guess any value
edittext.setY(getDeviceFrame(context).getHeight() / 1.2f);
}
With a layout of :
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="textPersonName"
android:text="Name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
And a manifest of :
<activity android:name=".MainActivity"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Resulting in a layout :
Edittext after programmatic setX and setY
And after clicking on editext we get :
Hidden editext
Which you can see is hidden by the keyboard.
UPDATE :
I have tried to use the different variations to moving the EditText with still no avail.
Ex.
edittext.animate().x(200);
UPDATE 2:
I have yet to solve this issue. I have been getting closer as I have done some test and it appears that using layout params to control the positioning of the view works to an extent.
Ex.
ConstraintLayout.LayoutParams layoutParams =
(ConstraintLayout.LayoutParams) view.getLayoutParams();
layoutParams.leftMargin = 200; //Your X Coordinate
layoutParams.topMargin = 200; //Your Y Coordinate
view.setLayoutParams(layoutParams);
By some extent, I mean I have messed around with the functionality of these methods in a test library and it seems to work doing the above from onCreate(). However, in my app, I am doing the call in a callback. SetX works fine without me having to set it in a 'post' thread or 'runOnMainUI' thread. So I am not sure why this is not working.
Okay, so I figured out how to solve the issue. Using the answer from my "Update 2" above I was able to get the solution with a little tweaking.
This code:
ConstraintLayout.LayoutParams layoutParams =
(ConstraintLayout.LayoutParams) view.getLayoutParams();
layoutParams.leftMargin = 200; //Your X Coordinate
layoutParams.topMargin = 200; //Your Y Coordinate
view.setLayoutParams(layoutParams);
(You can use whatever Layout you are using instead of Constraint)
Is an alternative to setX() and setY() and allows adjustPan to work. However, getting the views x or y positioning with :
view.getX();
view.getY();
Will not return the visual positioning of the view. You need to get the view's position by getting the layout params associated with the view and getting its margins.
Now what I came to realize and is key for the Constraint Layout is that you MUST have the constraint defined in xml or via code.
Ex.
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
Having this allows the margins to have a reference to what it was extended from, without this, the positioning of the view with not move.
Replace setX and setY with leftMargin and topMargin for pop up positioning, and it will work as intended on API greater than 23.
I can't explain why but the problem did not exist on API 23.
as suggested, here is a sample of the code used to move popup layout by drag on button and keep adjust pan working.
final View myNote = getActivity().getLayoutInflater().inflate(R.layout.viewer_annotation_layout, null);
final RelativeLayout.LayoutParams noteLayoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
myNote.setLayoutParams(noteLayoutParams);
mainScreenLayout.addView(myNote, noteLayoutParams);
btnmove.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//addednotedX = myNote.getX() - event.getRawX();
//addednotedY = myNote.getY() - event.getRawY();
addednotedX = noteLayoutParams.leftMargin - event.getRawX();
addednotedY = noteLayoutParams.topMargin - event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int newposx = event.getRawX() + addednotedX;
int newposy = event.getRawY() + addednotedY;
/* this is move the Note layout but prevent adjsutpan working */
//myNote.setX(newposx);
//myNote.setY(newposy);
/* this is move the Note layout and keep adjsutpan working */
noteLayoutParams.leftMargin = newposx;
noteLayoutParams.topMargin = newposy;
myNote.setLayoutParams(noteLayoutParams);
break;
default:
return false;
}
return false;
}
});

Android databinding set padding if value is true

I want to be able to to be able to set padding values if a boolean is true. The problem is that Android studio cannot parse the layout because it thinks 2dp is a decimal with a value of 2 and then doesn't know what to do with the p. how do I format this so that it understands i mean 2 density pixels.
Data layout:
<data class=".ItemBinding">
<variable name="isGroupType" type="Boolean"/>
</data>
View layout(whats important):
<android.support.v7.widget.AppCompatImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:paddingBottom='#{isGroupType ? 2dp : 0dp}'
android:paddingTop='#{isGroupType ? 8dp : 0dp}'
android:paddingRight='#{isGroupType ? 2dp : 0dp}'
android:paddingLeft='#{isGroupType ? 2dp : 0dp}'/>
Store padding value in dimen.xml and use it. Please keep habit to write binding string with " " (double quotes)
android:paddingBottom="#{isGroupType ? #dimen/padding_normal : #dimen/padding_null}"
and so on for other paddings also.
For anyone looking to set margins via DataBinding, you'll have to use BindingAdapter as well:
#BindingAdapter("layoutMarginBottom")
fun setLayoutMarginBottom(view: View, dimen: Float) {
val layoutParams = view.layoutParams as MarginLayoutParams
layoutParams.bottomMargin = dimen.toInt()
view.layoutParams = layoutParams
}
And your xml property will look like this:
app:layoutMarginBottom="#{someCondition ? #dimen/zero_dp : #dimen/twenty_dp}"
Just as a heads-up this does not work with layout_margin's :(
Not sure why, but think it's due to the parent layout needs to be remeasured..
#Ravi's answer is correct.
But for more flexibility you can also try this:
#BindingAdapter({"padding", "shouldAdd"})
public static void setPadding(AppCompatImageView imageView, boolean shouldAdd, int padding){
if (shouldAdd){
imageView.setPadding(padding, padding, padding, padding);
}
}
Then:
<android.support.v7.widget.AppCompatImageView
android:layout_width="64dp"
android:layout_height="64dp"
shouldAdd="#{isGroupType}"
padding="#{10}"/>
#Ravi's answer is good, but it's working only for padding.
If You want to simply add margin, create empty view e.g TextView with padding.
Use a blank view
<View
android:layout_width = "8dp"
android:layout_height = "8dp"
>
Now constraint this view to the layout you want to control the visibility of .
Now disappear this view depending on the condition. would work the same as a margin
You can use logic and ternary statements in xml-binding, but you really shouldn't. It will come back to haunt you when you're looking the usual places you have logic and don't see what's going on.
BindingAdapter for all your margin needs:
fun bindingSetMargins(view: View, start: Float?, top: Float?, end: Float?, bottom: Float?) {
view.layoutParams = (view.layoutParams as ViewGroup.MarginLayoutParams).apply {
start?.toInt()?.let { leftMargin = it }
top?.toInt()?.let { topMargin = it }
end?.toInt()?.let { rightMargin = it }
bottom?.toInt()?.let { bottomMargin = it }
}
}

When is the width of an Android View set?

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.

Categories

Resources