diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 36842b5..65da200 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -23,6 +23,7 @@ app:backgroundColor="@color/colorPrimary" app:textColor="@color/colorTextPrimary" app:textSize="14sp" + app:cornerRadius="10dp" app:iconSize="24dp" app:indicatorColor="#2DFFFFFF" app:indicatorRadius="10dp" diff --git a/lib/src/main/java/me/ibrahimsn/lib/Constants.kt b/lib/src/main/java/me/ibrahimsn/lib/Constants.kt index 168ff1b..ebdd2b7 100644 --- a/lib/src/main/java/me/ibrahimsn/lib/Constants.kt +++ b/lib/src/main/java/me/ibrahimsn/lib/Constants.kt @@ -19,8 +19,12 @@ object Constants { const val DEFAULT_ICON_MARGIN = 4F const val DEFAULT_TEXT_SIZE = 11F const val DEFAULT_CORNER_RADIUS = 20F + const val DEFAULT_BAR_CORNER_RADIUS = 0F const val OPAQUE = 255 const val TRANSPARENT = 0 + + const val COS_45 = 0.525321988 + const val SHADOW_MULTIPLIER = 1.5f } diff --git a/lib/src/main/java/me/ibrahimsn/lib/RoundRectDrawable.kt b/lib/src/main/java/me/ibrahimsn/lib/RoundRectDrawable.kt new file mode 100644 index 0000000..83ec729 --- /dev/null +++ b/lib/src/main/java/me/ibrahimsn/lib/RoundRectDrawable.kt @@ -0,0 +1,237 @@ +/* +* Copyright 2020 Brook Mezgebu +* Copyright 2018 The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package me.ibrahimsn.lib + +import android.content.res.ColorStateList +import android.graphics.* +import android.graphics.drawable.Drawable +import androidx.annotation.RequiresApi +import me.ibrahimsn.lib.Constants.COS_45 +import me.ibrahimsn.lib.Constants.SHADOW_MULTIPLIER +import kotlin.math.ceil + +/** + * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also + * reports proper outline for Lollipop. + * + * + * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable. + */ +@RequiresApi(21) +class RoundRectDrawable( backgroundColor: ColorStateList?, private var mRadius: Float, + private val topLeft: Boolean = false, + private val topRight: Boolean = false, + private val bottomLeft: Boolean = false, + private val bottomRight: Boolean = false +) : Drawable() { + + private val mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG) + private val mBoundsF: RectF + private val mBoundsI: Rect + + var padding = 0f + private set + + private var mInsetForPadding = false + private var mInsetForRadius = true + private var mBackground: ColorStateList? = null + private var mTintFilter: PorterDuffColorFilter? = null + private var mTint: ColorStateList? = null + private var mTintMode: PorterDuff.Mode? = PorterDuff.Mode.SRC_IN + + private fun setBackground(color: ColorStateList?) { + mBackground = color ?: ColorStateList.valueOf(Color.TRANSPARENT) + mPaint.color = mBackground?.getColorForState(state, mBackground?.defaultColor ?: Color.TRANSPARENT) ?: Color.TRANSPARENT + } + + fun setPadding( padding: Float, insetForPadding: Boolean, insetForRadius: Boolean) { + if (padding == this.padding && mInsetForPadding == insetForPadding && mInsetForRadius == insetForRadius) { + return + } + + this.padding = padding + mInsetForPadding = insetForPadding + mInsetForRadius = insetForRadius + updateBounds(null) + invalidateSelf() + } + + override fun draw(canvas: Canvas) { + val paint = mPaint + val clearColorFilter: Boolean + if (mTintFilter != null && paint.colorFilter == null) { + paint.colorFilter = mTintFilter + clearColorFilter = true + } else clearColorFilter = false + + canvas.drawPath( + Util.roundedRect(mBoundsF, mRadius, mRadius, topLeft, topRight, bottomRight, bottomLeft), + paint + ); + + if (clearColorFilter) { + paint.colorFilter = null + } + } + + private fun calculateVerticalPadding(maxShadowSize: Float, cornerRadius: Float, addPaddingForCorners: Boolean): Float { + return if (addPaddingForCorners) { + (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius).toFloat() + } else { + maxShadowSize * SHADOW_MULTIPLIER + } + } + + private fun calculateHorizontalPadding( + maxShadowSize: Float, cornerRadius: Float, + addPaddingForCorners: Boolean + ): Float { + return if (addPaddingForCorners) { + (maxShadowSize + (1 - COS_45) * cornerRadius).toFloat() + } else { + maxShadowSize + } + } + + private fun updateBounds(bounds: Rect?) { + var bounds = bounds + + if (bounds == null) bounds = getBounds() + + mBoundsF[bounds!!.left.toFloat(), bounds.top.toFloat(), bounds.right.toFloat()] = bounds.bottom.toFloat() + mBoundsI.set(bounds) + + if (mInsetForPadding) { + val vInset: Float = calculateVerticalPadding( + padding, + mRadius, + mInsetForRadius + ) + + val hInset: Float = calculateHorizontalPadding( + padding, + mRadius, + mInsetForRadius + ) + + mBoundsI.inset( + ceil(hInset.toDouble()).toInt(), + ceil(vInset.toDouble()).toInt() + ) + + // to make sure they have same bounds. + mBoundsF.set(mBoundsI) + } + } + + override fun onBoundsChange(bounds: Rect) { + super.onBoundsChange(bounds) + updateBounds(bounds) + } + + override fun getOutline(outline: Outline) { + outline.setConvexPath( + Util.roundedRect(mBoundsF, + mRadius, mRadius, + tl = topLeft, tr = topRight, br = bottomRight, bl = bottomLeft + ) + ) + } + + override fun setAlpha(alpha: Int) { + mPaint.alpha = alpha + } + + override fun setColorFilter(cf: ColorFilter) { + mPaint.colorFilter = cf + } + + override fun getOpacity(): Int { + return PixelFormat.TRANSLUCENT + } + + var radius: Float + get() = mRadius + set(radius) { + if (radius == mRadius) { + return + } + mRadius = radius + updateBounds(null) + invalidateSelf() + } + + var color: ColorStateList? + get() = mBackground + set(color) { + setBackground(color) + invalidateSelf() + } + + override fun setTintList(tint: ColorStateList) { + mTint = tint + mTintFilter = createTintFilter(mTint, mTintMode) + invalidateSelf() + } + + override fun setTintMode(tintMode: PorterDuff.Mode) { + mTintMode = tintMode + mTintFilter = createTintFilter(mTint, mTintMode) + invalidateSelf() + } + + override fun onStateChange(stateSet: IntArray): Boolean { + val newColor = + mBackground!!.getColorForState(stateSet, mBackground!!.defaultColor) + val colorChanged = newColor != mPaint.color + if (colorChanged) { + mPaint.color = newColor + } + if (mTint != null && mTintMode != null) { + mTintFilter = createTintFilter(mTint, mTintMode) + return true + } + return colorChanged + } + + override fun isStateful(): Boolean { + return (mTint != null && mTint!!.isStateful + || mBackground != null && mBackground!!.isStateful || super.isStateful()) + } + + /** + * Ensures the tint filter is consistent with the current tint color and + * mode. + */ + private fun createTintFilter( + tint: ColorStateList?, + tintMode: PorterDuff.Mode? + ): PorterDuffColorFilter? { + if (tint == null || tintMode == null) { + return null + } + val color = tint.getColorForState(state, Color.TRANSPARENT) + return PorterDuffColorFilter(color, tintMode) + } + + init { + setBackground(backgroundColor) + mBoundsF = RectF() + mBoundsI = Rect() + } +} \ No newline at end of file diff --git a/lib/src/main/java/me/ibrahimsn/lib/SmoothBottomBar.kt b/lib/src/main/java/me/ibrahimsn/lib/SmoothBottomBar.kt index 1fe8b9a..46807da 100644 --- a/lib/src/main/java/me/ibrahimsn/lib/SmoothBottomBar.kt +++ b/lib/src/main/java/me/ibrahimsn/lib/SmoothBottomBar.kt @@ -4,10 +4,12 @@ import android.animation.ArgbEvaluator import android.animation.ValueAnimator import android.annotation.SuppressLint import android.content.Context +import android.content.res.ColorStateList import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.RectF +import android.os.Build import android.util.AttributeSet import android.view.MotionEvent import android.view.View @@ -16,6 +18,7 @@ import androidx.annotation.FontRes import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat import me.ibrahimsn.lib.Constants.DEFAULT_ANIM_DURATION +import me.ibrahimsn.lib.Constants.DEFAULT_BAR_CORNER_RADIUS import me.ibrahimsn.lib.Constants.DEFAULT_CORNER_RADIUS import me.ibrahimsn.lib.Constants.DEFAULT_ICON_MARGIN import me.ibrahimsn.lib.Constants.DEFAULT_ICON_SIZE @@ -38,6 +41,7 @@ class SmoothBottomBar : View { private var barIndicatorColor = Color.parseColor(DEFAULT_INDICATOR_COLOR) private var barIndicatorRadius = d2p(DEFAULT_CORNER_RADIUS) private var barSideMargins = d2p(DEFAULT_SIDE_MARGIN) + private var barCornerRadius = d2p(DEFAULT_BAR_CORNER_RADIUS) private var itemPadding = d2p(DEFAULT_ITEM_PADDING) private var itemAnimDuration = DEFAULT_ANIM_DURATION @@ -94,6 +98,7 @@ class SmoothBottomBar : View { barIndicatorColor = typedArray.getColor(R.styleable.SmoothBottomBar_indicatorColor, this.barIndicatorColor) barIndicatorRadius = typedArray.getDimension(R.styleable.SmoothBottomBar_indicatorRadius, this.barIndicatorRadius) barSideMargins = typedArray.getDimension(R.styleable.SmoothBottomBar_sideMargins, this.barSideMargins) + barCornerRadius = typedArray.getDimension(R.styleable.SmoothBottomBar_cornerRadius, this.barCornerRadius) itemPadding = typedArray.getDimension(R.styleable.SmoothBottomBar_itemPadding, this.itemPadding) itemTextColor = typedArray.getColor(R.styleable.SmoothBottomBar_textColor, this.itemTextColor) itemTextSize = typedArray.getDimension(R.styleable.SmoothBottomBar_textSize, this.itemTextSize) @@ -106,7 +111,9 @@ class SmoothBottomBar : View { items = BottomBarParser(context, typedArray.getResourceId(R.styleable.SmoothBottomBar_menu, 0)).parse() typedArray.recycle() - setBackgroundColor(barBackgroundColor) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + background = RoundRectDrawable(ColorStateList.valueOf(barBackgroundColor), barCornerRadius, topLeft = true, topRight = true) + } else setBackgroundColor(barBackgroundColor) // Update default attribute values paintIndicator.color = barIndicatorColor diff --git a/lib/src/main/java/me/ibrahimsn/lib/Util.kt b/lib/src/main/java/me/ibrahimsn/lib/Util.kt new file mode 100644 index 0000000..45d6423 --- /dev/null +++ b/lib/src/main/java/me/ibrahimsn/lib/Util.kt @@ -0,0 +1,37 @@ +package me.ibrahimsn.lib + +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.os.Build +import androidx.annotation.RequiresApi + +/** + * Created by BrookMG on 3/29/2020 in me.ibrahimsn.lib +inside the project SmoothBottomBar . + */ +class Util { + + companion object { + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun roundedRect( + rect: RectF, + rx: Float, ry: Float, + tl: Boolean, tr: Boolean, br: Boolean, bl: Boolean + ): Path { + val path = Path(); + val corners: FloatArray = floatArrayOf( + (if (tl) rx else 0f), (if (tl) ry else 0f), + (if (tr) rx else 0f), (if (tr) ry else 0f), + (if (br) rx else 0f), (if (br) ry else 0f), + (if (bl) rx else 0f), (if (bl) ry else 0f) + ); + + path.addRoundRect(rect, corners, Path.Direction.CW) + return path + } + + } + +} \ No newline at end of file diff --git a/lib/src/main/res/values/attrs.xml b/lib/src/main/res/values/attrs.xml index 2333acb..1358e0b 100644 --- a/lib/src/main/res/values/attrs.xml +++ b/lib/src/main/res/values/attrs.xml @@ -14,6 +14,7 @@ +