My solution of the challenge to chapter 21


#1

I’ve made the challenge in this way.

Firstly created a new ViewModel class.

FragmentViewModel.java:

public class FragmentViewModel extends BaseObservable {
    public ObservableField<String> mPlaybackSpeedRateLabel = new ObservableField<>();
    public ObservableInt mPlaybackSpeedRate = new ObservableInt();
    private BeatBox mBeatBox;
    public FragmentViewModel(BeatBox beatBox) {
        if (beatBox != null) {
            mBeatBox = beatBox;
            mPlaybackSpeedRateLabel.set(MessageFormat.format(
                    mBeatBox.getPlaybackSpeedLabel(),
                    (int) (mBeatBox.getPlaybackSpeedRate() * 100)));
            mPlaybackSpeedRate.set((int) (mBeatBox.getPlaybackSpeedRate() * 100 - 20));
        }
    }
    public void onPlaybackSpeedRateChanged(SeekBar seekBar, int progress, boolean fromUser) {
        mPlaybackSpeedRateLabel.set(MessageFormat.format(mBeatBox.getPlaybackSpeedLabel(), progress + 20));
        mPlaybackSpeedRate.set(progress);
        mBeatBox.setPlaybackSpeedRate((progress + 20.0f) / 100);
    }
}

Then modified the BeatBox class.

BeatBox.java:

public class BeatBox {

    private static final String TAG = "BeatBox";
    private static final String SOUND_FOLDER = "sample_sounds";
    private static final int MAX_SOUNDS = 5;

    private AssetManager mAssets;
    private List<Sound> mSounds = new ArrayList<>();
    private SoundPool mSoundPool;
    private float mPlaybackSpeedRate;
    private String mPlaybackSpeedLabel;

    public BeatBox(Context context) {
        mAssets = context.getAssets();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            AudioAttributes attributes = new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .build();
            mSoundPool = new SoundPool.Builder()
                    .setAudioAttributes(attributes)
                    .setMaxStreams(MAX_SOUNDS)
                    .build();
        } else {
            //noinspection deprecation
            mSoundPool = new SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, 0);
        }
        setPlaybackSpeedRate(1.0f);
        setPlaybackSpeedLabel(context.getString(R.string.playback_label));
        loadSounds();
    }

    public void play(Sound sound) {
        play(sound, mPlaybackSpeedRate);
    }

    public void play(Sound sound, float rate) {
        Integer soundId = sound.getSoundId();
        if (soundId == null) {
            return;
        }
        mSoundPool.play(soundId, 1.0f, 1.0f, 1, 0, rate);
    }

    public void release() {
        mSoundPool.release();
    }

    private void loadSounds() {
        String[] soundNames;
        try {
            soundNames = mAssets.list(SOUND_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 = SOUND_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 mSounds;
    }

    public void setPlaybackSpeedRate(float rate) {
        mPlaybackSpeedRate = rate;
    }

    public float getPlaybackSpeedRate() {
        return mPlaybackSpeedRate;
    }

    public String getPlaybackSpeedLabel() {
        return mPlaybackSpeedLabel;
    }

    public void setPlaybackSpeedLabel(String playbackSpeedLabel) {
        mPlaybackSpeedLabel = playbackSpeedLabel;
    }

}

Changed the fragment layout file.

fragment_beat_box.xml:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="ru.coffeeplanter.beatbox.FragmentViewModel"/>
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginBottom="12dp"
            android:layout_marginEnd="8dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            app:layout_constraintBottom_toTopOf="@+id/speed_rate_label"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

        <TextView
            android:id="@+id/speed_rate_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:text="@{viewModel.mPlaybackSpeedRateLabel}"
            app:layout_constraintBottom_toTopOf="@+id/seek_bar"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            tools:text="Playback Speed: 100 %"/>

        <SeekBar
            android:id="@+id/seek_bar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginStart="8dp"
            android:max="160"
            android:progress="@{viewModel.mPlaybackSpeedRate}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:onProgressChanged="@{viewModel::onPlaybackSpeedRateChanged}"/>

    </android.support.constraint.ConstraintLayout>

</layout>

And finally added to the onCreateView() method of BeatBoxFragment.java:
binding.setViewModel(new FragmentViewModel(mBeatBox));

All is working nice. )


#2

Hi there,

I’ve been working on a solution for few days now and I used a different approach. I learned few things on the way :slight_smile:

  1. First adding a TextView and a Seekbar in the fragment_beat_box.xml:

     <TextView
         android:id="@+id/progress_text"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:text="Playback Speed: 100%"/>
    
     <SeekBar
         android:id="@+id/seekBar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="bottom"
         android:progress="50"
         android:max="100"/>
    
  2. Adding a variable to BeatBox

private float mRate = 1.0f;

and pass on the value in the play function

public void play(Sound sound) {
    Integer soundId = sound.getSoundId();
    if (soundId == null) {
        return;
    }
    mSoundPool.play(soundId, 1.0f, 1.0f, 1, 0, mRate);
}
  1. In BeatBoxFragment
    Wire up the seekback (finally work after solving the NullPointerException) and set the rate of mBeatBox.

mSeekBar = (SeekBar) binding.getRoot().findViewById(R.id.seekBar);
mProgressText = (TextView) binding.getRoot().findViewById(R.id.progress_text);

    mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            mProgressText.setText("Playback Speed " + String.valueOf((progress + 50)) + "%");
            mBeatBox.setRate((float) (progress+50)/100);

        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {

        }

        @Override

        public void onStopTrackingTouch(SeekBar seekBar) {

        }
    });

I hope this help. It was a very interesting challenge!!


#3

Hi @Giboon,
For step #3 its not clear where you put the code for the SeekBar’s listener


#4

It goes in the onCreateView


#5

Hi @Giboon,
I tried your suggestion and it doesn’t work for me. I don’t see the SeekBar or the TextView beneath the RecyclerView. I tried this on Android OS v6.0.1.
I appreciate your help in sharing the following files:

  1. fragment_beat.box.xml
  2. BeatBox.java
  3. BeatBoxFragment.java

#6

Hi asett,

I hope this helps.

Best


#7

Hi @ilya,
What is the value you set for playback_label in strings.xml?


#8

Hi!
The string resource is <string name="playback_label">Playback Speed: {0} %</string>
Yoiu can check all the code here: https://github.com/coffeeplanter/BeatBox


#9

Thanks @ilya,
That was helpful


#10

setPlaybackSpeedLabel(context.getString(R.string.playback_label));
loadSounds();

I want to ask ] where u defined this variable “playback_lable”.Thanks in advance.


#11

QAQ
I read your early reply just now.Thanks again!