Merge pull request #5113 from ByteHamster/play-button

Play button animation
This commit is contained in:
ByteHamster 2021-04-15 21:25:14 +02:00 committed by GitHub
commit 58cdba45c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 153 additions and 87 deletions

View File

@ -17,6 +17,7 @@ import android.widget.TextView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import de.danoeh.antennapod.view.PlayButton;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
@ -72,7 +73,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
SeekBar sbPosition; SeekBar sbPosition;
private ImageButton butRev; private ImageButton butRev;
private TextView txtvRev; private TextView txtvRev;
private ImageButton butPlay; private PlayButton butPlay;
private ImageButton butFF; private ImageButton butFF;
private TextView txtvFF; private TextView txtvFF;
private ImageButton butSkip; private ImageButton butSkip;
@ -123,8 +124,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
} }
@Override @Override
public ImageButton getPlayButton() { protected void updatePlayButtonShowsPlay(boolean showPlay) {
return butPlay; butPlay.setIsShowPlay(showPlay);
} }
@Override @Override
@ -513,6 +514,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
txtvRev.setText(NumberFormat.getInstance().format(UserPreferences.getRewindSecs())); txtvRev.setText(NumberFormat.getInstance().format(UserPreferences.getRewindSecs()));
} }
butPlay = findViewById(R.id.butPlay); butPlay = findViewById(R.id.butPlay);
butPlay.setIsVideoScreen(true);
butFF = findViewById(R.id.butFF); butFF = findViewById(R.id.butFF);
txtvFF = findViewById(R.id.txtvFF); txtvFF = findViewById(R.id.txtvFF);
if (txtvFF != null) { if (txtvFF != null) {

View File

@ -50,6 +50,7 @@ import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.ChapterSeekBar; import de.danoeh.antennapod.view.ChapterSeekBar;
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView; import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
import de.danoeh.antennapod.view.PlayButton;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
@ -82,7 +83,7 @@ public class AudioPlayerFragment extends Fragment implements
private ChapterSeekBar sbPosition; private ChapterSeekBar sbPosition;
private ImageButton butRev; private ImageButton butRev;
private TextView txtvRev; private TextView txtvRev;
private ImageButton butPlay; private PlayButton butPlay;
private ImageButton butFF; private ImageButton butFF;
private TextView txtvFF; private TextView txtvFF;
private ImageButton butSkip; private ImageButton butSkip;
@ -381,8 +382,8 @@ public class AudioPlayerFragment extends Fragment implements
} }
@Override @Override
public ImageButton getPlayButton() { protected void updatePlayButtonShowsPlay(boolean showPlay) {
return butPlay; butPlay.setIsShowPlay(showPlay);
} }
@Override @Override

View File

@ -6,7 +6,6 @@ import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
@ -24,6 +23,7 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.view.PlayButton;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
@ -40,7 +40,7 @@ public class ExternalPlayerFragment extends Fragment {
private ImageView imgvCover; private ImageView imgvCover;
private TextView txtvTitle; private TextView txtvTitle;
private ImageButton butPlay; private PlayButton butPlay;
private TextView feedName; private TextView feedName;
private ProgressBar progressBar; private ProgressBar progressBar;
private PlaybackController controller; private PlaybackController controller;
@ -103,8 +103,8 @@ public class ExternalPlayerFragment extends Fragment {
} }
@Override @Override
public ImageButton getPlayButton() { protected void updatePlayButtonShowsPlay(boolean showPlay) {
return butPlay; butPlay.setIsShowPlay(showPlay);
} }
@Override @Override

View File

@ -0,0 +1,54 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.ui.common.ThemeUtils;
public class PlayButton extends AppCompatImageButton {
private boolean isShowPlay = true;
private boolean isVideoScreen = false;
public PlayButton(@NonNull Context context) {
super(context);
}
public PlayButton(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public PlayButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setIsVideoScreen(boolean isVideoScreen) {
this.isVideoScreen = isVideoScreen;
}
public void setIsShowPlay(boolean showPlay) {
if (this.isShowPlay != showPlay) {
this.isShowPlay = showPlay;
setContentDescription(getContext().getString(showPlay ? R.string.play_label : R.string.pause_label));
if (isVideoScreen) {
setImageResource(showPlay ? R.drawable.ic_av_play_white_80dp : R.drawable.ic_av_pause_white_80dp);
} else if (!isShown()) {
setImageResource(ThemeUtils.getDrawableFromAttr(getContext(),
showPlay ? R.attr.av_play : R.attr.av_pause));
} else if (showPlay) {
AnimatedVectorDrawableCompat drawable = AnimatedVectorDrawableCompat.create(
getContext(), R.drawable.ic_animate_pause_play);
setImageDrawable(drawable);
drawable.start();
} else {
AnimatedVectorDrawableCompat drawable = AnimatedVectorDrawableCompat.create(
getContext(), R.drawable.ic_animate_play_pause);
setImageDrawable(drawable);
drawable.start();
}
}
}
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_animate_play"
tools:ignore="NewApi">
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="@integer/fragment_transition_duration"
android:propertyName="pathData"
android:valueFrom="@string/svg_animatable_pause"
android:valueTo="@string/svg_animatable_play"
android:valueType="pathType"/>
</aapt:attr>
</target>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="@integer/fragment_transition_duration"
android:propertyName="rotation"
android:valueFrom="-90"
android:valueTo="0"/>
</aapt:attr>
</target>
</animated-vector>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<group android:name="group"
android:pivotX="12"
android:pivotY="12">
<path android:name="path"
android:fillColor="?attr/action_icon_color"
android:pathData="@string/svg_animatable_play"/>
</group>
</vector>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_animate_play"
tools:ignore="NewApi">
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="@integer/fragment_transition_duration"
android:propertyName="pathData"
android:valueFrom="@string/svg_animatable_play"
android:valueTo="@string/svg_animatable_pause"
android:valueType="pathType"/>
</aapt:attr>
</target>
<target android:name="group">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="@integer/fragment_transition_duration"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="90"/>
</aapt:attr>
</target>
</animated-vector>

View File

@ -140,7 +140,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp"> android:layout_marginBottom="24dp">
<ImageButton <de.danoeh.antennapod.view.PlayButton
android:id="@+id/butPlay" android:id="@+id/butPlay"
android:layout_width="@dimen/audioplayer_playercontrols_length_big" android:layout_width="@dimen/audioplayer_playercontrols_length_big"
android:layout_height="@dimen/audioplayer_playercontrols_length_big" android:layout_height="@dimen/audioplayer_playercontrols_length_big"

View File

@ -57,7 +57,7 @@
</LinearLayout> </LinearLayout>
<ImageButton <de.danoeh.antennapod.view.PlayButton
android:id="@+id/butPlay" android:id="@+id/butPlay"
android:layout_width="52dp" android:layout_width="52dp"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -41,7 +41,7 @@
android:contentDescription="@string/rewind_label" android:contentDescription="@string/rewind_label"
app:srcCompat="@drawable/ic_av_fast_rewind_white_80dp" /> app:srcCompat="@drawable/ic_av_fast_rewind_white_80dp" />
<ImageButton <de.danoeh.antennapod.view.PlayButton
android:id="@+id/butPlay" android:id="@+id/butPlay"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="svg_animatable_play" translatable="false">M 8 5 L 8 12 L 19 12 L 19 12 M 8 19 L 8 12 L 19 12 L 19 12</string>
<string name="svg_animatable_pause" translatable="false">M 5 6 L 5 10 L 19 10 L 19 6 M 5 18 L 5 14 L 19 14 L 19 18</string>
</resources>

View File

@ -7,7 +7,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.res.TypedArray;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
@ -15,7 +14,6 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.widget.ImageButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.event.MessageEvent;
@ -28,12 +26,6 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer;
import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import io.reactivex.Maybe;
import io.reactivex.MaybeOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
@ -60,8 +52,6 @@ public abstract class PlaybackController {
private boolean initialized = false; private boolean initialized = false;
private boolean eventsRegistered = false; private boolean eventsRegistered = false;
private Disposable mediaLoader;
public PlaybackController(@NonNull Activity activity) { public PlaybackController(@NonNull Activity activity) {
this.activity = activity; this.activity = activity;
} }
@ -77,7 +67,7 @@ public abstract class PlaybackController {
if (PlaybackService.isRunning) { if (PlaybackService.isRunning) {
initServiceRunning(); initServiceRunning();
} else { } else {
initServiceNotRunning(); updatePlayButtonShowsPlay(true);
} }
} }
@ -312,21 +302,6 @@ public abstract class PlaybackController {
* should be used to update the GUI or start/cancel background threads. * should be used to update the GUI or start/cancel background threads.
*/ */
private void handleStatus() { private void handleStatus() {
final int playResource;
final int pauseResource;
final CharSequence playText = activity.getString(R.string.play_label);
final CharSequence pauseText = activity.getString(R.string.pause_label);
if (PlaybackService.getCurrentMediaType() == MediaType.AUDIO || PlaybackService.isCasting()) {
TypedArray res = activity.obtainStyledAttributes(new int[]{ R.attr.av_play, R.attr.av_pause});
playResource = res.getResourceId(0, R.drawable.ic_av_play_black_48dp);
pauseResource = res.getResourceId(1, R.drawable.ic_av_pause_black_48dp);
res.recycle();
} else {
playResource = R.drawable.ic_av_play_white_80dp;
pauseResource = R.drawable.ic_av_pause_white_80dp;
}
Log.d(TAG, "status: " + status.toString()); Log.d(TAG, "status: " + status.toString());
switch (status) { switch (status) {
case ERROR: case ERROR:
@ -336,37 +311,31 @@ public abstract class PlaybackController {
case PAUSED: case PAUSED:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
onPositionObserverUpdate(); onPositionObserverUpdate();
updatePlayButtonAppearance(playResource, playText); updatePlayButtonShowsPlay(true);
if (!PlaybackService.isCasting() && if (!PlaybackService.isCasting() && PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
setScreenOn(false); setScreenOn(false);
} }
break; break;
case PLAYING: case PLAYING:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
if (!PlaybackService.isCasting() && if (!PlaybackService.isCasting() && PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
onAwaitingVideoSurface(); onAwaitingVideoSurface();
setScreenOn(true); setScreenOn(true);
} }
updatePlayButtonAppearance(pauseResource, pauseText); updatePlayButtonShowsPlay(false);
break; break;
case PREPARING: case PREPARING:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
if (playbackService != null) { if (playbackService != null) {
if (playbackService.isStartWhenPrepared()) { updatePlayButtonShowsPlay(!playbackService.isStartWhenPrepared());
updatePlayButtonAppearance(pauseResource, pauseText);
} else {
updatePlayButtonAppearance(playResource, playText);
}
} }
break; break;
case STOPPED: case STOPPED:
updatePlayButtonAppearance(playResource, playText); updatePlayButtonShowsPlay(true);
break; break;
case PREPARED: case PREPARED:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
updatePlayButtonAppearance(playResource, playText); updatePlayButtonShowsPlay(true);
onPositionObserverUpdate(); onPositionObserverUpdate();
break; break;
case SEEKING: case SEEKING:
@ -374,7 +343,7 @@ public abstract class PlaybackController {
break; break;
case INITIALIZED: case INITIALIZED:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
updatePlayButtonAppearance(playResource, playText); updatePlayButtonShowsPlay(true);
break; break;
} }
} }
@ -386,16 +355,8 @@ public abstract class PlaybackController {
mediaInfoLoaded = true; mediaInfoLoaded = true;
} }
private void updatePlayButtonAppearance(int resource, CharSequence contentDescription) { protected void updatePlayButtonShowsPlay(boolean showPlay) {
ImageButton butPlay = getPlayButton();
if(butPlay != null) {
butPlay.setImageResource(resource);
butPlay.setContentDescription(contentDescription);
}
}
public ImageButton getPlayButton() {
return null;
} }
public abstract void loadMediaInfo(); public abstract void loadMediaInfo();
@ -662,29 +623,4 @@ public abstract class PlaybackController {
public boolean isStreaming() { public boolean isStreaming() {
return playbackService != null && playbackService.isStreaming(); return playbackService != null && playbackService.isStreaming();
} }
private void initServiceNotRunning() {
if (getPlayButton() == null) {
return;
}
Log.v(TAG, "initServiceNotRunning()");
mediaLoader = Maybe.create((MaybeOnSubscribe<Playable>) emitter -> {
Playable media = getMedia();
if (media != null) {
emitter.onSuccess(media);
} else {
emitter.onComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
if (media.getMediaType() == MediaType.AUDIO) {
getPlayButton().setImageResource(
ThemeUtils.getDrawableFromAttr(activity, de.danoeh.antennapod.core.R.attr.av_play));
} else {
getPlayButton().setImageResource(R.drawable.ic_av_play_white_80dp);
}
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
} }