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)
}
Related
Hello I try to add view binding in my custom view but unfortunately my custom view is not visible, every thing works fine but when I open the activity the custom view is not displayed
my custom view class
class StickerView #JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet?=null,
defStyle:Int = 0
) : ConstraintLayout(context,attributeSet,defStyle),View.OnClickListener {
private val binding = ItemStickerBinding.inflate(LayoutInflater.from(context),this,true)
private var segmentedVerticalSeekBar: SegmentedVerticalSeekBar? = null
private var btn1: ImageButton?=null
private var btn2: ImageButton?=null
private var btn3: ImageButton?=null
private var btn4: ImageButton?=null
private var btn5: ImageButton?=null
private var btn6: ImageButton?=null
private var btn0: ImageButton?=null
init {
init(context)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.appCompatButton ->{
}
R.id.appCompatButton1 ->{
}
R.id.appCompatButton2 ->{
}
R.id.appCompatButton3 ->{
}
R.id.appCompatButton4 ->{
}
R.id.appCompatButton5 ->{
}
R.id.appCompatButton6 ->{
}
}
}
private fun init(context: Context){
inflate(context,R.layout.item_sticker,this)
segmentedVerticalSeekBar = binding.svsLevelView
btn0 = binding.appCompatButton
btn1 = binding.appCompatButton1
btn2 = binding.appCompatButton2
btn3 = binding.appCompatButton3
btn4 = binding.appCompatButton4
btn5 = binding.appCompatButton5
btn6 = binding.appCompatButton6
btn6?.setOnClickListener(this)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(Int.MAX_VALUE,400)
}
}
main activity
<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="match_parent"
android:orientation="vertical"
android:background="#color/background"
tools:context=".MainActivity">
<com.example.emoticker.StickerView
android:id="#+id/stickerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<include layout="#layout/item_sticker"/>
</com.example.emoticker.StickerView>
</androidx.constraintlayout.widget.Constraint
my customview layout
item_sticker.xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="150dp"
xmlns:tools="http://schemas.android.com/tools"
android:background="#color/background"
tools:context=".StickerView"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.ss.svs.SegmentedVerticalSeekBar
android:id="#+id/svsLevelView"
android:layout_width="60dp"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_marginStart="20dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
app:backgroundColor="#color/white"
app:cornerRadius="10dp"
app:currentValue="2"
app:delimiterColor="#color/white"
app:isAllRadius="true"
app:layout_constraintBottom_toTopOf="#+id/appCompatButton6"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:maxValue="4"
app:progressColor="#color/color_progress"
app:pyramidViewEnable="true"
app:step="1"
app:touchDisabled="false" />
<ImageView
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toTopOf="#+id/linearLayout"
app:layout_constraintEnd_toEndOf="#+id/linearLayout"
app:layout_constraintStart_toStartOf="#+id/linearLayout"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="#+id/linearLayout"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="15dp"
android:layout_marginBottom="5dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/svsLevelView">
<ImageButton
android:id="#+id/appCompatButton"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/laughing_emoji_svgrepo_com" />
<ImageButton
android:id="#+id/appCompatButton1"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/scare_svgrepo_com" />
<ImageButton
android:id="#+id/appCompatButton2"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/crying_emoji_svgrepo_com" />
<ImageButton
android:id="#+id/appCompatButton3"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/sick_svgrepo_com" />
<ImageButton
android:id="#+id/appCompatButton4"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/shocked_emoji_svgrepo_com" />
<ImageButton
android:id="#+id/appCompatButton5"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/angry_svgrepo_com" />
</LinearLayout>
<ImageButton
android:id="#+id/appCompatButton6"
android:layout_width="50dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:layout_weight="1"
android:background="#color/white"
android:src="#drawable/ic_baseline_subdirectory_arrow_left_24"
app:layout_constraintBottom_toBottomOf="#+id/linearLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/linearLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
I appreciate you guys if u help me
sorry for my bad english
You're overriding onLayout but there's no code in it. So when the view needs to lay itself out... it doesn't! Remove the onLayout function and it should work.
Also, don't call inflate in that init function. You already inflated the layout and added it to your custom view here:
private val binding = ItemStickerBinding.inflate(LayoutInflater.from(context),this,true)
If you inflate again, you create a new layout and replace the old one. binding was created from the old layout, and all its references point to the old views.
You also don't want this, it's a duplicate layout:
<include layout="#layout/item_sticker"/>
Just as an example of how I'd write this class with view binding - maybe it helps! It should be easier to work with anyway:
// subclass FrameLayout instead - you're inflating a layout and putting it -inside-
// your custom view, so a FrameLayout (which is meant to hold a single view) is better
// and safer than a ConstraintLayout (which you're not providing constraints for -
// it could break at some point, or act weird)
// I've removed the View.OnClickListener interface because I'm setting the listeners
// another way
class StickerView #JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet?=null,
defStyle:Int = 0
) : FrameLayout(context,attributeSet,defStyle) {
// this inflates your layout and adds it to the view - job done!
private val binding =
ItemStickerBinding.inflate(LayoutInflater.from(context),this,true)
// If you need to refer to a view you've bound, you usually do it through
// the binding object - so "binding.appCompatButton0" etc, and there are ways
// to avoid saying "binding" all the time (see the init block below).
// But if you really want aliases, you could do it this way:
val btn0: ImageButton get() = binding.appCompatButton
init {
// this lets you avoid saying binding.this, binding.that - it can look neater
with(binding) {
// set a single click listener on multiple views
listOf(
appCompatButton, appCompatButton1, appCompatButton2,
appCompatButton3, appCompatButton4, appCompatButton5, appCompatButton6
).forEach { button ->
// call our onClick function with the clicked view
button.setOnClickListener { view -> onClick(view)}
// or setOnClickListener(::onClick)
}
// or you could set each button's action here, like this
appCompatButton1.setOnClickListener { doThing() }
appCompatButton2.setOnClickListener { doSomethingElse() }
}
}
// handle all your button clicks in a function if you want -
// v doesn't need to be nullable
private fun onClick(v: View) {
// because you're using view binding, you have references to all the views -
// so you don't need to use IDs at all
when (v) {
binding.appCompatButton -> { doThing() }
}
}
Personally, if you want the buttons to be named btn0 etc, I'd rename their IDs from appCompatButton to btn0 - that way you can refer to them as binding.btn0, if that's how you prefer to think about it! Give them the names you want to use.
Hope that helps!
I'm new to Kotlin and Android development in general. I have created a custom adapter that extends BaseAdapter and it works as I want it to, but as soon as I was to add a click listener to the view that gets returned in the getView, it doesn't seem to work as expected.
I have looked everywhere for an answer. I have seen people talking about clickable, duplicateParentState, focusable,... But nothing seems to work
The click listener does react if I click on the left/right border of my layout RelativeLayout, but I want the entire RelativeLayout to react when I click.
Am I missing something in my XML? Or is there some kind of "hack" that works. I'm a little confused as to why my click listener is not working as expected
My TokenAdapter (extends BaseAdapter)
class TokenAdapter(private val ctx: Context) : BaseAdapter() {
private val tokenPersistence: TokenPersistence = TokenPersistence(ctx)
private val clipboardManager: ClipboardManager =
ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
var seconds: Int
var percentage: Int = 100
var shouldGenerateToken: Boolean = true
init {
seconds = getSecondsUntilRefresh()
}
fun getSecondsUntilRefresh() : Int{
val secondsElapsedInMinute = LocalDateTime.now().second
return if(secondsElapsedInMinute < 30) 30 - secondsElapsedInMinute else 60 - secondsElapsedInMinute
}
override fun getCount(): Int {
return tokenPersistence.length()
}
override fun getItem(position: Int): Token? {
return tokenPersistence.get(position)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val v: View = if (convertView == null) {
val inflater: LayoutInflater =
ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.list_item, parent, false)
} else {
convertView
}
val t: Token = getItem(position)!!
val title: TextView = v.findViewById(R.id.list_item_title)
val code: TextView = v.findViewById(R.id.list_item_subtitle)
title.text = t.getLabel()
if (shouldGenerateToken) {
var generatedCode : String = t.generateCode()
generatedCode = generatedCode.substring(0, 3) + " " + generatedCode.substring(3, generatedCode.length)
code.text = generatedCode
shouldGenerateToken = false
}
val countdown: ProgressBar = v.findViewById(R.id.progress_circular)
val countdownText: TextView = v.findViewById(R.id.progress_circular_text)
countdownText.text = seconds.toString()
countdown.progress = percentage
v.setOnClickListener {
val copyText = code.text.split(' ').joinToString("")
println("===================")
println("Copied: $copyText")
val clip: ClipData = ClipData.newPlainText("2FA Code", copyText)
clipboardManager.setPrimaryClip(clip)
Toast.makeText(ctx, "Copied: $copyText to clipboard", Toast.LENGTH_LONG).show()
}
return v
}
}
The list_item XML layout
<?xml version="1.0" encoding="utf-8"?>
<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/list_item_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/list_item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:paddingHorizontal="8dp"
android:paddingTop="16dp"
android:paddingBottom="0dp"
android:clickable="false"
/>
<TextView
android:id="#+id/list_item_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="#id/list_item_title"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="#id/progress_circular_container"
android:layout_toLeftOf="#id/progress_circular_container"
android:paddingHorizontal="8dp"
android:paddingTop="0dp"
android:text="123456"
android:textColor="#color/primary"
android:textSize="32sp"
android:textStyle="bold"
android:clickable="false"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/progress_circular_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_below="#id/list_item_title"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:clickable="false">
<ProgressBar
android:id="#+id/progress_circular"
android:layout_width="48sp"
android:layout_height="48sp"
android:indeterminateOnly="false"
android:progressDrawable="#drawable/pb_circular_determinative"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:progress="100"/>
<TextView
android:id="#+id/progress_circular_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/black"
app:layout_constraintBottom_toBottomOf="#+id/progress_circular"
app:layout_constraintEnd_toEndOf="#+id/progress_circular"
app:layout_constraintStart_toStartOf="#+id/progress_circular"
app:layout_constraintTop_toTopOf="#+id/progress_circular"
tools:text="30" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
So I managed to fix my problem.
I did 2 "major" things after which my problem was resolved:
I rewrote my list_item.xml making use of LinearLayout instead of RelativeLayout (although I doubt this did anything)
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="#+id/list_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:paddingHorizontal="8dp"
android:paddingTop="16dp"
android:paddingBottom="0dp"
/>
<TextView
android:id="#+id/list_item_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:paddingTop="0dp"
android:textColor="#color/primary"
android:textSize="32sp"
android:textStyle="bold"
/>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<ProgressBar
android:id="#+id/progress_circular"
android:layout_width="48sp"
android:layout_height="48sp"
android:indeterminateOnly="false"
android:progressDrawable="#drawable/pb_circular_determinative"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:progress="100"/>
<TextView
android:id="#+id/progress_circular_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/black"
app:layout_constraintBottom_toBottomOf="#+id/progress_circular"
app:layout_constraintEnd_toEndOf="#+id/progress_circular"
app:layout_constraintStart_toStartOf="#+id/progress_circular"
app:layout_constraintTop_toTopOf="#+id/progress_circular"
tools:text="30" />
</androidx.constraintlayout.widget.ConstraintLayout>
I moved my ClickListener from my Adapter getView() to my MainActivity with an setOnItemClickListener on my ListView
val listview = findViewById<ListView>(R.id.tokenList)
listview.setOnItemClickListener { parent: AdapterView<*>, view: View, position: Int, id ->
val token = tokenAdapter.getItem(position)
if(token != null) {
val code = token.generateCode()
val clip: ClipData = ClipData.newPlainText("2FA Code", code)
clipboardManager.setPrimaryClip(clip)
Toast.makeText(this, "Copied: $code", Toast.LENGTH_LONG).show()
}
}
Using this method on my Listview I also didn't have to use clickable, or any of those kinds of properties on my XML layouts. If someone knows what exactly made it work, please tell me. Personally I think it is the Listener being an 'OnItemClick' and it being moved to the MainActivity, although don't take my word for it as it could just be something else random that I did.
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()
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.
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