So I'm trying to create a custom component that consists of two labels and one TextInputLayout views. When I implement it in my fragment I want to bind visibility of one label, colour of TextInputLayout boxStroke and text inside TextInputLayout to viewModel MutableLiveData variables. But somehow I can't get it to work, can anybody help?
My custom component layout
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/custom_input_label"
style="#style/BasicText"
android:layout_marginStart="#dimen/margin_normal"
android:text="#string/first_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/custom_input_layout"
style="#style/TextInputLayoutStyle"
android:layout_marginStart="#dimen/margin_normal"
android:layout_marginTop="#dimen/margin_12dp"
android:layout_marginEnd="#dimen/margin_normal"
app:errorEnabled="true"
app:errorTextColor="#color/colorPrimary"
app:helperTextEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/custom_input_label">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/custom_edit_text"
style="#style/InputTextStyle" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="#+id/custom_input_error"
style="#style/ErrorText"
android:layout_marginStart="#dimen/margin_normal"
android:layout_marginTop="#dimen/margin_11dp"
android:visibility="visible"
android:text="#string/first_name_error"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/custom_input_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</merge>
My CustomView class
class CustomInputField #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleRes: Int = 0
) : ConstraintLayout(context, attrs, defStyleRes) {
init {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.custom_text_input, this)
}
}
My MutableLiveData inside ViewModel
private val _nameErrorVisibility = MutableLiveData(View.GONE)
val nameErrorVisibility: LiveData<Int> = _nameErrorVisibility
private val _nameBoxColor =
MutableLiveData(R.color.mtrl_textinput_default_box_stroke_color)
val nameBoxColor: LiveData<Int> = _nameBoxColor
AND BASICALLY THIS IS WHAT I WANT TO DO IN MY FRAGMENT LAYOUT
<ba.project.project.ui.base.view.CustomInputField
android:id="#+id/txtFirstName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:boxColor="#{context.getColor(viewModel.nameBoxColor)}"
app:errorText="#string/first_name_error"
app:errorVisibility="#{viewModel.nameErrorVisibility}"
app:inputText="#={viewModel.firstName}"
app:labelText="#string/first_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
Related
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()
Custom class ::
class TimerView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
var totalDays: Int = 0
var daysLeft: Int = 0
init {
inflate(context, R.layout.view_timer_progress_bar,this)
circularProgressbar.progress = 28
}
}
view_timer_progress_bar
<TimerView
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/progressbar_timer_view"
android:layout_width="#dimen/dp_64"
android:layout_height="#dimen/dp_64"
android:layout_gravity="center_horizontal"
>
<ProgressBar
android:id="#+id/circularProgressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="#dimen/dp_142"
android:layout_height="#dimen/dp_142"
android:layout_centerInParent="true"
android:indeterminate="false"
android:progressDrawable="#drawable/timer_circular"
app:layout_constraintBottom_toBottomOf="#+id/progressbar_timer_view"
app:layout_constraintEnd_toEndOf="#+id/progressbar_timer_view"
app:layout_constraintStart_toStartOf="#+id/progressbar_timer_view"
app:layout_constraintTop_toTopOf="#+id/progressbar_timer_view"
tools:max="100"
tools:progress="50"
tools:secondaryProgress="100" />
<TextView
android:id="#+id/tv_time_days"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="#dimen/sp_14"
android:lineSpacingExtra="0sp"
android:gravity="center_horizontal"
tools:text="28\ndays"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</TimerView>
I am trying this TimerView custom view include in another layout like below
<RelativeLayout
android:id="#+id/frame_timer_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="MissingConstraints">
<include layout="#layout/widget_timer_progress_bar" />
</RelativeLayout>
In class view, I initialize like this
private lateinit var timerView: TimerView
private lateinit var timerView: TimerView
onCreateView(){
timerView = contentView.findViewById(R.id.progressbar_timer_view)
}
I am getting above binary inflate exception. I want to create a custom view so that I can use any place as with dynamic value with a different view I can reuse this please suggest and help me what I am doing wrong
Stack trace:: <P>paste.ofcode.org/34qQkj2dFnEMBpak8HdRTja</p>
I have a problem regarding livedata in custom views in android, I have a custom class with a text livedata that is two-way binded to the xml but the problem is that whenever the user writes any new value in the TextInputEditText the value of livedata is not changed.
class MaterialTextInput #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : MaterialCardView(context, attrs) {
val viewModel = MaterialTextInputViewModel()
init {
ViewMaterialTextInputBinding.inflate(
LayoutInflater.from(context), this, true
).apply {
viewModel = this#MaterialTextInput.viewModel
}
}
class MaterialTextInputViewModel : ViewModel() {
var text = MutableLiveData("")
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.app.utils.views.MaterialTextInput.MaterialTextInputViewModel" />
</data>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp"
app:strokeColor="#{viewModel.text.length() > 0 ? #color/color_4 : #color/white}"
app:strokeWidth="1dp">
<com.google.android.material.textfield.TextInputLayout
style="#style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/email_address"
app:boxBackgroundColor="#color/white"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/textField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
android:text="#={viewModel.text}" />
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.card.MaterialCardView>
</layout>
I am trying to handle the onClick event for custom view component, but still not working,
Here's my custom view layout
<RelativeLayout 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/followButtonRView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true">
<TextView
android:id="#+id/followSourceButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fontFamily="#font/droidkufi_bold"
android:padding="0dp"
android:text="#string/followSourceButton"
android:textAlignment="center"
android:textSize="14sp"
android:visibility="gone"
android:duplicateParentState="true"/>
<ProgressBar
android:id="#+id/followButtonProgress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:duplicateParentState="true"/>
Although attribute Android: clickable = True, still not working
Below is the code block of using the above custom view
<today.akhbarna.news.ui.custom.FollowSourceButton
android:id="#+id/button2"
setFollowState="#{source.selected}"
android:layout_width="75dp"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:onClick="#{(view) ->sourceListeners.onClick(view,source.sourceId,SourceClickTypes.FollowSource)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
FollowSourceButton source code
class FollowSourceButton(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) {
private var btn: TextView;
private var progressBar: ProgressBar
private var relativeLayout : RelativeLayout
init {
inflate(context,R.layout.follow_source_button,this)
btn = findViewById(R.id.followSourceButton)
progressBar = findViewById(R.id.followButtonProgress)
relativeLayout = findViewById(R.id.followButtonRView)
isClickable = true
}
Please help, Thanks
How can we add Views dynamically in androidx.constraintlayout.helper.widget.Flow and add reference Ids dynamically.
You should add first your view (with an id set) to the parent ConstraintLayout. Then you can add it's reference id to your Flow with Flow.addView(). For example:
val view = LayoutInflater.from(context).inflate(R.layout.item, this, false)
view.layoutParams = ConstraintLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
view.id = View.generateViewId()
constraintLayout.addView(view)
flow.addView(view)
with this xml as your ViewGroup:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/constraintLayout"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/flow"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:flow_wrapMode="chain"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Here is Kotlin Implementation
for (i in 0..4) {
val customView = CustomComponent (this)
customView.id = generateViewId()
constraintLayout.addView(customView,i)
flow.addView(customView)
}
Following is my XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/constraintLayout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
>
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/flow"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:flow_wrapMode="chain"
app:flow_horizontalGap="2dp"
app:flow_verticalGap="2dp"
app:flow_verticalStyle = "spread_inside"
app:flow_horizontalStyle="packed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Following is my Custom View
class CustomComponent #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0):LinearLayout(context, attrs, defStyle, defStyleRes) {
init {
LayoutInflater.from(context)
.inflate(R.layout.custom_view, this, true)
}}
view1.setId(View.generateViewId());
view2.setId(View.generateViewId());
view3.setId(View.generateViewId());
flow.setReferencedIds(new int[]{view1.getId(), view2.getId(), view3.getId()});