🎈 A lightweight popup like tooltips, fully customizable with arrow and animations.
Add below codes to your root build.gradle
file (not your module build.gradle file).
allprojects {
repositories {
mavenCentral()
}
}
And add a dependency code to your module's build.gradle
file.
dependencies {
implementation "com.github.skydoves:balloon:1.3.9"
}
Snapshots of the current development version of Balloon are available, which track the latest versions.
repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
Here is a basic example of implementing a balloon popup that has an icon and text using Balloon.Builder
class.
Balloon balloon = new Balloon.Builder(context)
.setArrowSize(10)
.setArrowOrientation(ArrowOrientation.TOP)
.setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR)
.setArrowPosition(0.5f)
.setWidth(BalloonSizeSpec.WRAP)
.setHeight(65)
.setTextSize(15f)
.setCornerRadius(4f)
.setAlpha(0.9f)
.setText("You can access your profile from now on.")
.setTextColor(ContextCompat.getColor(context, R.color.white_93))
.setTextIsHtml(true)
.setIconDrawable(ContextCompat.getDrawable(context, R.drawable.ic_profile))
.setBackgroundColor(ContextCompat.getColor(context, R.color.colorPrimary))
.setOnBalloonClickListener(onBalloonClickListener)
.setBalloonAnimation(BalloonAnimation.FADE)
.setLifecycleOwner(lifecycleOwner)
.build();
We can create an instance of the Balloon using the Kotlin DSL.
val balloon = createBalloon(context) {
setArrowSize(10)
setWidth(BalloonSizeSpec.WRAP)
setHeight(65)
setArrowPosition(0.7f)
setCornerRadius(4f)
setAlpha(0.9f)
setText("You can access your profile from now on.")
setTextColorResource(R.color.white_93)
setTextIsHtml(true)
setIconDrawable(ContextCompat.getDrawable(context, R.drawable.ic_profile))
setBackgroundColorResource(R.color.colorPrimary)
setOnBalloonClickListener(onBalloonClickListener)
setBalloonAnimation(BalloonAnimation.FADE)
setLifecycleOwner(lifecycleOwner)
}
We can adjust specific width and height sizes of the balloon using the below builder methods.
If we would not set any specific sizes of the width and height of the balloon, the size of the popup will be decided by the content.
We can set the specific size of the balloon regardless size of the contents.
balloon.setWidth(220) // sets 220dp width size.
balloon.setHeight(160) // sets 160dp height size.
balloon.setWidth(BalloonSizeSpec.WRAP) // sets width size depending on the content's size.
balloon.setHeight(BalloonSizeSpec.WRAP) // sets height size depending on the content's size.
Also, we can set the width according to the ratio of the horizontal screen's size.
balloon.setWidthRatio(0.5f) // sets width as 50% of the horizontal screen's size.
Balloon wraps a content. We can control the content size of the balloon using padding of the content.
balloon.setPadding(6) // sets 6dp padding to all directions (left-top-right-bottom)
balloon.setPaddingLeft(8) // sets 8dp padding to content's left.
balloon.setPaddingTop(12) // sets 12dp padding to content's top.
We can show up the balloon using the below functions.
Basically, we can show up the balloon based on top/bottom/right/left alignment if we use showAlign__
method.
Also, we can adjust more specific positions of the balloon using x-Offset
and y-Offset
parameters.
balloon.showAlignTop(anchor: View) // shows the balloon on an anchor view as the top alignment.
balloon.showAlignTop(anchor: View, xOff: Int, yOff: Int) // shows top alignment with x-off and y-off.
balloon.showAlignBottom(anchor: View) // shows the balloon on an anchor view as the bottom alignment.
balloon.showAlignBottom(anchor: View, xOff: Int, yOff: Int) // shows bottom alignment with x-off and y-off.
balloon.showAlignRight(anchor: View) // shows the balloon on an anchor view as the right alignment.
balloon.showAlignRight(anchor: View, xOff: Int, yOff: Int) // shows right alignment with x-off and y-off.
balloon.showAlignLeft(anchor: View) // shows the balloon on an anchor view as the left alignment.
balloon.showAlignLeft(anchor: View, xOff: Int, yOff: Int) // shows left alignment with x-off and y-off.
balloon.showAsDropDown(anchor: View) // shows the balloon as a dropdown without any alignments.
balloon.showAsDropDown(anchor: View, xOff: Int, yOff: Int) // shows no alignments with x-off and y-off.
balloon.showAtCenter(anchor: View, xOff: Int, yOff: Int, centerAlign: BalloonCenterAlign.TOP)
// shows the balloon over the anchor view (overlap) as the center aligns.
Or we can show up the balloon using Kotlin extensions.
myButton.showAlignTop(balloon)
We can dismiss popup simply using Balloon.dismiss()
method.
balloon.dismiss()
balloon.dismissWithDelay(1000L) // dismisses 1000 milliseconds later when the popup is shown
We can dismiss automatically some milliseconds later when the popup is shown using
setAutoDismissDuration
method on Balloon.Builder
.
Balloon.Builder(context)
// dismisses automatically 1000 milliseconds later when the popup is shown.
.setAutoDismissDuration(1000L)
...
We can show up multiple balloons sequentially using the relayShow
method.
The relayShow
method makes that setOnDismissListener
of the first balloon is reset to show the
next balloon and returns an instance of the next balloon.
customListBalloon
.relayShowAlignBottom(customProfileBalloon, circleImageView) // relay to customListBalloon
.relayShowAlignTop(customTagBalloon, bottomNavigationView, 130, 0) // relay to customProfileBalloon
// show sequentially customListBalloon-customProfileBalloon-customTagBalloon
customListBalloon.showAlignBottom(toolbar_list)
If the location of the balloon according to the anchor would be located at the boundaries on the screen,
the balloon will be stick to the end of the screen. In this case, we can give horizontal margins to the balloon.
.setMargin(12) // sets the margin on the balloon all directions.
.setMarginLeft(14) // sets the left margin on the balloon.
.setMarginRight(14) // sets the right margin on the balloon.
We can customize the arrow on the balloon.
.setIsVisibleArrow(true) // sets the visibility of the arrow.
.setArrowSize(10) // sets the arrow size.
.setArrowSize(BalloonSizeSpec.WRAP) // sets arrow size depending on the original resources' size.
.setArrowPosition(0.8f) // sets the arrow position using the popup size's ratio (0 ~ 1.0)
.setArrowOrientation(ArrowOrientation.TOP) // sets the arrow orientation. top, bottom, left, right
.setArrowDrawable(ContextCompat.getDrawable(context, R.drawable.arrow)) // sets the arrow drawable.
We can decide the position of the arrow depending on the aligning rules using the ArrowPositionRules
.
// Align the arrow position depending on an anchor.
// if `arrowPosition` is 0.5, the arrow will be located in the middle of an anchor.
.setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR) // default
// Align the arrow position depending on the balloon popup body.
// if `arrowPosition` is 0.5, he arrow will be located in the middle of the tooltip.
.setArrowPositionRules(ArrowPositionRules.ALIGN_BALLOON)
We can decide the orientation of the arrow depending on the aligning rules using the ArrowOrientationRules
.
// Align depending on the position of an anchor.
// For example, `arrowOrientation` is ArrowOrientation.TOP and
// we want to show up the balloon under an anchor using the `Balloon.showAlignBottom`.
// However, if there is not enough free space to place the tooltip at the bottom of the anchor,
// tooltips will be placed top of the anchor and the orientation of the arrow will be `ArrowOrientation.BOTTOM`.
.setArrowOrientationRules(ArrowOrientationRules.ALIGN_ANCHOR) // default
// Align to fixed ArrowOrientation value.
.setArrowOrientationRules(ArrowOrientationRules.ALIGN_FIXED)
Below previews are implemented using setArrowOrientation
and setArrowPosition
methods.
setArrowPosition
measures the balloon popup size and sets the arrow's position using the ratio value.
Orientation: BOTTOM Position: 0.62 showAlignTop |
Orientation: TOP Position : 0.5 showAlignBottom |
Orientation: LEFT Position: 0.5 showAlignRight |
Orientation: RIGHT Position: 0.5 showAlignLeft |
---|---|---|---|
We can customize the text on the balloon popup.
.setText("You can edit your profile now!")
.setTextSize(15f)
.setTextTypeface(Typeface.BOLD)
.setTextColor(ContextCompat.getColor(context, R.color.white_87))
If your text has HTML in it, you can enable HTML rendering by adding this:
.setTextIsHtml(true)
This will parse the text using Html.fromHtml(text)
.
TextForm
is a property class that has some attributes about TextView
for customizing the text of the balloon.
TextForm textForm = new TextForm.Builder(context)
.setText("This is a TextForm")
.setTextColorResource(R.color.colorPrimary)
.setTextSize(14f)
.setTextTypeface(Typeface.BOLD)
.build();
builder.setTextForm(textForm);
Here is how to create the TextForm
using the Kotlin DSL.
val form = textForm(context) {
setText("This is a TextForm")
setTextColorResource(R.color.white_87)
setTextSize(14f)
setTextTypeface(Typeface.BOLD)
}
We can customize the icon on the balloon.
.setIconSpace(10) // sets right margin of the icon.
.setIconSize(20) // sets size of the icon.
.setIconDrawable(ContextCompat.getDrawable(context, R.drawable.ic_edit)) // sets a drawable resource.
IconForm
is a property class that has some attributes about ImageView
for customizing the icon of the balloon.
IconForm iconForm = new IconForm.Builder(context)
.setDrawableResource(R.drawable.arrow)
.setIconColorResource(R.color.colorPrimary)
.setIconSize(20)
.setIconSpace(12)
.build();
builder.setIconForm(iconForm);
Here is how to create IconForm
using kotlin dsl.
val form = iconForm(context) {
drawable = ContextCompat.getDrawable(context, R.drawable.arrow)
iconColor = ContextCompat.getColor(context, R.color.skyblue)
iconSize = 20
iconSpace = 12
}
We can listen to the balloon is clicked, dismissed, and touched outside by users using the below listeners.
balloon.setOnBalloonClickListener(new OnBalloonClickListener() {
@Override
public void onBalloonClick() {
// doSomething;
}
});
balloon.setOnBalloonDismissListener(new OnBalloonDismissListener() {
@Override
public void onBalloonDismiss() {
// doSomething;
}
});
balloon.setOnBalloonOutsideTouchListener(new OnBalloonOutsideTouchListener() {
@Override
public void onBalloonOutsideTouch() {
// doSomething;
}
});
We can simplify them on Kotlin.
.setOnBalloonClickListener { Toast.makeText(context, "clicked", Toast.LENGTH_SHORT).show() }
.setOnBalloonDismissListener { Toast.makeText(context, "dismissed", Toast.LENGTH_SHORT).show() }
.setOnBalloonOutsideTouchListener { Toast.makeText(context, "touched outside", Toast.LENGTH_SHORT).show() }
We can fully customize the layout of the balloon using below method.
.setLayout(R.layout.my_balloon_layout)
Here is an example of implementing custom balloon.
Firstly, create an XML layout file like layout_custom_profile
on your taste.
val balloon = Balloon.Builder(context)
.setLayout(R.layout.layout_custom_profile)
.setArrowSize(10)
.setArrowOrientation(ArrowOrientation.TOP)
.setArrowPosition(0.5f)
.setWidthRatio(0.55f)
.setHeight(250)
.setCornerRadius(4f)
.setBackgroundColor(ContextCompat.getColor(this, R.color.black))
.setBalloonAnimation(BalloonAnimation.CIRCULAR)
.setLifecycleOwner(lifecycleOwner)
.build()
And next, we can get the inflated custom layout using the getContentView()
method from an instance of your Balloon
.
val button: Button =
balloon.getContentView().findViewById(R.id.button_edit)
button.setOnClickListener {
Toast.makeText(context, "Edit", Toast.LENGTH_SHORT).show()
balloon.dismiss()
}
If we want to show up the balloon only once or a specific number of times, here is how to implement it simply.
balloon.setPreferenceName("MyBalloon") // sets preference name of the Balloon.
balloon.setShowCounts(3) // show-up three of times the popup. the default value is 1.
balloon.runIfReachedShowCounts {
// do something after the preference showing counts is reached the goal.
}
We can clear all persisted preferences using the below method.
balloon.clearAllPreferences()
If you want to implement these kinds of persisting features more variously, consider using Only.
only("introPopup", times = 3) {
onDo { balloon.showAlignTop(anchor) }
}
Dialog, PopupWindow and etc, have memory leak issue if not dismissed before activity or fragment are destroyed.
But Lifecycles are now integrated with the Support Library since Architecture Components 1.0 Stable released.
So we can solve the memory leak issue very easily like the below.
Just use setLifecycleOwner
method. Then the dismiss()
method will be called automatically before your activity or fragment would be destroyed.
.setLifecycleOwner(lifecycleOwner)
We can initialize the balloon property lazily using the balloon()
extension and Balloon.Factory
abstract class.
The balloon
extension keyword can be used on your Activity
, Fragment
, and View
.
Before
CustomActivity.kt
class CustomActivity : AppCompatActivity() {
private val profileBalloon by lazy { BalloonUtils.getProfileBalloon(context = this, lifecycleOwner = this) }
// ...
}
After
CustomActivity.kt
class CustomActivity : AppCompatActivity() {
private val profileBalloon by balloon<ProfileBalloonFactory>()
// ...
}
We should create a class which extends Balloon.Factory
.
An implementation class of the factory must have a default(non-argument) constructor.
ProfileBalloonFactory.kt
class ProfileBalloonFactory : Balloon.Factory() {
override fun create(context: Context, lifecycle: LifecycleOwner): Balloon {
return createBalloon(context) {
setLayout(R.layout.layout_custom_profile)
setArrowSize(10)
setArrowOrientation(ArrowOrientation.TOP)
setArrowPosition(0.5f)
setWidthRatio(0.55f)
setHeight(250)
setCornerRadius(4f)
setBackgroundColor(ContextCompat.getColor(context, R.color.background900))
setBalloonAnimation(BalloonAnimation.CIRCULAR)
setLifecycleOwner(lifecycle)
}
}
}
We can show an overlay window over the whole screen except an anchor view.
balloon.setIsVisibleOverlay(true) // sets the visibility of the overlay for highlighting an anchor.
balloon.setOverlayColorResource(R.color.overlay) // background color of the overlay using a color resource.
balloon.setOverlayPadding(6f) // sets a padding value of the overlay shape internally.
balloon.setOverlayPaddingColorResource(R.color.colorPrimary) // sets color of the overlay padding using a color resource.
balloon.setBalloonOverlayAnimation(BalloonOverlayAnimation.FADE) // default is fade.
balloon.setDismissWhenOverlayClicked(false) // disable dismissing the balloon when the overlay is clicked.
We can change the shape of the highlighting using .setOverlayShape
.
balloon.setOverlayShape(BalloonOverlayOval) // default shape
balloon.setOverlayShape(BalloonOverlayRect)
balloon.setOverlayShape(BalloonOverlayCircle(radius = 36f))
balloon.setOverlayShape(BalloonOverlayRoundRect(12f, 12f))
OVAL | CIRCLE | RECT | ROUNDRECT |
---|---|---|---|
And we can set the specific position of the overlay shape using the below method.
balloon.setOverlayPosition(Point(x, y)) // sets a specific position of the overlay shape.
We can implement popup animations when showing and dismissing.
BalloonAnimation.NONE
BalloonAnimation.FADE
BalloonAnimation.OVERSHOOT
BalloonAnimation.ELASTIC
BalloonAnimation.CIRCULAR
FADE | OVERSHOOT | ELASTIC | CIRCULAR |
---|---|---|---|
We can give repeated dynamic animations to Balloon when it's showing.
The animation would work differently by the position of the arrow.
HEARTBEAT | SHAKE | BREATH | ROTATE |
---|---|---|---|
BalloonHighlightAnimation.NONE
BalloonHighlightAnimation.HEARTBEAT
BalloonHighlightAnimation.SHAKE
BalloonHighlightAnimation.BREATH
BalloonHighlightAnimation.ROTATE
.setBalloonHighlightAnimation(BalloonHighlightAnimation.SHAKE)
We can implement the rotate animation like the below.
.setBalloonHighlightAnimation(BalloonHighlightAnimation.ROTATE)
.setBalloonRotationAnimation(
BalloonRotateAnimation.Builder().setLoops(2).setSpeeds(2500).setTurns(INFINITE).build())
We can reference every specification of this library here.
.setWidth(value: Int)
.setWidthRatio(@FloatRange(from = 0.0, to = 1.0) value: Float)
.setHeight(value: Int)
.setSize(value: Int, value: Int)
.setSpace(value: Int)
.setPadding(value: Int)
.setPaddingLeft(value: Int)
.setPaddingTop(value: Int)
.setPaddingRight(value: Int)
.setPaddingBottom(value: Int)
.setMargin(value: Int)
.setMarginLeft(value: Int)
.setMarginTop(value: Int)
.setMarginRight(value: Int)
.setMarginBottom(value: Int)
.setElevation(value: Int)
.setIsVisibleArrow(value: Boolean)
.setArrowSize(value: Int)
.setArrowPosition(@FloatRange(from = 0.0, to = 1.0) value: Float)
.setArrowOrientation(value: ArrowOrientation)
.setArrowPositionRules(ArrowPositionRules.ALIGN_ANCHOR)
.setArrowColor(value: Int)
.setArrowColorResource(@ColorRes value: Int)
.setArrowDrawable(value: Drawable?)
.setArrowDrawableResource(@DrawableRes value: Int)
.setArrowAlignAnchorPadding(value: Int)
.setArrowAlignAnchorPaddingRatio(value: Float)
.setBackgroundColor(value: Int)
.setBackgroundColorResource(@ColorRes value: Int)
.setBackgroundDrawable(value: Drawable?)
.setBackgroundDrawableResource(@DrawableRes value: Int)
.setCornerRadius(value: Float)
.setText(value: String)
.setTextResource(value: Int)
.setTextColor(value: Int)
.setTextColorResource(value: Int)
.setTextSize(value: Float)
.setTextTypeface(value: Int)
.setTextGravity(value: Int)
.setTextForm(value: TextForm)
.setIconDrawable(value: Drawable?)
.setIconDrawableResource(@DrawableRes value: Int)
.setIconSize(value: Int)
.setIconWidth(value: Int)
.setIconHeight(value: Int)
.setIconColor(value: Int)
.setIconColorResource(@ColorRes value: Int)
.setIconSpace(value: Int)
.setIconForm(value: IconForm)
.setIconGravity(value: IconGravity)
.setAlpha(@FloatRange(from = 0.0, to = 1.0) value: Float)
.setLayout(@LayoutRes layout: Int)
.setIsVisibleOverlay(value: Boolean)
.setOverlayColor(@ColorInt value: Int)
.setOverlayColorResource(@ColorRes value: Int)
.setOverlayPadding(@Dp value: Float)
.setOverlayPosition(value: Point)
.setOverlayShape(value: BalloonOverlayShape)
.setPreferenceName(value: String)
.setShowCount(value: Int)
.setFocusable(value: Boolean)
.setLifecycleOwner(value: LifecycleOwner)
.setDismissWhenClicked(value: Boolean)
.setDismissWhenLifecycleOnPause(value: Boolean)
.setDismissWhenTouchOutside(value: Boolean)
.setDismissWhenShowAgain(value: Boolean)
.setDismissWhenOverlayClicked(value: Boolean)
.setBalloonAnimation(value: BalloonAnimation)
.setOnBalloonClickListener(value: OnBalloonClickListener)
.setOnBalloonDismissListener(value: OnBalloonDismissListener)
.setOnBalloonInitializedListener(value: OnBalloonInitializedListener)
.setOnBalloonOutsideTouchListener(value: OnBalloonOutsideTouchListener)
.setOnBalloonOverlayClickListener(value: OnBalloonOverlayClickListener)
.setDismissWhenTouchOutside(value: Boolean)
Support it by joining stargazers for this repository. ⭐
Copyright 2019 skydoves (Jaewoong Eum)
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.