From 8b4d5b982378f9d5090e96229af3665948709ded Mon Sep 17 00:00:00 2001 From: Ryan Bishop Mayobre Date: Mon, 22 Jun 2020 18:11:56 -0400 Subject: [PATCH 1/7] Created the FAB View. Will become the base view for SpeedDial and ExtendedFloatingActionButton (ExtendedFAB). --- .../com/multibutton/example/MainActivity.kt | 104 ++++++++++-------- app/src/main/res/layout/activity_main.xml | 40 +++++-- build.gradle | 2 +- .../main/java/com/multibutton/library/FAB.kt | 95 ++++++++++++++++ .../library/HideOnScrollBehavior.kt | 1 + .../library/expanding/ExpandableButton.kt | 4 + .../library/{ => speeddial}/SpeedDial.kt | 52 ++++++--- .../{ => speeddial}/SpeedDialAction.kt | 22 ++-- .../res/drawable/background_circle_ripple.xml | 11 +- .../src/main/res/drawable/fab_background.xml | 12 ++ .../fab_background_circle_without_icon.xml | 27 +++++ library/src/main/res/layout/extended_fab.xml | 17 +++ library/src/main/res/values/attrs.xml | 4 + library/src/main/res/values/ids.xml | 2 + 14 files changed, 305 insertions(+), 88 deletions(-) create mode 100644 library/src/main/java/com/multibutton/library/FAB.kt create mode 100644 library/src/main/java/com/multibutton/library/expanding/ExpandableButton.kt rename library/src/main/java/com/multibutton/library/{ => speeddial}/SpeedDial.kt (93%) rename library/src/main/java/com/multibutton/library/{ => speeddial}/SpeedDialAction.kt (91%) create mode 100644 library/src/main/res/drawable/fab_background.xml create mode 100644 library/src/main/res/drawable/fab_background_circle_without_icon.xml create mode 100644 library/src/main/res/layout/extended_fab.xml diff --git a/app/src/main/java/com/multibutton/example/MainActivity.kt b/app/src/main/java/com/multibutton/example/MainActivity.kt index 6e74183..7c4d0d3 100644 --- a/app/src/main/java/com/multibutton/example/MainActivity.kt +++ b/app/src/main/java/com/multibutton/example/MainActivity.kt @@ -8,61 +8,73 @@ import androidx.annotation.DrawableRes import androidx.annotation.IdRes import androidx.annotation.LayoutRes import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.multibutton.library.FAB import com.multibutton.library.HideOnScrollBehavior -import com.multibutton.library.SpeedDial -import com.multibutton.library.SpeedDialAction +import com.multibutton.library.speeddial.SpeedDial +import com.multibutton.library.speeddial.SpeedDialAction import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity(), View.OnClickListener { - private lateinit var button: SpeedDial +// private lateinit var button: SpeedDial + + private lateinit var button: FAB override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(MAIN_ACTIVITY) - button = findViewById(R.id.speedDialButton).apply { - addAction(SpeedDialAction.Builder(this@MainActivity) - .setId(FAB_1) - .setImage(ACTION_DRAWABLE) - .setLabel("test 1") - .setOnClickListener(this@MainActivity) - .setShowAnimation(R.anim.fab_fade_in) - .setHideAnimation(R.anim.fab_fade_out) - .build()) - addAction(SpeedDialAction.Builder(this@MainActivity) - .setId(FAB_2) - .setImage(ACTION_DRAWABLE) - .setLabel("test 2") - .setOnClickListener(this@MainActivity) - .setShowAnimation(R.anim.fab_fade_in) - .setHideAnimation(R.anim.fab_fade_out) - .build()) - addAction(SpeedDialAction.Builder(this@MainActivity) - .setId(FAB_3) - .setImage(ACTION_DRAWABLE) - .setLabel("test 3") - .setOnClickListener(this@MainActivity) - .setShowAnimation(R.anim.fab_fade_in) - .setHideAnimation(R.anim.fab_fade_out) - .build()) - addAction(SpeedDialAction.Builder(this@MainActivity) - .setId(FAB_4) - .setImage(ACTION_DRAWABLE) - .setLabel("test 4") - .setOnClickListener(this@MainActivity) - .setShowAnimation(R.anim.fab_fade_in) - .setHideAnimation(R.anim.fab_fade_out) - .build()) - addAction(SpeedDialAction.Builder(this@MainActivity) - .setId(FAB_5) - .setImage(ACTION_DRAWABLE) - .setLabel("test 5") - .setOnClickListener(this@MainActivity) - .setShowAnimation(R.anim.fab_fade_in) - .setHideAnimation(R.anim.fab_fade_out) - .build()) - } + button = findViewById(R.id.fab) + +// button = findViewById(R.id.speedDialButton).apply { +// addAction( +// SpeedDialAction.Builder(this@MainActivity) +// .setId(FAB_1) +// .setImage(ACTION_DRAWABLE) +// .setLabel("test 1") +// .setOnClickListener(this@MainActivity) +// .setShowAnimation(R.anim.fab_fade_in) +// .setHideAnimation(R.anim.fab_fade_out) +// .build()) +// addAction( +// SpeedDialAction.Builder(this@MainActivity) +// .setId(FAB_2) +// .setImage(ACTION_DRAWABLE) +// .setLabel("test 2") +// .setOnClickListener(this@MainActivity) +// .setShowAnimation(R.anim.fab_fade_in) +// .setHideAnimation(R.anim.fab_fade_out) +// .build()) +// addAction( +// SpeedDialAction.Builder(this@MainActivity) +// .setId(FAB_3) +// .setImage(ACTION_DRAWABLE) +// .setLabel("test 3") +// .setOnClickListener(this@MainActivity) +// .setShowAnimation(R.anim.fab_fade_in) +// .setHideAnimation(R.anim.fab_fade_out) +// .build()) +// addAction( +// SpeedDialAction.Builder(this@MainActivity) +// .setId(FAB_4) +// .setImage(ACTION_DRAWABLE) +// .setLabel("test 4") +// .setOnClickListener(this@MainActivity) +// .setShowAnimation(R.anim.fab_fade_in) +// .setHideAnimation(R.anim.fab_fade_out) +// .build()) +// addAction( +// SpeedDialAction.Builder(this@MainActivity) +// .setId(FAB_5) +// .setImage(ACTION_DRAWABLE) +// .setLabel("test 5") +// .setOnClickListener(this@MainActivity) +// .setShowAnimation(R.anim.fab_fade_in) +// .setHideAnimation(R.anim.fab_fade_out) +// .build()) +// } val texts: MutableList = mutableListOf() texts.add("Text 1") @@ -93,7 +105,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { adapter = TextAdapter(texts) } - HideOnScrollBehavior.from(speedDialButton).hideAtEnd(false) +// HideOnScrollBehavior.from(speedDialButton).hideAtEnd(false) } override fun onClick(v: View) { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c718d87..f36f091 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -31,17 +31,33 @@ - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1b0572b..b56f24e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/library/src/main/java/com/multibutton/library/FAB.kt b/library/src/main/java/com/multibutton/library/FAB.kt new file mode 100644 index 0000000..464d31d --- /dev/null +++ b/library/src/main/java/com/multibutton/library/FAB.kt @@ -0,0 +1,95 @@ +package com.multibutton.library + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.annotation.DrawableRes +import androidx.annotation.IdRes +import androidx.annotation.StyleableRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.appcompat.widget.AppCompatImageButton +import androidx.core.content.ContextCompat +import com.multibutton.library.speeddial.SpeedDial +import kotlin.math.roundToInt + +class FAB : FrameLayout { + + private val button: Button + + private val icon: ImageView + + constructor(context: Context): this(context, null) + + constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) { + with(context.obtainStyledAttributes(attrs, STYLEABLE_RES, 0, 0)) { + val fabElevation: Float = getFloat(ELEVATION_STYLEABLE_RES, DEFAULT_FAB_ELEVATION) + icon = ImageView(context).apply { + id = FAB_ICON_ID + scaleType = ImageView.ScaleType.FIT_XY + foregroundGravity = Gravity.CENTER + elevation = fabElevation * 4.0f + layoutParams = LayoutParams( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_DIMENSION, resources.displayMetrics).roundToInt(), + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_DIMENSION, resources.displayMetrics).roundToInt() + ).apply { + gravity = Gravity.CENTER + } + } + setIcon(getResourceId(ICON_STYLEABLE_RES, View.NO_ID)) + addView(icon) + button = Button(context).apply { + id = FAB_ID + isClickable = true + elevation = fabElevation + background = ContextCompat.getDrawable(context, FAB_BACKGROUND_DRAWABLE) + layoutParams = LinearLayout.LayoutParams( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, FAB_DIMENSION, resources.displayMetrics).roundToInt(), + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, FAB_DIMENSION, resources.displayMetrics).roundToInt() + ) + } + addView(button) + } + } + + fun setIcon(@DrawableRes iconRes: Int): Boolean = if (iconRes != View.NO_ID) { + setIcon(AppCompatResources.getDrawable(context, iconRes)) + } else false + + fun setIcon(iconDrawable: Drawable?): Boolean = iconDrawable?.let { drawable -> + icon.setImageDrawable(drawable) + true + } ?: false + + + companion object { + private const val DEFAULT_FAB_ELEVATION = 4.0f + private const val BUTTON_LAYOUT_WIDTH = 24 + private const val BUTTON_LAYOUT_HEIGHT = 24 + private const val FAB_DIMENSION = 56.0f + private const val ICON_DIMENSION = 24.0f + + @IdRes private val FAB_ID = R.id.fab_button + @IdRes private val FAB_ICON_ID = R.id.fab_icon + + /* Styleables Resources */ + @StyleableRes private val STYLEABLE_RES = R.styleable.FAB + @StyleableRes private val ICON_STYLEABLE_RES = R.styleable.FAB_icon + @StyleableRes private val ELEVATION_STYLEABLE_RES = R.styleable.FAB_elevation_height + + /* Drawable Resources */ + @DrawableRes private val FAB_BACKGROUND_DRAWABLE = R.drawable.fab_background +// @DrawableRes private val FAB_BACKGROUND_WITH_ICON_DRAWABLE = R.drawable.fab_background_circle_with_icon +// @DrawableRes private val FAB_BACKGROUND_WITHOUT_ICON_DRAWABLE = R.drawable.fab_background_circle_without_icon + } +} \ No newline at end of file diff --git a/library/src/main/java/com/multibutton/library/HideOnScrollBehavior.kt b/library/src/main/java/com/multibutton/library/HideOnScrollBehavior.kt index e97399d..80ae239 100644 --- a/library/src/main/java/com/multibutton/library/HideOnScrollBehavior.kt +++ b/library/src/main/java/com/multibutton/library/HideOnScrollBehavior.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.multibutton.library.speeddial.SpeedDial class HideOnScrollBehavior: CoordinatorLayout.Behavior { private var listener: ScrollListener = ScrollListener() diff --git a/library/src/main/java/com/multibutton/library/expanding/ExpandableButton.kt b/library/src/main/java/com/multibutton/library/expanding/ExpandableButton.kt new file mode 100644 index 0000000..35ad68e --- /dev/null +++ b/library/src/main/java/com/multibutton/library/expanding/ExpandableButton.kt @@ -0,0 +1,4 @@ +package com.multibutton.library.expanding + +class ExpandableButton { +} \ No newline at end of file diff --git a/library/src/main/java/com/multibutton/library/SpeedDial.kt b/library/src/main/java/com/multibutton/library/speeddial/SpeedDial.kt similarity index 93% rename from library/src/main/java/com/multibutton/library/SpeedDial.kt rename to library/src/main/java/com/multibutton/library/speeddial/SpeedDial.kt index 9fa94de..d28f37f 100644 --- a/library/src/main/java/com/multibutton/library/SpeedDial.kt +++ b/library/src/main/java/com/multibutton/library/speeddial/SpeedDial.kt @@ -1,4 +1,4 @@ -package com.multibutton.library +package com.multibutton.library.speeddial import android.content.Context import android.content.res.ColorStateList @@ -18,6 +18,7 @@ import android.widget.LinearLayout import androidx.annotation.* import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat +import com.multibutton.library.R import kotlin.math.roundToInt class SpeedDial : LinearLayout, Animation.AnimationListener { @@ -41,13 +42,16 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { private val actions: MutableSet = mutableSetOf() - constructor(context: Context): this(context, null) //super(context) TODO why did i not apply attributes? + constructor(context: Context): this(context, null) constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) { with(context.obtainStyledAttributes(attrs, STYLEABLE_RES, 0, 0)) { - val overlayColor = getColor(STYLEABLE_OVERLAY_COLOR, ATTRIBUTE_NOT_SET) + val overlayColor = getColor( + STYLEABLE_OVERLAY_COLOR, + ATTRIBUTE_NOT_SET + ) backgroundTransition = if (overlayColor != ATTRIBUTE_NOT_SET) { val colors: Array = arrayOf( ColorDrawable(ContextCompat.getColor(context, COLOR_COLLAPSED_BACKGROUND)), @@ -63,8 +67,14 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { TransitionDrawable(colors).also { background = it } } - val showAnimResId: Int = getResourceId(STYLEABLE_FAB_SHOW_ANIMATION, RESOURCE_ID_NULL) - val hideAnimResId: Int = getResourceId(STYLEABLE_FAB_HIDE_ANIMATION, RESOURCE_ID_NULL) + val showAnimResId: Int = getResourceId( + STYLEABLE_FAB_SHOW_ANIMATION, + RESOURCE_ID_NULL + ) + val hideAnimResId: Int = getResourceId( + STYLEABLE_FAB_HIDE_ANIMATION, + RESOURCE_ID_NULL + ) showAnimation = if (showAnimResId != RESOURCE_ID_NULL) AnimationUtils.loadAnimation(context, showAnimResId) else null showAnimation?.setAnimationListener(this@SpeedDial) @@ -76,9 +86,8 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { id = FAB_ID isClickable = true isFocusable = true - background = ContextCompat.getDrawable(context, - BACKGROUND_DRAWABLE - ) + background = ContextCompat.getDrawable(context, BACKGROUND_DRAWABLE) + scaleType = getScaleType( getInt( @@ -103,7 +112,10 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { val metrics = context.resources.displayMetrics - val margin = getDimension(STYLEABLE_FAB_MARGIN, MARGIN_NOT_SET) + val margin = getDimension( + STYLEABLE_FAB_MARGIN, + MARGIN_NOT_SET + ) val marginTop = getDimension(STYLEABLE_FAB_MARGIN_TOP, margin) val marginBottom = getDimension(STYLEABLE_FAB_MARGIN_BOTTOM, margin) val marginStart = getDimension(STYLEABLE_FAB_MARGIN_START, margin) @@ -137,11 +149,17 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { init { orientation = VERTICAL gravity = Gravity.BOTTOM - setOnTouchListener { _: View, _: MotionEvent -> - if (state == State.EXPANDED) { - changeState() - true - } else false + } + + override fun onTouchEvent(event: MotionEvent?): Boolean = performClick() + + override fun performClick(): Boolean { + super.performClick() + return if (state == State.EXPANDED) { + changeState() + true + } else { + false } } @@ -155,6 +173,8 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { override fun onAnimationRepeat(animation: Animation?) {} + + fun show() { if (visibility != View.VISIBLE) { visibility = View.VISIBLE @@ -315,7 +335,9 @@ class SpeedDial : LinearLayout, Animation.AnimationListener { */ fun setOverlayColor(@ColorRes color: Int) { val colors: Array = arrayOf( - ColorDrawable(ContextCompat.getColor(context, COLOR_COLLAPSED_BACKGROUND)), + ColorDrawable(ContextCompat.getColor(context, + COLOR_COLLAPSED_BACKGROUND + )), ColorDrawable(ContextCompat.getColor(context, color))) backgroundTransition = TransitionDrawable(colors).also { background = it } diff --git a/library/src/main/java/com/multibutton/library/SpeedDialAction.kt b/library/src/main/java/com/multibutton/library/speeddial/SpeedDialAction.kt similarity index 91% rename from library/src/main/java/com/multibutton/library/SpeedDialAction.kt rename to library/src/main/java/com/multibutton/library/speeddial/SpeedDialAction.kt index 6090c83..1018dc9 100644 --- a/library/src/main/java/com/multibutton/library/SpeedDialAction.kt +++ b/library/src/main/java/com/multibutton/library/speeddial/SpeedDialAction.kt @@ -1,4 +1,4 @@ -package com.multibutton.library +package com.multibutton.library.speeddial import android.content.Context import android.content.res.ColorStateList @@ -14,6 +14,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.multibutton.library.R class SpeedDialAction private constructor(context: Context) : LinearLayout(context), Animation.AnimationListener { @@ -29,7 +30,8 @@ class SpeedDialAction private constructor(context: Context) : LinearLayout(conte private var labelHideAnimation: Animation? = null init { - with(inflate(context, FLOATING_ACTION_LAYOUT, this)) { + with(inflate(context, + FLOATING_ACTION_LAYOUT, this)) { layoutParams = LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -162,7 +164,9 @@ class SpeedDialAction private constructor(context: Context) : LinearLayout(conte } - fun build(): SpeedDialAction = SpeedDialAction(context).apply { + fun build(): SpeedDialAction = SpeedDialAction( + context + ).apply { this@Builder.id?.let { id = it } imageId?.let { @@ -212,12 +216,16 @@ class SpeedDialAction private constructor(context: Context) : LinearLayout(conte } companion object { - @LayoutRes private var FLOATING_ACTION_LAYOUT = R.layout.item_speed_dial + @LayoutRes private var FLOATING_ACTION_LAYOUT = + R.layout.item_speed_dial - @DimenRes private var HORIZONTAL_MARGIN = R.dimen.speed_dial_item_horizontal_margin + @DimenRes private var HORIZONTAL_MARGIN = + R.dimen.speed_dial_item_horizontal_margin - @IdRes private var CARD_VIEW = R.id.item_card_view - @IdRes private var TEXT_VIEW = R.id.item_text_view + @IdRes private var CARD_VIEW = + R.id.item_card_view + @IdRes private var TEXT_VIEW = + R.id.item_text_view @IdRes private var BUTTON = R.id.item_button } } \ No newline at end of file diff --git a/library/src/main/res/drawable/background_circle_ripple.xml b/library/src/main/res/drawable/background_circle_ripple.xml index fab822c..4fcf97e 100644 --- a/library/src/main/res/drawable/background_circle_ripple.xml +++ b/library/src/main/res/drawable/background_circle_ripple.xml @@ -7,13 +7,10 @@ + android:bottom="12dp" + android:left="12dp" + android:right="12dp" + android:top="12dp" /> - - - \ No newline at end of file diff --git a/library/src/main/res/drawable/fab_background.xml b/library/src/main/res/drawable/fab_background.xml new file mode 100644 index 0000000..ac0cdf8 --- /dev/null +++ b/library/src/main/res/drawable/fab_background.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/fab_background_circle_without_icon.xml b/library/src/main/res/drawable/fab_background_circle_without_icon.xml new file mode 100644 index 0000000..de954d5 --- /dev/null +++ b/library/src/main/res/drawable/fab_background_circle_without_icon.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/extended_fab.xml b/library/src/main/res/layout/extended_fab.xml new file mode 100644 index 0000000..b172ea5 --- /dev/null +++ b/library/src/main/res/layout/extended_fab.xml @@ -0,0 +1,17 @@ + + + +