создание LayoutParams на основе типа родителя

Мне нужно полностью создать представление на 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 в обоих направлениях. Поскольку это адаптер, я хотел бы избежать связывания адаптера с конкретным типом родителя, но LayoutParams, которые я даю ему в setLayoutParams(), должен быть правильным конкретным типом, иначе приложение выйдет из строя (т.е. если родитель ListView должен быть ListView.LayoutParams, если это LinearLayout, то это должен быть LinearLayout.LayoutParams и т. д.). Я также не хочу использовать оператор switch, так как это просто более гибкая форма связи, и если я присоединю этот адаптер к представлению, которого не ожидал, все равно произойдет сбой. Есть ли общий способ сделать это?


person keyboardr    schedule 10.08.2011    source источник
comment
+1 За желание правильно кодировать   -  person samis    schedule 10.10.2012


Ответы (4)


Вы можете сделать это, используя следующий код:

LayoutParams params = parent.generateLayoutParams(null);

EDIT: описанный выше метод не работает, потому что ViewGroup.generateLayoutParams() требует установки android:layout_width и android:layout_height в переданном AttributeSet.

Если вы используете ViewGroup.LayoutParams с любым макетом, все будет работать нормально. Но если вы используете LinearLayout.LayoutParams, например, с RelativeLayout, будет выдано исключение.

EDIT: есть одно рабочее решение этой проблемы, которое мне не очень нравится. Решение состоит в том, чтобы вызвать generateLayoutParams() с действительным AttributeSet. Вы можете создать объект 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_width, android:layout_height и другие необходимые вам атрибуты макета.

person Michael    schedule 10.08.2011
comment
Это вызывает исключение во время выполнения: вы должны предоставить атрибут layout_width. - person keyboardr; 11.08.2011
comment
Судя по исходному коду, это похоже на правду. Чтобы использовать этот метод, вы должны предоставить объект, который реализует AttributeSet и может использоваться для получения значений layout_width и layout_height. И я не знаю простого способа создать такой объект. Вы уверены, что вам нужно предоставить конкретные LayoutParams подклассы? Я всегда использую ViewGroup.LayoutParams и все работает нормально. - person Michael; 11.08.2011
comment
У меня уже был сбой на мне раньше, и я устранил его до этого. - person keyboardr; 12.08.2011
comment
Думаю если использовать ViewGroup.LayoutParams с любой раскладкой то все будет нормально работать. Но если вы используете LinearLayout.LayoutParams, например, с RelativeLayout, то будет выдано исключение. - person Michael; 12.08.2011
comment
Я знаю, что опаздываю... но этот ответ неверен. Использование Viewgroup.LayoutParams вместо, например, TableRow.LayoutParams не очень хорошо. Я столкнулся со многими случаями, когда представления просто не отображались в результате. Кроме того, в ViewGroup.LayoutParams отсутствуют методы, которые вы можете захотеть использовать, такие как setMargin. - person aleph_null; 13.11.2011
comment
Не работает. Установите ViewGroup.LayoutParams в своем представлении, а затем добавьте его в RelativeLayout. Результат: java.lang.ClassCastException: android.view.ViewGroup$LayoutParams нельзя преобразовать в android.widget.RelativeLayout$LayoutParams - person cdhabecker; 01.09.2012
comment
@aleph-null, я добавил к ответу рабочее решение. Это не очень элегантно, но я надеюсь, что это поможет вам. - person Michael; 03.09.2012
comment
@cdhabecker, взгляните на новое решение. Может быть, это будет полезно для вас. - person Michael; 03.09.2012
comment
@Michael Вы знаете, как долго я пытался создать свой собственный объект AttributeSet !!! Вы просто сделали мой день, спасибо! - person samis; 10.10.2012
comment
@Michael Хорошо, я не вижу примера AttributeSet, но вашего предложения о том, как его использовать, будет достаточно. - person samis; 10.10.2012
comment
@SamusArin, может быть, я добавлю, если у меня будет достаточно времени. Но я не могу обещать. Если у вас есть рабочий образец, вы можете добавить его самостоятельно. - person Michael; 11.10.2012

У меня есть следующий обходной путь для этого:

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

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

Вы не можете автоматически сгенерировать правильный LayoutParams самостоятельно, если только вы не сделаете что-то хакерское, поэтому вы должны просто создать ситуацию, когда они будут автоматически сгенерированы для вас: просто добавьте представление в контейнер. После этого вы можете получить их из представления и делать то, что вам нужно.

Единственное предостережение: если вам не нужно добавлять представление в контейнер самостоятельно, вам придется удалить представление из него позже, но это не должно быть проблемой.

person Malcolm    schedule 11.12.2014
comment
Это не работает для ListAdapters, так как View будет добавлено к родителю после возврата getView() - person keyboardr; 25.04.2015
comment
@keyboardr Это не проблема, вы можете сами добавить представление, получить параметры и удалить его. - person Malcolm; 25.04.2015

почему никто (пока -> см. 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);
}
person ceph3us    schedule 08.05.2016
comment
Потому что отражение на Android дорого обходится. - person Paul Woitaschek; 19.06.2018
comment
@PaulWoitaschek у нас теперь есть современные устройства (4/8 ядер и 2-6 ГБ ОЗУ), поэтому для удобства пользователей стоимость ничего не стоит - если вы считаете иначе, пожалуйста, опубликуйте доказательство - person ceph3us; 20.06.2018

Все классы 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)
person Alexey    schedule 15.03.2019