Пользовательский элемент управления Android с детьми
Я пытаюсь создать пользовательский элемент управления Android, который содержит LinearLayout. Вы можете думать об этом как о расширенном LinearLayout с фантастическими границами, фоном, изображением слева...
Я мог бы все это сделать в XML (отлично работает), но поскольку у меня есть десятки случаев в моем приложении, его трудно поддерживать. Я думал, что было бы лучше иметь что-то вроде этого:
/* Main.xml */
<MyFancyLayout>
<TextView /> /* what goes inside my control 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)?
Ура!
Ромны
----- edit -------
По-прежнему вам не повезло, поэтому я изменил свой дизайн, чтобы использовать более простой макет и решил жить с немного повторяющимся XML. Все еще очень интересно, кто-нибудь знает, как это сделать, хотя!
Ответы
Ответ 1
Этот точный вопрос прослушивал меня уже некоторое время, но только сейчас, когда я его решил.
С первого взгляда проблема заключается в том, что декларативный контент (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>
Ответ 2
Этот подход сэкономит мне много кода!:)
Как следует из объяснения, просто поменяйте содержимое, определенное 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 все становится очень удобным.
Ответ 3
Вы можете создать свой класс 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);
Или вы можете создавать представления в коде и добавлять их:
ImageView someImageView = new ImageView(getContext());
someImageView.setImageDrawable(myDrawable);
someImageView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addView(someImageView);
Если вы собираетесь использовать контекст много, вы можете сохранить ссылку на него в своих конструкторах и использовать это, а не getContext()
, чтобы сохранить небольшие накладные расходы.
Ответ 4
просто используйте что-то вроде этого:
<org.myprogram.MyFancyLayout>
...
</org.myprogram.MyFancyLayout>
Полезная ссылка - http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html