是否可以在 Android 中添加由标准视图组成的自定义视图并在代码中使用它,但布局只知道标准视图?

Is it possible in Android to add a custom view composed of standard views and use it in code but have the layout know only about the standard views?

我在代表自定义视图的单独文件中有这个伪布局结构:

<FrameLayout>
   <TextView/>
   <Button/>
</FrameLayout>

这是我的片段伪布局:

<ConstraintLayout>
   <TextView/>
   <MyCustomView/>
   <TextView/>
</ConstraintLayout>

我想在两个需要内部 TextViewButton 样式的地方重用 MyCustomView,但我不知道有什么方法可以做到这一点一个简单的方法。如果我可以将片段布局中的 MyCustomView 扩展为:

会容易得多
<ConstraintLayout>
   <TextView/>
   <FrameLayout> <!--(or MyCustomView instead of FrameLayout, it would be a better indicator of what it is)-->
      <TextView style="fancy_background"/>
      <Button/>
   </FrameLayout>
   <TextView/>
</ConstraintLayout>

在Android中真的可行吗?如果是,那么如何设置?

可以通过include + databinding实现

第 1 步。为您的自定义视图创建布局,如下所示。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>

    <variable
        name="myText"
        type="java.lang.String" />

</data>


<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{myText}" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</FrameLayout>
</layout>

第 2 步。将其包含在您的 Fragment/Activity 布局中

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <include
            android:id="@+id/MyCustomView"
            layout="@layout/my_custom_view"
            app:myText="@{`aaa`}"

            />
    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

You might need to use binding adapter to pass style as an attribute

我会使用自定义属性。

假设您有一个包含标题和图像的“自定义视图”。您可以在 attrs.xml 中添加以下内容:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyCustomView">
        <attr name="title" format="string|reference" />
        <attr name="android:drawable" />
    </declare-styleable>

</resources>

然后您将在代码中实现此自定义视图:

(类似这样的东西......请记住这是“伪代码”,我从我的自定义视图中获取它,但为了这个目的它被大大简化了......) 在此自定义视图中,我可以提供标题和图像,还将使用自定义背景(未包含在演示中):)

class MyCustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    @AttrRes defStyle: Int = 0
) : ConstraintLayout(context, attrs, defStyle) {

    private val title: MaterialTextView
    private val image: ImageView

    init {
        View.inflate(context, R.layout.HERE_GOES_THE_LAYOUT, this).also {
            title = it.findViewById(R.id.title)
            image = it.findViewById(R.id.image
            // set some properties (just an example)
            it.setBackgroundResource(R.drawable.EXAMPLE_OF_A_BACKGROUND)
            it.isClickable = true
            it.isFocusable = true

            // I want this view to be clickable and provide material feedback...
            val foregroundResId = context.theme.getThemeAttributeValue(R.attr.selectableItemBackground, false)
            it.foreground = context.theme.getDrawable(foregroundResId)
        }


        context.theme.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0).run {
            initProperties(this)
            recycle()
        }
    }

    private fun initProperties(typedArray: TypedArray) = with(typedArray) {
        // The names of these attrs are a mix from attr.xml + the attr name.
        getString(R.styleable.MyCustomView_title).also { setTitle(it) }
        getDrawable(R.styleable.MyCustomView_android_drawable).also { setImage(it) }
    }

    private fun setTitle(title: CharSequence?) {
        title.text = title
    }

    private fun setImage(source: Drawable?) {
        source?.let {
            image.setImageDrawable(it)
        }
    }
}

这都是我的布局支持的:R.layout.HERE_GOES_THE_LAYOUT

这是一个简化版本:

<?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:layout_height="wrap_content"
    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">

    <ImageView
        android:id="@+id/image"
        android:layout_width="40dp"
        android:layout_height="40dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/title"
        app:layout_constraintBottom_toBottomOf="parent" />

    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/image"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:gravity="center_vertical" />

</merge>

我该如何使用它?

在任何布局中!例如:

<Some ViewGroup like LinearLayout It Doesn't Matter .... >

   <com.the.package.of.your.MyCustomView
      android:id="@+id/yourCustomViewOne"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
   <!-- HERE ARE THE TWO 'custom' ATTRIBUTES -->
      android:drawable="@drawable/some_drawable_of_your_choice"
      app:title="@string/some_string" />

   <com.the.package.of.your.MyCustomView
      android:id="@+id/yourCustomViewTwo"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:drawable="@drawable/a_different_drawable_perhaps"
      app:title="@string/and_also_a_different_string />

</Some ViewGroup like LinearLayout It Doesn't Matter>

希望你明白了。您可以添加更多自定义属性,甚至(像我一样)使用 Android 一个,并在内部赋予它自己的含义(覆盖 android 属性并滥用它可能会使您的用户感到困惑,所以“聪明的”)。例如:如果上面的自定义视图有不止一张图片,使用 android:Drawable 可能不是一个很好的选择,因为用户不确定那个东西设置的可绘制对象是什么......在这个例子中它工作“很好”因为你只有一张图片。

最后但同样重要的是,不要忽视辅助功能!在您认为合适的地方提供描述,使用 TalkBack 等进行测试。

这如何回答问题?

好吧,您不需要“扩展”自定义视图,您只需要 封装 动态属性,这样使用 Widget/View 的任何人都可以通过 XML(或以编程方式)提供值,您的自定义视图 在幕后完成工作