I have a JSON like this:
{
"name": "view",
"attributes": {
"style": {
"backgroundColor": "yellow",
}
},
"children": [
{
"name": "view",
"attributes": {
"style": {
"backgroundColor": "red",
"width": 400,
"height": 400,
}
}
},
{
"name": "view",
"attributes": {
"style": {
"backgroundColor": "yellow",
"top": 150,
"left": 100,
"width": 200,
"height": 200,
}
}
}
]
}
I'm using recursion to display a layout based on that JSON.
My CustomLayout extends from ViewGroup. It's similar to LinearLayout.
public static class CustomLayout extends ViewGroup {
private int orientation; // 0 for horizontal, 1 for vertical
public CustomLayout(Context context) {
super(context);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
// Set the orientation (horizontal or vertical)
public void setOrientation(int orientation) {
this.orientation = orientation;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure the size of your layout and its children here
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
// Measure each child view
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
if (orientation == 0) { // Horizontal layout
width += child.getMeasuredWidth();
height = Math.max(height, child.getMeasuredHeight());
} else { // Vertical layout
width = Math.max(width, child.getMeasuredWidth());
height += child.getMeasuredHeight();
}
}
// Take padding into account
width += getPaddingLeft() + getPaddingRight();
height += getPaddingTop() + getPaddingBottom();
// Respect the given measure spec
width = resolveSizeAndState(width, widthMeasureSpec, 0);
height = resolveSizeAndState(height, heightMeasureSpec, 0);
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width,
heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Position child views within your layout here
int childLeft = getPaddingLeft();
int childTop = getPaddingTop();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (orientation == 0) { // Horizontal layout
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
childLeft += child.getMeasuredWidth();
} else { // Vertical layout
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight());
childTop += child.getMeasuredHeight();
}
}
}
}
The recursion ends up with something like this under the hood:
LinearLayout rootView = activity.findViewById(R.id.root);
CustomLayout view = new CustomLayout(activity);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
view.setOrientation(1);
view.setBackgroundColor(Color.parseColor("yellow"));
View child1 = new CustomLayout(activity);
View child2 = new CustomLayout(activity);
child1.setLayoutParams(new LayoutParams(400, 400));
child1.setBackgroundColor(Color.parseColor("red"));
child2.setLayoutParams(new LayoutParams(200, 200));
child2.setBackgroundColor(Color.parseColor("green"));
view.addView(child1);
view.addView(child2);
rootView.addView(view);
My question is, how to apply top
and left
?
This is what I want to achieve:
The desired result is made with React Native. This is the code:
function App(): React.JSX.Element {
return (
<View style={{backgroundColor: 'yellow'}}>
<View style={{backgroundColor: 'red', width: 200, height: 200}}></View>
<View
style={{
backgroundColor: 'green',
top: 100,
left: 50,
width: 150,
height: 150,
}}></View>
</View>
);
}
As you can see, the yellow wrapper gets the right width and height based on its children, and the green view is moved outside of the wrapper without being clipped.
React Native uses its own layout extending ViewGroup also, so there must be a way.
The solution is to add these lines to every parent:
setClipChildren(false);
setClipToPadding(false);
Probably something like this:
public CustomLayout(Context context) {
super(context);
setClipChildren(false);
setClipToPadding(false);
}
And inside onLayout we have something like this for the children:
int x = 30; // 30 to the left
int y = 0;
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
child.layout(x, y, x + childWidth, y + childHeight);