generating a LayoutParams based on the type of parent
I have the following workaround for this:
View view = new View(context);
parent.addView(view);
LayoutParams params = view.getLayoutParams();
//Do whatever you need with the parameters
view.setLayoutParams(params);
You cannot autogenerate the correct LayoutParams
yourself unless you do something hacky, so you should just create a situation where they will be autogenerated for you: just add the view to the container. After that you can get them from the view and do what you need.
The only caveat is that if you don't need to add the view to the container yourself, you'll have to remove the view from it later, but this shouldn't be a problem.
why no one (yet -> see 2016.05) has mentioned here an approach based on reflections ?
1. entry point:
/**
* generates default layout params for given view group
* with width and height set to WLayoutParams.RAP_CONTENT
*
* @param viewParent - parent of this layout params view
* @param <L> - layout param class
* @return layout param class object
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@NonNull
private <L extends ViewGroup.LayoutParams> L generateDefaultLayoutParams(@NonNull ViewGroup viewParent)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method generateDefaultLayoutParamsMethod = ViewGroup.class.getDeclaredMethod("generateDefaultLayoutParams");
// caution: below way to obtain method has some flaw as we need traverse superclasses to obtain method in case we look in object and not a class
// = viewParent.getClass().getDeclaredMethod("generateDefaultLayoutParams");
generateDefaultLayoutParamsMethod.setAccessible(true);
return (L) generateDefaultLayoutParamsMethod.invoke(viewParent);
}
2. usages:
@NonNull
protected <T extends ViewGroup.LayoutParams> T createLayoutParamsForView(ViewGroup viewParent,
@IdRes int belowViewId)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return createLayoutParamsForView(null,null,viewParent,belowViewId);
}
@NonNull
protected <T extends ViewGroup.LayoutParams> T createLayoutParamsForView(@NonNull Context context,
@NonNull Class<? extends ViewGroup> parentClass,
@IdRes int belowViewId)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
return createLayoutParamsForView(context,parentClass,null,belowViewId);
}
@NonNull
private <T extends ViewGroup.LayoutParams> T createLayoutParamsForView(@Nullable Context context,
@Nullable Class<? extends ViewGroup> parentClass,
@Nullable ViewGroup parent,
@IdRes int belowViewId)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if(context == null && parent == null) throw new IllegalStateException("either context and parent class or must be non null!");
T layoutParams = (T) (parent != null ? generateDefaultLayoutParams(parent) : generateDefaultLayoutParams(context, parentClass));
if (belowViewId != NO_ID && RelativeLayout.LayoutParams.class.isAssignableFrom(layoutParams.getClass())){
((RelativeLayout.LayoutParams)layoutParams).addRule(RelativeLayout.BELOW, belowViewId);
}
return layoutParams;
}
@NonNull
private <P extends ViewGroup> P instantiateParent(Class parentClass, Context context)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor constructor = parentClass.getDeclaredConstructor(Context.class);
constructor.setAccessible(true);
return (P) constructor.newInstance(context);
}
@NonNull
private <L extends ViewGroup.LayoutParams, P extends ViewGroup> L generateDefaultLayoutParams(Context context, @NonNull Class<? extends ViewGroup> viewParentClass)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
P viewParent = instantiateParent(viewParentClass, context);
return generateDefaultLayoutParams(viewParent);
}
You can do this using the following code:
LayoutParams params = parent.generateLayoutParams(null);
EDIT:
The method above doesn't work because ViewGroup.generateLayoutParams()
requires android:layout_width
and android:layout_height
to be set in the passed AttributeSet
.
If you use ViewGroup.LayoutParams
with any layout then everything will work fine. But if you use LinearLayout.LayoutParams
with RelativeLayout
for example, then an exception will be thrown.
EDIT:
There's one working solution for this problem which I don't really like. The solution is to call generateLayoutParams()
with valid AttributeSet
. You can create an AttributeSet
object using at least two different approaches. One of them I've implemented:
res\layout\params.xml:
<?xml version="1.0" encoding="utf-8"?>
<view xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dip" />
SomeActivity.java:
private void addView(ViewGroup viewGroup, View view) {
viewGroup.addView(view);
view.setLayoutParams(generateLayoutParams(viewGroup));
}
private ViewGroup.LayoutParams generateLayoutParams(ViewGroup viewGroup) {
XmlResourceParser parser = getResources().getLayout(R.layout.params);
try {
while(parser.nextToken() != XmlPullParser.START_TAG) {
// Skip everything until the view tag.
}
return viewGroup.generateLayoutParams(parser);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Another way to create an AttributeSet
object is to implement AttributeSet
interface and make it return android:layout_width
, android:layout_height
and other layout attributes you need.