Reputation: 3592
I am trying to create a custom button with progress bar inside of it in Android.
The button should have 2 states: Normal and Loading.
In Normal state it should show a text while in Loading state it should show a centerred circular progress indicator instead of the text! When the button state returns to "Normal" state it should show the text again.
To achieve this, I've thought about create a custom view which build from a RelativeLayout and inside of it there is a TextView and a Circular progress indicator and change their visibility in code according to the state.
This idea and logic works pretty good.
Please refer to images of my buttons with the progress indicators:
However, the problem comes when I want to apply a selector to this view, I've created a style and a selector for each button but it just not setting the right background to the view when its disabled.
A RelativeLayout doesn't has an enabled
attribute available in its xml so I had to add a styleable attr and change its state in code with isEnabled = false
or something like that.
This makes it disabled in did, but the background stays as it is enabled (The selector not working).
This is my "Button" source code:
class ProgressButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
private val progressBar: LottieAnimationView
private val buttonTextView: TextView
init {
val root = LayoutInflater.from(context).inflate(R.layout.progress_button, this, true)
buttonTextView = root.findViewById(R.id.button_text)
progressBar = root.findViewById(R.id.progress_indicator)
loadAttr(attrs, defStyleAttr)
}
private fun loadAttr(attrs: AttributeSet?, defStyleAttr: Int) {
val arr = context.obtainStyledAttributes(
attrs,
R.styleable.ProgressButton,
defStyleAttr,
0
)
val buttonText = arr.getString(R.styleable.ProgressButton_text)
val loading = arr.getBoolean(R.styleable.ProgressButton_loading, false)
val enabled = arr.getBoolean(R.styleable.ProgressButton_enabled, true)
isEnabled = enabled
arr.recycle()
buttonTextView.text = buttonText
setLoading(loading)
}
fun setLoading(loading: Boolean){
if(loading){
buttonTextView.visibility = View.GONE
progressBar.visibility = View.VISIBLE
} else {
buttonTextView.visibility = View.VISIBLE
progressBar.visibility = View.GONE
}
}
}
This its layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/button_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceButton"
android:text="OK" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/progress_indicator"
android:layout_width="@dimen/progressbar_width"
android:layout_height="@dimen/progressbar_width"
android:layout_centerInParent="true"
android:visibility="gone"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/lottile_button_loader" />
</RelativeLayout>
This a the background with selector for it:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/components_corner_radius" />
<solid android:color="@color/button_black_bg_selector" />
</shape>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/black" android:state_enabled="true" />
<item android:color="@color/buttons_black_pressed" android:state_pressed="true" />
<item android:color="@color/buttons_black_disabled" android:state_enabled="false" />
<item android:color="@color/black" />
This is the styling and theme:
<style name="Theme.Widget.ProgressButton" parent="">
<item name="android:textAppearanceButton">@style/TextAppearance.Body.White</item>
</style>
<style name="Widget.ProgressButton.Black" parent="@style/Theme.Widget.ProgressButton">
<item name="android:colorControlHighlight">@color/buttons_black_pressed</item>
<item name="android:background">@drawable/progress_button_black</item>
</style>
And finally, this how i use it in a fragment layout xml:
<com.example.widgets.ProgressButton
android:id="@+id/button_black_loading"
android:theme="@style/Widget.ProgressButton.Black". //This is where it gets its style and theme
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="16dp"
app:loading="true"/>
Any help will be appreciated.
Upvotes: 1
Views: 4426
Reputation: 3592
Finally I've came to a pattern that seems to work with a single style line in the custom view usage xml (e.g. Fragment or Activity layout).
For each button I've defined similar styling blocks that looks like this:
<style name="Theme.Widget.Button.Black" parent="">
<item name="android:textAppearanceButton">@style/TextAppearance.BlackButton</item> //This will set the theme for the button internal TextView
<item name="android:colorControlHighlight">@color/buttons_black_pressed</item> //This will set the highlight color for ripple effect
</style>
<style name="Widget.Button.Black" parent="">
**<item name="android:theme">@style/Theme.Widget.Button.Black</item>** //Note this theme attribute which takes that above styling and applying it as a theme!!
<item name="android:background">@drawable/button_black</item> //A shape with selector drawable
</style>
The background drawable:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight"> //This comes from the theme for the ripple effect
<item android:drawable="@drawable/button_black_shape"/> //The selector
</ripple>
The background shape drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/components_corner_radius" />
<solid android:color="@color/button_black_bg_selector" />
</shape>
The selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/black" android:state_enabled="true" />
<item android:color="@color/buttons_black_pressed" android:state_pressed="true" />
<item android:color="@color/buttons_black_disabled" android:state_enabled="false" />
<item android:color="@color/black" />
</selector>
And finally using the button in fragment XML like this:
<com.sample.widgets.ProgressButton
android:id="@+id/button_black_enabled"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="16dp"
app:enabled="true"
app:text="OK"
style="@style/Widget.Button.Black"/> //The style brings a theme also!
Upvotes: 1
Reputation: 21043
After adding the selector you need to change the state of ProgressButton
I am adding essential code below which should work.
Selector should be like :-
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/black" android:state_enabled="true" />
<item android:drawable="@color/buttons_black_pressed" android:state_pressed="true" />
<item android:drawable="@color/buttons_black_disabled" android:state_enabled="false" />
and ProgressButton.kt
class ProgressButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr) {
private val progressBar: LottieAnimationView
private val buttonTextView: TextView
init {
val root = LayoutInflater.from(context).inflate(R.layout.progress_button, this, true)
buttonTextView = root.findViewById(R.id.button_text)
progressBar = root.findViewById(R.id.progress_indicator)
loadAttr(attrs, defStyleAttr)
}
private fun loadAttr(attrs: AttributeSet?, defStyleAttr: Int) {
// this line can be removed if you are setting selector in xml
setBackgroundResource(R.drawable.button_selector)
setLoading(true)
}
fun setLoading(loading: Boolean) {
if (loading) {
buttonTextView.visibility = View.GONE
progressBar.visibility = View.VISIBLE
} else {
buttonTextView.visibility = View.VISIBLE
progressBar.visibility = View.GONE
}
isEnabled = !loading
}
}
Upvotes: 0