如何在自定义ViewGroup中正确地填充XML布局文件?

64

我希望在自定义的ViewGroup类中填充XML-Layout文件,但我的问题是它只产生一个空屏幕。在Activity类中做同样的事情却可以正常工作。 这是我的简单XML-Layout文件:

shownumberlayout.xml:

    <RelativeLayout 
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent" 
        android:layout_height="fill_parent"
        android:background="#FFFFFF" 
        android:id="@+id/layoutForNumber">

        <TextView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:id="@+id/tvNumber"
            android:layout_centerHorizontal="true" 
            android:textColor="#000000" 
            android:text="Test" 
            android:layout_centerVertical="true" 
            android:textSize="30dip">
        </TextView>

    </RelativeLayout>

这是可用版本,将 shownumberlayout.xml 填充到活动 ShowNumber 中:
ShowNumber.class

public class ShowNumber extends Activity {
    /** Called when the activity is first created. */

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        ViewGroup vg = (ViewGroup) inflater.inflate(R.layout.shownumberlayout, null);
        setContentView(vg);
    }
}

这显示了一个白色背景,黑色文本“测试”居中。

现在,在自定义ViewGroup类中填充xml的版本:

ViewGroup.class
public class ViewNumber extends ViewGroup {

    private LayoutInflater inflater;

    public ViewNumber(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        initView(context);
    }

    private void initView(Context context){
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.shownumberlayout, null);  
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
    }

}

ShowNumber.class public class ShowNumber extends Activity { // 当活动首次创建时调用此方法。 }

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ViewGroup vg = new ViewNumber(this); 
    setContentView(vg);
}

我基本上是按照这个答案所解释的方式来做。 但是这只会产生一个空黑屏。我做错了什么吗?

更新1

@Konstantin 我应用了你的更改,但仍然只有一个空白屏幕,我还为获取子元素数量进行了日志输出。它始终保持为1,即使我在XML布局文件中添加了另一个Textview。在更改之前,它始终为0。

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        //inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        //inflater.inflate(R.layout.shownumberlayout, null);
        View.inflate(context, R.layout.shownumberlayout,this); 
        Log.v("ViewNumber", "Number of Child: " + this.getChildCount());//output is 1,before it remains 0
    }
...
}

@Sankar 这是经过Konstantin更改后的Logcat:

12-16 09:24:23.606: DEBUG/AndroidRuntime(8951): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
12-16 09:24:23.606: DEBUG/AndroidRuntime(8951): CheckJNI is OFF
12-16 09:24:23.606: DEBUG/dalvikvm(8951): creating instr width table
12-16 09:24:23.656: DEBUG/AndroidRuntime(8951): --- registering native functions ---
12-16 09:24:23.916: DEBUG/AndroidRuntime(8951): Shutting down VM
12-16 09:24:23.916: DEBUG/dalvikvm(8951): Debugger has detached; object registry had 1 entries
12-16 09:24:23.916: INFO/AndroidRuntime(8951): NOTE: attach of thread 'Binder Thread #3' failed
12-16 09:24:24.076: DEBUG/AndroidRuntime(8960): >>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<
12-16 09:24:24.076: DEBUG/AndroidRuntime(8960): CheckJNI is OFF
12-16 09:24:24.076: DEBUG/dalvikvm(8960): creating instr width table
12-16 09:24:24.126: DEBUG/AndroidRuntime(8960): --- registering native functions ---
12-16 09:24:24.376: INFO/ActivityManager(78): Starting activity: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=org.customview.harold/.ShowNumber }
12-16 09:24:24.426: DEBUG/AndroidRuntime(8960): Shutting down VM
12-16 09:24:24.426: DEBUG/jdwp(8960): Got wake-up signal, bailing out of select
12-16 09:24:24.426: DEBUG/dalvikvm(8960): Debugger has detached; object registry had 1 entries
12-16 09:24:24.456: INFO/AndroidRuntime(8960): NOTE: attach of thread 'Binder Thread #3' failed
12-16 09:24:24.456: VERBOSE/ViewNumber(8923): Number of Child: 1
12-16 09:24:24.496: VERBOSE/RenderScript_jni(164): surfaceDestroyed
12-16 09:24:24.526: INFO/ActivityManager(78): Displayed activity org.customview.harold/.ShowNumber: 104 ms (total 104 ms)
12-16 09:24:24.576: DEBUG/dalvikvm(158): GC_FOR_MALLOC freed 10631 objects / 526248 bytes in 52ms
12-16 09:24:34.606: DEBUG/dalvikvm(164): GC_EXPLICIT freed 1776 objects / 106960 bytes in 91ms

更新2

内容最终正确显示了。 缺少的是在RelativeLayout子类中覆盖方法onLayout(感谢Franco):

public class ViewNumber extends RelativeLayout {
...

    @Override
            protected void onLayout(boolean changed, int l, int t, int r, int b) {
                // TODO Auto-generated method stub
                for(int i = 0 ; i < getChildCount() ; i++){
                    getChildAt(i).layout(l, t, r, b);
                }
            }
...
}

注意:稍后您还应该覆盖方法onMeasurement(),但当前未覆盖也可以正确显示内容。
现在,来自FrancoinitView方法的解决方案不会将TextView居中,而是将其放在左上角。来自Konstantin的解决方案将其正确地放在视图中心。
public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        View.inflate(context, R.layout.shownumberlayout,this); 
    }
...
}

@Harlod:请查看您的Logcat并告诉我您在Logcat中捕获了什么消息? - Sankar Ganesh PMP
是的,Harold,你说得对,我试过了,在层次结构视图工具中可以看到TextView与宽度和高度均为0,位于左上角对齐。 - Franco
6个回答

48

我不知道这些答案在说什么,我认为手动调用onLayout有点疯狂。

LayoutInflater.inflate函数相当直白。如果你使用接受第三个布尔参数(attachToRoot)的函数并且它是true,它会将XML中的视图添加到父视图(第二个参数)中。如果你使用只接受2个参数的函数,并且提供的父视图不是null,它会默认传递true给attachToRoot。

无论如何,你遇到的大部分问题都是因为XML中的视图被添加到了你的自定义视图中。由于你的XML具有RelativeLayout根并且你的自定义视图也是一个RelativeLayout - 你正在得到一个相对布局内嵌在相对布局中。

这可能不是你想要的。

Konstantin给出的答案很有道理,但是你浪费了一个视图,因为你把RelativeLayout放在了FrameLayout中。由于Android无法容纳太多嵌套视图,所以最好进行优化,不要添加这个不必要的FrameLayout

我建议让你的自定义视图保持为RelativeLayout,并将你的XML根更改为<merge>。然后使用添加XML视图到父视图中的inflate形式 - 如View.inflate(context,int,this)

<merge>标记的目的是跳过XML中的根节点..查一下。


3
的确如此。对于仍然对布局填充感到困惑的人,这是一篇非常好的文章,它很好地解释了应该如何完成:http://www.doubleencore.com/2013/05/layout-inflation-as-intended/ - fgeorgiew
1
@Ran 这不是一个真正的答案。这只是一种抱怨。真正的答案应该简洁明了,并展示像被接受的答案或Konstantin的答案那样的代码片段。然而对于那个抱怨+1. - 1111161171159459134

36

你的布局没有被填充到任何地方.. 让你的ViewNumber继承FrameLayout并将其填充到其中:

public class ViewNumber extends FrameLayout {

    public ViewNumber(Context context) {
        super(context);
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public ViewNumber(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(context);
    }

    private void initView(Context context){
        View.inflate(context, R.layout.shownumberlayout, this);  //correct way to inflate..
    }
}

更新:你不需要覆盖onLayout方法,那看起来非常不正确... 至少在一开始调用super.onLayout():

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}

我已经应用了你的更改,但仍然是一个空白屏幕。请查看我的更新帖子。 - Harold
@Harold:我认为这是因为你的onLayout实现完全不正确,详见更新。 - Konstantin Burov
@Harold:哦,我看到@Franco已经指出了这一点。但是如果你仔细看,我的原始片段中没有onLayout方法,也许下次我应该更详细地说明 :) - Konstantin Burov
@KonstantinBurov,一开始很难理解你参数的变量选择。你能否将它们改为Left、Top、Right、Bottom,而不是l、t、r、b呢? :) - codingbiz
1
@codingbiz 谢谢,已更新。虽然我相信原始代码是由基于方法签名的IDE生成的。 - Konstantin Burov

33

你试过这个吗?

private void initView(Context context){
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.addView(inflater.inflate(R.layout.shownumberlayout, null));  
    }

编辑:我会快速响应……ViewGroup有责任为其子视图准备布局,在layout方法中尝试以下操作:

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            // TODO Auto-generated method stub
            for(int i = 0 ; i < getChildCount() ; i++){
                getChildAt(i).layout(l, t, r, b);
            }
        }

是的!它正在工作,内容正在显示。非常感谢!我会更新我的帖子。 - Harold
没问题!确保在子类化ViewGroup类时覆盖onLayout和onMeasurement两个方法。 - Franco
1
这个特定的答案在从同一XML实例化多个视图时给了我一个问题。我通过用您的答案中的onLayout方法替换super(changed, l, t, r, b)来解决了这个问题。否则它非常好,我感谢您! - Leo supports Monica Cellio
嘿,我有一个XML文件,其中包含一个LinearLayout和2个嵌套的TextView,就像Franco在他的帖子中展示的那样。inflate工作正常,但在onLayout方法中,我遇到了NullpointerException。具体来说,getChildAt(i)给我LinearLayout,但是当我调用getChildAt(i).layout(l,t,r,b)时,我会得到NullPointerException。有人能给我提示为什么吗? - moony

2

当您创建一个ViewNumber对象(ViewGroup vg = new ViewNumber(this))时,重写onLayout是必要的。这类似于以下xml文件的膨胀:

<ViewNumber
           I have no child :(
</ViewNumber>

因此,如果您不覆盖onLayout,则ViewNumber的onLayout将找不到任何子项,那么它将为空白。

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
}

在重写后,它将使用ViewNumber的父级的onLayout方法。如果您的代码如下:

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        View.inflate(context, R.layout.shownumberlayout,this); 
    }
...
}

然后就像是在展开 XML。
  <RelativeLayout
    <RelativeLayout 
            your xml file
                   />
  </RelativeLayout>

还有一个RelativeLayout。为了解决这个问题,你可以这样写:

public class ViewNumber extends RelativeLayout {
...
private void initView(Context context){
        inflater.inflate( R.layout.login_view_layout, null );
              //change (..,this) to (..,null)
    }
...
}

然而,有些意外情况会发生。你xml文件中的android:layout_xxx=".."可能会改变(这就是为什么你的文本放在左上角的原因)。要了解更多和更好的解决方案,您可以查看talkol的回答和fgeorgiew的评论 :)

1

试试这个:

 LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
 inflater.inflate( R.layout.login_view_layout, null );

1
你能否详细说明一下你的答案,并对你所提供的解决方案进行更详细的描述? - abarisone

1

我遇到了同样的问题,并用Kotlin回答了我的版本:

您可以通过xml或代码添加自定义视图。区别在于构造函数。

YourView.kt

class YourView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {

    init {
        LayoutInflater.from(context).inflate(R.layout.your_view, this, true)
     orientation = HORIZONTAL
    }
}

如果您想从代码创建布局,请使用以下代码:
class YourView(context: Context) : LinearLayout(context)

your_view.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">


        <TextView
            android:id="@+id/searchView"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Cancel" />


</merge>

这里的<merge>是干什么用的?
如果你在布局中使用了YourView,就像这样:
<LinearLayout> 
   <YourView>
   </YourView>
</LinearLayout>

实际的视图层次结构是:

具有 <merge>

   <LinearLayout>
      <TextView> </TextView>
      <Button> </Button>
   </LinearLayout >

没有 <merge> (你需要使用像<LinearLayout>这样的ViewGroup

 <LinearLayout> 
       < LinearLayout >
          <TextView> </TextView>
          <Button> </Button>
       </LinearLayout >
    </LinearLayout>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接