I have an activity that has two fragments. I have created a layout that is reusable as following:
Here is the xml of custom layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tvFormTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/black1"
android:textSize="#dimen/ssp_10"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Email" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/clForm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/sdp_4"
android:background="#drawable/sh_all_round_form"
android:paddingStart="#dimen/sdp_12"
android:paddingEnd="#dimen/sdp_12"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvFormTitle">
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/etForm"
style="#style/et_form"
android:layout_width="0dp"
android:layout_weight="1"
app:layout_constraintEnd_toStartOf="#+id/ivSwitchPassword"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivSwitchPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:paddingStart="#dimen/sdp_8"
android:paddingTop="#dimen/sdp_8"
android:paddingEnd="#dimen/sdp_2"
android:paddingBottom="#dimen/sdp_8"
android:src="#drawable/ic_eye"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="#+id/etForm"
app:layout_constraintEnd_toStartOf="#+id/ivCancel"
app:layout_constraintTop_toTopOf="#+id/etForm" />
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/ivCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:paddingStart="#dimen/sdp_12"
android:paddingTop="#dimen/sdp_12"
android:paddingBottom="#dimen/sdp_12"
android:src="#drawable/white_close"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="#+id/etForm"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/etForm"
app:tint="#color/grey_shade5" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tvMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/sdp_5"
android:textColor="#color/colorError"
android:textSize="#dimen/ssp_10"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/clForm"
tools:text="This field is required" />
</androidx.constraintlayout.widget.ConstraintLayout>
I am using this custom la in Fragment A as follow:
<include
android:id="#+id/email"
layout="#layout/layout_form_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/sdp_36"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvDescription" />
<include
android:id="#+id/password"
layout="#layout/layout_form_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/sdp_12"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/email" />
I am using Fragment navigation to move from Fragment A to B. Entering values in email and password and move to Fragment B and get back to Fragment A by pressing back or navigateUp(). Email field contains the value of password field.
Edit:
Same layout it used in Custom Compound class as following getting same issue.
class FormView #JvmOverloads
constructor(
private val ctx: Context,
private val attributeSet: AttributeSet? = null,
private val defStyleAttr: Int = 0
) : ConstraintLayout(ctx, attributeSet, defStyleAttr) {
private var formTitle = ""
private var passwordType = false
private var showMessage = false
init {
val inflater = ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val attributes = ctx.obtainStyledAttributes(attributeSet, R.styleable.FormView)
attributes.getString(R.styleable.FormView_formTitle)?.let { formTitle = it }
passwordType = attributes.getBoolean(R.styleable.FormView_passwordType, passwordType)
showMessage = attributes.getBoolean(R.styleable.FormView_showErrorMessage, false)
attributes.recycle()
inflater.inflate(R.layout.layout_form_edittext, this)
setTitle(formTitle)
setPasswordType(passwordType)
errorMessageVisibility(showMessage)
borderVisibility(false)
ivSwitchPassword.setOnClickListener {
updatePasswordVisibility()
}
ivCancel.setOnClickListener {
etForm.text?.clear()
clearError()
}
}
fun showError(msg: String) {
msg?.let {
tvMessage.text = it
cancelBtnVisibility(true)
errorMessageVisibility(true)
borderVisibility(true)
}
}
fun clearError() {
tvMessage.text = ""
cancelBtnVisibility(false)
errorMessageVisibility(false)
borderVisibility(false)
}
private fun errorMessageVisibility(flag: Boolean) {
if (flag) tvMessage.visible() else tvMessage.gone()
}
private fun cancelBtnVisibility(flag: Boolean) {
if (flag) ivCancel.visible() else ivCancel.gone()
}
private fun borderVisibility(flag: Boolean) {
val drawable = clForm.background as GradientDrawable
if(flag) drawable.setStroke(1, Color.parseColor("#c32329"))
else drawable.setStroke(0, Color.TRANSPARENT)
}
fun setPasswordType(passwordType: Boolean) {
if(!passwordType) ivSwitchPassword.gone()
else {
ivSwitchPassword.visible()
etForm.inputType = InputTypeUtils.getInputTypeByProperty("textPassword")
}
}
fun setTitle(title: String) {
tvFormTitle.text = title
}
private fun updatePasswordVisibility() {
if (etForm.transformationMethod is PasswordTransformationMethod) {
etForm.transformationMethod = null
} else {
etForm.transformationMethod = PasswordTransformationMethod()
}
etForm.setSelection(etForm.length())
}
}
This is because include edit text has one id for both field, solution is to generate new id add this code on your onViewCreated method:
email.etForm.id = View.generateViewId()
password.etForm.id = View.generateViewId()
Related
I have created a custom input dialog for my application, and in terms of funcionality it works, but every time it has a sort of square background which distones from the actual dialog content. I tried to set a background color to tranparent e to a custom background to remove it, but it did not work. I would like help to know how can I make it so only the actual dialog appears without this background. I'm also using Material components to create the dialog, with MaterialCardView for the base, MaterialTextView and MaterialButton.
This is my dialog xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:id="#+id/material_name_input_field"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:theme="#style/Theme.MaterialComponents.DayNight.Dialog.Alert"
app:cardCornerRadius="16dp"
app:cardElevation="1dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/input_field"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<com.google.android.material.textview.MaterialTextView
android:id="#+id/input_name_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="#string/input_serie_name"
android:textSize="16pt"
app:layout_constraintEnd_toStartOf="#+id/name_input_field"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="#+id/name_input_field"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:text="#string/empty_text_input_field"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/input_name_title"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="#+id/negative"
style="#style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="#string/cancel"
android:textSize="8pt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/name_input_field" />
<com.google.android.material.button.MaterialButton
android:id="#+id/positive"
style="#style/Widget.Material3.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="#string/done"
android:textSize="8pt"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/name_input_field" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
Creating dialog here:
var inputDialog: InputDialog = InputDialog(parent)
inputDialog
.setCallbackFunction {
doSetSeriesName(series, it.toString(), onCardCreatedCallback)
inputDialog.dismiss()
}
.setTitle("Series Name:")
.show()
Dialog code
class InputDialog(
private var activity: Activity) : Dialog(activity), View.OnClickListener {
companion object {
private var TAG = "InputDialog"
}
private lateinit var textView: EditText
private lateinit var titleTextView: TextView
private lateinit var positive: MaterialButton
private lateinit var negative: MaterialButton
private lateinit var callbackFunction: (value: Any) -> Unit
fun setCallbackFunction(callbackFunction: (value: Any) -> Unit): InputDialog {
this.callbackFunction = callbackFunction
return this
}
fun setTitle(title: String): InputDialog {
titleTextView = findViewById(R.id.input_name_title)
titleTextView.text = title
return this
}
override fun onClick(view: View?) {
when (view?.id) {
R.id.positive -> {
callbackFunction(textView.text)
}
R.id.negative -> {
dismiss()
}
R.id.name_input_field -> {
SCLog.d(TAG, "Editing Name")
textView.text.clear()
}
else -> {
SCLog.d(TAG, "onClick: Unknown view.id")
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
SCLog.d(TAG, "onCreate: creating dialog")
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.input_dialog)
textView = findViewById(R.id.name_input_field)
textView.text.clear()
textView.setHint(R.string.series_name_title)
positive = findViewById(R.id.positive)
negative = findViewById(R.id.negative)
positive.setOnClickListener(this)
negative.setOnClickListener(this)
}
}
And this is a image of how it currently is:
I am trying to make a grid layout with expandable cards, but the problem is that when a card is expanded, its height gets bigger and so does the height of the other cards in the row (to match the height of the first card), but when the card is collapsed back, the height of all the cards does not change as if they were expanded. Anyone knows what could be the problem?
EDIT :
recyclerview_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:id="#+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#color/misty_rose"
android:layout_margin="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Media -->
<ImageView
android:id="#+id/imageView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="center_horizontal"
android:padding="8dp"
android:contentDescription="Photo"
android:src="#drawable/unsplash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:background="#color/isabelline"
/>
<!-- Title, secondary and supporting text -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/misty_rose"
app:layout_constraintTop_toBottomOf="#+id/imageView"
android:padding="8dp">
<TextView
android:id="#+id/textViewCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Code"
android:textAppearance="?attr/textAppearanceHeadline6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/iconExpandCard"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:padding="8dp"
android:src="#drawable/ic_baseline_expand_more_36"
android:background="?attr/selectableItemBackgroundBorderless"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="#+id/textViewDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Description"
android:textAppearance="?attr/textAppearanceBody1"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewCode" />
<TextView
android:id="#+id/textViewPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Price"
android:textAppearance="?attr/textAppearanceBody1"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewDescription"/>
<TextView
android:id="#+id/textViewComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Comment"
android:textAppearance="?attr/textAppearanceBody1"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewPrice"
android:visibility="gone"/>
<!-- Buttons -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewComment">
<com.google.android.material.button.MaterialButton
android:id="#+id/buttonMinusArticle"
style="?attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:width="88dp"
android:minWidth="40dp"
android:backgroundTint="#color/purple_200"
android:text="#string/minus"
android:textColor="#color/white" />
<EditText
android:id="#+id/editNumberOfProducts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="16dp"
android:inputType="numberDecimal|number"
android:text="#string/zero"
android:textAppearance="?attr/textAppearanceBody1"
android:textColor="?android:attr/textColorSecondary"
android:textSize="18sp" />
<com.google.android.material.button.MaterialButton
android:id="#+id/buttonPlusArticle"
style="?attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:width="88dp"
android:minWidth="40dp"
android:backgroundTint="#color/purple_200"
android:text="#string/plus"
android:textColor="#color/white" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
And in ProductListAdapter.kt (the important part is in expandButton.setOnClickListener() ):
class ProductListAdapter() : ListAdapter<Product, ProductListAdapter.ProductViewHolder>(ProductsComparator()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
return ProductViewHolder.create(parent)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current!!)
}
class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val productItemView: TextView = itemView.findViewById(R.id.textViewCode)
private val productDescription : TextView = itemView.findViewById(R.id.textViewDescription)
private val productPrice : TextView = itemView.findViewById(R.id.textViewPrice)
fun bind(product: Product) {
productItemView.text = product.code
//productDescription.text = product.description
//productPrice.text = "Price " + product.client_price.toString()
}
companion object {
val mapOfProducts :HashMap<String, Int> = hashMapOf<String, Int>()
fun create(parent: ViewGroup): ProductViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.recyclerview_item, parent, false)
val minusButton : Button = view.findViewById(R.id.buttonMinusArticle)
val plusButton : Button = view.findViewById(R.id.buttonPlusArticle)
val productItemViewCode: TextView = view.findViewById(R.id.textViewCode)
val expandButton : androidx.appcompat.widget.AppCompatImageButton = view.findViewById(R.id.iconExpandCard)
val commentView : TextView = view.findViewById(R.id.textViewComment)
val cardView : CardView = view.findViewById(R.id.cardView)
expandButton.setOnClickListener{
if (commentView.visibility == View.GONE){
TransitionManager.beginDelayedTransition(cardView, AutoTransition())
commentView.visibility = View.VISIBLE
expandButton.setImageResource(R.drawable.ic_baseline_expand_less_36)
} else {
TransitionManager.beginDelayedTransition(cardView, AutoTransition())
commentView.visibility = View.GONE
expandButton.setImageResource(R.drawable.ic_baseline_expand_more_36)
}
}
val editNumberOfProducts: EditText = view.findViewById(R.id.editNumberOfProducts)
editNumberOfProducts.doAfterTextChanged {
val code = productItemViewCode.text.toString()
if (it.isNullOrBlank()) {
modifyText("0", view)
mapOfProducts.remove(code)
return#doAfterTextChanged
}
val originalText = it.toString()
try {
val number = originalText.toInt()
val numberText = originalText.toInt().toString()
if (originalText != numberText) {
modifyText(numberText, view)
}
if (number > 0) {
mapOfProducts[code] = number
d("CodeOfView", "$code $number")
}else {
mapOfProducts.remove(code)
}
} catch (e: Exception) {
modifyText("0", view)
mapOfProducts.remove(code)
}
}
minusButton.setOnClickListener {
var number = editNumberOfProducts.text.toString().toInt()
if (number>0) {
number -= 1
modifyText(number.toString(), view)
}
}
plusButton.setOnClickListener {
//val code = productItemViewCode.text.toString()
var number = editNumberOfProducts.text.toString().toInt()
number += 1
modifyText(number.toString(), view)
}
return ProductViewHolder(view)
}
private fun modifyText(numberText: String, view: View) {
val editNumberOfProducts = view.findViewById<EditText>(R.id.editNumberOfProducts)
editNumberOfProducts.setText(numberText)
editNumberOfProducts.setSelection(numberText.length)
}
}
}
class ProductsComparator : DiffUtil.ItemCallback<Product>() {
override fun areItemsTheSame(oldItem: Product, newItem: Product): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Product, newItem: Product): Boolean {
return oldItem.code == newItem.code
}
}
}
Example with images of the problem
I was facing a same issue, in my case it was vertical expandable cards and I managed to solve it by using
Adapter.notifyDataSetChanged()
in the right place.
In my app I'm using custom view containing a TextInputLayout with the following code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/input_layout"
style="#style/UbiInput2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="-"
android:orientation="horizontal"
app:boxBackgroundColor="#color/white">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/input_value"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPersonName" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
And the kotlin
class TextInputView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
var errorMessage: String? = ""
init {
inflate(context, R.layout.text_input_view, this)
val attributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputView)
val inputLayout = findViewById<TextInputLayout>(R.id.input_layout)
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputLayout.hint = attributes.getString(R.styleable.TextInputView_hint)
inputValue.hint = attributes.getString(R.styleable.TextInputView_placeholderText)
inputLayout.isExpandedHintEnabled =
attributes.getBoolean(R.styleable.TextInputView_expandedHintEnabled, true)
errorMessage = attributes.getString(R.styleable.TextInputView_errorMessage)
inputLayout.isHelperTextEnabled = false
inputValue.inputType =
attributes.getInt(
R.styleable.TextInputView_android_inputType,
InputType.TYPE_CLASS_TEXT
)
if (attributes.hasValue(R.styleable.TextInputView_android_maxLength)) {
inputValue.filters += InputFilter.LengthFilter(
attributes.getInt(
R.styleable.TextInputView_android_maxLength,
100
)
)
}
inputValue.gravity =
attributes.getInt(R.styleable.TextInputView_android_gravity, Gravity.START)
if (attributes.getBoolean(R.styleable.TextInputView_android_gravity, false)) {
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
if (attributes.getBoolean(R.styleable.TextInputView_helperTextEnabled, false)) {
inputLayout.isHelperTextEnabled = true
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
inputLayout.startIconDrawable =
attributes.getDrawable(R.styleable.TextInputView_startIconDrawable)
attributes.recycle()
}
}
#BindingAdapter("textValue")
fun TextInputView.setTextValue(value: String?) {
value?.let {
setValue(value)
}
}
#InverseBindingAdapter(attribute = "textValue")
fun TextInputView.getTextValue(): String {
return value()
}
#BindingAdapter("textValueAttrChanged")
fun TextInputView.setListener(textAttrChanged: InverseBindingListener) {
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputValue.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
textAttrChanged.onChange()
}
})
}
I have the following screen which go to the next fragment on button click. Using Navigation component.
But from the next screen, when I'm pressing back button to come back to the search form, the TextInputLayout values for hint, and helperText are all the same.
The only place I set those is inside the custom view. And from debugging I can see that all the correct values are set at that time.
I'm a bit out of ideas about what's going on there. Any hints would be appreciated.
Material library version used: 1.3.0-alpha04
All codebase has been migrated to view binding according the latest changes
The View is used as followed:
<?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>
<variable
name="viewModel"
type="com.ubigo.features.v2.products.taxi.SearchViewModel" />
<variable
name="dateFormat"
type="com.ubigo.ubicore.date.UbiDate" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/rental_search_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background">
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_form"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView10">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/pickup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:hint="#string/product_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.pickupDateTime)}" />
<View
android:id="#+id/divider6"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/pickup" />
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/dropoff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:hint="#string/product_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider6"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.destinationDatetime)}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.ubigo.ubicore.ui.LoaderButton
android:id="#+id/rental_search_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:text="#string/product_rental_show_car"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_location" />
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_location"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_form">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/user_location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:helperText="#string/product_rental_location"
app:helperTextEnabled="true"
app:hint="#string/product_pickup_location"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_location_on_black_24dp"
app:textValue="#{viewModel.depAddress}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="#+id/textView10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#string/product_rental_weekend"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And the initialization of the Fragment:
class RentalSearchFragment : Fragment() {
val viewModel: SearchViewModel by viewModel()
private val binding get() = _binding!!
private var _binding: FragmentRentalSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (_binding == null) {
_binding = FragmentRentalSearchBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.dateFormat = UbiDate()
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I was able to solve this exact problem by implementing onSaveInstanceState, onRestoreInstanceState, dispatchSaveInstanceState, dispatchRestoreInstanceState, and setValues as described here
http://www.devexchanges.info/2016/03/custom-compound-view-in-android.html
The linked above goes into all the necessary detail about how to implement these functions. There's also undoubtedly countless other sources you can find about how to implement this android paradigm.
I have a checkbox and textview and I want to set the visibility of textview based on isChecked of the checkbox, can that be done using databinding in xml file directly ?
EDIT
class SquareCheckBox #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0) : ConstraintLayout (context, attrs, defStyle){
private var layout: ConstraintLayout
private var checkImageView : ImageView
private var iconImageView : ImageView
private var titleTextView : TextView
var isChecked : Boolean = (false)
var iconImage : Int = -1
var title : String = ""
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.item_square_checkbox, this, true)
layout = view.layout
checkImageView = view.checkImageView
iconImageView = view.iconImageView
titleTextView = view.titleTextView
initAttributes(attrs)
applyUIChanges()
}
fun initAttributes(attrs: AttributeSet?){
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.square_checkbox_attributes, 0, 0)
isChecked = typedArray.getBoolean(R.styleable.square_checkbox_attributes_isChecked, false)
iconImage = typedArray.getResourceId(R.styleable.square_checkbox_attributes_iconImage, -1)
title = typedArray.getString(R.styleable.square_checkbox_attributes_title)
typedArray.recycle()
}
}
fun applyUIChanges () {
if (isChecked) {
checkImageView.visibility = View.VISIBLE
titleTextView.setTextColor(resources.getColor(android.R.color.black))
layout.setBackgroundResource(R.drawable.xml_square_checkbox_selected)
} else {
checkImageView.visibility = View.INVISIBLE
titleTextView.setTextColor(resources.getColor(R.color.lightGray))
layout.setBackgroundResource(R.drawable.xml_square_checkbox_unselected)
}
if (iconImage != -1) {
iconImageView.setImageResource(iconImage)
}
titleTextView.setText(title)
}
fun performOnClick () {
isChecked = !isChecked
applyUIChanges()
}
companion object {
#JvmStatic
#BindingAdapter("app:isChecked")
fun setIsChecked(view: SquareCheckBox, checked: Boolean) {
view.isChecked = checked
view.applyUIChanges()
}
#InverseBindingAdapter(attribute = "app:isChecked")
#JvmStatic fun getIsChecked(view: SquareCheckBox) : Boolean {
return view.isChecked
}
#BindingAdapter("app:isCheckedAttrChanged")
#JvmStatic fun setListeners(
view: SquareCheckBox,
attrChange: InverseBindingListener) {
}
}
}
and this is the xml
<?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"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/xml_square_checkbox_unselected">
<ImageView
android:id="#+id/checkImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="#drawable/ic_ckeck"
android:tint="#color/colorPrimary"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="4dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"/>
<ImageView
android:id="#+id/iconImageView"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerHorizontal="true"
android:src="#drawable/ic_placeholder_squre" app:layout_constraintTop_toBottomOf="#+id/checkImageView"
app:layout_constraintBottom_toTopOf="#+id/titleTextView"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/>
<TextView
android:id="#+id/titleTextView"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_below="#id/iconImageView"
android:ellipsize="end"
android:gravity="center"
android:lines="2"
android:text="#string/text"
android:maxLines="2"
android:textColor="#android:color/black"
android:textSize="12sp"
android:layout_marginBottom="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/iconImageView"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>
EDIT2
I have also added a variable in view model and try to set the visibility based on it
<com.google.android.material.textfield.TextInputLayout
android:visibility="#{viewModel.doNotDisturbEnabled ? View.GONE : View.VISIBLE}"
android:id="#+id/from_textinput"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:fontFamily="sans-serif"
android:hint="#string/from"
android:textColor="#color/colorDarkGrey"
android:textColorHint="#color/colorDarkGrey"
android:textSize="12sp"
android:textStyle="normal"
app:boxCornerRadiusBottomEnd="5dp"
app:boxCornerRadiusBottomStart="5dp"
app:boxCornerRadiusTopEnd="5dp"
app:boxCornerRadiusTopStart="5dp"
app:boxStrokeColor="#color/colorPrimary"
app:boxStrokeWidth="3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/doNotDisturbCheckBox"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toStartOf="#+id/to_textinput">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/from_textedit"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.google.android.material.textfield.TextInputLayout>
but it still not working
yes you can do this using data binding for that you need to DataBinding Obserable and make your own #Binder for your model and the change the value of this observed property on the change of status of your checkbox status
I have created a custom view as following
class SquareCheckBox #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0) : ConstraintLayout (context, attrs, defStyle){
private var layout: ConstraintLayout
private var checkImageView : ImageView
private var iconImageView : ImageView
private var titleTextView : TextView
var isChecked : Boolean = false
var iconImage : Int = -1
var title : String = ""
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.item_square_checkbox, this, true)
layout = view.layout
checkImageView = view.checkImageView
iconImageView = view.iconImageView
titleTextView = view.titleTextView
initAttributes(attrs)
applyUIChanges()
addAction()
}
fun initAttributes(attrs: AttributeSet?){
attrs?.let {
val typedArray = context.obtainStyledAttributes(it, R.styleable.square_checkbox_attributes, 0, 0)
isChecked = typedArray.getBoolean(R.styleable.square_checkbox_attributes_isChecked, false)
iconImage = typedArray.getResourceId(R.styleable.square_checkbox_attributes_iconImage, -1)
title = typedArray.getString(R.styleable.square_checkbox_attributes_title)
typedArray.recycle()
}
}
fun applyUIChanges () {
if (isChecked) {
checkImageView.visibility = View.VISIBLE
titleTextView.setTextColor(resources.getColor(android.R.color.black))
layout.setBackgroundResource(R.drawable.xml_square_checkbox_selected)
} else {
checkImageView.visibility = View.INVISIBLE
titleTextView.setTextColor(resources.getColor(R.color.lightGray))
layout.setBackgroundResource(R.drawable.xml_square_checkbox_unselected)
}
if (iconImage != -1) {
iconImageView.setImageResource(iconImage)
}
titleTextView.setText(title)
}
fun addAction () {
layout.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
isChecked = !isChecked
applyUIChanges()
}
})
}
}
and this is the xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/xml_square_checkbox_unselected">
<ImageView
android:id="#+id/checkImageView"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="#drawable/ic_ckeck"
android:tint="#color/colorPrimary"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="4dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"/>
<ImageView
android:id="#+id/iconImageView"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerHorizontal="true"
android:src="#drawable/ic_placeholder_squre" app:layout_constraintTop_toBottomOf="#+id/checkImageView"
app:layout_constraintBottom_toTopOf="#+id/titleTextView"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/>
<TextView
android:id="#+id/titleTextView"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_below="#id/iconImageView"
android:ellipsize="end"
android:gravity="center"
android:lines="2"
android:text="#string/text"
android:maxLines="2"
android:textColor="#android:color/black"
android:textSize="12sp"
android:layout_marginBottom="4dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/iconImageView"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>
and now when i try to use my custom view in my MVVM like this
<com.playground.components.SquareCheckBox
android:id="#+id/inAppMessageCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:isChecked="#{viewModel.inAppEnabled}"
app:iconImage="#drawable/ic_allow_inapp"
app:title="#string/post_listing_settings_in_app_message"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#+id/voipCheckBox" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp">
it gives me the following error
****/ data binding error ****msg:Cannot find the setter for attribute 'app:isChecked' with parameter type boolean on com.playground.components.SquareCheckBox. file:/Volumes/Data/Work/Amira_Playground/Playground/app/src/main/res/layout/recycler_view_basic_information_settings_item.xml loc:20:33 - 20:54 ****\ data binding error ****
can anyone please advice ?
Seems like it should work, but maybe there's a problem with setter's name/parameter resolving. The docs state that:
For an attribute named example, the library automatically tries to
find the method setExample(arg) that accepts compatible types as the
argument. The namespace of the attribute isn't considered, only the
attribute name and type are used when searching for a method.
You can work this issue around easily with a custom binding adapter. See: https://developer.android.com/topic/libraries/data-binding/binding-adapters
Like this:
#BindingAdapter("isChecked")
fun setIsChecked(view: SquareCheckBox, checked: Boolean) {
view.setChecked(checked)
}