我发现自己需要完全用Java创建一个View而不知道父类的具体类型。
例:
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。由于这是一个适配器,我想避免将Adapter与父类的具体类型耦合,但我在setLayoutParams()中给出的LayoutParams必须是正确的具体类型,否则应用程序将崩溃(即如果父类是一个ListView它必须是ListView.LayoutParams,如果它是一个LinearLayout,它必须是一个LinearLayout.LayoutParams等)。我不想使用switch语句,因为这只是一种更灵活的耦合形式,如果我将这个适配器连接到一个视图,我没想到我仍然会崩溃。有没有通用的方法来做到这一点?
您可以使用以下代码执行此操作:
LayoutParams params = parent.generateLayoutParams(null);
编辑:上述方法不起作用,因为ViewGroup.generateLayoutParams()
要求android:layout_width
和android:layout_height
设置在传递的AttributeSet
。
如果你使用ViewGroup.LayoutParams
任何布局,那么一切都会正常工作。但是如果你使用LinearLayout.LayoutParams
和RelativeLayout
,那么就会抛出异常。
编辑:有一个解决这个问题的工作方案,我不太喜欢。解决方案是使用有效的generateLayoutParams()
调用AttributeSet
。您可以使用至少两种不同的方法创建AttributeSet
对象。其中一个我已实施:
水库\布局\ 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" />
some activity.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_width
,android:layout_height
和您需要的其他布局属性。
我有以下解决方法:
View view = new View(context);
parent.addView(view);
LayoutParams params = view.getLayoutParams();
//Do whatever you need with the parameters
view.setLayoutParams(params);
你不能自己自动生成正确的LayoutParams
,除非你做了一些hacky,所以你应该创建一个为你自动生成它们的情况:只需将视图添加到容器中。之后,您可以从视图中获取它们并执行您需要的操作。
唯一需要注意的是,如果您不需要自己将视图添加到容器中,则必须稍后从中删除视图,但这应该不是问题。
为什么没有人(> - 见2016.05)在这里提到了一种基于反思的方法?
入口点:
/**
* 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);
}
用途:
@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);
}
所有LayoutParams
类都有一个通用超类:ViewGroup.LayoutParams
。所有流行的布局(FrameLayout
,LinearLayout
,ConstraintLayout
等)都使用ViewGroup.MarginLayoutParams
作为他们各自的LayoutParams
类的基类。
因此,如果您只需要宽度,高度和边距,则可以创建ViewGroup.MarginLayoutParams
并将其作为布局参数传递给ViewGroup
的任何子类。然后会发生什么是容器会使用ViewGroup.MarginLayoutParams
自动将更多通用的protected LayoutParams ViewGroup#generateLayoutParams(ViewGroup.LayoutParams p)
转换为自己的布局参数。
此代码适用于任何container
类,包括LinearLayout
,RelativeLayout
等:
val layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
container.addView(view, layoutParams)