Material Cardview - rounded corner of TabLayout not getting clipped - android

When I'm putting my TabLayout inside MaterialCardView to make TabLayout rounder but I'm not getting desired result
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cardCornerRadius="#dimen/dp_20"
android:theme="#style/Theme.MaterialComponents.Light"
app:strokeColor="#color/dark_blue"
app:cardPreventCornerOverlap="true"
app:strokeWidth="1dp">
<com.google.android.material.tabs.TabLayout
android:id = "#+id/tab_layout"
android:layout_width = "match_parent"
android:layout_height = "#dimen/dp_35"
app:tabGravity = "fill"
app:tabIndicatorColor = "#color/dark_blue"
app:tabIndicatorGravity = "stretch"
app:tabMaxWidth = "0dp"
app:tabMode = "fixed"
app:tabSelectedTextColor = "#android:color/white"
app:tabTextAppearance = "#style/AppTabTextTools"
app:tabTextColor = "?attr/colorPrimary">
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.viewpager.widget.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Please help me to figure out what's the problem in above layout.
I need result like below image

As suggested by #Malik Saifullah followed https://stackoverflow.com/a/50621395/770703
Created custom RoundedTabLayout class for simplification
class RoundedTabLayout : TabLayout, TabLayout.OnTabSelectedListener {
private lateinit var currentContext: Context
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
currentContext = context
addOnTabSelectedListener(this)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs, 0) {
currentContext = context
addOnTabSelectedListener(this)
}
constructor(context: Context) : super(context)
override fun onTabSelected(tab: Tab?) {
if (selectedTabPosition == 0) {
setTabBG(R.drawable.tab_left_select, R.drawable.tab_right_unselect)
} else {
setTabBG(R.drawable.tab_left_unselect, R.drawable.tab_right_select)
}
}
override fun onTabUnselected(tab: Tab?) {
}
override fun onTabReselected(tab: Tab?) {
}
private fun setTabBG(tab1: Int, tab2: Int) {
val tabStrip = getChildAt(0) as ViewGroup
val tabView1 = tabStrip.getChildAt(0)
val tabView2 = tabStrip.getChildAt(1)
if (tabView1 != null) {
val paddingStart = tabView1.paddingStart
val paddingTop = tabView1.paddingTop
val paddingEnd = tabView1.paddingEnd
val paddingBottom = tabView1.paddingBottom
ViewCompat.setBackground(tabView1, AppCompatResources.getDrawable(tabView1.context, tab1))
ViewCompat.setPaddingRelative(tabView1, paddingStart, paddingTop, paddingEnd, paddingBottom)
}
if (tabView2 != null) {
val paddingStart = tabView2.paddingStart
val paddingTop = tabView2.paddingTop
val paddingEnd = tabView2.paddingEnd
val paddingBottom = tabView2.paddingBottom
ViewCompat.setBackground(tabView2, AppCompatResources.getDrawable(tabView2.context, tab2))
ViewCompat.setPaddingRelative(tabView2, paddingStart, paddingTop, paddingEnd, paddingBottom)
}
}
}

Related

View has extra pixels when rounding corners

I have some missunderstading why this is happening. I have a parent FrameLayout that has rounded corners, and a TextView with bottom rounded corners. Corner raduses are equal, but on the screen displaying some extra pixels.
I got the same behavior with default xml rounding, and i would like to receive right work of rounded corners with not affect of parent view.
Custom text View
class SampleTextView #JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attributeSet, defStyleAttr) {
private val cornerRadius: Float = 20f
init {
outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
val left = 0
val top = 0
val right = view.width
val bottom = view.height
outline.setRoundRect(
left,
(top - cornerRadius).toInt(),
right,
bottom,
cornerRadius
)
}
}
clipToOutline = true
}
}
Xml
<?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="match_parent"
android:background="#color/black"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/fl"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.example.testproject.SampleTextView
android:id="#+id/child"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_gravity="bottom"
android:background="#color/black"
android:text="Some text text"
android:textColor="#color/white" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val parent = findViewById<FrameLayout>(R.id.fl)
parent.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
val left = 0
val top = 0
val right = view.width
val bottom = view.height
outline.setRoundRect(left, top, right, bottom, 20.toFloat())
}
}
parent.clipToOutline = true
}
}
Strange behavior

Custom Button component: shadow is clipped

I have custom LoadingButton class implemented as FrameLayout which is then used inside XMLs as component. Its basically Button but made completely custom with its own layout and components.
What I want is to add shadow there (elevation, translationZ) but this shadow is clipped everywhere.
I want to have this button dynamic that I can adjust its margins or change its shape like adding static width and height on different screens without distorting shadow around. Shadow is clipped either from top or bottom all the time.
Example of xml view:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="CustomRes" type="com.project.utils.CustomResources"/>
</data>
<merge
android:duplicateParentState="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:id="#+id/buttonParent"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:duplicateParentState="true"
android:clipToPadding="false"
android:clipChildren="false"
android:gravity="center">
<ImageView
android:id="#+id/buttonIcon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:layout_gravity="center"
android:scaleType="fitCenter"/>
<TextView
android:id="#+id/buttonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:textSize="#dimen/text_medium"
android:textStyle="bold"
android:lines="1"
android:layout_gravity="center"/>
</LinearLayout>
<com.project.components.loading_indicator.LoadingIndicator
android:id="#+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" />
</merge>
</layout>
Class:
#Suppress("DEPRECATION")
class LoadingButton : FrameLayout {
#StyleableRes
internal val btnIcoIndex = 0
#SuppressLint("ResourceType")
#StyleableRes
internal val btnTextResIndex = 1
#StyleableRes
#SuppressLint("ResourceType")
internal val btnTextIndex = 2
#StyleableRes
#SuppressLint("ResourceType")
internal val btnTextSizeIndex = 3
private val buttonParent: LinearLayout
private val progressBar: LoadingIndicator
private val buttonIcon: ImageView
private val buttonText: TextView
private var buttonTextVal: String? = null
init {
CustomResources.inflateLayout(LayoutInflater.from(context), R.layout.loading_button, this)
buttonParent = findViewById(R.id.buttonParent)
buttonIcon = findViewById(R.id.buttonIcon)
buttonText = findViewById(R.id.buttonText)
progressBar = findViewById(R.id.progress)
buttonText.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
textViewInitWidth = maxOf(v.measuredWidth, textViewInitWidth)
}
}
#SuppressLint("ClickableViewAccessibility")
constructor(context: Context) : super(context) {
parseAttrs(context)
}
#SuppressLint("ClickableViewAccessibility")
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
parseAttrs(context, attrs)
}
#SuppressLint("ClickableViewAccessibility")
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
parseAttrs(context, attrs, defStyleAttr)
}
#SuppressLint("ResourceType")
private fun parseAttrs(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int? = null) {
//Load from custom attributes
setLoading(false)
val sets = intArrayOf(R.attr.l_buttonIcon, R.attr.l_buttonTextId, R.attr.l_buttonText, R.attr.l_buttonTextSize)
if (attrs != null){
val typedArray = context.obtainStyledAttributes(attrs, sets)
val buttonIco = typedArray.getResourceId(btnIcoIndex, 0)
val buttonTxtRes = typedArray.getText(btnTextResIndex)?.let { res->
App.getString(res as String).toLowerCase(Locale.getDefault()).replaceFirstChar { it.toUpperCase() }
}
val buttonTxtRaw = typedArray.getText(btnTextIndex)?.let { it as String }?:"null"
val buttonTxtSize = typedArray.getDimension(btnTextSizeIndex, resources.getDimension(R.dimen.text_medium))
val buttonStyle = attrs.styleAttribute
App.log("RawTextButton: $buttonTxtRaw, hasValue: ${typedArray.getString(btnTextIndex)}")
setButtonTextSize(buttonTxtSize)
buttonTxtRes?.let { setButtonText(buttonTxtRes) }?:kotlin.run{ setButtonText(buttonTxtRaw) }
setButtonIcon(buttonIco)
buttonParent.gravity = Gravity.CENTER
buttonTextVal = buttonTxtRes?.let { buttonTxtRes.toString() }?:buttonTxtRaw
typedArray.recycle()
clipToPadding = false
clipChildren = false
when(buttonStyle){
R.style.button_primary -> {
App.log("BtnStyleId - primary")
setupTextStyle(buttonStyle)
setProgBarColor(R.color.button_light)
setIconTint(ContextCompat.getColor(context, R.color.button_light))
maybeSetAmbientShadow(R.color.button_primary)
}
R.style.button_secondary -> {
App.log("BtnStyleId - secondary")
setupTextStyle(buttonStyle)
setProgBarColor(R.color.button_primary)
setIconTint(ContextCompat.getColor(context, R.color.button_light))
maybeSetAmbientShadow(R.color.button_primary)
}
R.style.button_secondary_alert -> {
App.log("BtnStyleId - secondary alert")
setupTextStyle(buttonStyle)
setProgBarColor(R.color.button_invalid)
setIconTint(ContextCompat.getColor(context, R.color.button_invalid))
maybeSetAmbientShadow(R.color.button_invalid)
}
else -> App.log("BtnStyleId -> $buttonStyle")
}
} else {
App.log("BtnStyleId -> attrs==null")
}
}
#SuppressLint("ResourceType")
private fun setupTextStyle(buttonStyle: Int){
val attrs = intArrayOf(android.R.attr.textAppearance)
val typedAttrs = context.obtainStyledAttributes(buttonStyle, attrs)
val textAppearance = typedAttrs.getResourceId(0, 0)
TextViewCompat.setTextAppearance(buttonText, textAppearance)
typedAttrs.recycle()
}
private fun setProgBarColor(color: Int){
progressBar.setColorTint(color)
}
fun setIconTint(color: Int){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
buttonIcon.colorFilter = BlendModeColorFilter(color, BlendMode.SRC_IN)
} else {
App.log("setting color filter")
buttonIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN)
}
}
private fun maybeSetAmbientShadow(color: Int){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P){
outlineAmbientShadowColor = ContextCompat.getColor(this.context, color)
outlineSpotShadowColor = ContextCompat.getColor(this.context, color)
}
}
}
I need some variable to set which will set clipToPadding false and clipChildren false for every single screen where this button is implemented without rewriting 80+ xml files. Because some screens have marginTop set for this button but not marginBottom, and that will cause clipping even if I set those 2 parameters to false. Clearly bad design from Android for handling shadows.
Base style for primary and secondary button:
<style name="button" parent="#android:style/Widget.Material.Button">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
<item name="android:paddingStart">#dimen/button_padding_start</item>
<item name="android:paddingEnd">#dimen/button_padding_end</item>
<item name="android:paddingTop">#dimen/button_padding_top</item>
<item name="android:paddingBottom">#dimen/button_padding_bottom</item>
<item name="android:elevation">4dp</item>
<item name="android:translationZ">4dp</item>
<item name="android:stateListAnimator">#null</item>
<item name="android:clipToPadding">false</item>
</style>
Usage:
<com.project.components.loading_button.LoadingButton
android:id="#+id/loginButton"
android:layout_gravity="center"
android:layout_weight="0.5"
android:layout_marginStart="8dp"
app:l_buttonTextId="button_login"
style="#style/button.primary" />

How to inflate custom view with XML layout with use of ViewBinding?

I have layout in XML:
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/hsv"
android:layout_width="match_parent"
android:layout_height="92dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#555555"
android:orientation="horizontal"
android:paddingHorizontal="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</HorizontalScrollView>
And extended HorizontalScrollView as custom view definition:
class TopBubblesWidget(context: Context, attrs: AttributeSet? = null) : HorizontalScrollView(context, attrs) {
private var binding: FragmentBiometricTopBubblesBinding = FragmentBiometricTopBubblesBinding.inflate(LayoutInflater.from(context))
private var data: List<BubblesWidget.Data>? = null
override fun onFinishInflate() {
super.onFinishInflate()
binding.rv.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
}
private fun initView(data: List<BubblesWidget.Data>) {
binding.rv.adapter = TopBubblesAdapter(data)
}
fun updateData(data: List<BubblesWidget.Data>) {
initView(data)
}
}
The problem is that TopBubblesWidget is not inflated by the XML and I do not see the RecyclerView.
What am I doing wrong here?
I have a feeling this is what you are looking for. This is a sample code -
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PartyViewHolder {
return PartyViewHolder(
PartyListItemBinding.inflate(LayoutInflater.from(parent.context)),
viewModel
)
}
and if you want to inflate a custom view like a prompt, here's a sample code -
private fun logOutAndExit() {
val dialogBox = Dialog(requireContext())
val promptLogOutBinding = PromptLogOutBinding.inflate(layoutInflater)//declaration done here
dialogBox.apply {
setContentView(promptLogOutBinding.root)
window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setCancelable(false)
show()
}
in the top, my layout file is party_list_item while, in the bottom example, my layout is prompt_log_out
and this -
PartyListItemBinding.inflate(LayoutInflater.from(parent.context))
is how you inflate a custom layout

How to set indicator in BottomNavigationView? [duplicate]

This question already has answers here:
Badge on BottomNavigationView
(4 answers)
Closed 2 years ago.
I want to set indicator on bottomNavigationView.You can see that orange line at bottom side
How to set indicator?
I didn't find any example for indicator.
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#drawable/bottom_navigation_bg"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_menu_main"/>
My previous answer offers a lot of flexibility and animates nicely between selected states but involves a relatively large amount of code. A simpler solution that comes without animation, would be to use the itemBackground attribute of the BottomNavigationView:
<?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="match_parent"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="#drawable/bottom_nav_tab_background"
android:layout_gravity="bottom"
app:menu="#menu/menu_bottom_nav" />
</LinearLayout>
bottom_nav_tab_background
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<layer-list>
<item android:gravity="bottom">
<shape android:shape="rectangle">
<size android:height="4dp" />
<solid android:color="?attr/colorPrimary" />
</shape>
</item>
</layer-list>
</item>
</selector>
Here's something I whipped up to add an indicator and move it around.
class IndicatorBottomNavigationView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.bottomNavigationStyle
) : BottomNavigationView(context, attrs, defStyleAttr) {
private val indicator: View = View(context).apply {
layoutParams = LayoutParams(0, 5)
setBackgroundColor(ContextCompat.getColor(context, R.color.carecredit_green))
}
init {
addView(indicator)
}
var animateIndicator = true
override fun setOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener?) {
OnNavigationItemSelectedListener { selectedItem ->
menu
.children
.first { item ->
item.itemId == selectedItem.itemId
}
.itemId
.let {
findViewById<View>(it)
}
.let { view ->
this.post {
indicator.layoutParams = LayoutParams(view.width, 5)
if (animateIndicator) {
indicator
.animate()
.x(view.x)
.start()
} else {
indicator.x = view.x
}
indicator.y = view.y
}
}
listener?.onNavigationItemSelected(selectedItem) ?: false
}.let {
super.setOnNavigationItemSelectedListener(it)
}
}
}
To get this working you need to extend the BottomNavigationView to be able to have multiple listeners. The original implementation only allows for one but you need another to be able to tell the indicator when to move.
class CustomBottomNavigationView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BottomNavigationView(context, attrs, defStyleAttr), OnNavigationItemSelectedListener {
private val onNavigationItemSelectedListeners =
mutableListOf<OnNavigationItemSelectedListener>()
init {
super.setOnNavigationItemSelectedListener(this)
itemIconTintList = null
}
override fun setOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener?) {
if (listener != null) addOnNavigationItemSelectedListener(listener)
}
fun addOnNavigationItemSelectedListener(listener: OnNavigationItemSelectedListener) {
onNavigationItemSelectedListeners.add(listener)
}
fun addOnNavigationItemSelectedListener(listener: (Int) -> Unit) {
addOnNavigationItemSelectedListener(OnNavigationItemSelectedListener {
for (i in 0 until menu.size()) if (menu.getItem(i) == it) listener(i)
false
})
}
override fun onNavigationItemSelected(item: MenuItem) = onNavigationItemSelectedListeners
.map { it.onNavigationItemSelected(item) }
.fold(false) { acc, it -> acc || it }
}
Then create an indicator item:
class BottomNavigationViewIndicator #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
val size = 8f
private val targetId: Int
private var target: BottomNavigationMenuView? = null
private var rect = Rect()
// val drawPaint = Paint()
private val backgroundDrawable: Drawable
private var index = 0
private var animator: AnimatorSet? = null
init {
if (attrs == null) {
targetId = NO_ID
backgroundDrawable = ColorDrawable(Color.TRANSPARENT)
} else {
with(context.obtainStyledAttributes(attrs, BottomNavigationViewIndicator)) {
targetId =
getResourceId(BottomNavigationViewIndicator_targetBottomNavigation, NO_ID)
val clippableId =
getResourceId(BottomNavigationViewIndicator_clippableBackground, NO_ID)
backgroundDrawable = if (clippableId != NO_ID) {
androidx.appcompat.content.res.AppCompatResources.getDrawable(
context,
clippableId
) ?: ColorDrawable(Color.TRANSPARENT)
} else {
ColorDrawable(
getColor(
BottomNavigationViewIndicator_clippableBackground,
Color.TRANSPARENT
)
)
}
recycle()
}
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (targetId == NO_ID) return attachedError("invalid target id $targetId, did you set the app:targetBottomNavigation attribute?")
val parentView =
parent as? View ?: return attachedError("Impossible to find the view using $parent")
val child = parentView.findViewById<View?>(targetId)
if (child !is CustomBottomNavigationView) return attachedError("Invalid view $child, the app:targetBottomNavigation has to be n ListenableBottomNavigationView")
for (i in 0 until child.childCount) {
val subView = child.getChildAt(i)
if (subView is BottomNavigationMenuView) target = subView
}
elevation = child.elevation
child.addOnNavigationItemSelectedListener { updateRectByIndex(it, true) }
post { updateRectByIndex(index, false) }
}
private fun attachedError(message: String) {
Log.e("BNVIndicator", message)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
target = null
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.clipRect(rect)
// canvas.drawCircle(size,size,size, drawPaint)
backgroundDrawable.draw(canvas)
}
private fun updateRectByIndex(index: Int, animated: Boolean) {
this.index = index
target?.apply {
if (childCount < 1 || index >= childCount) return
val reference = getChildAt(index)
val start = reference.left + left
val end = reference.right + left
backgroundDrawable.setBounds(left, top, right, bottom)
val newRect = Rect(start, 0, end, height)
if (animated) startUpdateRectAnimation(newRect) else updateRect(newRect)
}
}
private fun startUpdateRectAnimation(rect: Rect) {
animator?.cancel()
animator = AnimatorSet().also {
it.playTogether(
ofInt(this, "rectLeft", this.rect.left, rect.left),
ofInt(this, "rectRight", this.rect.right, rect.right),
ofInt(this, "rectTop", this.rect.top, rect.top),
ofInt(this, "rectBottom", this.rect.bottom, rect.bottom)
)
it.interpolator = FastOutSlowInInterpolator()
it.duration = resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
it.start()
}
}
private fun updateRect(rect: Rect) {
this.rect = rect
postInvalidate()
}
#Keep
fun setRectLeft(left: Int) = updateRect(rect.apply { this.left = left })
#Keep
fun setRectRight(right: Int) = updateRect(rect.apply { this.right = right })
#Keep
fun setRectTop(top: Int) = updateRect(rect.apply { this.top = top })
#Keep
fun setRectBottom(bottom: Int) = updateRect(rect.apply { this.bottom = bottom })
}
Put some attributes into attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BottomNavigationViewIndicator">
<attr name="targetBottomNavigation" format="reference"/>
<attr name="clippableBackground" format="reference|color"/>
</declare-styleable>
</resources>
Create a background drawable that'll be clipped as the indicator moves:
my_orange_background.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="360"
android:viewportHeight="4">
<path
android:fillType="nonZero"
android:pathData="M0,0h359.899v3.933H0z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="360"
android:endY="2"
android:startX="0"
android:startY="2"
android:type="linear">
<item
android:color="#color/orange"
android:offset="0" />
<item
android:color="#color/orange"
android:offset="0.55" />
</gradient>
</aapt:attr>
</path>
</vector>
Create your activity/ fragment 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"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="#+id/bottom_nav"
app:layout_constraintTop_toTopOf="parent" />
<com.example.app.view.CustomBottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?attr/colorSurface"
app:labelVisibilityMode="unlabeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/menu_bottom_nav" />
<com.example.app.view.BottomNavigationViewIndicator
android:layout_width="0dp"
android:layout_height="4dp"
app:clippableBackground="#drawable/my_orange_background"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="#+id/bottom_nav"
app:targetBottomNavigation="#+id/bottom_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>
Sources: Medium article GitHub project
It is bad practice but if you wish to do like this you can take Tab layout.
here is the code
<android.support.design.widget.CoordinatorLayout
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">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/tabs"
/>
<android.support.design.widget.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:tabBackground="#color/colorPrimary"
app:tabIndicatorColor="#android:color/white"
app:tabMode="fixed"
app:tabGravity="fill"/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout>

Glide onLoadFailed() trigger after all image loaded after scroll down then scroll up

I have the following view to show brand item scroll horizontally...
onLoadFail() trigger when I scroll up, but if I comment onLoadFail() every work as expect.. What happen here !?
With onLoadFail()
// Setup product image
Glide.with(context)
.load(item.shopImageURL)
.into(holder.imgBrand)
.onLoadFailed(ContextCompat.getDrawable(context, android.R.drawable.ic_dialog_alert))
GIF Link: google drive
Without onLoadFail()
// Setup product image
Glide.with(context)
.load(item.shopImageURL)
.into(holder.imgBrand)
GIF Link: google drive
LoveBrandView.kt
class LoveBrandView : LinearLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val adapter = LoveBrandAdapter()
init {
// Set layout orientation to vertical
orientation = VERTICAL
setBackgroundColor(Color.parseColor("#262626"))
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.view_home_highlight_brand, this, true)
// Attach the adapter to the recyclerview to populate items
rcvLoveBrand.adapter = adapter
// Set layout manager to position the items
rcvLoveBrand.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
// Snap list item after scroll on the gravity start of screen
val snapHelperTop = GravitySnapHelper(Gravity.START)
snapHelperTop.attachToRecyclerView(rcvLoveBrand)
adapter.setListItem(mock())
adapter.notifyDataSetChanged()
}
fun mock(): MutableList<ShopItem> {
val items = mutableListOf<ShopItem>()
items.add(ShopItem(shopImageURL = "https://static.webshopapp.com/shops/208373/files/133254608/500x300x2/calvin-klein.jpg"))
items.add(ShopItem(shopImageURL = "https://lux-store.com/image/catalog/PopularBrands/brand-logo-9.jpg"))
items.add(ShopItem(shopImageURL = "https://logoeps.com/wp-content/uploads/2011/08/uniqlo-logo-vector.jpg"))
items.add(ShopItem(shopImageURL = "https://logoeps.com/wp-content/uploads/2011/08/esprit-logo.jpg"))
items.add(ShopItem(shopImageURL = "https://i.pinimg.com/736x/b5/2c/51/b52c51f556c88216c1795f4ca433fd01--product-logo-fashion-logos.jpg"))
items.add(ShopItem(shopImageURL = "https://www.logodesignteam.com/blog/wp-content/uploads/2017/06/Ralph-Lauren-Logo.png"))
items.add(ShopItem(shopImageURL = "https://i.pinimg.com/736x/95/6f/1e/956f1edc1ec624cc97301e72c6d5b90e--fashion-logos-fashion-typography.jpg"))
items.add(ShopItem(shopImageURL = "https://www.logodesignteam.com/blog/wp-content/uploads/2017/06/Chanel_Logo.png"))
items.add(ShopItem(shopImageURL = "https://www.logodesignteam.com/blog/wp-content/uploads/2017/06/Paul-Smith-Logo.png"))
items.add(ShopItem(shopImageURL = "https://www.logodesignteam.com/blog/wp-content/uploads/2017/06/Louis-Vuitton-Logo.png"))
items.add(ShopItem(shopImageURL = "https://i.pinimg.com/originals/53/29/06/53290622f51318026dd537a2a9e7da94.jpg"))
items.add(ShopItem(shopImageURL = "https://prowly-uploads.s3.amazonaws.com/uploads/PressRoom/563/cover_photo/large_slack-imgs.com.png"))
items.add(ShopItem(shopImageURL = "https://www.logodesignteam.com/blog/wp-content/uploads/2017/07/Intel-Logo.png"))
return items
}
inner class LoveBrandAdapter : RecyclerView.Adapter<LoveBrandAdapter.ViewHolder>() {
private var items = mutableListOf<ShopItem>()
fun setListItem(newItem: MutableList<ShopItem>) {
items = newItem
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
val item = items[position]
if (holder != null) {
// Setup product image
Glide.with(context)
.load(item.shopImageURL)
.into(holder.imgBrand)
.onLoadFailed(ContextCompat.getDrawable(context, android.R.drawable.ic_dialog_alert))
}
}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent?.context)
val view = inflater.inflate(R.layout.listitem_home_highlight_brand, parent, false)
return ViewHolder(view)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imgBrand: ImageView = itemView.findViewById(R.id.imgBrand)
}
}
}
XML
<merge 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"
tools:background="#262626"
tools:parentTag="android.widget.LinearLayout"
tools:orientation="vertical"
tools:context=".ui.home.HomeActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginBottom="32dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal">
<TextView
android:id="#+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Brand We Love"
android:textAllCaps="true"
android:textColor="#color/colorAccent"
android:textSize="18sp"
android:textStyle="bold" />
<View
android:id="#+id/guideline"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_alignBaseline="#id/tvTitle"
android:layout_gravity="bottom"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="#+id/tvTitle"
android:layout_toRightOf="#+id/tvTitle" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_above="#+id/guideline"
android:layout_gravity="bottom"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="#+id/tvTitle"
android:layout_toRightOf="#+id/tvTitle"
android:background="#color/colorAccent" />
</RelativeLayout>
<com.abfabmarket.mobile.abfab.ui.view.CustomRecycleView
android:id="#+id/rcvLoveBrand"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_marginBottom="32dp"
android:orientation="horizontal"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
tools:listitem="#layout/listitem_home_highlight_brand" />
</merge>

Categories

Resources