【问题标题】:generating a LayoutParams based on the type of parent根据父类型生成 LayoutParams
【发布时间】:2011-10-24 11:59:30
【问题描述】:

我发现自己需要完全在 Java 中创建一个视图,而不知道父级是什么具体类型。

示例:

public View getView(int position, View convertView, ViewGroup parent){
    if(null == convertView){
        convertView = new TextView(parent.getContext());
    }
    ((TextView) convertView).setText(getItem(position).getName());
}

现在假设我想更改它,以便 convertView 在两个方向上都是 wrap_content。由于这是一个适配器,我想避免将适配器与父级的具体类型耦合,但我在 setLayoutParams() 中给它的 LayoutParams 必须是正确的具体类型,否则应用程序将崩溃(即,如果父级是ListView 必须是 ListView.LayoutParams,如果是 LinearLayout,则必须是 LinearLayout.LayoutParams,等等)。我也不想使用 switch 语句,因为这只是一种更灵活的耦合形式,如果我将此适配器附加到视图,我没想到我仍然会崩溃。有没有通用的方法来做到这一点?

【问题讨论】:

  • +1 想要以“正确的方式”编码

标签: android android-layout viewgroup


【解决方案1】:

您可以使用以下代码执行此操作:

LayoutParams params = parent.generateLayoutParams(null);

编辑: 上面的方法不起作用,因为ViewGroup.generateLayoutParams()需要在传递的AttributeSet中设置android:layout_widthandroid:layout_height

如果您将ViewGroup.LayoutParams 与任何布局一起使用,那么一切都会正常工作。但是如果你使用LinearLayout.LayoutParamsRelativeLayout 为例,那么就会抛出异常。

编辑: 对于这个问题,有一个我不太喜欢的可行解决方案。解决方案是使用有效的AttributeSet 调用generateLayoutParams()。您可以使用至少两种不同的方法创建AttributeSet 对象。我已经实现了其中之一:

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

另一种创建AttributeSet对象的方法是实现AttributeSet接口并使其返回android:layout_widthandroid:layout_height和您需要的其他布局属性。

【讨论】:

  • 这会导致运行时异常:您必须提供 layout_width 属性。
  • 根据源代码似乎是这样。要使用此方法,您必须提供一个实现 AttributeSet 的对象,并可用于获取 layout_widthlayout_height 值。而且我不知道创建这样一个对象的任何简单方法。您确定需要提供具体的LayoutParams 子类吗?我总是使用ViewGroup.LayoutParams,一切正常。
  • 我之前遇到过它崩溃并解决了问题。
  • 我知道我迟到了……但这个答案是错误的。例如,使用 Viewgroup.LayoutParams 代替 TableRow.LayoutParams 是不好的。我遇到了许多视图根本不显示结果的情况。此外,ViewGroup.LayoutParams 缺少您可能想要使用的方法,例如 setMargin。
  • 不起作用。在视图上设置 ViewGroup.LayoutParams,然后将其添加到 RelativeLayout。结果:java.lang.ClassCastException: android.view.ViewGroup$LayoutParams 无法转换为 android.widget.RelativeLayout$LayoutParams
【解决方案2】:

我有以下解决方法:

View view = new View(context);
parent.addView(view);

LayoutParams params = view.getLayoutParams();
//Do whatever you need with the parameters
view.setLayoutParams(params);

你不能自己自动生成正确的LayoutParams,除非你做一些hacky,所以你应该创建一个为你自动生成它们的情况:只需将视图添加到容器中。之后,您可以从视图中获取它们并执行您需要的操作。

唯一需要注意的是,如果您不需要自己将视图添加到容器中,则必须稍后从中删除视图,但这应该不是问题。

【讨论】:

  • 这不适用于ListAdapters,因为View 将在getView() 返回后添加到父级
  • @keyboardr 没问题的,你可以自己加个视图,获取参数,去掉。
【解决方案3】:

为什么没有人(尚未 -> 见 2016.05)在这里提到基于反射的方法?

1.入口点:

 /**
 *  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。用法:

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

【讨论】:

  • 因为在 Android 上的反射成本很高。
  • @PaulWoitaschek “曾经”我们现在拥有现代设备(4/8 核和 2-6gb 内存),因此对于用户体验而言,成本不高 - 如果您认为不同,请发布证明
【解决方案4】:

所有LayoutParams 类都有一个通用超类:ViewGroup.LayoutParams。并且所有流行的布局(FrameLayoutLinearLayoutConstraintLayout 等)都使用ViewGroup.MarginLayoutParams 作为它们各自LayoutParams 类的基类。

因此,如果您只需要宽度、高度和边距,您可以创建ViewGroup.MarginLayoutParams 并将其作为布局参数传递给ViewGroup 的任何子类。然后会发生的是容器会使用protected LayoutParams ViewGroup#generateLayoutParams(ViewGroup.LayoutParams p) 自动将更通用的ViewGroup.MarginLayoutParams 转换为自己的布局参数。

此代码适用于任何container 类,包括LinearLayoutRelativeLayout 等:

val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
container.addView(view, layoutParams)

【讨论】:

    猜你喜欢
    • 2019-11-16
    • 1970-01-01
    • 1970-01-01
    • 2018-08-06
    • 2013-05-09
    • 2019-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多