与儿童一起定制 Android 控件

发布于 2024-10-10 14:36:30 字数 1068 浏览 2 评论 0原文

我正在尝试创建一个自定义 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);
    }
}

如何将用户指定的内容(main.xml 中的 TextView)插入到正确的位置(id=container)?

干杯!

Romain

----- 编辑 -------

仍然没有运气,所以我改变了我的设计以使用更简单的布局,并决定接受一些重复的 XML。但仍然对任何人知道如何做到这一点非常感兴趣!

I'm trying to create a custom Android control that contains a LinearLayout. You can think of it as an extended LinearLayout with fancy borders, a background, an image on the left...

I could do it all in XML (works great) but since I have dozens of occurences in my app it's getting hard to maintain. I thought it would be nicer to have something like this:

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

How would you approach this? I'd like to avoid re-writing the whole linear layout onMeasure / onLayout methods. This is what I have for the moment:

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

and

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

How would you go about inserting the user-specified content (the TextView in main.xml) in the right place (id=container)?

Cheers!

Romain

----- edit -------

Still no luck on this, so I changed my design to use a simpler layout and decided to live with a bit of repeated XML. Still very interested in anyone knows how to do this though!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

葵雨 2024-10-17 14:36:30

这个确切的问题已经困扰我一段时间了,但直到现在我才解决了它。

乍一看,问题在于这样一个事实:声明性内容(在您的例子中为 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> 

This exact question bugged me for some time already but it's only now that I've solved it.

From a first glance, the problem lies in the fact that a declarative content (TextView in Your case) is instantiated sometime after ctor (where we're usually inflating our layouts), so it's too early have both declarative and template content at hand to push the former inside the latter.

I've found one such place where we can manipulate the both: it's a onFinishInflate() method. Here's how it goes in my case:

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

A labeled_layout.xml referenced above is not unlike something like this:

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

Now (still omitting some details) elsewhere we might use it like this:

        <com.example.widget.LabeledLayout
            android:layout_width    ="fill_parent"
            android:layout_height   ="wrap_content">
            <!-- example content -->
        </com.example.widget.LabeledLayout> 
我爱人 2024-10-17 14:36:30

这种方法节省了我很多代码! :)

正如 esteewhy 所解释的,只需将 xml 定义的内容交换到您自己的布局中您想要的内部位置,即 onFinishInflate() 中。示例:

我获取在 xml 中指定的内容:

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

... 并将它们移动到名为 contents 的内部 LinearLayout 中,我希望它们位于以下位置:

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 属性变得非常易于维护。

This approach saves me a lot of code! :)

As esteewhy explains, just swap the xml-defined contents into where you want them internally in your own layout, in onFinishInflate(). Example:

I take the contents that I specify in the xml:

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

... and move them to my internal LinearLayout called contents where I want them to be:

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

Combined with custom XML attributes things gets very maintainable.

生活了然无味 2024-10-17 14:36:30

您可以通过扩展 LinearLayout 来创建 MyFancyLayout 类。添加三个调用方法(在本例中为“初始化”)的构造函数来设置其余视图:

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

如果您要大量使用 Context,您可以在构造函数中存储对它的引用并使用它而不是 getContext() 以节省一点开销。

You can create your MyFancyLayout class by extending LinearLayout. Add the three constructors which call a method ("initialize" in this case) to set up the rest of the Views:

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

Within initialize, you do anything you need to to add the extra views. You can get the LayoutInflater and inflate another layout:

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

Or you can create Views in code and add them:

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

If you're going to use the Context a lot, you can store a reference to it in your constructors and use that rather than getContext() to save a little overhead.

梦晓ヶ微光ヅ倾城 2024-10-17 14:36:30

只需使用这样的东西:

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

有用的链接 - http://www.anddev.org/creating_custom_views_- _the_togglebutton-t310.html

just use something like this:

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

Useful link - http://www.anddev.org/creating_custom_views_-_the_togglebutton-t310.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文