I find myself needing to create a View completely in Java without knowing what concrete type the parent is.
example:
public View getView(int position, View convertView, ViewGroup parent){
if(null == convertView){
convertView = new TextView(parent.getContext());
}
((TextView) convertView).setText(getItem(position).getName());
}
Now suppose I wanted to change this so that the convertView was wrap_content in both directions. Since this is an Adapter, I'd like to avoid coupl开发者_如何学Going the Adapter with the concrete type of the parent, but the LayoutParams I give it in setLayoutParams() has to be the correct concrete type otherwise the app will crash (i.e. if parent is a ListView it has to be ListView.LayoutParams, if it's a LinearLayout it must be a LinearLayout.LayoutParams, etc.). I don't want to use a switch statement either since that's just a more flexible form of coupling, and if I attach this adapter to a view I didn't anticipate I still end up with a crash. Is there a generic way to do this?
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.
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.
All LayoutParams
classes have one general superclass: ViewGroup.LayoutParams
. And all popular layouts (FrameLayout
, LinearLayout
, ConstraintLayout
, etc.) use ViewGroup.MarginLayoutParams
as base class for their respective LayoutParams
classes.
So if you just need width, height and margins, you can create ViewGroup.MarginLayoutParams
and pass it as layout params to any subclass of ViewGroup
. What will happen then is container will automatically convert more generic ViewGroup.MarginLayoutParams
into its own layout parameters using protected LayoutParams ViewGroup#generateLayoutParams(ViewGroup.LayoutParams p)
.
This code will work for any container
class, including LinearLayout
, RelativeLayout
, etc:
val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
container.addView(view, layoutParams)
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);
}
精彩评论