How to use inset dividers in RecyclerView using AndroidX? - android

I have read many posts about dividers in RecyclerViews but I did not find any implementation example of an inset dividers as suggested by Material Design:
This picture is taken from https://material.io/components/dividers#types. So I am looking for a divider that is aligned with the text on the left. Can anyone tell me how to implement such a divider using AndroidX?
EDIT:
This is my layout that contains the RecyclerView:
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/offeringsCoordLayout"
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">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/offerings_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:visibility="gone" />
<com.turingtechnologies.materialscrollbar.DragScrollBar
android:id="#+id/dragScrollBar"
android:layout_width="wrap_content"
app:msb_recyclerView="#id/offerings_recyclerview"
app:msb_lightOnTouch="false"
app:msb_handleColor="#color/accent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true" />
<include layout="#layout/resourcesview_empty"/>
</RelativeLayout>
<com.leinardi.android.speeddial.SpeedDialOverlayLayout
android:id="#+id/offerings_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:clickable_overlay="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<com.leinardi.android.speeddial.SpeedDialView
android:id="#+id/speedDialOfferings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:sdMainFabClosedSrc="#drawable/ic_add_white_24dp"
app:sdMainFabClosedBackgroundColor="#color/accent"
app:sdMainFabOpenedSrc="#drawable/ic_pencil_black"
app:sdMainFabOpenedBackgroundColor="#color/accent"
app:sdMainFabClosedIconColor="#android:color/white"
app:sdMainFabOpenedIconColor="#android:color/white"
app:layout_behavior="#string/speeddial_scrolling_view_snackbar_behavior"
app:sdOverlayLayout="#id/offerings_overlay"/>
Individual row layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/listicon_imageview"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_marginStart="10dp"
android:scaleType="centerInside"
android:clickable="true"
android:src="#drawable/ic_launcher"
android:focusable="true" />
<TextView
android:id="#+id/title_offeringslist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_toEndOf="#+id/listicon_imageview"
android:layout_toStartOf="#+id/date_offeringlist_item"
android:layout_marginEnd="5dp"
android:layout_alignBaseline="#+id/check_favorite_list"
android:text="Burnt offering"
android:maxLines="1"
android:ellipsize="end"
android:textStyle="bold"
android:textColor="#color/primary_text"
android:textSize="16sp" />
<TextView
android:id="#+id/date_offeringlist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/title_offeringslist_item"
android:layout_toStartOf="#+id/check_favorite_list"
android:layout_marginEnd="10dp"
android:maxLines="1"
android:text="Burnt offering"
android:textColor="#color/secondary_text" />
<TextView
android:id="#+id/verses_offeringlist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/title_offeringslist_item"
android:layout_alignStart="#+id/title_offeringslist_item"
android:layout_toStartOf="#+id/check_favorite_list"
android:ellipsize="end"
android:maxLines="1"
android:textColor="#color/secondary_text"
android:text="verses" />
<TextView
android:id="#+id/experience_offeringslist_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="#+id/verses_offeringlist_item"
android:layout_toStartOf="#+id/check_favorite_list"
android:maxLines="1"
android:layout_below="#+id/verses_offeringlist_item"
android:ellipsize="end"
android:textColor="#color/secondary_text"
android:text="experience" />
<CheckBox
android:id="#+id/check_favorite_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:clickable="false"
android:focusable="false"
android:button="#android:drawable/btn_star"/>
<CheckBox
android:id="#+id/check_offered_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/check_favorite_list"
android:layout_alignParentEnd="true"
android:focusable="false"
android:clickable="false"
android:layout_alignBaseline="#+id/experience_offeringslist_item"
android:layout_marginEnd="10dp" />
</RelativeLayout>

You can use a custom RecyclerView.ItemDecoration for that, check code below:
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class InsetDividerItemDecoration(
context: Context,
private val insetDividerLeft: Int) : RecyclerView.ItemDecoration() {
private val attributesArray = intArrayOf(android.R.attr.listDivider)
private var dividerDrawable: Drawable? = null
init {
val typedArray = context.obtainStyledAttributes(attributesArray)
dividerDrawable = typedArray.getDrawable(0)
if (dividerDrawable == null) {
Log.w("InsetDivider", "#android:attr/listDivider was not set in the theme used here")
}
typedArray.recycle()
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
if (parent.layoutManager == null || dividerDrawable == null) {
return
}
val left = parent.paddingLeft + insetDividerLeft
val right = parent.width - parent.paddingRight
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom: Int = top + (dividerDrawable?.intrinsicHeight ?: 0)
dividerDrawable?.setBounds(left, top, right, bottom)
dividerDrawable?.draw(canvas)
}
}
override fun getItemOffsets(
outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State
) = if (dividerDrawable == null) {
outRect.set(0, 0, 0, 0)
} else {
outRect.set(0, 0, 0, dividerDrawable?.intrinsicHeight ?: 0)
}
}
The code above was inspired by DividerItemDecoration.
Now that we have the new custom ItemDecoration, you can add it to the RecyclerView:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
// Ideally you would load the offsets from a dimensions file,
// and that is how you could easily support RTL as well.
recyclerView.addItemDecoration(InsetDividerItemDecoration(view.context, 64.toPx()))
...
}
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density).toInt()
Edit
I created a Github repo showcasing this solution in action

Related

Constraint Layout put view below if there is not enough space horizontally

Is anyone aware of how to position the buttons vertically if the button text is too long inside of the xml?
I want the buttons to be positioned horizontally if they fit, if one of the buttons has to hyphenate, then I would like to avoid that by positioning the buttons below each other. Is there a way to do it inside of the XML only?
Thanks!
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv_header"
style="#style/TitleTextAppearance.WithPrimaryBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/header_enter_startcode" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/dialog_background_with_margin">
<TextView
android:id="#+id/tv_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/pls_enter_startcode"
android:textSize="#dimen/text_size_default"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/til_startcode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="#dimen/grid_2"
app:helperText="#string/example_startcode"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tv_status">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/tiet_startcode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:maxLines="1"
android:textStyle="bold" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.constraintlayout.widget.Guideline
android:id="#+id/gl_middle"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintGuide_percent="0.5"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_continue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/grid_2"
android:layout_marginTop="#dimen/grid_2"
android:layout_marginEnd="#dimen/grid_2"
android:text="#string/btn_continue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/gl_middle"
app:layout_constraintTop_toBottomOf="#+id/til_startcode" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_cancel"
style="?materialButtonOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/grid_2"
android:layout_marginTop="#dimen/grid_2"
android:layout_marginEnd="#dimen/grid_2"
android:text="#string/cancel"
app:layout_constraintEnd_toStartOf="#+id/gl_middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/til_startcode" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
I was able to solve this problem by creating a custom view which extends ConstraintLayout Flow. (Kudos #guglhupf for suggestion).
This should also adapt to when Foldable device is being folded.
Kotlin
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.Button
import androidx.constraintlayout.helper.widget.Flow
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.allViews
import androidx.core.view.updateLayoutParams
internal class FlexLayout(context: Context, attrs: AttributeSet) : Flow(context, attrs) {
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
makeChildrenMatchParentIfTheyArePositionedVertically()
}
private fun makeChildrenMatchParentIfTheyArePositionedVertically() {
allViews.forEach { flexLayoutCandidate ->
(flexLayoutCandidate as? FlexLayout)?.let { flexLayout ->
val mutableMapOfYCoordinatesAndView = mutableMapOf<View, Int>()
//Check all the Id's that Flow layout is referencing and store them in a map
flexLayout.referencedIds.forEach { referenceId ->
val foundView = rootView.findViewById<View>(referenceId)
if (foundView != null) {
val locationOnScreen = IntArray(size = 2)
foundView.getLocationOnScreen(locationOnScreen)
Log.d("FlexLayout", "Name ${(foundView as? Button)?.text}")
Log.d("FlexLayout", "Location on screen: x = ${locationOnScreen[0]} y = ${locationOnScreen[1]}")
mutableMapOfYCoordinatesAndView[foundView] = locationOnScreen[1]
}
}
var previousMapEntry: Map.Entry<View, Int>? = null
mutableMapOfYCoordinatesAndView.forEach loopToCompareWhetherTheYCoordinatesAreDifferent#{
val currentMapEntry = it
if (previousMapEntry == null) {
previousMapEntry = currentMapEntry
return#loopToCompareWhetherTheYCoordinatesAreDifferent
}
//Check if Flow layout decided to put the view on the next line
if (previousMapEntry?.value != currentMapEntry.value) {
previousMapEntry?.key?.updateLayoutParams<ConstraintLayout.LayoutParams> {
width = ConstraintLayout.LayoutParams.MATCH_PARENT
}
currentMapEntry.key.updateLayoutParams<ConstraintLayout.LayoutParams> {
width = ConstraintLayout.LayoutParams.MATCH_PARENT
}
}
previousMapEntry = currentMapEntry
}
}
}
}
}
XML
<company.name.customview.FlexLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="#dimen/margin_between_view_blocks"
app:constraint_referenced_ids="btn_dialog_negative, btn_dialog_positive"
app:flow_horizontalGap="#dimen/space_between_two_buttons"
app:flow_horizontalStyle="spread_inside"
app:flow_verticalGap="#dimen/space_between_two_buttons"
app:flow_wrapMode="aligned"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#id/tv_dialog_body"
app:layout_constraintStart_toStartOf="#id/tv_dialog_body"
app:layout_constraintTop_toBottomOf="#+id/tv_dialog_body" />
You can put the elements in a Flow helper and configure it to automatically wrap. Remove the unnecessary constraints from btn_continue and btn_cancel
<androidx.constraintlayout.helper.widget.Flow
android:id="#+id/flow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/grid_2"
android:layout_marginStart="#dimen/grid_2"
android:layout_marginEnd="#dimen/grid_2"
app:constraint_referenced_ids="btn_continue,btn_cancel"
app:flow_horizontalGap="#dimen/grid_2"
app:flow_horizontalStyle="spread"
app:flow_wrapMode="aligned"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/til_startcode" />
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_continue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/btn_continue"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_cancel"
style="?materialButtonOutlinedStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/cancel" />

RecyclerView doesn't display all data

I am working on an application with multiple recycler views inside a fragment. Everything is working properly, except from one recycler view which doesn't display all data. I get the data after an API call, and I have already checked that they are loaded successfully. Any help would be much appreciated, thank you in advance!!
Here is the recycler view inside the fragment:
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
tools:context=".ui.fragments.ExploreFragment">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:fillViewport="true"
android:scrollbars="none">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvExploreFragmentFeaturedProducts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="30dp"
>
</androidx.recyclerview.widget.RecyclerView>
</RelativeLayout>
</ScrollView>
</RelativeLayout>
Here is the cardview which will be loaded repeatedly inside the recycler view, which is not properly displayed (only the image view and shapeable image view gets displayed):
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/layoutFeaturedProductRowCardView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp"
app:cardElevation="5dp"
android:layout_marginBottom="40dp">
<RelativeLayout
android:id="#+id/rlFeaturedProducts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:background="#drawable/rounded_frame_recommproducts_explore">
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/ivFeaturedProduct"
android:layout_width="335dp"
android:layout_height="180dp"
android:scaleType="centerCrop"
app:shapeAppearanceOverlay="#style/rounded_cornersTop"
tools:src="#tools:sample/backgrounds/scenic" />
<ImageView
android:id="#+id/imageViewFavorite"
android:layout_width="wrap_content"
android:layout_height="26dp"
android:layout_alignEnd="#id/ivFeaturedProduct"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:src="#drawable/ic_favorite_up"/>
<TextView
android:id="#+id/textViewProductTitle"
android:layout_width="295dp"
android:layout_height="wrap_content"
android:layout_below="#+id/ivFeaturedProduct"
android:layout_marginStart="15dp"
android:layout_marginTop="20dp"
android:fontFamily="#font/semibold"
android:textColor="#color/colorPrimaryDarkV2"
android:textSize="16sp" />
<TextView
android:id="#+id/textViewCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/textViewProductTitle"
android:layout_alignStart="#id/textViewProductTitle"
android:fontFamily="#font/semibold"
android:textSize="12sp"/>
<TextView
android:id="#+id/textViewDuration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/textViewCategory"
android:layout_alignStart="#id/textViewCategory"
android:layout_marginTop="20dp"
android:drawablePadding="10dp"
android:fontFamily="#font/medium"
android:text="#string/durationlabel"
android:textColor="#color/colorPrimaryLightV2"
android:textSize="12sp"
app:drawableLeftCompat="#drawable/ic_time_icon" />
<TextView
android:id="#+id/textViewDurationValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="#id/textViewDuration"
android:layout_alignBaseline="#id/textViewDuration"
android:layout_marginStart="2dp"
android:fontFamily="#font/medium"
android:textColor="#color/colorPrimaryDarkV2"
android:textSize="12sp"/>
<TextView
android:id="#+id/textViewLanguage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/textViewDuration"
android:layout_alignStart="#id/textViewDuration"
android:layout_marginTop="12dp"
android:drawablePadding="10dp"
android:fontFamily="#font/medium"
android:text="#string/languagelabel"
android:textColor="#color/colorPrimaryLightV2"
android:textSize="12sp"
app:drawableLeftCompat="#drawable/ic_audio_icon" />
<TextView
android:id="#+id/textViewLanguageValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="#id/textViewLanguage"
android:layout_alignBaseline="#id/textViewLanguage"
android:layout_marginStart="2dp"
android:fontFamily="#font/medium"
android:textColor="#color/colorPrimaryDarkV2"
android:textSize="12sp" />
<TextView
android:id="#+id/textViewThreeSixtyImages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/textViewLanguage"
android:layout_alignStart="#id/textViewLanguage"
android:layout_marginTop="12dp"
android:drawablePadding="10dp"
android:fontFamily="#font/medium"
android:text="#string/theesixtylabel"
android:textColor="#color/colorPrimaryLightV2"
android:textSize="12sp"
app:drawableLeftCompat="#drawable/ic_images_icon"
android:visibility="gone"/>
<TextView
android:id="#+id/ratingLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#id/ratingValue"
android:layout_toStartOf="#id/ratingValue"
android:layout_marginTop="2dp"
android:drawablePadding="10dp"
android:fontFamily="#font/semibold"
android:textColor="#color/colorPrimaryDarkV2"
android:textSize="14sp"
app:drawableLeftCompat="#drawable/ic_rating_small_tag" />
<TextView
android:id="#+id/ratingValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="#id/ratingCount"
android:layout_alignBaseline="#id/ratingCount"
android:layout_marginEnd="2dp"
android:fontFamily="#font/semibold"
android:textColor="#color/colorPrimaryDarkV2"
android:textSize="14sp"
/>
<TextView
android:id="#+id/ratingCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/ivFeaturedProduct"
android:layout_alignTop="#id/textViewCategory"
android:layout_alignRight="#id/ivFeaturedProduct"
android:layout_marginEnd="15dp"
android:fontFamily="#font/semibold"
android:textColor="#color/colorPrimaryLightV2"
android:textSize="14sp" />
<TextView
android:id="#+id/priceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/ratingCount"
android:layout_alignEnd="#id/ratingCount"
android:layout_marginTop="20dp"
android:fontFamily="#font/medium"
android:text="#string/fromLabel"
android:textColor="#color/colorPrimaryLightV2"
android:textSize="12sp" />
<TextView
android:id="#+id/salesValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="#id/priceLabel"
android:layout_below="#id/priceLabel"
android:layout_marginTop="2dp"
android:fontFamily="#font/medium"
android:textSize="12sp"
android:textColor="#color/colorPrimaryV2"
android:visibility="gone"/>
<TextView
android:id="#+id/priceValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/priceLabel"
android:layout_alignEnd="#id/priceLabel"
android:layout_marginTop="18dp"
android:fontFamily="#font/bold"
android:textColor="#color/colorPrimaryDarkV2"
android:textSize="18sp" />
<TextView
android:id="#+id/perPersonLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#id/priceValue"
android:layout_alignBaseline="#id/textViewThreeSixtyImages"
android:layout_alignEnd="#+id/priceValue"
android:fontFamily="#font/medium"
android:layout_marginBottom="24dp"
android:text="#string/perPersonLabel"
android:textColor="#color/colorPrimaryLightV2"
android:textSize="12sp" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
Then, here is the adapter giving the card view the values:
class ProductsAdapter(private val products: List<Product>) :
RecyclerView.Adapter<ProductsAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.view_layout_featuredproduct_row, parent, false)
view.layoutParams.height = (parent.measuredWidth - 40)/2 + 30
view.requestLayout()
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindProduct(products[position])
}
override fun getItemCount(): Int = products.size
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val image = view.findViewById<ShapeableImageView>(R.id.ivFeaturedProduct)
private val featuredProductTitle = view.findViewById<TextView>(R.id.textViewProductTitle)
private val category = view.findViewById<TextView>(R.id.textViewCategory)
private val featuredProductDuration = view.findViewById<TextView>(R.id.textViewDurationValue)
private val language = view.findViewById<TextView>(R.id.textViewLanguageValue)
private val averageRating = view.findViewById<TextView>(R.id.ratingValue)
private val ratingcount = view.findViewById<TextView>(R.id.ratingCount)
private val pricevalue = view.findViewById<TextView>(R.id.priceValue)
private val context = view.context
private val resources = view.resources
fun bindProduct(product: Product) {
with(product) {
val uri = this.header_image
Picasso.get().load(uri).
placeholder( R.drawable.progress_animation ).
transform(ColorFilterTransformation(ContextCompat.getColor(context, R.color.colorOverlay))).
into(image)
val categoryHelper = this.sku
if (categoryHelper[0].equals('S')){
category.text = resources.getString(R.string.skipTheLineTours)
category.setBackgroundResource(R.drawable.label_skipthelinetour)
category.setTextColor(resources.getColor(R.color.colorSkipTheLineTourTxt))
}
if (categoryHelper[0].equals('A')){
category.text = resources.getString(R.string.audioTours)
category.setBackgroundResource(R.drawable.label_audiotour)
category.setTextColor(resources.getColor(R.color.colorAudioTourTxt))
}
if(categoryHelper[0].equals('V')){
category.text = resources.getString(R.string.virtualTours)
category.setBackgroundResource(R.drawable.label_virtualtour)
category.setTextColor(resources.getColor(R.color.colorVirtualTourTxt))
}
featuredProductTitle.text = this.title
featuredProductDuration.text = this.duration+" minutes"
language.text = languages.size.toString()+" languages"
//category.text = this.sku
averageRating.text = this.average_rating
ratingcount.text = "("+this.rating_count.toString()+")"
pricevalue.text = this.retail_price.toString()+"€"
}
}
}
}
And last but not least, here is the code generating the recycler view:
class ExploreFragment : Fragment() {
private lateinit var viewProductsAdapter: ProductsAdapter
private val model: ProductsViewModel by activityViewModels()
private lateinit var featuredProducts: MutableList<Product>
private lateinit var viewManager : LinearLayoutManager
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_explore, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
loadFeaturedProducts()
}
private fun loadFeaturedProducts() {
if(!NetworkState().isNetworkAvailable(this#ExploreFragment.context!!.applicationContext)){
return;
}
featuredProducts = mutableListOf()
val recyclerView = rvExploreFragmentFeaturedProducts
viewProductsAdapter = ProductsAdapter(this.featuredProducts)
viewManager = LinearLayoutManager(this.context, LinearLayoutManager.VERTICAL, false)
recyclerView.layoutManager = viewManager
//recyclerView.setHasFixedSize(true)
recyclerView.adapter = viewProductsAdapter
Timber.i("Fragment: load featured products")
model.getProducts().observe(viewLifecycleOwner, { products->
if(products.status == Status.SUCCESS) {
this.featuredProducts.addAll(products.data!!)
viewProductsAdapter.notifyDataSetChanged()
}else {
Timber.i("Featured Products message: "+products.message)
Snackbar.make(view!!, "Cannot load featured products! Error:"+products.message, Snackbar.LENGTH_LONG).show()
}
})
}
}
Thank you for your answers - I figured out my mistake:
I couldn't see anything that was below the ImageView inside my CardView, even though it loaded correctly.
On ProductsListAdapter I forgot to remove an unnecessary calculation:
view.layoutParams.height = (parent.measuredWidth - 40)/2 + 30
view.requestLayout()
which was what caused the problem!
Instead of using the following code:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvExploreFragmentFeaturedProducts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="30dp"
>
</androidx.recyclerview.widget.RecyclerView>
Please update it with
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvExploreFragmentFeaturedProducts"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="30dp"
/>
the Reason you are not getting data is tags! You are not closing tag of Recycler View instead you are creating a new scope to add more things in it like we do with Layouts.
So remove : </androidx.recyclerview.widget.RecyclerView> and add "/" to the closing tag of recycler view or just simply copy and paste my code above

BottomSheet not hiding fully when swipe down. Partially Hidden

I have a BottomSheet on my App but the problem is when I swipe it down instead of hiding it fully it stays partially. Here is the picture
My goal here is to hide it fully when swiping down
Here is my code
open class RoundedBottomSheetFull : BottomSheetDialogFragment() {
override fun getTheme(): Int = R.style.BottomSheetDialogTheme
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = BottomSheetDialog(requireContext(), theme)
dialog.setOnShowListener {
val bottomSheetDialog = it as BottomSheetDialog
val parentLayout =
bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
parentLayout?.let { it ->
val behaviour = BottomSheetBehavior.from(it)
setupFullHeight(it)
behaviour.state = BottomSheetBehavior.STATE_EXPANDED
}
dialog.behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss()
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
if (BottomSheetBehavior.STATE_HALF_EXPANDED == 1) {
dismiss()
BottomSheetBehavior.STATE_HIDDEN
}
}
})
}
return dialog
}
}
private fun setupFullHeight(bottomSheet: View) {
val layoutParams = bottomSheet.layoutParams
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT
bottomSheet.layoutParams = layoutParams
}
and here is my layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
app:behavior_hideable="false"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.cardview.widget.CardView
android:id="#+id/btnBarManagement"
android:layout_width="match_parent"
android:layout_height="50dp"
android:elevation="10dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/imgBack"
android:backgroundTint="#f39c12"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:padding="10dp"
android:layout_gravity="center"
android:background="#drawable/rounded_border_edittext"
android:src="#drawable/ic_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"></ImageView>
<LinearLayout
android:gravity="center_vertical"
android:orientation="vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent">
<TextView
android:id="#+id/tvTitle"
android:textSize="15dp"
android:fontFamily="#font/man_bold"
android:text="Comments about Bar Name"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<TextView
android:id="#+id/tvCommentCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="#font/man_reg"
android:text="Total Comment(s) : 0"
android:textSize="11dp"></TextView>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.core.widget.NestedScrollView
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#fff"
android:orientation="vertical"
android:fillViewport="true">
<androidx.recyclerview.widget.RecyclerView
tools:listitem="#layout/layout_list_comments"
android:id="#+id/rvComments"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
<View
android:layout_width="match_parent"
android:layout_height="0.50sp"
android:background="#757575" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:weightSum="3"
android:orientation="horizontal"
android:padding="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_weight="3"
android:paddingTop="15sp"
android:paddingBottom="15sp"
android:drawablePadding="10sp"
android:id="#+id/etComment"
android:backgroundTint="#F0F0F0"
android:layout_marginTop="5sp"
android:layout_marginStart="13sp"
android:layout_marginBottom="10sp"
android:singleLine="false"
android:textSize="13sp"
android:fontFamily="#font/man_reg"
android:paddingStart="10sp"
android:inputType="textMultiLine"
android:paddingEnd="10sp"
android:hint="Type here to start commenting..."
android:text=""
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="#drawable/rounded_border_edittext"></EditText>
<LinearLayout
android:padding="10dp"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<ImageView
android:alpha="0.5"
android:id="#+id/lnSumbmit"
android:layout_gravity="bottom"
android:src="#drawable/ic_send"
android:layout_width="30dp"
android:layout_height="30dp"></ImageView>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
In case you need more just tell me. This is the only code I think needed. My target is to hide the bottomsheet when swipping down
val behavior = BottomSheetBehavior.from<LinearLayout>(binding.root)
behavior.addBottomSheetCallback(mBottomSheetCallback)
private val mBottomSheetCallback: BottomSheetCallback = object : BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
// ignores
}
}
use skipCollapsed in order to avoid BottomSheetDialogFragment to have the collapsed state
behaviour.state = BottomSheetBehavior.STATE_EXPANDED
// Add this
behaviour.skipCollapsed = true
Also, you dont need addBottomSheetCallback any more because the dialog will dismess it self when the user slide the dialog down.
you can set:
app:behavior_hideable="true"
or
bottomSheetBehavior.isHideable = true
and then, set:
bottomSheetBehavior.skipCollapsed = true
Add this property in XML
app:behavior_skipCollapsed="true"
When the BottomSheet is in EXPANDED state and you swipe down, it initially enters COLLAPSED state where it is partially visible. The flag behavior_skipCollapsed allows it to skip this COLLAPSED state and directly enter HIDDEN state where it is completely hidden.

RadioButton items not resetting in RecyclerView

This has been asked before but those solutions didn't help me out. I have a RecyclerView that is scrollable and only partially visible on the screen at any one time. It holds a list of RadioButtons and some text related to each item. When you click on a RadioButton, the last button selected prior to this one should turn off. It does not. Each button you click gets selected and the other ones that you selected before stay selected as well.
It's set up like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#color/white"
tools:context=".ui.SettingsActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/video_settings"
android:textColor="#color/black"
android:textStyle="bold"
android:textSize="#dimen/clickableTextSize"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="100"
android:gravity="bottom"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="15"
android:paddingStart="15dp"
android:paddingEnd="15dp"
android:text="#string/set_resolution_and_fps"
android:textSize="#dimen/history_text_size"
android:gravity="center_vertical"
android:background="#color/grey_200"
android:textColor="#color/black"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_video_resolution"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="80"
android:layout_marginTop="5dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/submitVideoSettings"
android:text="#string/submit"
android:layout_marginBottom="5dp"
android:textColor="#color/black"
android:layout_gravity="center|bottom"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
The recycler looks like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:orientation="horizontal"
xmlns:android="http://schemas.android.com/apk/res/android">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/resolutionRVRadioButton"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/resolutionRVTV"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="#string/at_symbol"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/framesPerSecondRVTV"
android:layout_marginStart="5dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/fps"
/>
</androidx.appcompat.widget.LinearLayoutCompat>
The code for the adapter looks like this.
class ResolutionRVAdapter(private val sharedNavAndMenuViewModel: SharedNavAndMenuViewModel,
private val lastResolutionSelected: String,
private val capabilityDataList: List<CameraCapabilitySpecificFPS>) : RecyclerView.Adapter<CameraResolutionRVAdapter.ViewHolder>(){
private var lastCheckedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CameraResolutionRVAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.camera_resolution_recycler_item, parent, false)
val viewHolder = ViewHolder(v)
Timber.i("Added viewHolder: ${viewHolder.resolutionText.text}")
return ViewHolder(v)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val resolutionRadioButton : RadioButton = itemView.findViewById(R.id.resolutionRVRadioButton)
val resolutionText : TextView = itemView.findViewById(R.id.resolutionRVTV)
val framesPerSecondText : TextView = itemView.findViewById(R.id.framesPerSecondRVTV)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val child = capabilityDataList[position]
Timber.i("onBindViewHolder currently working with resolution ${child.resolution} range of $child.range")
holder.resolutionText.text = child.resolution.toString()
holder.framesPerSecondText.text = child.fps
Timber.d("OnBindViewholder and lastResolutionSelected = $lastResolutionSelected")
if(lastResolutionSelected.isEmpty()) {
if(position == 0) {
holder.resolutionRadioButton.isChecked = true
Timber.i("No last resolution was selected. Setting index 0 to true")
}
} else if(lastResolutionSelected == holder.resolutionText.text.toString()) {
Timber.i("An old resolution of ${holder.resolutionText.text} was selected. Setting index $position to true")
holder.resolutionRadioButton.isChecked = true
}
holder.resolutionRadioButton.setOnClickListener {
Timber.i("Index hit was: $position resolution at that index was: ${holder.resolutionText.text}")
if(holder.resolutionRadioButton.isChecked) {
//Button was already checked...must be the same button hit before...nothing to do
} else {
holder.resolutionRadioButton.isChecked = true
runOnUiThread {
notifyItemChanged(lastCheckedPosition)
notifyItemChanged(position)
}
lastCheckedPosition = position
}
}
}
override fun getItemCount(): Int {
return capabilityDataList.size
}
}
Can anyone tell me what it is I'm doing wrong?

Viewholder clickable area has margin bottom that's not defined

Here is how the problem looks -
And here is my ViewHolder code -
<?xml version="1.0" encoding="utf-8"?>
<layout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/groups_list_root_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:drawable/list_selector_background"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="#+id/groups_list_group_image"
android:layout_width="60dp"
android:layout_gravity="center"
android:layout_height="60dp"
tools:src="#drawable/multi_user_group" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="#+id/groups_list_group_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:textSize="16sp"
tools:text="Galipoly 38 Apt" />
<TextView
android:id="#+id/groups_list_group_members"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxEms="15"
android:maxLines="1"
tools:text="Alon Shlider, Dekel Aslan, Omer..."
android:textColor="#color/colorPrimary"
android:textSize="16sp" />
<TextView
android:id="#+id/groups_list_group_open_tasks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="9 Open Tasks"
android:textColor="#color/orange"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</layout>
class GroupsListViewHolder(private val binding: GroupsListViewHolderBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(model: GroupModel, onClick: (model: GroupModel) -> Unit) {
binding.groupsListGroupName.text = model.groupName
binding.groupsListGroupMembers.text = TextUtils.join(", ", model.usersFullNames)
if (model.tasksCounter < 1) {
binding.groupsListGroupOpenTasks.setAsGone()
} else {
binding.groupsListGroupOpenTasks.setAsVisible()
binding.groupsListGroupOpenTasks.text = model.tasksCounter.toString()
.plus(" ")
.plus(TeamitApplication.context!!.getString(R.string.ongoing_tasks))
.plus(TeamitApplication.context!!.getString(R.string.dot))
}
if (model.usersFullNames.size == 1) {
binding.groupsListGroupImage.setImageResource(R.drawable.single_user_group)
} else {
binding.groupsListGroupImage.setImageResource(R.drawable.multi_user_group)
}
binding.groupsListRootLayout.setOnClickListener {
onClick(model)
}
}
}
private fun initAdapter() {
fetchGroupData { groupModelList ->
if (groupModelList.isEmpty()) {
binding.groupsListNoGroupsMessageTitle.setAsVisible()
binding.groupsListNoGroupsMessageDescription.setAsVisible()
return#fetchGroupData
}
binding.groupsListNoGroupsMessageTitle.setAsGone()
binding.groupsListNoGroupsMessageDescription.setAsGone()
adapter.submitList(null)
adapter.submitList(groupModelList)
binding.groupsListRecyclerview.setAdapterWithItemDecoration(requireContext(), adapter)
}
}
fun <T, VH : RecyclerView.ViewHolder> RecyclerView.setAdapterWithItemDecoration(context: Context, adapter: ListAdapter<T, VH>) {
this.adapter = adapter
this.setHasFixedSize(true)
this.layoutManager = LinearLayoutManager(context)
this.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
As you can see, the click area has some margin bottom that I can't understand why happens. Sometimes it happens and sometimes it doesn't and I can't figure out what wrong.
As for the extension function that I am using - I am using it for the entire application
In your first LinearLayout you have defined android:padding="10dp". I assume this causes the unwanted margin.

Categories

Resources