diff --git a/catalog/src/main/assets/component_per_question_custom_style.json b/catalog/src/main/assets/component_per_question_custom_style.json
new file mode 100644
index 0000000000..2f2e8f3b86
--- /dev/null
+++ b/catalog/src/main/assets/component_per_question_custom_style.json
@@ -0,0 +1,149 @@
+{
+ "resourceType": "Questionnaire",
+ "item": [
+ {
+ "linkId": "1",
+ "text": "Custom style 1",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "2",
+ "text": "Custom style 2",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_2"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "3",
+ "text": "Custom style 3",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_3"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "4",
+ "text": "Custom style 4",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_4"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "5",
+ "text": "Custom style 5",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_5"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "6",
+ "text": "Custom style 6",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_6"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "7",
+ "text": "Custom style 7",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_7"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "8",
+ "text": "Custom style 8",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_8"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "linkId": "9",
+ "text": "Custom style 9",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_9"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt
index bdafbcd17a..d9e4637ada 100644
--- a/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt
+++ b/catalog/src/main/java/com/google/android/fhir/catalog/ComponentListViewModel.kt
@@ -152,6 +152,11 @@ class ComponentListViewModel(application: Application, private val state: SavedS
R.string.component_name_location_widget,
"component_location_widget.json",
),
+ QUESTION_ITEM_CUSTOM_STYLE(
+ R.drawable.text_format_48dp,
+ R.string.component_name_per_question_custom_style,
+ "component_per_question_custom_style.json",
+ ),
}
val viewItemList =
@@ -177,6 +182,7 @@ class ComponentListViewModel(application: Application, private val state: SavedS
ViewItem.ComponentItem(Component.ITEM_ANSWER_MEDIA),
ViewItem.ComponentItem(Component.INITIAL_VALUE),
ViewItem.ComponentItem(Component.LOCATION_WIDGET),
+ ViewItem.ComponentItem(Component.QUESTION_ITEM_CUSTOM_STYLE),
)
fun isComponent(context: Context, title: String) =
diff --git a/catalog/src/main/res/drawable/ic_location_on.xml b/catalog/src/main/res/drawable/ic_location_on.xml
index 0f96a89039..9821fffba8 100644
--- a/catalog/src/main/res/drawable/ic_location_on.xml
+++ b/catalog/src/main/res/drawable/ic_location_on.xml
@@ -1,7 +1,7 @@
+
+
+
+
diff --git a/catalog/src/main/res/values-night/colors.xml b/catalog/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000000..d80470737a
--- /dev/null
+++ b/catalog/src/main/res/values-night/colors.xml
@@ -0,0 +1,36 @@
+
+
+
+ #000000
+ #0C0A20
+ #201441
+ #341F63
+ #482A85
+ #5C35A6
+ #7F5FBA
+ #A289CF
+ #C5B3E3
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #000000
+ #000000
+
diff --git a/catalog/src/main/res/values/colors.xml b/catalog/src/main/res/values/colors.xml
index 8a1561f3da..f9b63e732a 100644
--- a/catalog/src/main/res/values/colors.xml
+++ b/catalog/src/main/res/values/colors.xml
@@ -102,4 +102,26 @@
#C4C7C5
#8E918F
+
+
+ #7A9FFF
+ #668FFF
+ #5581FF
+ #476FFF
+ #3B5CFF
+ #3249FF
+ #2936FF
+ #2024FF
+ #1816FF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+ #FFFFFF
+
diff --git a/catalog/src/main/res/values/strings.xml b/catalog/src/main/res/values/strings.xml
index 7e09047feb..ae47067784 100644
--- a/catalog/src/main/res/values/strings.xml
+++ b/catalog/src/main/res/values/strings.xml
@@ -37,6 +37,9 @@
Repeated Group
Attachment
Location Widget
+ Per question custom style
Default
Paginated
Review
diff --git a/catalog/src/main/res/values/styles.xml b/catalog/src/main/res/values/styles.xml
index f88c5ea9fa..f2afa052f3 100644
--- a/catalog/src/main/res/values/styles.xml
+++ b/catalog/src/main/res/values/styles.xml
@@ -80,7 +80,10 @@
@@ -98,4 +101,170 @@
- 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt
index 022964c9db..fd4690ea26 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreHeaderViews.kt
@@ -104,3 +104,41 @@ fun appendAsteriskToQuestionText(
}
}
}
+
+internal fun applyCustomOrDefaultStyle(
+ questionnaireItem: Questionnaire.QuestionnaireItemComponent,
+ prefixTextView: TextView,
+ questionTextView: TextView,
+ instructionTextView: TextView,
+) {
+ applyCustomOrDefaultStyle(
+ context = prefixTextView.context,
+ view = prefixTextView,
+ customStyleName =
+ questionnaireItem.readCustomStyleExtension(
+ StyleUrl.PREFIX_TEXT_VIEW,
+ ),
+ defaultStyleResId =
+ getStyleResIdFromAttribute(questionTextView.context, R.attr.questionnaireQuestionTextStyle),
+ )
+ applyCustomOrDefaultStyle(
+ context = questionTextView.context,
+ view = questionTextView,
+ customStyleName =
+ questionnaireItem.readCustomStyleExtension(
+ StyleUrl.QUESTION_TEXT_VIEW,
+ ),
+ defaultStyleResId =
+ getStyleResIdFromAttribute(questionTextView.context, R.attr.questionnaireQuestionTextStyle),
+ )
+ applyCustomOrDefaultStyle(
+ context = instructionTextView.context,
+ view = instructionTextView,
+ customStyleName =
+ questionnaireItem.readCustomStyleExtension(
+ StyleUrl.SUBTITLE_TEXT_VIEW,
+ ),
+ defaultStyleResId =
+ getStyleResIdFromAttribute(questionTextView.context, R.attr.questionnaireSubtitleTextStyle),
+ )
+}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt
new file mode 100644
index 0000000000..3067d969bd
--- /dev/null
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionItemStyle.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.fhir.datacapture.extensions
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.view.View
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import com.google.android.fhir.datacapture.R
+
+/**
+ * Applies either a custom style or a default style to the given view based on the provided custom
+ * style name and default style resource ID.
+ *
+ * If the custom style resource name is valid, it applies the custom style to the view. If the
+ * custom style resource name is not valid or not found, it falls back to applying the default style
+ * defined by the given style resource ID. It sets the view's tag to resourceId to indicate that the
+ * custom style has been applied.
+ *
+ * @param context the context used to access resources.
+ * @param view the view to which the style should be applied.
+ * @param customStyleName the name of the custom style to apply.
+ * @param defaultStyleResId the default style resource ID to use if no custom style is found.
+ */
+internal fun applyCustomOrDefaultStyle(
+ context: Context,
+ view: View,
+ customStyleName: String?,
+ defaultStyleResId: Int,
+) {
+ val customStyleResId = customStyleName?.let { getStyleResIdByName(context, it) } ?: 0
+ when {
+ customStyleResId != 0 -> {
+ view.tag = customStyleResId
+ QuestionItemCustomStyle().applyStyle(context, view, customStyleResId)
+ }
+ defaultStyleResId != 0 -> {
+ applyDefaultStyleIfNotApplied(context, view, defaultStyleResId)
+ }
+ }
+}
+
+/**
+ * Applies the default style to the given view if the default style has not already been applied.
+ *
+ * This function checks the `view`'s tag to determine if a style has been previously applied. If the
+ * tag is an integer, it will apply the default style specified by `defaultStyleResId`. After
+ * applying the style, it resets the view's tag to `null` to indicate that the default style has
+ * been applied.
+ *
+ * @param context The context used to access resources and themes.
+ * @param view The view to which the default style will be applied.
+ * @param defaultStyleResId The resource ID of the default style to apply.
+ */
+private fun applyDefaultStyleIfNotApplied(
+ context: Context,
+ view: View,
+ defaultStyleResId: Int,
+) {
+ (view.tag as? Int)?.let {
+ QuestionItemDefaultStyle().applyStyle(context, view, defaultStyleResId)
+ view.tag = null
+ }
+}
+
+/**
+ * Retrieves the resource ID of a style given its name.
+ *
+ * This function uses the `getIdentifier` method to look up the style resource ID based on the style
+ * name provided. If the style name is not found, it returns 0.
+ *
+ * @param context The context used to access resources.
+ * @param styleName The name of the style whose resource ID is to be retrieved.
+ * @return The resource ID of the style, or 0 if the style name is not found.
+ */
+private fun getStyleResIdByName(context: Context, styleName: String): Int {
+ return context.resources.getIdentifier(styleName, "style", context.packageName)
+}
+
+/**
+ * Retrieves the style resource ID associated with a specific attribute from the current theme.
+ *
+ * This function obtains the style resource ID that is linked to a given attribute in the current
+ * theme. It uses the `obtainStyledAttributes` method to fetch the attributes and extract the
+ * resource ID.
+ *
+ * @param context The context to access the current theme and resources.
+ * @param attr The attribute whose associated style resource ID is to be retrieved.
+ * @return The resource ID of the style associated with the specified attribute, or 0 if not found.
+ */
+internal fun getStyleResIdFromAttribute(context: Context, attr: Int): Int {
+ val typedArray = context.theme.obtainStyledAttributes(intArrayOf(attr))
+ val styleResId = typedArray.getResourceId(0, 0)
+ typedArray.recycle()
+ return styleResId
+}
+
+internal abstract class QuestionItemStyle {
+
+ /**
+ * Applies a style to a view.
+ *
+ * @param context The context used to apply the style.
+ * @param view The view to which the style will be applied.
+ * @param styleResId The resource ID of the style to apply.
+ */
+ abstract fun applyStyle(context: Context, view: View, styleResId: Int)
+
+ /**
+ * Applies the style from a TypedArray to a view.
+ *
+ * @param context The context used to apply the style.
+ * @param view The view to which the style will be applied.
+ * @param typedArray The TypedArray containing the style attributes.
+ */
+ internal fun applyStyle(context: Context, view: View, typedArray: TypedArray) {
+ applyGenericViewStyle(context, view, typedArray)
+ if (view is TextView) {
+ applyTextViewSpecificStyle(view, typedArray)
+ }
+ typedArray.recycle()
+ }
+
+ /**
+ * Abstract function to apply generic view styles from a TypedArray.
+ *
+ * @param context The context used to apply the style.
+ * @param view The view to which the style will be applied.
+ * @param typedArray The TypedArray containing the style attributes.
+ */
+ abstract fun applyGenericViewStyle(context: Context, view: View, typedArray: TypedArray)
+
+ /**
+ * Abstract function to apply TextView-specific styles from a TypedArray.
+ *
+ * @param textView The TextView to which the style will be applied.
+ * @param typedArray The TypedArray containing the style attributes.
+ */
+ abstract fun applyTextViewSpecificStyle(textView: TextView, typedArray: TypedArray)
+
+ /**
+ * Applies the background color from a TypedArray to a view.
+ *
+ * @param context The context used to apply the background color.
+ * @param view The view to which the background color will be applied.
+ * @param typedArray The TypedArray containing the background color attribute.
+ * @param index The index of the background color attribute in the TypedArray.
+ */
+ protected fun applyBackgroundColor(
+ context: Context,
+ view: View,
+ typedArray: TypedArray,
+ index: Int,
+ ) {
+ val backgroundColor =
+ typedArray.getColor(index, ContextCompat.getColor(context, android.R.color.transparent))
+ view.setBackgroundColor(backgroundColor)
+ }
+
+ /**
+ * Applies the text appearance from a TypedArray to a TextView.
+ *
+ * @param textView The TextView to which the text appearance will be applied.
+ * @param typedArray The TypedArray containing the text appearance attribute.
+ * @param index The index of the text appearance attribute in the TypedArray.
+ */
+ protected fun applyTextAppearance(textView: TextView, typedArray: TypedArray, index: Int) {
+ val textAppearance = typedArray.getResourceId(index, -1)
+ if (textAppearance != -1) {
+ textView.setTextAppearance(textAppearance)
+ }
+ }
+}
+
+internal class QuestionItemCustomStyle : QuestionItemStyle() {
+ private enum class CustomStyleViewAttributes(val attrId: Int) {
+ TEXT_APPEARANCE(R.styleable.QuestionnaireCustomStyle_questionnaire_textAppearance),
+ BACKGROUND(R.styleable.QuestionnaireCustomStyle_questionnaire_background),
+ }
+
+ override fun applyStyle(context: Context, view: View, styleResId: Int) {
+ val typedArray =
+ context.obtainStyledAttributes(styleResId, R.styleable.QuestionnaireCustomStyle)
+ applyStyle(context, view, typedArray)
+ }
+
+ override fun applyGenericViewStyle(context: Context, view: View, typedArray: TypedArray) {
+ for (i in 0 until typedArray.indexCount) {
+ when (typedArray.getIndex(i)) {
+ CustomStyleViewAttributes.BACKGROUND.attrId -> {
+ applyBackgroundColor(context, view, typedArray, i)
+ }
+ }
+ }
+ }
+
+ override fun applyTextViewSpecificStyle(
+ textView: TextView,
+ typedArray: TypedArray,
+ ) {
+ for (i in 0 until typedArray.indexCount) {
+ when (typedArray.getIndex(i)) {
+ CustomStyleViewAttributes.TEXT_APPEARANCE.attrId -> {
+ applyTextAppearance(textView, typedArray, i)
+ }
+ }
+ }
+ }
+}
+
+internal class QuestionItemDefaultStyle : QuestionItemStyle() {
+ private enum class DefaultStyleViewAttributes(val attrId: Int) {
+ TEXT_APPEARANCE(android.R.attr.textAppearance),
+ BACKGROUND(android.R.attr.background),
+ // Add other attributes you want to apply
+ }
+
+ override fun applyStyle(context: Context, view: View, styleResId: Int) {
+ val attrs = DefaultStyleViewAttributes.values().map { it.attrId }.toIntArray()
+ val typedArray: TypedArray = context.obtainStyledAttributes(styleResId, attrs)
+ applyStyle(context, view, typedArray)
+ }
+
+ override fun applyGenericViewStyle(context: Context, view: View, typedArray: TypedArray) {
+ for (i in 0 until typedArray.indexCount) {
+ when (DefaultStyleViewAttributes.values()[i]) {
+ DefaultStyleViewAttributes.BACKGROUND -> {
+ applyBackgroundColor(context, view, typedArray, i)
+ }
+ else -> {
+ // Ignore view specific attributes.
+ }
+ }
+ }
+ }
+
+ override fun applyTextViewSpecificStyle(
+ textView: TextView,
+ typedArray: TypedArray,
+ ) {
+ for (i in 0 until typedArray.indexCount) {
+ when (DefaultStyleViewAttributes.values()[i]) {
+ DefaultStyleViewAttributes.TEXT_APPEARANCE -> {
+ applyTextAppearance(textView, typedArray, i)
+ }
+ else -> {
+ // applyGenericViewDefaultStyle for other attributes.
+ }
+ }
+ }
+ }
+}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt
index 55dd4e5738..0acd26c93f 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt
@@ -61,6 +61,13 @@ internal const val EXTENSION_ITEM_CONTROL_URL_ANDROID_FHIR =
internal const val EXTENSION_ITEM_CONTROL_SYSTEM_ANDROID_FHIR =
"https://github.com/google/android-fhir/questionnaire-item-control"
+internal enum class StyleUrl(val url: String) {
+ BASE("https://github.com/google/android-fhir/tree/master/datacapture/android-style"),
+ PREFIX_TEXT_VIEW("prefix_text_view"),
+ QUESTION_TEXT_VIEW("question_text_view"),
+ SUBTITLE_TEXT_VIEW("subtitle_text_view"),
+}
+
// Below URLs exist and are supported by HL7
internal const val EXTENSION_ANSWER_EXPRESSION_URL: String =
@@ -1017,3 +1024,17 @@ val Resource.logicalId: String
get() {
return this.idElement?.idPart.orEmpty()
}
+
+internal fun QuestionnaireItemComponent.readCustomStyleExtension(styleUrl: StyleUrl): String? {
+ // Find the base extension
+ val baseExtension = extension.find { it.url == StyleUrl.BASE.url }
+ baseExtension?.let { ext ->
+ // Extract nested extension based on the given StyleUrl
+ ext.extension.forEach { nestedExt ->
+ if (nestedExt.url == styleUrl.url) {
+ return nestedExt.value.asStringValue()
+ }
+ }
+ }
+ return null
+}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt
index 4fff12a122..da85cbac4e 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/GroupHeaderView.kt
@@ -23,6 +23,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.fhir.datacapture.QuestionnaireViewHolderType
import com.google.android.fhir.datacapture.R
+import com.google.android.fhir.datacapture.extensions.applyCustomOrDefaultStyle
import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility
import com.google.android.fhir.datacapture.extensions.getLocalizedInstructionsSpanned
import com.google.android.fhir.datacapture.extensions.initHelpViews
@@ -60,5 +61,11 @@ class GroupHeaderView(context: Context, attrs: AttributeSet?) : LinearLayout(con
questionnaireViewItem.enabledDisplayItems.getLocalizedInstructionsSpanned(),
)
visibility = getHeaderViewVisibility(prefix, question, hint)
+ applyCustomOrDefaultStyle(
+ questionnaireViewItem.questionnaireItem,
+ prefixTextView = prefix,
+ questionTextView = question,
+ instructionTextView = hint,
+ )
}
}
diff --git a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt
index c48e546f38..7e5e77231d 100644
--- a/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt
+++ b/datacapture/src/main/java/com/google/android/fhir/datacapture/views/HeaderView.kt
@@ -23,6 +23,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.google.android.fhir.datacapture.R
import com.google.android.fhir.datacapture.extensions.appendAsteriskToQuestionText
+import com.google.android.fhir.datacapture.extensions.applyCustomOrDefaultStyle
import com.google.android.fhir.datacapture.extensions.getHeaderViewVisibility
import com.google.android.fhir.datacapture.extensions.getLocalizedInstructionsSpanned
import com.google.android.fhir.datacapture.extensions.initHelpViews
@@ -64,6 +65,12 @@ class HeaderView(context: Context, attrs: AttributeSet?) : LinearLayout(context,
// Make the entire view GONE if there is nothing to show. This is to avoid an empty row in the
// questionnaire.
visibility = getHeaderViewVisibility(prefix, question, hint)
+ applyCustomOrDefaultStyle(
+ questionnaireViewItem.questionnaireItem,
+ prefixTextView = prefix,
+ questionTextView = question,
+ instructionTextView = hint,
+ )
}
/**
diff --git a/datacapture/src/main/res/values/attrs.xml b/datacapture/src/main/res/values/attrs.xml
index 46d076ed59..42cc7e5fb1 100644
--- a/datacapture/src/main/res/values/attrs.xml
+++ b/datacapture/src/main/res/values/attrs.xml
@@ -188,4 +188,24 @@
extend Theme.Questionnaire. If unspecified, Theme.Questionnaire will be used. -->
+
+
+
+
+
+
+
+
+
diff --git a/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md b/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md
index 3ed391bded..4ac8947ef7 100644
--- a/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md
+++ b/docs/use/SDCL/Customize-how-a-Questionnaire-is-displayed.md
@@ -67,6 +67,140 @@ the new theme you just created:
```
+## Custom Style per Question Item
+
+With this change, you can apply individual custom styles per question item. If a custom style is not mentioned in the question item, the default style will be applied, which is present in the DataCapture module or overridden in the application.
+
+### Add a Custom Style Extension to the Question Item
+
+```json
+{
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "prefix_text_view",
+ "valueString": "CustomStyle_1"
+ },
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_1"
+ },
+ {
+ "url": "subtitle_text_view",
+ "valueString": "CustomStyle_2"
+ }
+ ]
+ }
+ ]
+}
+```
+### Custom Style Extension URL
+"https://github.com/google/android-fhir/tree/master/datacapture/android-style"
+
+It identifies extensions for applying the custom style to a given questionnaire item.
+
+### Question Item View
+* `prefix_text_view`: Used to show the prefix value of the question item.
+* `question_text_view`: Used to show the text value of the question item.
+* `subtitle_text_view`: Used to show the instructions of the question item.
+ For more information about supported views, please see the [Question Item View](https://github.com/google/android-fhir/blob/master/datacapture/src/main/java/com/google/android/fhir/datacapture/extensions/MoreQuestionnaireItemComponents.kt).
+
+### Custom Style Values
+In the above example:
+
+`CustomStyle_1` is the custom style for prefix_text_view and question_text_view.
+`CustomStyle_2` is the custom style for subtitle_text_view.
+Both styles are defined in the application.
+
+### Custom Style Attributes
+* `questionnaire_textAppearance`: Specifies the text appearance for the questionnaire text. Example: `@style/TextAppearance.AppCompat.Headline`
+* `questionnaire_background`: Specifies the background for the view. Example: `@color/background_color or #FFFFFF`
+
+For more information on custom style attributes, please see the [QuestionnaireCustomStyle](https://github.com/google/android-fhir/blob/master/datacapture/src/main/res/values/attrs.xml)
+
+### Example Custom Styles
+
+```
+
+
+
+
+
+
+
+
+```
+
+The above custom styles are defined in the `res/values/styles.xml` of the application.
+
+### questionnaire.json with custom style
+```
+{
+ "resourceType": "Questionnaire",
+ "item": [
+ {
+ "linkId": "1",
+ "text": "Question text custom style",
+ "type": "display",
+ "extension": [
+ {
+ "url": "https://github.com/google/android-fhir/tree/master/datacapture/android-style",
+ "extension": [
+ {
+ "url": "prefix_text_view",
+ "valueString": "CustomStyle_1"
+ },
+ {
+ "url": "question_text_view",
+ "valueString": "CustomStyle_1"
+ },
+ {
+ "url": "subtitle_text_view",
+ "valueString": "CustomStyle_2"
+ }
+ ]
+ }
+ ],
+ "item": [
+ {
+ "extension": [
+ {
+ "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
+ "valueCodeableConcept": {
+ "coding": [
+ {
+ "system": "http://hl7.org/fhir/questionnaire-display-category",
+ "code": "instructions"
+ }
+ ]
+ }
+ }
+ ],
+ "linkId": "1.3",
+ "text": "Instructions custom style.",
+ "type": "display"
+ }
+ ]
+ } ]
+}
+```
+
## Custom questionnaire components
The Structured Data Capture Library uses