Programmatically set margins in a MotionLayout - android

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);
}

Related

Is there a way to make RecyclerView requiresFadingEdge unaffected by paddingTop and paddingBottom

Currently, I need to use paddingTop and paddingBottom of RecyclerView, as I want to avoid complex space calculation, in my first RecyclerView item and last item.
However, I notice that, requiresFadingEdge effect will be affected as well.
This is my XML
<androidx.recyclerview.widget.RecyclerView
android:requiresFadingEdge="vertical"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:overScrollMode="always"
android:background="?attr/recyclerViewBackground"
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
When paddingTop and paddingBottom is 40dp
As you can see, the fading effect shift down by 40dp, which is not what I want.
When paddingTop and paddingBottom is 0dp
Fading effect looks fine. But, I need to have non-zero paddingTop and paddingBottom, for my RecyclerView.
Is there a way to make RecyclerView's requiresFadingEdge unaffected by paddingTop and paddingBottom?
I found the best and kind of official solution for this. Override this 3 methods of RecyclerView. Which play most important role for fading edge.
First create your recyclerView.
public class MyRecyclerView extends RecyclerView {
// ... required constructor
#Override
protected boolean isPaddingOffsetRequired() {
return true;
}
#Override
protected int getTopPaddingOffset() {
return -getPaddingTop();
}
#Override
protected int getBottomPaddingOffset() {
return getPaddingBottom();
}
}
That's it. Use this recyclerview and you will see fadding edge unaffected.
Following content is just for explanation. If you want to know behind the scene.
To understand how this edge effect is working I dig into the class where android:requiresFadingEdge is used, And I found that it's not handled by RecyclerView instead It's handled by View class which is parent for all view.
In onDraw method of View class I found the code for drawing fade edge by using help of this method isPaddingOffsetRequired. Which used only for handling the fade effect.
According to documentation this method should be overridden by child class If you want to change the behaviour of fading edge. Bydefault It return false. So By returning true we are asking view to apply some offset for edge at the time of view drawing.
Look following snippet of onDraw method of View class to understand the calculation.
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
As we can see top variable is initialize using getFadeTop(offsetRequired).
protected int getFadeTop(boolean offsetRequired) {
int top = mPaddingTop;
if (offsetRequired) top += getTopPaddingOffset();
return top;
}
In this method, top is calculated by adding value of topOffSet when offset is needed. So to reverse the effect we need to pass negative value of padding which you are passing. so we need to return -getPaddingTop().
Now for bottom we are not passing negative value because bottom is working on top + height. So passing negative value make fade more shorter from the bottom so we need to add bottom padding to make it proper visible.
You can override this 4 method to play with it. getLeftPaddingOffset(), getRightPaddingOffset(), getTopPaddingOffset(), getBottomPaddingOffset()
I suggest a few solutions
, I hope to be helpful
1- RecyclerView.ItemDecoration (keep requiresFadingEdge)
class ItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration(){
override fun getItemOffsets(outRect: Rect, view: View, parent:
RecyclerView, state: RecyclerView.State?) {
val position = parent.getChildAdapterPosition(view)
when(position)
0 -> { outRect.top = spacing /*40dp*/ }
parent.adapter.itemCount-1 -> { outRect.bottom = spacing /*40dp*/ }
}
2- Multiple ViewHolders (depending on the ViewHolder either keep or remove requiresFadingEdge)
https://stackoverflow.com/a/26245463/5255963
3- Gradient (remove requiresFadingEdge)
Make two gradients with drawables like this:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:angle="90"
android:startColor="#000"
android:endColor="#FFF" />
</shape>
And set background to two views at the top and bottom of the Recyclerview.
There are many ways to achieve that but I preferred below solution that works for me.
You can use a third-party library called Android-FadingEdgeLayout checkout here
Here is a dependency.
implementation 'com.github.bosphere.android-fadingedgelayout:fadingedgelayout:1.0.0'
In yours.xml
<com.bosphere.fadingedgelayout.FadingEdgeLayout
android:id="#+id/fading_edge_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fel_edge="top|left|bottom|right"
app:fel_size_top="40dp"
app:fel_size_bottom="40dp"
app:fel_size_left="0dp"
app:fel_size_right="0dp">
<androidx.recyclerview.widget.RecyclerView
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:overScrollMode="always"
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
</com.bosphere.fadingedgelayout.FadingEdgeLayout>
Change your ReacyclerView property according to your need. below is example image with all sided sades. I hope that will help you.
Credits Android-FadingEdgeLayout

A lot of spacing between views and margin doesn't do the stuff

I have a HorizontalScrollView with a vertical LinearLayout in it. There I add some custom views of the same type. By default there is a lot of spacing between the views. So I guessed I have to set the margin of my views to 0 or something. But there ist absolutely no result.
First I tried to change the margin in the xml
<gui.CardUi
android:id="#+id/cardUi"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="0dp"
</gui.CardUi>
Than I tried it to change the margin in code:
private void setMargins ( int left, int top, int right, int bottom) {
if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) getLayoutParams();
p.setMargins(left, top, right, bottom);
requestLayout();
}
}
setMargins(0, 0, 0, 0);
Not sure if the info is important but I add the views programmatically with LayoutInflater.
Your margins would not be set because of the way you inflate the views.
I guess you used such a way:
View child = getLayoutInflater().inflate(R.layout.mylayout, null);
The point is when you want to inflate a view and add it to another view, You should inform the inflater about the container view (in your example Linear layout). So your layout parameter such as margins and weight and gravity would be set correctly.
So use this method instead:
View child = getLayoutInflater().inflate(R.layout.child, item, false);
There is no need to add margin in your code.

Dynamically modify a statically defined layout. Can't see the applied changes

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.

Android TextView baseline related margin

I need to position a TextView the way its baseline is 20dp from the bottom of the container.
How can I achieve this?
The layout with bottom margin or padding produces the same result.
I would like to make the text 'sit' on the purple line.
When I write 'sit' I mean, the 'wert' should touch the line, not 'q...y'.
The padding / margin is equal to the purple square size:
If you still need it, I wrote custom method, to not create lots of custom views. It works for me with TextView:
public static void applyExistingBotMarginFromBaseline(View view) {
final int baseline = view.getBaseline();
final int height = view.getHeight();
final ViewGroup.MarginLayoutParams marginLayoutParams;
try {
marginLayoutParams = ((ViewGroup.MarginLayoutParams) view.getLayoutParams());
} catch (ClassCastException e) {
throw new IllegalArgumentException("Applying margins on a view with wrong layout params.");
}
final int baselineMarginValue = baseline + marginLayoutParams.bottomMargin;
marginLayoutParams.bottomMargin = baselineMarginValue - height;
view.setLayoutParams(marginLayoutParams);
}
You can apply it when view is measured already, so like this:
final TextView title = (TextView) findViewById(R.id.title);
title.post(new Runnable() {
#Override public void run() {
Utils.applyExistingBotMarginFromBaseline(title);
}
});
Also you can use databinding framework and write your own custom BindingAdapter with a bit customized method, to use it from xml.
Your problem is not the padding/margin referenced to the parent, I think is about your font, I recommend you to change the fontFamily:"yourStyle"
even worst you have to re-difine your own font style which is explained here Custom fonts and XML layouts (Android) or Set specific font in a styles.xml

Animate view's width to match parent?

I want to create the following concept:
First expand the view to its parent's width (it's not the same).
Then expand the view to its parent's height.
I know I can create the sequence with AnimatorSet(). What I cannot find is what property/-ies to animate to create the result.
My thoughts for the width animation:
I would probably need to animate either two points which define left and right or alternatively animate the translation and the width.
I'm a bit confused, which properties would do the trick?
You'll need to define custom properties to do this (I know, silly). Height example:
public static final Property<View, Integer> PROPERTY_HEIGHT =
new Property<View, Integer>(Integer.class, "viewLayoutHeight") {
#Override
public void set(View object, Integer value) {
object.getLayoutParams().height = value.intValue();
object.requestLayout();
}
#Override
public Integer get(View object) {
return object.getLayoutParams().height;
}
};
Then use as a normal property:
ObjectAnimator.ofInt(view, PROPERTY_HEIGHT, minHeight, maxHeight);
You'll need to get the parent dimensions at runtime (perhaps using a ViewTreeObserver.OnGlobalLayoutListener).
I think that there is no need to complicate things, you can use width property first then followed by height in AnimatorSet, Your view gravity property will define in which direction view will expand ...

Categories

Resources