If reprinted, please indicate the source of the article in a prominent place
0 foreword
Netease Cloud Music is a very excellent music player, especially the playing interface, using turntable style, looks particularly classical and elegant. The author out of the idea of learning and challenge, thinking about the implementation principle behind the interface, and wrote a small program.
The author tried his best to imitate the official visual and interactive effects, including details of turntable and stylus switching, background gradient, etc. This article will share some visual implementation methods and design ideas, but there will be mistakes and omissions. If readers find any mistakes or better implementation methods, please leave a comment and hope to make progress together with everyone. The effect is shown below:
1 Source code address
If you need the source code, you can download it from github: github.com/AchillesLzg…
2. Content of this Article
- Project Structure Introduction
- Solve the problem of loading large image OOM
- The easiest way to generate a circle graph
- Use LayerDrawable for image composition
- Achieve background frosted glass effect
- Use LayerDrawable and property animation to achieve the gradient effect when switching backgrounds
- How should you code complex scenarios
- Cooperate with Service and local radio to play music
- conclusion
3 Project structure
The project structure introduction includes the following contents:
- Main interface layout design
- Turntable layout design
- Dynamic layout
- Disc control DiscView external interface and method
- Music state control sequence diagram
3.1 Main interface layout design
The layout of the home screen is divided into several areas from top to bottom, as shown in Figure 3-1.
- The title bar is implemented using the ToolBar, and the font may need to be customized.
- Turntable area Turntable area includes turntable, stylus, chassis, and ViewPager controls for switching, the layout is complex, this case uses custom controls to achieve turntable area.
- The duration display area uses a RelativeLayout as the root layout, and the progress bar is implemented using SeekBar.
-
The playback control area is relatively simple, using the LinearLayout as the root layout.
In addition, the main interface uses a RelativeLayout as the root layout.
3.2 Turntable layout design
The turntable area is implemented by the DiscView control, with a RelativeLayout layout as the root. The child controls include: chassis, stylus, ViewPager, and so on. Among them, the chassis and stylus are realized with ImageView, and then use ViewPager to load ImageView to realize the record switch. See Figure 3-2.
The turntable layout code looks like this:
<?ml version="1.0" encoding="utf-8"? >
<com.achillesl.neteasedisc.widget.DiscView
mlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<! -- -- -- > chassis
<ImageView
android:id="@+id/ivDiscBlackgound"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
/>
<! --ViewPager -->
<android.support.v4.view.ViewPager
android:id="@+id/vpDiscContain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
/>
<! - needle - >
<ImageView
android:id="@+id/ivNeedle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_needle"/>
</com.achillesl.neteasedisc.widget.DiscView>Copy the code
3.3 Dynamic Layout
The layout does not specify width, height, margins, etc., so how do you ensure that the controls are displayed in the correct position? We don’t have the design of netease Cloud Music, so we can’t know the official layout parameters, so what should we do? In fact, there is a stupid method, we can open netease cloud music playing interface and screenshots, and then manually to measure the height, margin and other parameters.
The ratio of control parameters is obtained by dividing the width or height of the screenshots by the width or height of the screenshots. When using, we can get the control width height and margin under the screen size by multiplying the corresponding proportion according to the screen width and height of the mobile phone.
Of course, this dynamic layout will definitely consume more performance, but it is the best of all alternatives.
The proportion of relevant control parameters is uniformly put in the DisplayUtil. Java file by the author, and the code is as follows:
public class DisplayUtil {
/* Handle starting Angle */
public static final float ROTATION_INIT_NEEDLE = -30;
/* Screen width and height */
private static final float BASE_SCREEN_WIDTH = (float) 1080.0;
private static final float BASE_SCREEN_HEIGHT = (float) 1920.0;
/* Width, height, distance, etc. */
public static final float SCALE_NEEDLE_WIDTH = (float) (276.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_MARGIN_LEFT = (float) (500.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_PIVOT_ = (float) (43.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_PIVOT_Y = (float) (43.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_HEIGHT = (float) (413.0 / BASE_SCREEN_HEIGHT);
public static final float SCALE_NEEDLE_MARGIN_TOP = (float) (43.0 / BASE_SCREEN_HEIGHT);
/* Turntable ratio */
public static final float SCALE_DISC_SIZE = (float) (813.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_DISC_MARGIN_TOP = (float) (190 / BASE_SCREEN_HEIGHT);
/* Album image ratio */
public static final float SCALE_MUSIC_PIC_SIZE = (float) (533.0 / BASE_SCREEN_WIDTH);
/* Device screen width */
public static int getScreenWidth(Contet contet) {
return contet.getResources().getDisplayMetrics().widthPiels;
}
/* Device screen height */
public static int getScreenHeight(Contet contet) {
returncontet.getResources().getDisplayMetrics().heightPiels; }}Copy the code
For example, if we need to set the top margin of the turntable chassis, we first get the ratio, then multiply it by the current screen height to get the specific value, and finally set it dynamically through the LayoutParams class.
int marginTop = (int) (DisplayUtil.SCALE_DISC_MARGIN_TOP * mScreenHeight);
RelativeLayout.LayoutParams layoutParams = (LayoutParams) mDiscBlackground.getLayoutParams();
layoutParams.setMargins(0, marginTop, 0.0);Copy the code
3.4 DiscView External Interface and methods
The turntable control DiscView provides an interface IPlayInfo, the code is as follows:
public interface IPlayInfo {
/* Used to update title bar changes */
public void onMusicInfoChanged(String musicName, String musicAuthor);
/* To update the background image */
public void onMusicPicChanged(int musicPicRes);
/* To update the status of music playback */
public void onMusicChanged(MusicChangedStatus musicChangedStatus);
}Copy the code
IPlayInfo contains three methods, which are used to update the title bar (music name, author name), update the background image and control the music playing state (play, pause, last/next, etc.).
Readers may have some questions. Right? 1. The first and second methods of the IPlayInfo interface belong to the same type. Why are they split into two methods? 2. Why is music playback controlled through callback? Click the control button on the main interface, directly control the music playback can not also?
These two problems, the author also after many times to consider.
The first problem is that in netease cloud music interaction, the timing of updating the title bar is different from updating the background image (the title bar is updated when the ViewPager shifts the page by 1/2, while the background image is updated after the ViewPager stops sliding). Merging the two interfaces into one is not conducive to decoupling, and may cause developer misunderstanding and waste of resources.
Second, the author considers that clicking the control button on the main interface does not mean that the state of the music needs to be changed immediately (for example, clicking the play button can only start playing the music after the stylus animation ends). Therefore, controlling the timing of music depends on the state of DiscView. Therefore, we use the onMusicChanged method in the interface to first call back the music control to the Activity at the appropriate time, and then send instructions through the Activity to achieve the effect of switching the music state.
When clicking the play/pause, up/Next buttons on the main screen, call the DiscView exposed method:
@Override
public void onClick(View v) {
if (v == mIvPlayOrPause) {
mDisc.playOrPause();
} else if (v == mIvNet) {
mDisc.net();
} else if (v == mIvLast) { mDisc.last(); }}Copy the code
When the main interface receives the DiscView callback, call the relevant method to control the music playback:
public void onMusicChanged(MusicChangedStatus musicChangedStatus) {
switch (musicChangedStatus) {
case PLAY:{
play();
break;
}
case PAUSE:{
pause();
break;
}
case NET:{
net();
break;
}
case LAST:{
last();
break;
}
case STOP:{
stop();
break; }}}Copy the code
3.5 Timing diagram of music state control
As shown in Figure 3-3, when clicking the button of the Activity, relevant methods of DiscView will be called first, and the state will be called back to the Activity at an appropriate time (such as the end of animation), and the music state will be switched by sending instructions to the Service through broadcasting, and finally the UI state will be updated through broadcasting.
Here is the architecture of the project, followed by part of the visual effects and design ideas.
4 Resolve the OOM loading problem
Avoiding OOM (memory overflow) is a cliche that I’ll write about in a future article, but here’s the conclusion.
There are several solutions to solve the problem: 1. Set largeHeap to true. 2. Select a decoding format based on the image type. 3. Set the image sampling rate according to the width and height of the original image and the target display width and height.
The first method can increase the heap memory space, but this method only delays the occurrence of OOM, so it is not recommended to use this method.
Second, Android decodes images in ARGB_8888 by default, meaning each pixel is 32 bits long. If the image format is JPG, ARGB_8888 is a waste of time because JPG images have no transparent channels. We usually use RGB_565 format to decode JPG images. RGB_565 means that each pixel is 16 bits, so the memory usage of decoding images is only half of that of ARGB_8888.
The third method, which is the most common way to online, is also one of the most common, sampling rate can be understood as: when the sampling rate is 4, said the four points “merge” as a point to read, to reduce the image size but also reduce the image take up space, get out of the picture decoding so occupy less space than the original image.
Take the code that loads music album images:
private Bitmap getMusicPicBitmap(int musicPicSize, int musicPicRes) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(),musicPicRes,options);
int imageWidth = options.outWidth;
int sample = imageWidth / musicPicSize;
int dstSample = 1;
if (sample > dstSample) {
dstSample = sample;
}
options.inJustDecodeBounds = false;
// Set the image sampling rate
options.inSampleSize = dstSample;
// Set the image decoding format
options.inPreferredConfig = Bitmap.Config.RGB_565;
return Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(),
musicPicRes, options), musicPicSize, musicPicSize, true);
}Copy the code
First in the code above, we set up the options. InJustDecodeBounds = true, such BitmapFactory. DecodeResource only can loading pictures of some of the information, and then through the options. OutWidth access to the width of the image, Calculate the sampling rate according to the target image size. Finally, set the decoding format through inPreferredConfig to formally load the image.
The easiest way to generate a circle graph
We can see that there is a base on the back of netease Cloud Music turntable, which is a transparent circular figure, as shown in Figure 5-1. The author looked for all the picture resources of netease cloud music, only found a transparent square map, it seems that we need to generate our own round picture.
There are various ways to generate a circle graph, such as custom control clone onDraw method, add a circle mask to the picture, etc., there are a lot of information on the Internet, no more to say here.
I would like to share with you what I think is the easiest way:
RoundedBitmapDrawable is android. Support. The v4. Graphics. Drawable inside of a class, through the round and round images can be easily realized.
More introduction, please see: www.cnblogs.com/liunanjava/…
Usage: Use RoundedBitmapDrawable to generate a circle graph, first adjust the initial picture to a square, because the netease cloud music picture itself is a square, so the author omitted this step.
The code is very simple, as follows:
private Drawable getDiscBlackgroundDrawable() {
int discSize = (int) (mScreenWidth * DisplayUtil.SCALE_DISC_SIZE);
Bitmap bitmapDisc = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R
.drawable.ic_disc_blackground), discSize, discSize, false);
RoundedBitmapDrawable roundDiscDrawable = RoundedBitmapDrawableFactory.create
(getResources(), bitmapDisc);
return roundDiscDrawable;
}Copy the code
We turn the image resource file into a Bitmap object, initialize the RoundedBitmapDrawable object, and return it directly.
6. Use LayerDrawable for image composition
This step is mainly used to combine the turntable and album pictures, as shown in Figure 6-1. When the author checked the layout of netease Cloud Music turntable with UI Automation tool, he found that there were two ImageViews in it. It is estimated that one is used to display turntable and the other is used to display album pictures (not sure). But if you can combine a turntable and an album image into a single image, an ImageView will suffice.
LayerDrawable can also contain an array of Drawable objects, so the system draws these Drawable objects in their array order. The largest Drawable object is drawn at the top. LayerDrawable is somewhat similar to the concept of layers in PhotoShop. 1. Generate a circular album chart. 2. Use LayerDrawable to load the turntable and album images. 3. Adjust the margins of the album chart so that it appears right in the middle of the turntable. 4. Display it in ImageView.
Code:
private Drawable getDiscDrawable(int musicPicRes) {
int discSize = (int) (mScreenWidth * DisplayUtil.SCALE_DISC_SIZE);
int musicPicSize = (int) (mScreenWidth * DisplayUtil.SCALE_MUSIC_PIC_SIZE);
Bitmap bitmapDisc = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R
.drawable.ic_disc), discSize, discSize, false);
Bitmap bitmapMusicPic = getMusicPicBitmap(musicPicSize,musicPicRes);
BitmapDrawable discDrawable = new BitmapDrawable(bitmapDisc);
RoundedBitmapDrawable roundMusicDrawable = RoundedBitmapDrawableFactory.create
(getResources(), bitmapMusicPic);
/ / anti-aliasing
discDrawable.setAntiAlias(true);
roundMusicDrawable.setAntiAlias(true);
Drawable[] drawables = new Drawable[2];
drawables[0] = roundMusicDrawable;
drawables[1] = discDrawable;
LayerDrawable layerDrawable = new LayerDrawable(drawables);
int musicPicMargin = (int) ((DisplayUtil.SCALE_DISC_SIZE - DisplayUtil
.SCALE_MUSIC_PIC_SIZE) * mScreenWidth / 2);
// Adjust the four margins of the album image
layerDrawable.setLayerInset(0, musicPicMargin, musicPicMargin, musicPicMargin,
musicPicMargin);
return layerDrawable;
}Copy the code
RoundedBitmapDrawable generates the round album image, which is stored in the Drawable[] array and used to initialize LayerDrawable. Finally, we use the setLayerInset method to adjust the four margins of the album image so that it appears in the middle of the turntable.
7 Achieve background frosted glass effect
Obviously, the background image of netease Cloud Music is generated by the album image and frosted glass effect, as shown in Figure 7-1.
The ground-glass effect can be achieved by StackBlur blur algorithm, which is widely used and can get very good ground-glass effect. We use its Java implementation here.
Usage:
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap)Copy the code
The first parameter is the Bitmap to be blurred, the second parameter is the blur radius (generally set to 8), and the third parameter indicates whether to reuse.
Before we blur the image, let’s consider a few questions about the playback interface:
1. Netease Cloud music album images are square. If the album images are loaded in full screen, the images will be distorted. 2. Directly blur the large image, it is easy to appear OOM, and the performance will be damaged. 3. Some album pictures may be too bright (such as pure white), which will affect the visual effect of buttons.
The first point is easier to solve. We can cut a picture corresponding to the screen width and height ratio in the middle of the original picture. Secondly, before image blurring, we usually reduce the large image first and then blur it with the algorithm, so that OOM is not easy to appear and performance is not affected. Third, we can add gray mask layer on the basis of the blurred picture, so that even if the pure white background, it will not cause visual impact on the control of the main interface.
The code looks like this:
private Drawable getForegroundDrawable(int musicPicRes) {
/* Get the aspect ratio of the screen in order to cut parts of the picture in proportion */
final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(MainActivity.this)
*1.0 / DisplayUtil.getScreenHeight(this) * 1.0);
Bitmap bitmap = getForegroundBitmap(musicPicRes);
int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight());
int cropBitmapWidth = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0);
/* Cut part of the picture */
Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidth, 0, cropBitmapWidth,
bitmap.getHeight());
/* Zoom out the image */
Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap
.getHeight() / 50.false);
/* Obfuscate */
final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 8.true);
final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap);
/* Add gray mask layer to avoid image too bright affect other controls */
foregroundDrawable.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
return foregroundDrawable;
}Copy the code
This part of the code might block the UI thread, so I put it in a separate thread to execute.
private void try2UpdateMusicPicBackground(final int musicPicRes) {
if (mRootLayout.isNeed2UpdateBackground(musicPicRes)) {
new Thread(new Runnable() {
@Override
public void run(a) {
final Drawable foregroundDrawable = getForegroundDrawable(musicPicRes);
runOnUiThread(new Runnable() {
@Override
public void run(a) { mRootLayout.setForeground(foregroundDrawable); mRootLayout.beginAnimation(); }}); } }).start(); }}Copy the code
8. Use LayerDrawable and property animation to achieve the gradient effect during background switching
A careful observation of netease Cloud Music shows that the background image changes with the song change, as shown in Figure 8-1. The author has also been thinking about this effect for a long time, how is this effect realized? Then I came up with a very simple way to do this, using the LayerDrawable plus property animation introduced earlier.
1. Set up two layers for LayerDrawable, the first layer is the previous background, and the second layer is the background to display. 2. First, set the opacity of the background to 0, so it is completely transparent. At this time, only the previous background image will be displayed. 3. Dynamically adjust the opacity of the second layer from 0 to 100 by animating the properties and constantly update the background of the control.
With an idea, it’s easy to write code. We show by RelativeLayout background, considering the need to encapsulate code, since we define a class BackgourndAnimationRelativeLayout RelativeLayout, inheritance, and implement these ideas in this class, the key code is as follows:
/** * Define a custom control that inherits from RelativeLayout **/
public class BackgourndAnimationRelativeLayout etends RelativeLayout/ / initializationLayerDrawableobjectprivate void initLayerDrawable(a){
Drawable backgroundDrawable = getContet().getDrawable(R.drawable.ic_blackground);
Drawable[] drawables = new Drawable[2];
/* Set foreground and background to the same color */
drawables[INDE_BACKGROUND] = backgroundDrawable;
drawables[INDE_FOREGROUND] = backgroundDrawable;
layerDrawable = new LayerDrawable(drawables);
}
private void initObjectAnimator(a) {
objectAnimator = ObjectAnimator.ofFloat(this."number".0f.1.0 f);
objectAnimator.setDuration(DURATION_ANIMATION);
objectAnimator.setInterpolator(new AccelerateInterpolator());
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int foregroundAlpha = (int) ((float) animation.getAnimatedValue() * 255);
/* Dynamically set the transparency of Drawable so that the foreground image gradually displays */
layerDrawable.getDrawable(INDE_FOREGROUND).setAlpha(foregroundAlpha);
BackgourndAnimationRelativeLayout.this.setBackground(layerDrawable); }}); objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}@Override
public void onAnimationEnd(Animator animation) {
/* After the animation, remember to update the original background image */
layerDrawable.setDrawable(INDE_BACKGROUND, layerDrawable.getDrawable(
INDE_FOREGROUND));
}
@Override
public void onAnimationCancel(Animator animation) {}@Override
public void onAnimationRepeat(Animator animation) {}}); }// Provide an external method for playing gradient animations
public void beginAnimation(a) {
objectAnimator.start();
}Copy the code
How to code complex scenarios
This is a very interesting question, and we often encounter a lot of complicated scenarios when writing code. Take netease Cloud Music as an example. We can watch the stylus animation in detail, and you will see how excellent netease Cloud Music is.
Stylus animation details:
- When the initial state is pause/stop, click the play button and the stylus moves to the bottom. See Figure 9-1.
- When the initial state is play, click the pause button and the stylus moves to the top. See Figure 9-2.
- In the initial state of playing, the finger holds the turntable and slightly offsets. When the stylus does not move to the top, release the finger immediately. At this time, the stylus returns to the top and then immediately returns to the turntable position. See Figure 9-3.
- When the initial state is pause/stop, click Play, and the stylus will move down. When the stylus is not moved to the bottom, the finger will press the turntable and offset immediately, and the stylus will move to the top immediately. See Figure 9-4.
- When the initial state is play/pause/stop, slide the record left and right to switch music. When the stylus animation is not finished, click the up/next button immediately to switch music. At this time, the stylus state should not be confused. See Figure 9-5.
Effects 1 and 2 are easy to implement by simply listening for the ViewPager state to work with the property animation, but effects 3, 4, and 5 (essentially 5 is the same as 3 and 4) are more difficult to implement. We can simply add a Boolean to mark the violence solution, although the tag can be used, but should not be used arbitrarily, otherwise the code will become very readable sentence.
The author here talks about a bit of their own experience, encounter this kind of problem, the principle is as follows:
Analyze calmly, simplify and find the triggered states of different scenarios.
When we analyzed these scenarios, there were two factors: whether the record was off track and where the stylus was when the action was triggered. Thus, we can divide states into two categories, six states:
1. Record state (two kinds) : including offset middle and offset end. See Figure 9-6. 2. The state of the stylus (four kinds) : at the far end (away from the record), at the near end (close to the record), moving from the far end to the near end, moving from the near end to the far end. As shown in Figure 9-7.
The state of the record (ViewPager) can be obtained via PageChangeListener. The author uses enumeration to represent the stylus state, and updates the stylus state in time at the beginning and end of animation.
Enumeration of stylus states:
private enum NeedleAnimatorStatus {/* Move: move from the turntable to a distance */ TO_FAR_END, /* Moving: From a distance to the turntable */ TO_NEAR_END, /* At rest: Away from the turntable */ IN_FAR_END, /* At rest: Close to the turntable */ IN_NEAR_END }Copy the code
When the animation starts, update the stylus state:
@Override
public void onAnimationStart(Animator animator) {
NeedleAnimatorStatus = NeedleAnimatorStatus = NeedleAnimatorStatus = NeedleAnimatorStatus
if (needleAnimatorStatus == NeedleAnimatorStatus.IN_FAR_END) {
needleAnimatorStatus = NeedleAnimatorStatus.TO_NEAR_END;
} else if (needleAnimatorStatus == NeedleAnimatorStatus.IN_NEAR_END) {
needleAnimatorStatus= NeedleAnimatorStatus.TO_FAR_END; }}Copy the code
When the animation ends, update the stylus state:
@Override
public void onAnimationEnd(Animator animator) {
if (needleAnimatorStatus == NeedleAnimatorStatus.TO_NEAR_END) {
needleAnimatorStatus = NeedleAnimatorStatus.IN_NEAR_END;
int inde = mVpContain.getCurrentItem();
playDiscAnimator(inde);
} else if (needleAnimatorStatus == NeedleAnimatorStatus.TO_FAR_END) { needleAnimatorStatus = NeedleAnimatorStatus.IN_FAR_END; }}Copy the code
Each state is clearly defined so that the code is fairly clear to write.
For example, when animation needs to be played, there are two states: 1. When the stylus animation is paused, the stylus is at the far end. 2. When the stylus animation is playing, the stylus moves from the near end to the far end (the problem in scene 3 above)
/* Play animation */
private void playAnimator(a) {
/* When the stylus is on the far side, play the animation directly */
if (needleAnimatorStatus == NeedleAnimatorStatus.IN_FAR_END) {
mNeedleAnimator.start();
}
/* When the stylus is moving to the far end, set the mark and wait for the animation to end before playing the animation */
else if (needleAnimatorStatus == NeedleAnimatorStatus.TO_FAR_END) {
mIsNeed2StartPlayAnimator = true; }}Copy the code
For example, when you need to pause an animation, there are also two states:
1. The stylus animation is paused when the stylus is near. 2. When the stylus moves to the near end during the animation playback (solve the 4th detail of the above scene)
/* Pause animation */
private void pauseAnimator() {
/* Pause animation while playing */
if (needleAnimatorStatus == NeedleAnimatorStatus.IN_NEAR_END) {
int index = mVpContain.getCurrentItem();
pauseDiscAnimatior(index);
}
/* Pause animation when the stylus moves to the turntable */
else if (needleAnimatorStatus == NeedleAnimatorStatus.TO_NEAR_END) {
mNeedleAnimator.reverse(a);/** * If the reverse method is executed before the animation ends, the listener's onStart method will not be executed, so you need to manually set ** /
needleAnimatorStatus = NeedleAnimatorStatus.TO_FAR_END;
}
/** * The animation may be executed multiple times, and the pause command ** / is executed only when the music is in the stop/pause state
if (musicStatus == MusicStatus.STOP) {
notifyMusicStatusChanged(MusicChangedStatus.STOP);
} else if(musicStatus == MusicStatus.PAUSE) { notifyMusicStatusChanged(MusicChangedStatus.PAUSE); }}Copy the code
10 Cooperate with Service and local radio to play music
10.1 Why Service is Selected?
During your interview, you may be asked: In what situations would using a Service have an advantage over an Activity? Many people answered that it was run in scenarios that did not require a display interface, but did not give a specific scenario.
To play audio, you need to use the MediaPlayer class. In my opinion, the control of MediaPlayer is best handled by a Service: a Service doesn’t need an interface, but more importantly, it doesn’t need to handle the logic of screen rotation. If you put MediaPlayer in your Activity, you will probably need to save the state of ediaPlayer as the screen rotates, because the Activity will be rebuilt.
My example does not involve screen rotation, but for the sake of demonstrating the flow, use a Service to handle music playback.
10.2 Why Do I Choose Local Broadcast?
Android broadcast global, once issued, will spread in the system, if other apps know your Action information and permissions, may cause information leakage, and even can send broadcast to control your APP. LocalBroadcast, which is transmitted only within the application, does not have this concern.
Local broadcasting has the following characteristics:
- The broadcast spreads within the application without worrying about information leakage.
- Other apps can’t send broadcasts to control your APP, making it more secure.
- The transmitted broadcast does not need to be transferred by the system, which is more efficient.
The usage of local broadcast is simple as follows:
Registration:
LocalBroadcastManager.getInstance(this).registerReceiver(receiver.intentFilter);Copy the code
Cancel registration:
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);Copy the code
Broadcast to send:
LocalBroadcastManager.getInstance(contet).sendBroadcast(new Intent(ACTION));Copy the code
10.3 Handling the Progress Bar
In this case, we use Handler+SeekBar to dynamically update the progress of the song, and send an event every 1 second through Handler’s sendEmptyMessageDelayed method. When an event is received, update the SeekBar’s progress and then call the sendEmptyMessageDelayed method again so that the progress can be dynamically updated.
The key codes are as follows:
private Handler mMusicHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mSeekBar.setProgress(mSeekBar.getProgress() + 1000); mTvMusicDuration.setTet(duration2Time(mSeekBar.getProgress())); startUpdateSeekBarProgress(); }};private void startUpdateSeekBarProgress(a) {
/* Avoid repeating messages */
stopUpdateSeekBarProgree();
mMusicHandler.sendEmptyMessageDelayed(0.1000);
}Copy the code
When SeekBar slides, use the removeMessages method to remove delayed messages from the Handler and suspend Handler updates to SeekBar. When the SeekBar slide is over, the music is updated based on the current progress value.
The key codes are as follows:
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mTvMusicDuration.setTet(duration2Time(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
stopUpdateSeekBarProgree();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) { seekTo(seekBar.getProgress()); startUpdateSeekBarProgress(); }});Copy the code
11 the conclusion
More optimizations can be made in this case, such as unlimited ViewPager switching, displaying lyrics, and so on. But the margin is limited, the content of this chapter on this end, I hope to play a role in the implementation of more details can refer to the project source code.