【问题标题】:Create a custom View by inflating a layout?通过膨胀布局创建自定义视图?
【发布时间】:2011-05-18 17:58:02
【问题描述】:

我正在尝试创建一个自定义视图来替换我在多个地方使用的特定布局,但我正在努力这样做。

基本上,我想替换这个:

<RelativeLayout
 android:id="@+id/dolphinLine"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
    android:layout_centerInParent="true"
 android:background="@drawable/background_box_light_blue"
 android:padding="10dip"
 android:layout_margin="10dip">
  <TextView
   android:id="@+id/dolphinTitle"
   android:layout_width="200dip"
   android:layout_height="100dip"
   android:layout_alignParentLeft="true"
   android:layout_marginLeft="10dip"
   android:text="@string/my_title"
   android:textSize="30dip"
   android:textStyle="bold"
   android:textColor="#2E4C71"
   android:gravity="center"/>
  <Button
   android:id="@+id/dolphinMinusButton"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_toRightOf="@+id/dolphinTitle"
   android:layout_marginLeft="30dip"
   android:text="@string/minus_button"
   android:textSize="70dip"
   android:textStyle="bold"
   android:gravity="center"
   android:layout_marginTop="1dip"
   android:background="@drawable/button_blue_square_selector"
   android:textColor="#FFFFFF"
   android:onClick="onClick"/>
  <TextView
   android:id="@+id/dolphinValue"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_marginLeft="15dip"
   android:background="@android:drawable/editbox_background"
   android:layout_toRightOf="@+id/dolphinMinusButton"
   android:text="0"
   android:textColor="#2E4C71"
   android:textSize="50dip"
   android:gravity="center"
   android:textStyle="bold"
   android:inputType="none"/>
  <Button
   android:id="@+id/dolphinPlusButton"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_toRightOf="@+id/dolphinValue"
   android:layout_marginLeft="15dip"
   android:text="@string/plus_button"
   android:textSize="70dip"
   android:textStyle="bold"
   android:gravity="center"
   android:layout_marginTop="1dip"
   android:background="@drawable/button_blue_square_selector"
   android:textColor="#FFFFFF"
   android:onClick="onClick"/>
</RelativeLayout>

通过这个:

<view class="com.example.MyQuantityBox"
    android:id="@+id/dolphinBox"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:myCustomAttribute="@string/my_title"/>

所以,我不想要一个自定义布局,我想要一个自定义视图(这个视图应该不可能有子视图)。

唯一可以从一个 MyQuantityBox 实例更改为另一个实例的是标题。我非常希望能够在 XML 中指定这一点(就像我在最后一行 XML 中所做的那样)

我该怎么做?我应该将 RelativeLayout 放在 /res/layout 中的 XML 文件中,然后在 MyBoxQuantity 类中对其进行扩充吗?如果是,我该怎么做?

谢谢!

【问题讨论】:

标签: android xml view


【解决方案1】:

有点老了,但我想根据 chubbsondubs 的回答分享我的做法: 我使用FrameLayout(参见Documentation),因为它用于包含单个视图,并将来自xml 的视图膨胀到其中。

代码如下:

public class MyView extends FrameLayout {
    public MyView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

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

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

    private void initView() {
        inflate(getContext(), R.layout.my_view_layout, this);
    }
}

【讨论】:

  • 由于 View 类具有静态 inflate() 方法,因此不需要 LayoutInflater.from()
  • 这不只是 Johannes 从这里的解决方案吗:stackoverflow.com/questions/17836695/… 不过,这增加了内部的另一个布局。所以这不是我猜的最好的解决方案。
  • 它是,但 Johannes 解决方案来自 7.24.13,而心灵来自 7.1.13...另外,我的解决方案使用 FrameLayout 应该只包含一个视图(如文档中所写解决方案中引用)。所以实际上它是用作视图的占位符。我不知道任何不使用占位符作为膨胀视图的解决方案。
  • 我不明白。该方法(inflate)返回一个视图,该视图被忽略。似乎您需要将其添加到当前视图中。
  • @Jeffrey Blattman 请查看View.inflate 方法,我们使用这个(指定根为this,第三个参数)
【解决方案2】:

这是一个简单的演示,通过从 xml 膨胀来创建自定义视图(复合视图)

attrs.xml

<resources>
    
    <declare-styleable name="CustomView">
        <attr format="string" name="text"/>
        <attr format="reference" name="image"/>
    </declare-styleable>
</resources>

CustomView.kt

class CustomView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
        ConstraintLayout(context, attrs, defStyleAttr) {

    init {
        init(attrs)
    }

    private fun init(attrs: AttributeSet?) {
        View.inflate(context, R.layout.custom_layout, this)

        val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
        try {
            val text = ta.getString(R.styleable.CustomView_text)
            val drawableId = ta.getResourceId(R.styleable.CustomView_image, 0)
            if (drawableId != 0) {
                val drawable = AppCompatResources.getDrawable(context, drawableId)
                image_thumb.setImageDrawable(drawable)
            }
            text_title.text = text
        } finally {
            ta.recycle()
        }
    }
}

custom_layout.xml

我们应该在这里使用merge 而不是ConstraintLayout,因为

如果我们在这里使用ConstraintLayout,布局层次结构将是ConstraintLayout->ConstraintLayout -> ImageView + TextView => 我们有1个冗余ConstraintLayout => 对性能不太好

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:parentTag="android.support.constraint.ConstraintLayout">

    <ImageView
        android:id="@+id/image_thumb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="ContentDescription"
        tools:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="@id/image_thumb"
        app:layout_constraintStart_toStartOf="@id/image_thumb"
        app:layout_constraintTop_toBottomOf="@id/image_thumb"
        tools:text="Text" />

</merge>

使用 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <your_package.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f00"
        app:image="@drawable/ic_android"
        app:text="Android" />

    <your_package.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#0f0"
        app:image="@drawable/ic_adb"
        app:text="ADB" />

</LinearLayout>

结果

Github demo

【讨论】:

  • 这应该是该线程中被接受或投票最多的答案,因为它提到了不必要的布局层次结构。
  • 有没有办法以某种方式引用自定义视图、文本上可用的所有属性以及所有这些属性都来自图像,并以某种方式将它们挂钩到文本视图和图像视图而无需手动执行?
  • 在搜索了一些实际示例 2 小时后,我找到了您的示例。这就是为什么你必须向下滚动过去接受的答案。很有帮助,谢谢!
  • 感谢提及tools:parentTag,即使经过9年的Android开发我也不知道❤️
  • 很好的答案,谢谢。对于那些喜欢使用视图绑定的人:CustomViewBinding.bind(View.inflate(context, R.layout.custom_layout, this))
【解决方案3】:

是的,您可以这样做。 RelativeLayout、LinearLayout 等是视图,因此自定义布局是自定义视图。只是需要考虑的事情,因为如果您想创建自定义布局,您可以。

您要做的是创建一个复合控件。您将创建一个 RelativeLayout 的子类,在代码中添加我们所有的组件(TextView 等),并且在您的构造函数中,您可以读取从 XML 传入的属性。然后,您可以将该属性传递给您的标题 TextView。

http://developer.android.com/guide/topics/ui/custom-components.html

【讨论】:

    【解决方案4】:

    使用如下所示的 LayoutInflater。

    public View myView() {
        View v; // Creating an instance for View Object
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = inflater.inflate(R.layout.myview, null);
    
        TextView text1 = v.findViewById(R.id.dolphinTitle);
        Button btn1 = v.findViewById(R.id.dolphinMinusButton);
        TextView text2 = v.findViewById(R.id.dolphinValue);
        Button btn2 = v.findViewById(R.id.dolphinPlusButton);
    
        return v;
    }
    

    【讨论】:

    • 我也试过了。它工作正常。但是,当我单击 btn1 时,它会调用 Web 服务,并在收到服务器的响应后,我想更新一些特定位置的文本 text2..任何帮助请?
    【解决方案5】:

    在实践中,我发现你需要小心一点,尤其是在你反复使用一些 xml 的情况下。例如,假设您有一个表,您希望为列表中的每个条目创建一个表行。您已经设置了一些 xml:

    my_table_row.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <TableRow xmlns:android="http://schemas.android.com/apk/res/android" 
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" android:id="@+id/myTableRow">
        <ImageButton android:src="@android:drawable/ic_menu_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/rowButton"/>
        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="TextView" android:id="@+id/rowText"></TextView>
    </TableRow>
    

    然后你想用一些代码每行创建一次。它假定您已经定义了一个父 TableLayout myTable 来将 Rows 附加到。

    for (int i=0; i<numRows; i++) {
        /*
        * 1. Make the row and attach it to myTable. For some reason this doesn't seem
        * to return the TableRow as you might expect from the xml, so you need to
        * receive the View it returns and then find the TableRow and other items, as
        * per step 2.
        */
        LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v =  inflater.inflate(R.layout.my_table_row, myTable, true);
    
        // 2. Get all the things that we need to refer to to alter in any way.
        TableRow    tr        = (TableRow)    v.findViewById(R.id.profileTableRow);
        ImageButton rowButton = (ImageButton) v.findViewById(R.id.rowButton);
        TextView    rowText   = (TextView)    v.findViewById(R.id.rowText);
                                
        // 3. Configure them out as you need to
        rowText.setText("Text for this row");
        rowButton.setId(i); // So that when it is clicked we know which one has been clicked!
        rowButton.setOnClickListener(this); // See note below ...           
    
        /*
        * To ensure that when finding views by id on the next time round this
        * loop (or later) gie lots of spurious, unique, ids.
        */
        rowText.setId(1000+i);
        tr.setId(3000+i);
    }
    

    有关处理 rowButton.setOnClickListener(this) 的清晰简单示例,请参阅Onclicklistener for a programmatically created button

    【讨论】:

    • 嗨,尼尔,我也试过了。它工作正常。但是,当我单击 rowButton 时,它会调用 Web 服务,并在收到服务器的响应后,我想更新特定位置 rowText..任何帮助?
    • 也请参阅我的问题。 stackoverflow.com/questions/48719970/…
    【解决方案6】:

    有多个答案在不同的方法中指向相同的方法,我相信以下是最简单的方法,无需使用任何第三方库,即使您可以使用 Java。

    在 Kotlin 中;

    创建values/attr.xml

    <resources>
        <declare-styleable name="DetailsView">
            <attr format="string" name="text"/>
            <attr format="string" name="value"/>
        </declare-styleable>
    </resources>
    

    为您的视图创建layout/details_view.xml 文件

    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                android:id="@+id/text_label"
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_weight="0.5"
                tools:text="Label" />
    
            <TextView
                android:id="@+id/text_value"
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_weight="0.5"
                tools:text="Value" />
    
        </LinearLayout>
    
    </merge>
    

    创建自定义视图小部件DetailsView.kt

    import android.content.Context
    import android.content.res.TypedArray
    import android.util.AttributeSet
    import android.view.View
    import android.widget.LinearLayout
    import android.widget.TextView
    import com.payable.taponphone.R
    
    class DetailsView @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : LinearLayout(context, attrs, defStyleAttr) {
    
        private val attributes: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailsView)
        private val view: View = View.inflate(context, R.layout.details_view, this)
    
        init {
            view.findViewById<TextView>(R.id.text_label).text = attributes.getString(R.styleable.DetailsView_text)
            view.findViewById<TextView>(R.id.text_value).text = attributes.getString(R.styleable.DetailsView_value)
        }
    }
    

    就是这样,您现在可以在应用中的任何位置调用小部件,如下所示

    <com.yourapp.widget.DetailsView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:text="Welcome"
        app:value="Feb" />
    

    【讨论】:

      【解决方案7】:

      使用 Kotlin 的简单自定义视图

      将 FrameLayout 替换为您想要扩展的任何视图

      /**
       * Simple Custom view
       */
      class CustomView@JvmOverloads
      constructor(context: Context,
                  attrs: AttributeSet? = null,
                  defStyleAttr: Int = 0)
          : FrameLayout(context, attrs, defStyleAttr) {
      
          init {
              // Init View
              val rootView = (getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
                      .inflate(R.layout.custom_layout, this, true)
              val titleView= rootView.findViewById(id.txtTitle)
      
              // Load Values from XML
              val attrsArray = getContext().obtainStyledAttributes(attrs, R.styleable.CutsomView, defStyleAttr, 0)
              val titleString = attrsArray.getString(R.styleable.cutomAttrsText)
              attrsArray.recycle()
          }
      }
      

      【讨论】:

      • 什么是 R.styleable.CutsomView 和 R.styleable.cutomAttrsText?
      猜你喜欢
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多