Пользовательский элемент управления Android с детьми

Я пытаюсь создать пользовательский элемент управления Android , содержащий LinearLayout . Вы можете думать об этом как о расширенном LinearLayout с фантастическими границами, фоном, изображением слева …

Я мог бы все это сделать в XML (отлично работает), но поскольку у меня есть десятки случаев в моем приложении, его сложно поддерживать. Я подумал, что было бы лучше иметь что-то вроде этого:

/* Main.xml */ <MyFancyLayout> <TextView /> /* what goes inside my control's linear layout */ </MyfancyLayout> 

Как бы вы к этому подошли? Я бы хотел избежать повторной записи всей линейной компоновки методов onMeasure / onLayout. Это то, что у меня есть на данный момент:

 /* MyFancyLayout.xml */ <TableLayout> <ImageView /> <LinearLayout id="container" /> /* where I want the real content to go */ </TableLayout> 

а также

 /* MyFancyLayout.java */ public class MyFancyLayout extends LinearLayout { public MyFancyLayout(Context context) { super(context); View.inflate(context, R.layout.my_fancy_layout, this); } } 

Как вы собираетесь вставлять указанный пользователем контент (TextView в main.xml) в нужное место (id = container)?

Ура!

Ромен

—– редактировать ——-

Мне все равно не повезло, поэтому я изменил свой дизайн, чтобы использовать более простой макет и решил жить с немного повторяющимся XML. Все еще очень интересно, кто-нибудь знает, как это сделать, хотя!

Этот точный вопрос уже давно звучал, но только сейчас я решил это.

С первого взгляда проблема заключается в том, что декларативный контент (TextView в вашем случае) создается через некоторое время после ctor (где мы обычно раздуваем наши макеты), поэтому слишком рано иметь как декларативный, так и шаблонный контент под рукой Толкать первое внутри последнего.

Я нашел одно такое место, где мы можем манипулировать ими: это метод onFinishInflate (). Вот как это происходит в моем случае:

  @Override protected void onFinishInflate() { int index = getChildCount(); // Collect children declared in XML. View[] children = new View[index]; while(--index >= 0) { children[index] = getChildAt(index); } // Pressumably, wipe out existing content (still holding reference to it). this.detachAllViewsFromParent(); // Inflate new "template". final View template = LayoutInflater.from(getContext()) .inflate(R.layout.labeled_layout, this, true); // Obtain reference to a new container within "template". final ViewGroup vg = (ViewGroup)template.findViewById(R.id.layout); index = children.length; // Push declared children into new container. while(--index >= 0) { vg.addView(children[index]); } // They suggest to call it no matter what. super.onFinishInflate(); } 

Указанный выше labeled_layout.xml не похож на что-то вроде этого:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation ="vertical" android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:layout_marginLeft ="8dip" android:layout_marginTop ="3dip" android:layout_marginBottom ="3dip" android:layout_weight ="1" android:duplicateParentState ="true"> <TextView android:id ="@+id/label" android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:singleLine ="true" android:textAppearance ="?android:attr/textAppearanceMedium" android:fadingEdge ="horizontal" android:duplicateParentState="true" /> <LinearLayout android:id ="@+id/layout" android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:layout_marginLeft ="8dip" android:layout_marginTop ="3dip" android:duplicateParentState="true" /> </LinearLayout> 

Теперь (все еще опуская некоторые детали) в другом месте мы можем использовать его следующим образом:

  <com.example.widget.LabeledLayout android:layout_width ="fill_parent" android:layout_height ="wrap_content"> <!-- example content --> </com.example.widget.LabeledLayout> 

Такой подход позволяет мне сэкономить много кода! 🙂

Как следует из объяснения, просто поменяйте содержимое, определяемое xml, туда, где вы хотите их внутренне в своем собственном макете, в onFinishInflate() . Пример:

Я беру содержимое, которое я указываю в xml:

 <se.jog.custom.ui.Badge ... > <ImageView ... /> <TextView ... /> </se.jog.custom.ui.Badge> 

… и переместите их на мой внутренний LinearLayout называется contents где я хочу:

 public class Badge extends LinearLayout { //... private LinearLayout badge; private LinearLayout contents; // This way children can be added from xml. @Override protected void onFinishInflate() { View[] children = detachChildren(); // gets and removes children from parent //... badge = (LinearLayout) layoutInflater.inflate(R.layout.badge, this); contents = (LinearLayout) badge.findViewById(R.id.badge_contents); for (int i = 0; i < children.length; i++) addView(children[i]); //overridden, se below. //... super.onFinishInflate(); } // This way children can be added from other code as well. @Override public void addView(View child) { contents.addView(child); } 

В сочетании с пользовательскими атрибутами XML все становится очень удобным.

Вы можете создать свой класс MyFancyLayout, расширив LinearLayout. Добавьте три конструктора, которые вызывают метод («инициализируйте» в этом случае), чтобы настроить остальные части:

 public MyFancyLayout(Context context) { super(context); initialize(); } public MyFancyLayout(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public MyFancyLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } 

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

 final LayoutInflater inflator = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflator.inflate(R.layout.somecommonlayout, this); 

Или вы можете создавать Views в коде и добавлять их:

  ImageView someImageView = new ImageView(getContext()); someImageView.setImageDrawable(myDrawable); someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); addView(someImageView); 

Если вы собираетесь использовать контекст много, вы можете сохранить ссылку на него в своих конструкторах и использовать это вместо getContext() чтобы сэкономить немного накладных расходов.

Просто используйте что-то вроде этого:

 <org.myprogram.MyFancyLayout> ... </org.myprogram.MyFancyLayout> 

Полезная ссылка – http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html