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));
toonCreateView(...)
-
on further review “adapter” was probably not the best choice for a data object variable name here considering
binding.recyclerView
has asetAdapter(...)
toopublic 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();
}