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:

FormWidgets Sample, with Alternate Way of Centering Text
FormWidgets Sample, with Alternate Way of Centering Text

log()

That TextView is the log widget. Our log() function is set up to:

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:

Android Studio, Showing Lint Error
Android Studio, Showing Lint Error

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:

FormWidgets Sample, Showing SeekBar value
FormWidgets Sample, Showing SeekBar value

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:

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:

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.