CH 21 Speed Control Challenge with data binding

I had some trouble with data binding giving me all sorts of errors. I’m glad I persisted because it is helping me learn how data binding works, though perhaps not best practices. Feedback welcome!!! Here’s my solution:

BeatBox.java

  • add mPlaybackSpeed and constants to keep track of minimum and maximum speeds

  • add getters and setters; Not sure if overly cautious, but I included a check to enforce the minimum and maximum values getting passed into the setter

    public class BeatBox {

      public static final float MIN_PLAYBACK_SPEED = 0.5f;
      public static final float MAX_PLAYBACK_SPEED = 2.0f;
    
      private static final String TAG = "BeatBox";
    
      private static final String SOUNDS_FOLDER = "sample_sounds";
      private static final int MAX_SOUNDS = 5;
    
      private final AssetManager mAssets;
      private final List<Sound> mSounds = new ArrayList<>();
      private SoundPool mSoundPool;
      private float mPlaybackSpeed;
    
      public BeatBox(Context context) {
          mAssets = context.getAssets();
          mSoundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
          mPlaybackSpeed = 1.0f;
          loadSounds();
      }
    
      public void play(Sound sound) {
          Integer soundId = sound.getSoundId();
          if (soundId == null) {
              return;
          }
          mSoundPool.play(soundId, 1.0f, 1.0f, 1, 0, mPlaybackSpeed);
      }
    
      public void release() {
          mSoundPool.release();
      }
    
      private void loadSounds() {
          String[] soundNames;
          try {
              soundNames = mAssets.list(SOUNDS_FOLDER);
              Log.i(TAG, "Found " + soundNames.length + " sounds");
          } catch (IOException ioe) {
              Log.e(TAG, "Could not list assets", ioe);
              return;
          }
    
          for (String filename : soundNames) {
              try {
                  String assetPath = SOUNDS_FOLDER + "/" + filename;
                  Sound sound = new Sound(assetPath);
                  load(sound);
                  mSounds.add(sound);
              } catch (IOException ioe) {
                  Log.e(TAG, "Could not load sound " + filename, ioe);
              }
          }
      }
    
      private void load(Sound sound) throws IOException {
          AssetFileDescriptor afd = mAssets.openFd((sound.getAssetPath()));
          int soundId = mSoundPool.load(afd, 1);
          sound.setSoundId(soundId);
      }
    
      public List<Sound> getSounds() {
          return Collections.unmodifiableList(mSounds);
      }
    
      public float getPlaybackSpeed() {
          return mPlaybackSpeed;
      }
    
      public void setPlaybackSpeed(float playbackSpeed) {
          if (playbackSpeed > MAX_PLAYBACK_SPEED) {
              mPlaybackSpeed = MAX_PLAYBACK_SPEED;
          } else if (playbackSpeed < MIN_PLAYBACK_SPEED) {
              mPlaybackSpeed = MIN_PLAYBACK_SPEED;
          } else {
              mPlaybackSpeed = playbackSpeed;
          }
    
      }
    

    }

SpeedSeekBarAdapter.java

  • add this class to handle the flow between my BeatBox model and my view since they use different min/max/range/types

  • add a method with the same number and type of arguments as SeekBarBindingAdapter.onProgressChanged as listener for moving the slider

  • I’m not entirely sure why I didn’t have to use @Bindable anywhere…

    public class SpeedSeekBarAdapter extends BaseObservable {

      private BeatBox mBeatBox;
    
      private int mMinSeekBar;
      private int mMaxSeekBar;
      private int mRangeSeekBar;
      private float mMinSpeed;
      private float mMaxSpeed;
      private float mRangeSpeed;
    
      public SpeedSeekBarAdapter(BeatBox beatBox, SeekBar seekBar) {
          mBeatBox = beatBox;
          mMinSeekBar = 0;
          mMaxSeekBar = seekBar.getMax();
          mRangeSeekBar = mMaxSeekBar - mMinSeekBar;
          mMinSpeed = BeatBox.MIN_PLAYBACK_SPEED;
          mMaxSpeed = BeatBox.MAX_PLAYBACK_SPEED;
          mRangeSpeed = mMaxSpeed - mMinSpeed;
      }
    
      public float getSpeed() {
          return mBeatBox.getPlaybackSpeed();
      }
    
      public int getSeekBarValue() {
          int seekBarValue = Math.round((mBeatBox.getPlaybackSpeed() - mMinSpeed) / mRangeSpeed * mRangeSeekBar + mMinSeekBar);
    
          if (seekBarValue > mMaxSeekBar) {
              return mMaxSeekBar;
          } else if (seekBarValue < mMinSeekBar) {
              return mMinSeekBar;
          } else {
              return seekBarValue;
          }
      }
    
      public void setSpeed(int seekBarValue) {
          float speed = ((float) seekBarValue - mMinSeekBar) / mRangeSeekBar * mRangeSpeed + mMinSpeed;
          mBeatBox.setPlaybackSpeed(speed);
          notifyChange();
      }
    
      public void changeSpeed(SeekBar seekBar, int progress, boolean fromUser) {
          setSpeed(progress);
      }
    

    }

strings.xml
<resources> <string name="app_name">BeatBox</string> <string name="playback_speed">Playback Speed: %.1f x</string> </resources>

fragment_beat_box.xml

  • add TextView and SeekBar widgets

  • add data tag with reference to my new “adapter” (I don’t know what to call it)

  • call all my previously setup items

    <?xml version="1.0" encoding="utf-8"?>

      <data>
          <variable
              name="adapter"
              type="com.bignerdranch.android.beatbox.SpeedSeekBarAdapter"/>
      </data>
    
      <LinearLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">
    
          <android.support.v7.widget.RecyclerView
              android:id="@+id/recycler_view"
              android:layout_width="match_parent"
              android:layout_height="0dp"
              android:layout_weight="1"/>
    
          <TextView
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              tools:text="@string/playback_speed"
              android:text="@{String.format(@string/playback_speed, adapter.getSpeed)}"/>
          <SeekBar
              android:id="@+id/seekBar"
              android:layout_width="match_parent"
              android:layout_height="32dp"
              android:progress="@{adapter.seekBarValue}"
              android:onProgressChanged="@{adapter.changeSpeed}"/>
    
      </LinearLayout>
    

BeatBoxFragment.java

  • add binding.setAdapter(new SpeedSeekBarAdapter(mBeatBox, binding.seekBar)); to onCreateView(...)

  • on further review “adapter” was probably not the best choice for a data object variable name here considering binding.recyclerView has a setAdapter(...) too

    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    FragmentBeatBoxBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_beat_box, container, false);

      binding.recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
      binding.recyclerView.setAdapter(new SoundAdapter(mBeatBox.getSounds()));
    
      binding.setAdapter(new SpeedSeekBarAdapter(mBeatBox, binding.seekBar));
    
      return binding.getRoot();
    

    }

1 Like