是否可以在 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>
我想在两个需要内部 TextView
和 Button
样式的地方重用 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(或以编程方式)提供值,您的自定义视图 在幕后完成工作。
我在代表自定义视图的单独文件中有这个伪布局结构:
<FrameLayout>
<TextView/>
<Button/>
</FrameLayout>
这是我的片段伪布局:
<ConstraintLayout>
<TextView/>
<MyCustomView/>
<TextView/>
</ConstraintLayout>
我想在两个需要内部 TextView
和 Button
样式的地方重用 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(或以编程方式)提供值,您的自定义视图 在幕后完成工作。