开发者

generating a LayoutParams based on the type of parent

开发者 https://www.devze.com 2023-03-27 10:11 出处:网络
I find myself needing to create a View completely in Java without knowing what concrete type the parent is.

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

精彩评论

暂无评论...
验证码 换一张
取 消