The Android system provides the MediaPlayer control, which enables you to play audio.
From learning Android, when I was watching the tutorial, I thought that I would like to make a music player myself, because a complete music player has many functions, it involves many aspects of knowledge, which can help us better learn and learn about Android.
Let's implement our music player step by step.
So what is the idea? I was thinking like this. First, I had to do some functions to see and control the music. So the current functions are as follows:
1) take out the local music file and display it on a list.
1.1) Use ContentResolver to obtain local data. For details about how to obtain local music files or image files, see: Use ContentResolver in Android to obtain local music and photos.
2) There should be a row of buttons to enable playback, the first, the last, and the last, exit, and select the mode (sequential playback, loop playback, single loop, random playback, etc.). Put them at the bottom.
3) There must be a progress bar. With the playing of the music, you can click it one by one,
4) Since there is a progress bar, there must also be two display time controls, one to show how long the music is, and the other to show where it is played. This and the progress bar must be placed on the button.
5) place a TextView that shows the currently played song on the top.
The following interface is available at the beginning:
Because I won't be an artist, I used buttons to play and stop other control functions at the beginning. We are learning how to beautify things. (It does not look ugly, right ?)
But later I thought that since I was learning and could not do anything, I would like to implement a set of custom buttons, so I had the following interface.
The ugly buttons are missing. Haha, I drew them!
Now, let's talk about how the Custom button is implemented in this document.
I have written an article about custom View before: in fact, the principle is the same. Let's look at the Code directly:
1) first create an attrs. xml file in res/values/, in which the custom attributes are:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="CustomAudioIcon"> <attr name="type"> <enum name="start" value="0" /> <enum name="forward" value="1" /> <enum name="backward" value="2" /> <enum name="exit" value="3" /> <enum name="mode" value="4" /> </attr> <attr name="color" format="color"/> </declare-styleable></resources>
We have defined two attributes, where type is an enumeration type, including start, forward, backward, exit, and mode.
Although we have five types of buttons to display, we only need to implement a custom class, and then draw different images based on the input values of different types.
The code for the Custom button is as follows:
public class CustomAudioIcon extends View implements OnTouchListener {//private static final String TAG = "com.example.nature.CustoAudioIcon";private static final int defaultType = -1;private static final int start = 0;private static final int forward = 2;private static final int backward = 3;private static final int exit = 4;private static final int mode = 5;private int type;private int color;private Paint upPaint;private Paint pressPaint;private Paint boxPaint;private Paint paint;private int width,height;private boolean pressed = false;//Only for StartStopButtonprivate boolean flagStart = true;//Only for ModeButtonpublic static final int MODE_ONE_LOOP = 0;public static final int MODE_ALL_LOOP = 1;public static final int MODE_RANDOM = 2;public static final int MODE_SEQUENCE = 3; private int currentMode = 3;public CustomAudioIcon(Context context, AttributeSet attrs) {super(context, attrs);TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CustomAudioIcon);type = typedArray.getInt(R.styleable.CustomAudioIcon_type, defaultType);color = typedArray.getColor(R.styleable.CustomAudioIcon_color,Color.BLACK);typedArray.recycle();init();setClickable(true);//In order to make this view can accept the OnClickListenersetOnTouchListener(this);}private void init() {boxPaint = new Paint();boxPaint.setColor(color);boxPaint.setAntiAlias(true);boxPaint.setStrokeWidth(1);upPaint = new Paint();upPaint.setColor(Color.BLACK);upPaint.setAntiAlias(true);upPaint.setStrokeWidth(1);pressPaint = new Paint();pressPaint.setColor(Color.GREEN);pressPaint.setAntiAlias(true);pressPaint.setStrokeWidth(1);}public void onDraw(Canvas canvas) {paint = pressed ? pressPaint : upPaint;width = getMeasuredWidth();height = getMeasuredHeight();if(pressed){canvas.drawColor(Color.parseColor("#447744"));}switch (type) {case start:if(flagStart){drawStart(canvas, pressed);}else{drawStop(canvas, pressed);}break;case forward:drawForward(canvas, pressed);break;case backward:drawBackward(canvas, pressed);break;case exit:drawExit(canvas, pressed);break;case mode:drawMode(canvas, pressed);break;}boxPaint.setStyle(Style.STROKE);Rect rect = canvas.getClipBounds();rect.bottom--;rect.right--;canvas.drawRect(rect, boxPaint);}// public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){// setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),// MeasureSpec.getSize(heightMeasureSpec));// }private void drawStart(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.21 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.21 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.5 * scaleWidth) };canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);}private void drawStop(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.4 * scaleWidth), (float) (0.1 * scaleWidth), (float) (0.4 * scaleWidth), (float) (0.9 * scaleWidth), (float) (0.6 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.6 * scaleWidth), (float) (0.9 * scaleWidth)};canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[4], verticles[5], verticles[6], verticles[7],paint);}private void drawForward(Canvas canvas, boolean pressed) {// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);// calculte the vertexes.float[] verticles = { (float) (0.21 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.21 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.5 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.9 * scaleWidth) };canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));// draw the trianglecanvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);// draw the vertical linecanvas.drawLine(verticles[6], verticles[7], verticles[8], verticles[9],paint);canvas.restore();}private void drawBackward(Canvas canvas, boolean pressed) {// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);// calculte the vertexes.float[] verticles = { (float) (0.79 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.79 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.5 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.9 * scaleWidth) };canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));// draw the trianglecanvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);// draw the vertical linecanvas.drawLine(verticles[6], verticles[7], verticles[8], verticles[9],paint);canvas.restore();}private void drawExit(Canvas canvas, boolean pressed) {paint.setStyle(Style.STROKE);// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));canvas.drawCircle((float)(0.5 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.4 * scaleWidth), paint);canvas.restore();}private void drawMode(Canvas canvas, boolean pressed) {paint.setStyle(Style.STROKE);// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));switch(currentMode){case MODE_ONE_LOOP:canvas.drawCircle((float)(0.5 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;case MODE_ALL_LOOP:canvas.drawCircle((float)(0.4 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.6 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;case MODE_RANDOM:canvas.drawCircle((float)(0.3 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.5 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.7 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;case MODE_SEQUENCE:canvas.drawCircle((float)(0.2 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.4 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.6 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.8 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;}canvas.restore();}@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:pressed = true;invalidate();break;case MotionEvent.ACTION_UP:pressed = false;invalidate();if(type == start){flagStart = !flagStart;}if(type == mode){currentMode = (currentMode + 1) % 4;}break;}return false;}/** * If showing the start triangle, returns true, otherwise returns false * @return */public boolean isStartStatus() {return flagStart;}/** * Change the flag outside * @param flagStart */public void setFlagStart(boolean flagStart) {this.flagStart = flagStart;invalidate();}}
In the class, we first get our type value through typedArray, and then draw different content based on the type value.
Because I have defined the length and width in the layout file, I don't need to rewrite the onMeasure function here. We just need to care about how to draw a graph in Ondraw.
We can see that in the onDraw () method, we will draw different buttons based on different types, such as the start button, which has two States. When we click start, it will change to the stop (or pause, where I didn't implement pause, next implementation) status.
case start:if(flagStart){drawStart(canvas, pressed);}else{drawStop(canvas, pressed);}break;
In drawStart, we draw an equilateral triangle to the right,
private void drawStart(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.21 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.21 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.5 * scaleWidth) };canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);}
When we click start, it will become stop, and it will be painted stop, which is a vertical equal sign (|,
private void drawStop(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.4 * scaleWidth), (float) (0.1 * scaleWidth), (float) (0.4 * scaleWidth), (float) (0.9 * scaleWidth), (float) (0.6 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.6 * scaleWidth), (float) (0.9 * scaleWidth)};canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[4], verticles[5], verticles[6], verticles[7],paint);}
P.s. ^ _ ^. Interesting, Hahahaha.
Well, how does one achieve the effect of Button clicking? It is to implement OnTouchListener,
@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:pressed = true;invalidate();break;case MotionEvent.ACTION_UP:pressed = false;invalidate();if(type == start){flagStart = !flagStart;}if(type == mode){currentMode = (currentMode + 1) % 4;}break;}return false;}
In fact, the clicking of any control is composed of the two actions. The action goes Down and Up. Here, the touch event is obtained, and the pressed value is set based on the status, in the onDraw function, different paints are obtained based on the value of pressed,
public void onDraw(Canvas canvas) {paint = pressed ? pressPaint : upPaint;width = getMeasuredWidth();height = getMeasuredHeight();if(pressed){canvas.drawColor(Color.parseColor("#447744"));}
Then call the Invaldiate () function to refresh the page and click the page.
In general, if we call the OnTouch function, we all return a true value in the OnTouch function, indicating that the touch event has been consumed and we do not need to continue.
But here, we cannot do this because we need to set OnClickListener for these custom views in the Activity to control the pause of playing our music, therefore, false must be returned here.
However, if false is returned, the Up event will not be triggered after the Down event is triggered. This is because the default View cannot be clicked, so we only need to set this View as clickable during initialization, as shown below:
public CustomAudioIcon(Context context, AttributeSet attrs) {super(context, attrs);TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CustomAudioIcon);type = typedArray.getInt(R.styleable.CustomAudioIcon_type, defaultType);color = typedArray.getColor(R.styleable.CustomAudioIcon_color,Color.BLACK);typedArray.recycle();init();setClickable(true);//In order to make this view can accept the OnClickListenersetOnTouchListener(this);}
We recommend that you take a look at the two articles by Guo Daxia about the Touch and Click events:
The Android event distribution mechanism is completely parsed to give you a thorough understanding of the source code (I)
The Android event distribution mechanism is completely parsed to give you a thorough understanding of the source code (below)
Here, we can use the control. Next we will use it as a button in the layout file.
<com.example.nature.CustomAudioIcon android:id="@+id/btnMode" android:layout_width="64dip" android:layout_height="64dip" android:layout_alignParentBottom="true" custom:type="mode" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnPrevious" android:layout_width="64dip" android:layout_height="64dip" android:layout_alignBaseline="@+id/btnMode" android:layout_alignParentBottom="true" android:layout_toRightOf="@+id/btnMode" custom:type="backward" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnStartStop" android:layout_width="64dip" android:layout_height="64dip" android:layout_alignBaseline="@+id/btnMode" android:layout_alignParentBottom="true" android:layout_toRightOf="@+id/btnPrevious" custom:type="start" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnNext" android:layout_width="64dip" android:layout_height="64dip" android:layout_alignBaseline="@+id/btnMode" android:layout_alignParentBottom="true" android:layout_toRightOf="@+id/btnStartStop" custom:type="forward" custom:color="#66DD22" /> <com.example.nature.CustomAudioIcon android:id="@+id/btnExit" android:layout_width="64dip" android:layout_height="64dip" android:layout_alignBaseline="@+id/btnMode" android:layout_alignParentBottom="true" android:layout_toRightOf="@+id/btnNext" custom:type="exit" custom:color="#66DD22" />
We can see that the custom: type value we set is different.
Then we can see a large row of custom buttons. you can draw your own designs!
It's not enough!
I found a side effect ....
Because there is a progress bar on our interface, the interface has been refreshing, so our custom buttons have been refreshing...
Okay, go to bed. Please wait for the source code. I will change the source code and then release it later.