Last active
January 3, 2020 10:33
-
-
Save tunjid/b30b62f722a80af627cf5f032b705f02 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| private const val MIRROR = 180F | |
| private const val INITIAL_DELAY = 0.15F | |
| private val translucentBlack = Color.argb(50, 0, 0, 0) | |
| fun speedDial( | |
| anchor: View, | |
| @ColorInt tint: Int = anchor.context.themeColorAt(R.attr.colorPrimary), | |
| @StyleRes animationStyle: Int = android.R.style.Animation_Dialog, | |
| layoutAnimationController: LayoutAnimationController = LayoutAnimationController(speedDialAnimation, INITIAL_DELAY).apply { order = ORDER_NORMAL }, | |
| items: List<Pair<CharSequence?, Drawable>>, | |
| dismissListener: (Int?) -> Unit | |
| ) = LinearLayout(anchor.context).run root@{ | |
| rotationY = MIRROR | |
| rotationX = MIRROR | |
| clipChildren = false | |
| clipToPadding = false | |
| orientation = VERTICAL | |
| layoutAnimation = layoutAnimationController | |
| popOver(anchor = anchor, adjuster = getOffset(anchor)) popUp@{ | |
| this.animationStyle = animationStyle | |
| var dismissReason: Int? = null | |
| setOnDismissListener { dismissListener(dismissReason) } | |
| items.mapIndexed { index, pair -> speedDialLayout(pair, tint, View.OnClickListener { dismissReason = index; dismiss() }) } | |
| .forEach(this@root::addView) | |
| } | |
| } | |
| private fun LinearLayout.speedDialLayout(pair: Pair<CharSequence?, Drawable>, tint: Int, clickListener: View.OnClickListener) = LinearLayout(context).apply { | |
| rotationY = MIRROR | |
| rotationX = MIRROR | |
| clipChildren = false | |
| clipToPadding = false | |
| layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT) | |
| updatePadding(bottom = context.resources.getDimensionPixelSize(R.dimen.single_margin)) | |
| setOnClickListener(clickListener) | |
| addView(speedDialLabel(tint, pair.first, clickListener)) | |
| addView(speedDialFab(tint, pair, clickListener)) | |
| } | |
| private fun LinearLayout.speedDialLabel(tint: Int, label: CharSequence?, clicker: View.OnClickListener) = AppCompatTextView(context).apply { | |
| val dp4 = context.resources.getDimensionPixelSize(R.dimen.quarter_margin) | |
| val dp8 = context.resources.getDimensionPixelSize(R.dimen.half_margin) | |
| isClickable = true | |
| background = context.ripple(tint) { setAllCornerSizes(dp8.toFloat()) } | |
| isVisible = label != null | |
| text = label | |
| layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { | |
| marginEnd = context.resources.getDimensionPixelSize(R.dimen.single_margin) | |
| gravity = Gravity.CENTER_VERTICAL | |
| } | |
| updatePadding(left = dp8, top = dp4, right = dp8, bottom = dp4) | |
| setOnClickListener(clicker) | |
| } | |
| private fun LinearLayout.speedDialFab(tint: Int, pair: Pair<CharSequence?, Drawable>, clicker: View.OnClickListener) = AppCompatImageButton(context).apply { | |
| val dp40 = context.resources.getDimensionPixelSize(R.dimen.double_and_half_margin) | |
| imageTintList = null | |
| background = context.ripple(tint) { setAllCornerSizes(dp40.toFloat()) } | |
| layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { | |
| gravity = Gravity.CENTER_VERTICAL | |
| height = dp40 | |
| width = dp40 | |
| } | |
| setOnClickListener(clicker) | |
| setImageDrawable(if (pair.second !is BitmapDrawable) BitmapDrawable(context.resources, pair.second.toBitmap()) else pair.second) | |
| } | |
| private fun View.getOffset(anchor: View): () -> Point = { | |
| val dp40 = context.resources.getDimensionPixelSize(R.dimen.double_and_half_margin) | |
| val halfAnchorWidth = (anchor.width / 2) | |
| val halfMiniFabWidth = (dp40 / 2) | |
| val xOffset = if (width > anchor.width) halfAnchorWidth + halfMiniFabWidth - width else halfAnchorWidth - halfMiniFabWidth | |
| Point(xOffset, -(anchor.height / 2) - height) | |
| } | |
| private fun Context.ripple(tint: Int, shapeModifier: ShapeAppearanceModel.Builder.() -> Unit): RippleDrawable = RippleDrawable( | |
| ColorStateList.valueOf(translucentBlack), | |
| MaterialShapeDrawable(ShapeAppearanceModel.builder().run { | |
| shapeModifier(this) | |
| build() | |
| }).apply { | |
| tintList = ColorStateList.valueOf(tint) | |
| setShadowColor(Color.DKGRAY) | |
| initializeElevationOverlay(this@ripple) | |
| }, | |
| null | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment