I am currently using a scroll view inside an Alert Dialog and need the scroll view to grow in height until the dialog reaches its maximum default height. It's a bit tough for me to explain so i have attached an illustration to help. Hope it does.
The issue i'm getting is the scrollview does not grow in height and even if i remove the app:layout_constraintBottom_toTopOf="#id/linearLayout4", it grows however the bottom part will be partially hidden by the button layout. This is my current code :
<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:id="#+id/filter_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/dialog_box"
android:minHeight="300dp"
android:elevation="12dp">
<TextView
android:id="#+id/filter_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="#string/filter"
style="#style/element_header"
android:textColor="#color/textColorDark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/filter_reset_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:text="#string/reset"
style="#style/text_info_nBold"
android:textSize="14sp"
android:textColor="#color/textColorDark"
app:layout_constraintBottom_toBottomOf="#+id/filter_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/filter_header" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="#id/linearLayout4"
app:layout_constraintTop_toBottomOf="#id/filter_header"
app:layout_constraintVertical_bias="0.0">
<androidx.core.widget.NestedScrollView
android:id="#+id/filter_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:layout_marginTop="16dp"
android:scrollbarFadeDuration="1000">
<!-- VIEWS INSIDE HERE -->
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<LinearLayout
android:id="#+id/linearLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/dialog_bottombar_layout"
android:orientation="horizontal"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="#+id/dialog_secondary_button"
style="#style/low_emphasis_btn"
android:layout_width="0dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:backgroundTint="#color/textColorDark"
android:text="#string/dialog_cancel"
android:textColor="#color/textColorDark"
android:visibility="visible" />
<Button
android:id="#+id/dialog_primary_button"
style="#style/high_emphasis_btn"
android:layout_width="0dp"
android:layout_weight="1"
android:text="#string/apply" />
</LinearLayout>
Any help would be appreciated :)
Alert dialogs tend to wrap their content or can be forced to be full screen. A size in between the optimizes the screen real estate takes a some work, but it is not impossible.
One approach is to let the system lay out the alert dialog but, before it is displayed, use a ViewTreeObserver.OnGlobalLayoutListener to examine the resulting size of the dialog. In the layout listener, the size of the dialog can be adjusted up to fit the contents of the scrolling view or adjusted up to full screen if the scrolling view contents are too large for the screen.
Here is a demo app that shows how this can be done. Comments in the code explain more.
MainActivity.kt
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onClick(v: View) {
val text = when (v.id) {
R.id.customDialogShort -> getString(R.string.short_string)
R.id.customDialogMedium -> getString(R.string.lorem_medium)
else -> getString(R.string.lorem_long)
}
// Specifying the viewGroup as a parent to the inflater makes no difference.
val dialogView = LayoutInflater.from(v.context).inflate(R.layout.con_custom_view, null, false) as ConstraintLayout
(dialogView.findViewById(R.id.textView) as TextView).text = text
val alertDialog = AlertDialog.Builder(this).setView(dialogView).create()
val decorView = alertDialog.window!!.decorView
decorView.setBackgroundResource(R.drawable.alert_dialog_background)
// We need a layout pass to determine how big everything is and needs to be. Place a hook
// at the end of the layout process to examine the layout before display.
decorView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
decorView.viewTreeObserver.removeOnGlobalLayoutListener(this)
// Find out how much of the scrolling view is usable by its child.
val scrollingView: NestedScrollView = decorView.findViewById(R.id.filter_scroll)
val scrollingViewPadding = scrollingView.paddingTop + scrollingView.paddingBottom
val scrollingUsableHeight = scrollingView.height - scrollingViewPadding
// If the child view fits in the scrolling view, then we are done.
val childView = scrollingView.getChildAt(0)
if (childView.height <= scrollingUsableHeight) {
return
}
// Child doesn't currently fit in the scrolling view. Resize the top-level
// view so the child either fits or is forced to scroll because the maximum
// height is reached. First, find out how much space is allowed by the decor view.
val displayRectangle = Rect()
decorView.getWindowVisibleDisplayFrame(displayRectangle)
val decorViewPadding = decorView.paddingTop + decorView.paddingBottom
val decorUsableHeight = displayRectangle.height() - decorViewPadding - scrollingViewPadding
// Compute the height of the dialog that will 100% fit the scrolling content and
// reduce it if it won't fit in the maximum allowed space.
val heightToFit = dialogView.height + childView.height - scrollingUsableHeight
dialogView.minHeight = min(decorUsableHeight, heightToFit)
}
})
var buttonOk: Button = dialogView.findViewById(R.id.dialog_primary_button)
buttonOk.setOnClickListener { alertDialog.dismiss() }
buttonOk = dialogView.findViewById(R.id.dialog_secondary_button)
buttonOk.setOnClickListener { alertDialog.dismiss() }
alertDialog.show()
}
}
activity_main.xml
<LinearLayout
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="#+id/customDialogShort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Short text" />
<Button
android:id="#+id/customDialogMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Medium text" />
<Button
android:id="#+id/customDialogLong"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="Long text" />
</LinearLayout>
con_custom_view
Custom Layout for the AlertDialog.
<TextView
android:id="#+id/filter_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="#string/filter"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/filter_reset_btn"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:text="#string/reset"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="#+id/filter_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#+id/filter_header" />
<androidx.core.widget.NestedScrollView
android:id="#+id/filter_scroll"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:padding="16dp"
android:scrollbarFadeDuration="1000"
app:layout_constraintBottom_toTopOf="#id/linearLayout4"
app:layout_constraintTop_toBottomOf="#id/filter_header">
<TextView
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="#string/lorem_long" />
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:id="#+id/linearLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="#+id/dialog_secondary_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:text="#string/dialog_cancel"
android:visibility="visible" />
<Button
android:id="#+id/dialog_primary_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="#string/apply" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Related
I have 2 views and I need a barrier below but the barrier does not work as expected.
Here is my layout.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/textView15"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="This is a text view"
app:layout_constraintEnd_toStartOf="#+id/t1"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/t1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/textView15"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a demo text to check wrap content"/>
</com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.Barrier
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="textView15,t1"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The black dotted line is the barrier.
This might be a bug or I am doing it wrong, The result is same in preview and actual device
If you specify
app:layout_optimizationLevel="none"
in the XML for the ConstraintLayout, you will find that the barrier will be placed correctly. I am not sure what setting the optimization level achieves, but it has been an issue recently with barriers. (ConstraintLayout version 2.1.3).
Here is how the layout looks before suppressing optimization. The barrier rises up into the right TextView as noted.
We suppress optimization by stating in the XML with no other changes:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_optimizationLevel="none"
xmlns:app="http://schemas.android.com/apk/res-auto">
Now the layout looks like this:
The barrier has dropped below the right TextView where it belongs.
This is with ConstraintLayout version 2.1.3.
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
(It seems that setting optimization level to anything but standard solves this problem.)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="#+id/textView15"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:text="This is a text view"
app:layout_constraintEnd_toStartOf="#+id/t1"
android:textSize="20sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/t1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/textView15"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a demo text to check wrap content"/>
</com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.Barrier
android:id="#+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="textView15,t1"/>
<Space
android:id="#+id/space"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#id/barrier" />
</androidx.constraintlayout.widget.ConstraintLayout>
As was written before, ConstraintLayout versions 2.1.3 and 2.1.4 work incorrectly, you can downgrade it to 2.0.0. If not, you can use two ways. In both cases remove <androidx.constraintlayout.widget.Barrier>.
Insert LinearLayout to occupy needed views. For instance, if you have 2 TextInputLayouts in horizontal position, you can write so:
<LinearLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/upper_view">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/input_layout_1"
android:layout_weight="1"
...
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/input_layout_2"
android:layout_weight="1"
...
</LinearLayout>
If you have a complex layout with different views, write so.
<Space
android:id="#+id/space"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="#id/input_layout_2"
app:layout_constraintTop_toBottomOf="#+id/upper_view" />
Then you should change Space height in code. Probably you can apply this code to all TextInputLayouts.
// View size change listener
private fun View.onSizeChange(callback: () -> Unit) {
addOnLayoutChangeListener(object : OnLayoutChangeListener {
override fun onLayoutChange(
view: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
) {
view?.removeOnLayoutChangeListener(this)
if (right - left != oldRight - oldLeft || bottom - top != oldBottom - oldTop) {
callback()
}
}
})
}
binding.inputLayout2.onSizeChange {
// First remove bottom constraint
val layoutParams = binding.space.layoutParams as ConstraintLayout.LayoutParams
layoutParams.bottomToBottom = ConstraintLayout.LayoutParams.UNSET
// Now set height of a <Space> (maximum of two TextInputLayouts)
val h1 = binding.inputLayout1.height
val h2 = binding.inputLayout2.height
binding.inputLayout2.postDelayed({
binding.space.updateLayoutParams { height = max(h1, h2) }
}, 1)
}
Then bind bottom views to LinearLayout or Space instead of Barrier.
I have layouts which will be repetitive added in main layout:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/txt_temp_feels_like"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="sans-serif-medium"
android:text="-"
android:textColor="#color/gray"
android:textSize="18sp" />
I want it to be added to this container:
<LinearLayout
android:id="#+id/temp_feels_like_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:orientation="horizontal">
</LinearLayout>
And I want to do it in code. I'm using this method:
private fun setData(
layout: List<Int>,
container: List<ViewGroup>,
) {
for (i in layout.indices) {
container[i].addView(layoutInflater.inflate(layout[i], null))
}
}
where layout is link to resource of layout.
And it's fill it but i have some problems:
I can't set text to this added view
I can't set weight to this view
Maybe my algorithm is wrong. Can you help?
It depends on when should you set the text and gravity. If you can set it before adding to layout - you can do something like that:
val view = layoutInflater.inflate(layout[i], null) as TextView
view.apply {
text = "abc"
params = (params as LinearLayout.LayoutParams).apply {
weight = *value you need*
}
}
container[i].addView(view)
Or if you need to set text to view after views are added to layout, you can reference to view via container.children/container.getChildAt(i)
UPD
you can get the child in the following way:
val count = layout.getChildCount()
var v: TextView? = null
for (i in 0..count) {
val view = layout.getChildAt(i)
if (view is TextView) {
v = view
}
}
Or one more way - set tag to view while adding it to layout and then you can get it by tag in any time you need
Could something like this help?
<LinearLayout
android:id="#+id/temp_feels_like_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:layout_marginEnd="20dp"
android:layout_marginStart="20dp"
android:orientation="horizontal"
>
<TextView
android:id="#+id/txt_temp_feels_like1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="sans-serif-medium"
android:visibility="gone"
android:text="-"
android:textColor="#color/gray"
android:textSize="18sp"
/>
<TextView
android:id="#+id/txt_temp_feels_like2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="sans-serif-medium"
android:visibility="gone"
android:text="-"
android:textColor="#color/gray"
android:textSize="18sp"
/>
<TextView
android:id="#+id/txt_temp_feels_like3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="sans-serif-medium"
android:visibility="gone"
android:text="-"
android:textColor="#color/gray"
android:textSize="18sp"
/>
<TextView
android:id="#+id/txt_temp_feels_like4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="sans-serif-medium"
android:visibility="gone"
android:text="-"
android:textColor="#color/gray"
android:textSize="18sp"
/>
</LinearLayout>
You can have the gravity set appropriately (and the text if it's not dynamic),
and then set the visibility in code rather than try to add the view.
findViewById<View>(R.id.txt_temp_feels_like1).visibility = View.VISIBLE
I wanna achieve the following be behavior for one screen of my app.
I have a fragment's layout with ConstraintLayout as it's parent. Inside ConstraintLayout I have a ScrollView with nested ConstraintLayout (nested ConstraintLayout contains ImageView and TextView) and simple Button below the ScrollView.
I wanna enable button as soon as user reaches to the bottom of ScrollView and disable when user scrolls up.
Layout is below.
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
>
<ScrollView
android:id="#+id/scrollableView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toTopOf="#id/elevationShadow"
app:layout_constraintTop_toBottomOf="#id/appbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_marginStart="#dimen/spacing_large"
android:layout_marginEnd="#dimen/spacing_large"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/user_image"
android:layout_width="144dp"
android:layout_height="144dp"
android:layout_gravity="center"
android:layout_marginTop="#dimen/spacing_large"
android:src="#drawable/user_image"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="#+id/heading"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="#dimen/text_size_xxlarge"
android:textStyle="bold"
android:textAlignment="center"
android:textColor="#color/black_color"
android:layout_marginTop="#dimen/spacing_large"
tools:text="Tools text"
android:textAppearance="?tvptTextAppearanceBody"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/user_image"
/>
<TextView
android:id="#+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/spacing_large"
android:textAlignment="center"
android:textSize="#dimen/user_info_content_text_size"
android:textAppearance="?tvptTextAppearanceBody"
android:textColor="#color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/heading"
tools:text="Tools test content"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<View
android:id="#+id/elevationShadow"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#drawable/shadow_elevation"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="#dimen/user_info_activity_confirm_button_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="#id/button_confirm"/>
<com.travelportdigital.android.compasswidget.button.PercentageBasedStateButton
android:id="#+id/button_confirm"
style="#style/PrimaryButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:layout_marginBottom="#dimen/user_info_activity_confirm_button_margin"
android:text="#string/user_info_continueButton_title"
android:textAllCaps="true"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The problem is that content for TextView which is inside ScrollView can both long and short. That's why I had to add ScrollView if the content is long.
By the small piece of code I was able to achieve the behavior I needed with one small remark.
fun addScrollChangeListener() {
scrollView.viewTreeObserver
.addOnScrollChangedListener {
enableContinueButton(scrollView.getChildAt(0).bottom <= scrollView.height + scrollView.scrollY)
}
}
And the code above works fine for the scenario when content is long (when user arrives to this screen Continue button is disabled and when user scrolls to the bottom of the scroll view it become enabled if user scrolls up it becomes disabled again.
I wanna update this logic to enable button when user arrives to this screen and content of TextView inside ScrollView is short (no need scrolling for this scenario).
I made some researches in Google and could not find the solution which would work for me.
In onViewCreated() method I added logic to disable or enable button when user arrives to this screen.
enableContinueButton(!isScrollingRequired())
I tried this implementation
private fun isScrollingRequired(): Boolean {
val view = scrollView.getChildAt(scrollView.childCount - 1) as View
val diff = view.bottom - (scrollView.height + scrollView.scrollY)
return diff != 0
}
and this
return if (child != null) {
val childHeight = child.height
scrollView.height <= childHeight + scrollView.paddingTop + scrollView.paddingBottom;
} else {
false
}
but it did not work, because ScrollView height and it's child height is always 0
Looking forward your advices.
Regards,
Alex
I dont know is this what you want to do but it should be one of the solution.
I think you can just simply add the button in the "ScrollView" so when user scrolls at the bottom, user will see the button and when user scrolls up, user cannot press the button as well.
Below layout .XML works for me, Using ScrollView with ConstraintLayout:
(you may need extra dependencies)
<ScrollView
android:id="#+id/msg_scroll"
android:layout_width="0dp"
android:layout_height="200dp"
android:fillViewport="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#+id/infoSumm">
<!--Display the <ScrollView> under <TextView>"#+id/infoSumm" -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/inside_scroll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="#+id/infoDetail"
android:text=""
android:layout_marginTop="12dp"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="240dp"
app:layout_constraintTop_toTopOf="#id/inside_scroll"
app:layout_constraintStart_toStartOf="parent"
tools:text="Info Detail"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_infoClose"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/default_field_spacing"
android:backgroundTint="#color/colorPrimary"
android:textAppearance="#style/TextAppearance.MaterialComponents.Button"
android:textAlignment="center"
android:textStyle="bold"
android:textAllCaps="false"
android:textSize="20sp"
android:textColor="#color/colorWhite"
android:text="#string/btn_Close"
android:paddingTop="10dp"
android:paddingBottom="10dp"
app:cornerRadius="25dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/infoDetail"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
I am using TextInputLayout to take advantage of the floating hint and error display, the validation logic triggers when I press the continue button, it works fine if I don't select any textInput, the error is displayed correctly, but if I select a textInput, dismiss the keyboard and then press the continue button, the textInput is highlited in red as expected but the error text is not displayed.
What seems to be weird is that I tried the same code, same layout on a Acitvity and it worked fine (I am using a Fragment for this), but I would much rather stick to Fragment.
I also inspected the layout (when the error was not displayed) and the error textView seems to be there (in the view tree you can see the textView inside the TextInputLayout with a width of 0 and the text set to whatever I set it to)
My layout is:
<androidx.core.widget.NestedScrollView 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:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<TextView
android:id="#+id/text_acc_details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:text="#string/sign_up_details"
android:textAppearance="#style/headerText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="#+id/linear_acc_details"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/text_acc_details">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/text_input_name"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:boxBackgroundMode="outline"
app:errorEnabled="true"
app:hintAnimationEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/text_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/sign_up_text_name"
android:inputType="text"
android:textColor="#color/textColour"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/text_input_last_name"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/text_last_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/sign_up_text_last_name"
android:inputType="text"
android:textColor="#color/textColour"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/text_input_email1"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/text_email1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/sign_up_text_email1"
android:inputType="text"
android:textColor="#color/textColour"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/text_input_email2"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/text_email2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/sign_up_text_email2"
android:inputType="text"
android:textColor="#color/textColour"
android:textSize="18sp" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="#+id/button_sign_up_continue"
style="#style/PrimaryButton"
android:layout_width="300dp"
android:layout_marginBottom="23dp"
android:enabled="true"
android:text="#string/sign_up_button_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
My Fragment code:
class SignUpFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val rootView = inflater.inflate(R.layout.fragment_sign_up, container, false)
return rootView
}
override fun onStart() {
super.onStart()
button_sign_up_continue.setOnClickListener {
validateText()
}
}
private fun validateText() {
if (text_name.text.isNullOrEmpty()) {
text_input_name.error = resources.getText(R.string.sign_up_error_text)
} else {
text_input_name.error = null
}
if (text_last_name.text.isNullOrEmpty()) {
text_input_last_name.error = resources.getText(R.string.sign_up_error_text)
} else {
text_input_last_name.error = null
}
val isValidEmail = Patterns.EMAIL_ADDRESS.toRegex().matches(text_email1.text.toString())
if (text_email1.text.isNullOrEmpty() || !isValidEmail) {
text_input_email1.error = resources.getText(R.string.sign_up_invalid_email)
} else {
text_input_email1.error = null
}
}
The gif shows how by tapping the button first works fine, but if I select a textInput first and then tap the button the error text is not displayed (it is set and is there by checking in the layout inspector tool but the width is 0 for some reason)
After spending quite a lot of time in this issue, I found out that this seems to be a Material Android issue (at least on com.google.android.material:material:1.1.0 library version), you can find the issue tracker here:
https://issuetracker.google.com/issues/136435162
For the time being I found a work around to fix the errorTextView's width issue by creating a TextInputLayout extension and updating the errorTextView width manually:
val errorTextView = this.findViewById<TextView>(R.id.textinput_error)
var params = errorTextView?.layoutParams
params?.width = yourWidth
errorTextView?.layoutParams = params
It is not pretty but it does the trick for now, hope it helps someone
Put this method inside onActivityCreated() instead of onStart()
button_sign_up_continue.setOnClickListener {
validateText()
}
I have create an android app with Kotlin.This app contains an interface, which contains a recyclerview to get all the list of product.
To fill this list of product, i create an adapter, this adapter is a cardview : Imageview and two textview.
In this interface, i added two buttons, one when i click on all the product display with the list and the other one display with grid disposition.The default value for grid disposition.
I wsant to change the width and the height of a cardview, to display all the product in the List.
The following code is anadapter item:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/cardProductItem"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_marginTop="#dimen/spacing_medium"
android:layout_marginBottom="#dimen/spacing_medium"
android:layout_marginRight="#dimen/spacing_middle"
android:layout_marginLeft="#dimen/spacing_middle"
android:background="#color/grey">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="#+id/productImage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY"
android:adjustViewBounds="true"
android:layout_centerInParent="true"
android:layout_gravity="center"/>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="#dimen/spacing_middle">
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/productName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginLeft="#dimen/spacing_medium"
android:fontFamily="#font/lato_regular"
android:textAppearance="#style/TextAppearance.AppCompat.Medium"
android:textColor="#color/black"/>
<TextView
android:id="#+id/productPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:layout_marginLeft="#dimen/spacing_medium"
android:textStyle="bold"
android:fontFamily="#font/lato_regular"
android:textAppearance="#style/TextAppearance.AppCompat.Medium"
android:textColor="#color/black"/>
</LinearLayout>
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content">
<ImageView
android:id="#+id/arrowDetailProduct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:layout_margin="8dp"
android:visibility="gone"
android:layout_gravity="center|center_horizontal"
android:src="#drawable/ic_right_arrow"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
the following code is an extract from an xml file which cotains a recyclerview :
<ImageView
android:id="#+id/gridProducts"
android:layout_width="#dimen/spacing_mlarge"
android:layout_height="#dimen/spacing_mlarge"
android:scaleType="fitXY"
android:layout_toLeftOf="#+id/productNumber"
android:layout_margin="8dp"
android:src="#drawable/ic_divided_squares"/>
<ImageView
android:id="#+id/listProducts"
android:layout_width="#dimen/spacing_mlarge"
android:layout_height="#dimen/spacing_mlarge"
android:scaleType="fitXY"
android:layout_margin="8dp"
android:src="#drawable/ic_rounded_black_square_shape"/>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerViewProductByCategory"
android:layout_below="#+id/recyclerViewCategories"
android:layout_marginTop="66dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/spacing_middle"
android:layout_marginEnd="#dimen/spacing_middle"
android:orientation="horizontal"/>
the following code contains the onclick acion into the two buttom(Imageview) :
gridProducts.setOnClickListener {
recyclerViewProductByCategory.layoutManager =
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recyclerViewProductByCategory.adapter = products?.let { it -> ProductAdapter(it) }
gridProducts.visibility = View.INVISIBLE
listProducts.visibility = View.VISIBLE
}
listProducts.setOnClickListener {
recyclerViewProductByCategory.layoutManager =
LinearLayoutManager(this#CategoryByProduct, RecyclerView.VERTICAL, false)
recyclerViewProductByCategory.adapter = products?.let { it -> ProductAdapter(it) }
val layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT, // CardView width
LayoutParams.WRAP_CONTENT // CardView height
)
cardProductItem.layoutParams = layoutParams
listProducts.visibility = View.INVISIBLE
gridProducts.visibility = View.VISIBLE
}
after running my app an exception appear as the following :
java.lang.IllegalStateException: cardProductItem must not be null
could you please tell me where's the error and how can i correct it
Based on the code available, you are setting the value of cardProductItem outside of setOnClickListener. The error is stating that cardProductItem can not be set when a listProduct is clicked. Make sure that you are setting cardProductItem to a value before the UI is presented to the user.
For a more in-depth answer, I would need to look at the other places where cardProductItem is used.