【问题标题】:How to inflate XML-Layout-File correctly inside Custom ViewGroup?如何在自定义 ViewGroup 中正确扩展 XML-Layout-File?
【发布时间】:2011-05-25 19:53:01
【问题描述】:

我想在自定义 ViewGroup 类中扩展 XML-Layout-File,我的问题是它只产生一个空屏幕。在活动类中做同样的工作很好。 这是我的简单 XML 布局文件:

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>

这是工作版本,在 Activity ShowNumber 中膨胀 shownumberlayout.xml

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-Class 中膨胀 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 公共类 ShowNumber 扩展活动 { /** 在第一次创建活动时调用。 */

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

}

我基本上就像在this 回答中解释的那样。 这只会产生一个空的黑屏。我做错了什么?

更新 1

@康斯坦丁 我应用了您的更改,但仍然只是一个空白屏幕,我还进行了日志输出以获取孩子的数量。它始终保持为 1,即使我在 XML-Layout-File 中再添加一个 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 这是从康斯坦丁更改后的 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-Sublcas 中的方法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);
                }
            }
...
}

注意:稍后您还应该覆盖 Method onMeasurement(),但目前内容也可以正确显示而无需覆盖它。

现在Franco 的方法initView 的解决方案不将TextView 对齐在中心,而是将它放在左上角。 Konstantin 的解决方案正确地将其置于视图的中心:

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

【问题讨论】:

  • @Harlod:请查看您的 Logcat 并告诉您在您的 Logcat 中捕获了什么消息?
  • 是的,Harold,你说得对,我试过了,在层次视图工具中看到它,TextView 在左上角对齐,宽度和高度 = 0

标签: android android-layout


【解决方案1】:

我不知道这些答案是怎么回事。我认为手动调用onLayout 有点疯狂。

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

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

这可能不是你想要的。

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

我建议将您的自定义视图保留为 RelativeLayout,并将您的 XML 根更改为 &lt;merge&gt;。然后使用将 XML 视图添加到您的父级的膨胀表单 - 例如 View.inflate(context,int,this)

&lt;merge&gt; 标记的目的是跳过 XML 中的根节点.. 查找它。

【讨论】:

  • 确实如此。对于仍然对布局膨胀感到困惑的每个人,这里有一篇很棒的文章,它很好地解释了应该如何做:doubleencore.com/2013/05/layout-inflation-as-intended
  • @Ran 这不是一个真正的答案。这只是一个抱怨。一个真正的答案应该是简短、直接的,并且会像公认的那样显示一个代码 sn-p,或者像 Konstantin 的那样。 还对该投诉 +1。
【解决方案2】:

您的布局无处膨胀.. 使您的 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..
    }
}

UPD:你也不需要重写 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:我认为这是因为您的 onLayout 实现完全不正确,请参阅更新。
  • @Harold:哦,我看到@Franco 已经指出了这一点。但是如果你仔细看,我原来的sn-p里面没有onLayout方法,可能我下次应该更冗长:)
  • @KonstantinBurov 您为参数选择的变量一开始很难理解。您能否将它们更改为左、上、右、下而不是 l、t、r、b。 :)
  • @codingbiz 谢谢,已更新。虽然我相信原始代码是由 IDE 基于方法签名生成的。
【解决方案3】:

你试过了吗?

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

编辑:我反应很快...... ViewGroup 有责任为他们的孩子准备布局,在布局方法中试试这个:

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

【讨论】:

  • 是的!它的工作,内容正在显示..非常感谢您!我会更新我的帖子。
  • 好吧!确保您重写了 onLayout 和 onMeasurement 这两个方法,始终将 ViewGroup 类作为子类。
  • 从同一个 XML 实例化多个视图时,这个特定的答案给了我一个问题。我解决了这个问题,用 super(changed, l, t, r, b); 替换了答案中的 onLayout 方法;否则那就太好了,我感谢你!
  • 嘿,我有一个带有 LinearLayout 和 2 个嵌套 TextViews 的 XML 文件,正如 Franco 在他的帖子中显示的那样。膨胀有效,但在 onLayout 方法中我得到一个 NullpointerException。准确地说:getChildAt(i) 给了我 LinearLayout 但是当我调用 getChildAt(i).layout(l, t, r, b) 我得到 NullPointerException。谁能告诉我为什么?
【解决方案4】:

重写onLayout是必要的。当你创建一个ViewNumber(ViewGroup vg = new ViewNumber(this))的对象时,就像下面的膨胀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)
    }
...
}

但是,会发生一些意想不到的事情。android:layout_xxx=".."您的 xml 文件可能会发生变化(这就是为什么您的文本放在左上角的原因)。要了解更多和更好的解决方案,您可以查看 talkol 的答案和 fgeorgiew 的评论:)

【讨论】:

    【解决方案5】:

    试试这个:

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

    【讨论】:

    • 能否请您详细说明您的答案,添加更多关于您提供的解决方案的描述?
    【解决方案6】:

    我遇到了同样的问题并在 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>
    

    &lt;merge&gt; 是干什么用的?

    如果您在布局中使用YourView,例如:

    <LinearLayout> 
       <YourView>
       </YourView>
    </LinearLayout>
    

    那么实际的视图层次结构是:

    &lt;merge&gt;

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

    没有&lt;merge&gt;(你需要像&lt;LinearLayout&gt;一样使用ViewGroup

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

    【讨论】:

      猜你喜欢
      • 2021-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-02
      相关资源
      最近更新 更多