Skip to content

Instantly share code, notes, and snippets.

@oscarcidcoder
Created August 12, 2019 13:16
Show Gist options
  • Save oscarcidcoder/8ebf170ec8021fb7b937991e5e335e71 to your computer and use it in GitHub Desktop.
Save oscarcidcoder/8ebf170ec8021fb7b937991e5e335e71 to your computer and use it in GitHub Desktop.
FabBottomNavigationView Widget custom para el manejo del BottomNavigationBar con un FAB integrado con el mismo estilo de BottomApp Bar
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Paint
import android.util.AttributeSet
import androidx.core.animation.doOnEnd
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.internal.ThemeEnforcement
import com.google.android.material.resources.MaterialResources
import com.google.android.material.shape.MaterialShapeDrawable
@SuppressLint("RestrictedApi","PrivateResource")
class FabBottomNavigationView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : BottomNavigationView(context, attrs, defStyleAttr) {
private var topCurvedEdgeTreatment: TopCurvedEdgeTreatment
private var materialShapeDrawable: MaterialShapeDrawable
private var fabSize = 0F
var fabCradleMargin = 0F
var fabCradleRoundedCornerRadius = 0F
var cradleVerticalOffset = 0F
init {
val ta = context.theme.obtainStyledAttributes(attrs, R.styleable.FabBottomNavigationView, 0, 0)
fabSize = ta.getDimension(R.styleable.FabBottomNavigationView_fab_size, 0F)
fabCradleMargin = ta.getDimension(R.styleable.FabBottomNavigationView_fab_cradle_margin, 0F)
fabCradleRoundedCornerRadius = ta.getDimension(R.styleable.FabBottomNavigationView_fab_cradle_rounded_corner_radius, 0F)
cradleVerticalOffset = ta.getDimension(R.styleable.FabBottomNavigationView_cradle_vertical_offset, 0F)
val tai =
ThemeEnforcement.obtainTintedStyledAttributes(
context,
attrs,
R.styleable.BottomNavigationView,
0,
R.style.Widget_Design_BottomNavigationView,
R.styleable.BottomNavigationView_itemTextAppearanceInactive,
R.styleable.BottomNavigationView_itemTextAppearanceActive)
val backgroundTint =
MaterialResources.getColorStateList(
context, tai, R.styleable.BottomNavigationView_backgroundTint)
topCurvedEdgeTreatment =
TopCurvedEdgeTreatment(fabCradleMargin, fabCradleRoundedCornerRadius, cradleVerticalOffset).apply {
fabDiameter = fabSize
}
materialShapeDrawable = MaterialShapeDrawable().apply {
shapeAppearanceModel.topEdge = topCurvedEdgeTreatment
tintList = backgroundTint
//setTint(ContextCompat.getColor(context,backgroundTint))
shadowElevation = 4
shadowRadius = 8
elevation = 4f
shadowCompatibilityMode = MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS
initializeElevationOverlay(context)
paintStyle = Paint.Style.FILL_AND_STROKE
}
ViewCompat.setBackground(this,materialShapeDrawable)
//background = materialShapeDrawable
}
fun transform(fab: FloatingActionButton) {
if (fab.isVisible) {
fab.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
ValueAnimator.ofFloat(materialShapeDrawable.interpolation, 0F).apply {
addUpdateListener { materialShapeDrawable.interpolation = it.animatedValue as Float }
start()
}
}
})
} else {
ValueAnimator.ofFloat(materialShapeDrawable.interpolation, 1F).apply {
addUpdateListener { materialShapeDrawable.interpolation = it.animatedValue as Float }
doOnEnd { fab.show() }
start()
}
}
}
}
import com.google.android.material.shape.EdgeTreatment
import com.google.android.material.shape.ShapePath
class TopCurvedEdgeTreatment(
var fabCradleMargin: Float,
var fabCradleRoundedCornerRadius: Float,
var cradleVerticalOffset: Float
) : EdgeTreatment() {
var fabDiameter: Float = 0.toFloat()
var horizontalOffset: Float = 0.toFloat()
init {
if (cradleVerticalOffset < 0.0f) {
throw IllegalArgumentException("cradleVerticalOffset must be positive.")
} else {
this.horizontalOffset = 0.0f
}
}
override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
if (this.fabDiameter == 0.0f) {
shapePath.lineTo(length, 0.0f)
} else {
val cradleDiameter = this.fabCradleMargin * 2.0f + this.fabDiameter
val cradleRadius = cradleDiameter / 2.0f
val roundedCornerOffset = interpolation * this.fabCradleRoundedCornerRadius
val middle = length / 2.0f + this.horizontalOffset
val verticalOffset = interpolation * this.cradleVerticalOffset + (1.0f - interpolation) * cradleRadius
val verticalOffsetRatio = verticalOffset / cradleRadius
if (verticalOffsetRatio >= 1.0f) {
shapePath.lineTo(length, 0.0f)
} else {
val distanceBetweenCenters = cradleRadius + roundedCornerOffset
val distanceBetweenCentersSquared = distanceBetweenCenters * distanceBetweenCenters
val distanceY = verticalOffset + roundedCornerOffset
val distanceX = Math.sqrt((distanceBetweenCentersSquared - distanceY * distanceY).toDouble()).toFloat()
val leftRoundedCornerCircleX = middle - distanceX
val rightRoundedCornerCircleX = middle + distanceX
val cornerRadiusArcLength = Math.toDegrees(Math.atan((distanceX / distanceY).toDouble())).toFloat()
val cutoutArcOffset = 90.0f - cornerRadiusArcLength
shapePath.lineTo(leftRoundedCornerCircleX - roundedCornerOffset, 0.0f)
shapePath.addArc(
leftRoundedCornerCircleX - roundedCornerOffset,
0.0f,
leftRoundedCornerCircleX + roundedCornerOffset,
roundedCornerOffset * 2.0f,
270.0f,
cornerRadiusArcLength
)
shapePath.addArc(
middle - cradleRadius,
-cradleRadius - verticalOffset,
middle + cradleRadius,
cradleRadius - verticalOffset,
180.0f - cutoutArcOffset,
cutoutArcOffset * 2.0f - 180.0f
)
shapePath.addArc(
rightRoundedCornerCircleX - roundedCornerOffset,
0.0f,
rightRoundedCornerCircleX + roundedCornerOffset,
roundedCornerOffset * 2.0f,
270.0f - cornerRadiusArcLength,
cornerRadiusArcLength
)
shapePath.lineTo(length, 0.0f)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment