Other Notes About the Sample
There are a few more interesting elements of the sample app that are worth noting.
android:gravity
There is a TextView
at the top of the layout. Initially, it shows the message “Oh, c’mon, click something!”, but if you follow those instructions and start interacting with the other widgets, the message changes to reflect the recent event (e.g., “Button clicked!”).
The TextView
has an android:gravity="center_horizontal"
attribute:
<TextView
android:id="@+id/log"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/log_background"
android:gravity="center_horizontal"
android:text="@string/log_default"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
“Gravity” means “hey, if there is room, position my content here”. center_horizontal
gravity says “hey, if there is room horizontally, center my content”. For a TextView
, the “content” is the text. The width is set to 0dp
, which for a child of ConstraintLayout
means that the TextView
will be stretched between its start and end anchor points. Those are at the edges of the ConstraintLayout
, so the TextView
stretches to fill the width of the screen, less 8dp
of padding that we have in the ScrollView
. The TextView
has a gray background, so in the screenshots, you see the full extent of the TextView
plus the centered message.
Another way of centering things is through ConstraintLayout
itself. If we switch the width of the TextView
to wrap_content
, ConstraintLayout
will interpret that as meaning that the TextView
should be centered between its start and end anchor points (assuming there is no bias setting to slide it one way or another):
<TextView
android:id="@+id/log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/log_background"
android:text="@string/log_default"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
However, in that case, the TextView
itself is only as big as its content, so the gray background does not stretch to fill the screen:
log()
That TextView
is the log
widget. Our log()
function is set up to:
- Display a message in that
TextView
, and - Log that same message to Logcat
So, we have a log()
method in Java:
private void log(@StringRes int msg) {
binding.log.setText(msg);
Log.d(TAG, getString(msg));
}
…and a corresponding log()
function in Kotlin:
private fun log(@StringRes msg: Int) {
binding.log.setText(msg)
Log.d(TAG, getString(msg))
}
The parameter to log()
has a @StringRes
annotation. This has no effect at runtime. However, it will help the build tools avoid some compile-time problems akin to the bug from the chapter on debugging. There, we passed an arbitrary Int
to setText()
on a TextView
. That function expects a string resource, and so it crashes if you pass some Int
that does not represent a string resource. The @StringRes
annotation on an Int
says “hey! this Int
should point to a string resource!”. If we attempt to call log()
with an arbitrary Int
, we will get an error in Android Studio:
This error message is being provided by a tool called “Lint”. The IDE uses Lint and its set of rules to identify possible problem spots in the code, such as calling log()
with a value that is not a string resource ID. Consider it a reminder service, hinting about some possible flaws in your code.
We also want to log messages from our SeekBar
. There, though, we want to show the current value of the SeekBar
, based on where the user has positioned the thumb:
Our log()
function is not set up for that, so we instead just do that bit of logging directly from our OnSeekBarChangeListener
, whether that is implemented in Java:
binding.seekbar.setOnSeekBarChangeListener(
new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
String msg = getString(R.string.seekbar_changed, progress);
binding.log.setText(msg);
Log.d(TAG, msg);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// ignored
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// ignored
}
});
}
…or Kotlin:
binding.seekbar.setOnSeekBarChangeListener(object :
SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(
seekBar: SeekBar,
progress: Int,
fromUser: Boolean
) {
val msg = getString(R.string.seekbar_changed, progress)
binding.log.text = msg
Log.d(TAG, msg)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
// unused
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
// unused
}
})
Our string resource has a placeholder in it:
<string name="seekbar_changed">SeekBar changed to %d!</string>
Here, %d
in our string resource means “we will supply some Int
at runtime to fill in here”. getString()
not only takes a string resource ID, but it optionally takes objects to use for those placeholders.
The primary function of an OnSeekBarChangeListener
is onProgressChanged()
. This will be called when the SeekBar
value is adjusted. You are passed:
- The
Seekbar
itself, - The new value of the
SeekBar
, and - A
Boolean
indicating if the user changed theSeekBar
value or if you did programmatically
So, our getString(R.string.seekbar_changed, progress)
call takes the progress
value and uses that to replace the %d
in the string resource, giving us the text to display in the TextView
and Logcat.
OnSeekBarChangeListener
also has two other methods:
-
onStartTrackingTouch()
, which will be called when the user starts dragging the thumb -
onStopTrackingTouch()
, which will be called when the user stops dragging the thumb
Most uses of SeekBar
do not need to worry about those events, but we have to have those methods to satisfy the compiler, so the FormWidgets
sample just has them as empty functions.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.