瀏覽代碼

添加节拍器部分UI和交互

Pq 2 年之前
父節點
當前提交
b26a8246df

+ 2 - 0
BaseLibrary/src/main/res/values/colors.xml

@@ -142,6 +142,8 @@
     <color name="color_fff4ec">#fff4ec</color>
     <color name="color_fffdfb">#FFFDFB</color>
     <color name="color_f37c17">#F37C17</color>
+    <color name="color_10a0a0a0">#10A0A0A0</color>
+    <color name="color_102dc7aa">#102dc7aa</color>
 
     <color name="color_25292e">#25292E</color>
     <color name="color_F8F8F8">#F8F8F8</color>

+ 9 - 1
metronome/src/main/java/com/cooleshow/metronome/MetronomeActivity.java

@@ -2,6 +2,9 @@ package com.cooleshow.metronome;
 
 import androidx.viewbinding.ViewBinding;
 
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
 import android.os.Bundle;
 
 import com.cooleshow.base.ui.activity.BaseActivity;
@@ -9,6 +12,11 @@ import com.cooleshow.metronome.databinding.ActivityMetronomeLayoutBinding;
 
 public class MetronomeActivity extends BaseActivity<ActivityMetronomeLayoutBinding> {
 
+    public static void start(Context context) {
+        Intent intent = new Intent(context, MetronomeActivity.class);
+        context.startActivity(intent);
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -16,7 +24,7 @@ public class MetronomeActivity extends BaseActivity<ActivityMetronomeLayoutBindi
 
     @Override
     protected void initView() {
-
+        viewBinding.toolbarInclude.toolbar.setBackgroundColor(Color.TRANSPARENT);
     }
 
     @Override

+ 639 - 0
metronome/src/main/java/com/cooleshow/metronome/widget/CircularSeekBar.java

@@ -0,0 +1,639 @@
+package com.cooleshow.metronome.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.cooleshow.metronome.R;
+
+
+/**
+ * Author by pq, Date on 2022/9/15.
+ */
+public class CircularSeekBar extends View {
+
+    /** The context */
+    private Context mContext;
+
+    /** The listener to listen for changes */
+    private OnSeekChangeListener mListener;
+
+    /** The color of the progress ring */
+    private Paint circleColor;
+
+    /** the color of the inside circle. Acts as background color */
+    private Paint innerColor;
+
+    /** The progress circle ring background */
+    private Paint circleRing;
+
+    /** The markPaint*/
+    private Paint markPaint;
+
+    /** The angle of progress */
+    private int angle = 0;
+
+    /** The start angle (12 O'clock */
+    private int startAngle = 270;
+
+    /** The width of the progress ring */
+    private int barWidth = 5;
+
+    /** The width of the view */
+    private int width;
+
+    /** The height of the view */
+    private int height;
+
+    /** The maximum progress amount */
+    private int maxProgress = 100;
+
+    /** The current progress */
+    private int progress;
+
+    /** The progress percent */
+    private int progressPercent;
+
+    /** The radius of the inner circle */
+    private float innerRadius;
+
+    /** The radius of the outer circle */
+    private float outerRadius;
+
+    /** The circle's center X coordinate */
+    private float cx;
+
+    /** The circle's center Y coordinate */
+    private float cy;
+
+    /** The left bound for the circle RectF */
+    private float left;
+
+    /** The right bound for the circle RectF */
+    private float right;
+
+    /** The top bound for the circle RectF */
+    private float top;
+
+    /** The bottom bound for the circle RectF */
+    private float bottom;
+
+    /** The X coordinate for the top left corner of the marking drawable */
+    private float dx;
+
+    /** The Y coordinate for the top left corner of the marking drawable */
+    private float dy;
+
+    /** The X coordinate for 12 O'Clock */
+    private float startPointX;
+
+    /** The Y coordinate for 12 O'Clock */
+    private float startPointY;
+
+    /**
+     * The X coordinate for the current position of the marker, pre adjustment
+     * to center
+     */
+    private float markPointX;
+
+    /**
+     * The Y coordinate for the current position of the marker, pre adjustment
+     * to center
+     */
+    private float markPointY;
+
+    /**
+     * The adjustment factor. This adds an adjustment of the specified size to
+     * both sides of the progress bar, allowing touch events to be processed
+     * more user friendly (yes, I know that's not a word)
+     */
+    private float adjustmentFactor = 500;
+
+    /** The progress mark when the view isn't being progress modified */
+    private Bitmap progressMark;
+
+    /** The progress mark when the view is being progress modified. */
+    private Bitmap progressMarkPressed;
+
+    /** The flag to see if view is pressed */
+    private boolean IS_PRESSED = false;
+
+    /**
+     * The flag to see if the setProgress() method was called from our own
+     * View's setAngle() method, or externally by a user.
+     */
+    private boolean CALLED_FROM_ANGLE = false;
+
+    private boolean SHOW_SEEKBAR = true;
+
+    /** The rectangle containing our circles and arcs. */
+    private RectF rect = new RectF();
+
+    {
+        mListener = new OnSeekChangeListener() {
+
+            @Override
+            public void onProgressChange(CircularSeekBar view, int newProgress) {
+                Log.i("pq","newProgress:"+newProgress);
+            }
+        };
+
+        circleColor = new Paint();
+        innerColor = new Paint();
+        circleRing = new Paint();
+        markPaint = new Paint();
+
+        circleColor.setColor(Color.TRANSPARENT); // Set default
+        // progress
+        // color to holo
+        // blue.
+        innerColor.setColor(Color.TRANSPARENT); // Set default background color to
+        // black
+        circleRing.setColor(Color.TRANSPARENT);// Set default background color to Gray
+
+        circleColor.setAntiAlias(true);
+        innerColor.setAntiAlias(true);
+        circleRing.setAntiAlias(true);
+        markPaint.setAntiAlias(true);
+        markPaint.setDither(true);
+
+        circleColor.setStrokeWidth(5);
+        innerColor.setStrokeWidth(5);
+        circleRing.setStrokeWidth(5);
+
+        circleColor.setStyle(Paint.Style.FILL);
+    }
+
+    /**
+     * Instantiates a new circular seek bar.
+     *
+     * @param context
+     *            the context
+     * @param attrs
+     *            the attrs
+     * @param defStyle
+     *            the def style
+     */
+    public CircularSeekBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mContext = context;
+        initDrawable();
+    }
+
+    /**
+     * Instantiates a new circular seek bar.
+     *
+     * @param context
+     *            the context
+     * @param attrs
+     *            the attrs
+     */
+    public CircularSeekBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        initDrawable();
+    }
+
+    /**
+     * Instantiates a new circular seek bar.
+     *
+     * @param context
+     *            the context
+     */
+    public CircularSeekBar(Context context) {
+        super(context);
+        mContext = context;
+        initDrawable();
+    }
+
+    /**
+     * Inits the drawable.
+     */
+    public void initDrawable() {
+        progressMark = drawableToBitmap(mContext.getResources().getDrawable(R.drawable.shape_speed_mark));
+        progressMarkPressed = drawableToBitmap(mContext.getResources().getDrawable(R.drawable.shape_speed_mark));
+
+//        progressMark = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.shape_speed_mark);
+//        progressMarkPressed = BitmapFactory.decodeResource(mContext.getResources(),
+//                R.drawable.shape_speed_mark);
+    }
+
+    public static Bitmap drawableToBitmap(Drawable drawable) {
+        // 取 drawable 的长宽
+        int w = drawable.getIntrinsicWidth();
+        int h = drawable.getIntrinsicHeight();
+
+        // 取 drawable 的颜色格式
+        Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
+                : Bitmap.Config.RGB_565;
+        // 建立对应 bitmap
+        Bitmap bitmap = Bitmap.createBitmap(w, h, config);
+        // 建立对应 bitmap 的画布
+        Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, w, h);
+        // 把 drawable 内容画到画布中
+        drawable.draw(canvas);
+        return bitmap;
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see android.view.View#onMeasure(int, int)
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        width = getWidth(); // Get View Width
+        height = getHeight();// Get View Height
+
+        width = MeasureSpec.getSize(widthMeasureSpec);
+        height = MeasureSpec.getSize(heightMeasureSpec);
+
+        int size = (width > height) ? height : width; // Choose the smaller
+        // between width and
+        // height to make a
+        // square
+
+        cx = width / 2; // Center X for circle
+        cy = height / 2; // Center Y for circle
+        outerRadius = size / 2; // Radius of the outer circle
+
+        innerRadius = outerRadius - barWidth; // Radius of the inner circle
+
+        left = cx - outerRadius; // Calculate left bound of our rect
+        right = cx + outerRadius;// Calculate right bound of our rect
+        top = cy - outerRadius;// Calculate top bound of our rect
+        bottom = cy + outerRadius;// Calculate bottom bound of our rect
+
+        startPointX = cx; // 12 O'clock X coordinate
+        startPointY = cy - outerRadius;// 12 O'clock Y coordinate
+        markPointX = startPointX;// Initial locatino of the marker X coordinate
+        markPointY = startPointY;// Initial locatino of the marker Y coordinate
+
+        rect.set(left, top, right, bottom); // assign size to rect
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see android.view.View#onDraw(android.graphics.Canvas)
+     */
+    @Override
+    protected void onDraw(Canvas canvas) {
+
+        //可以不绘制
+//        canvas.drawCircle(cx, cy, outerRadius, circleRing);
+//        canvas.drawArc(rect, startAngle, angle, true, circleColor);
+//        canvas.drawCircle(cx, cy, innerRadius, innerColor);
+
+        if(SHOW_SEEKBAR){
+            dx = getXFromAngle();
+            dy = getYFromAngle();
+            drawMarkerAtProgress(canvas);
+        }
+        super.onDraw(canvas);
+    }
+
+    /**
+     * Draw marker at the current progress point onto the given canvas.
+     *
+     * @param canvas
+     *            the canvas
+     */
+    public void drawMarkerAtProgress(Canvas canvas) {
+        if (IS_PRESSED) {
+            Matrix matrix =new Matrix();
+            matrix.postRotate(getAngle());
+            matrix.postTranslate(dx,dy);
+//            canvas.drawBitmap(progressMarkPressed, dx, dy, null);
+            canvas.drawBitmap(progressMarkPressed,matrix,markPaint);
+        } else {
+            Matrix matrix =new Matrix();
+            matrix.postRotate(getAngle());
+            matrix.postTranslate(dx,dy);
+//            canvas.drawBitmap(progressMark, dx, dy, null);
+            canvas.drawBitmap(progressMark, matrix, markPaint);
+        }
+    }
+
+    /**
+     * Gets the X coordinate of the arc's end arm's point of intersection with
+     * the circle
+     *
+     * @return the X coordinate
+     */
+    public float getXFromAngle() {
+        int size1 = progressMark.getWidth();
+        int size2 = progressMarkPressed.getWidth();
+        int adjust = (size1 > size2) ? size1 : size2;
+        Log.i("pq","markPointX:"+markPointX);
+        Log.i("pq","progressMark width:"+size1);
+        float x = markPointX - (adjust / 2);
+        Log.i("pq","result:"+x);
+        return markPointX;
+    }
+
+    /**
+     * Gets the Y coordinate of the arc's end arm's point of intersection with
+     * the circle
+     *
+     * @return the Y coordinate
+     */
+    public float getYFromAngle() {
+        int size1 = progressMark.getHeight();
+        int size2 = progressMarkPressed.getHeight();
+        int adjust = (size1 > size2) ? size1 : size2;
+        Log.i("pq","markPointY:"+markPointY);
+        Log.i("pq","progressMark height:"+size1);
+        float y = markPointY - (adjust / 2);
+        Log.i("pq","result:"+y);
+        return markPointY;
+    }
+
+    /**
+     * Get the angle.
+     *
+     * @return the angle
+     */
+    public int getAngle() {
+        return angle;
+    }
+
+    /**
+     * Set the angle.
+     *
+     * @param angle
+     *            the new angle
+     */
+    public void setAngle(int angle) {
+        this.angle = angle;
+        float donePercent = (((float) this.angle) / 360) * 100;
+        float progress = (donePercent / 100) * getMaxProgress();
+        setProgressPercent(Math.round(donePercent));
+        CALLED_FROM_ANGLE = true;
+        setProgress(Math.round(progress));
+    }
+
+    /**
+     * Sets the seek bar change listener.
+     *
+     * @param listener
+     *            the new seek bar change listener
+     */
+    public void setSeekBarChangeListener(OnSeekChangeListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Gets the seek bar change listener.
+     *
+     * @return the seek bar change listener
+     */
+    public OnSeekChangeListener getSeekBarChangeListener() {
+        return mListener;
+    }
+
+    /**
+     * Gets the bar width.
+     *
+     * @return the bar width
+     */
+    public int getBarWidth() {
+        return barWidth;
+    }
+
+    /**
+     * Sets the bar width.
+     *
+     * @param barWidth
+     *            the new bar width
+     */
+    public void setBarWidth(int barWidth) {
+        this.barWidth = barWidth;
+    }
+
+    /**
+     * The listener interface for receiving onSeekChange events. The class that
+     * is interested in processing a onSeekChange event implements this
+     * interface, and the object created with that class is registered with a
+     * component using the component's
+     * <code>setSeekBarChangeListener(OnSeekChangeListener)<code> method. When
+     * the onSeekChange event occurs, that object's appropriate
+     * method is invoked.
+     *
+     * @see
+     */
+    public interface OnSeekChangeListener {
+
+        /**
+         * On progress change.
+         *
+         * @param view
+         *            the view
+         * @param newProgress
+         *            the new progress
+         */
+        public void onProgressChange(CircularSeekBar view, int newProgress);
+    }
+
+    /**
+     * Gets the max progress.
+     *
+     * @return the max progress
+     */
+    public int getMaxProgress() {
+        return maxProgress;
+    }
+
+    /**
+     * Sets the max progress.
+     *
+     * @param maxProgress
+     *            the new max progress
+     */
+    public void setMaxProgress(int maxProgress) {
+        this.maxProgress = maxProgress;
+    }
+
+    /**
+     * Gets the progress.
+     *
+     * @return the progress
+     */
+    public int getProgress() {
+        return progress;
+    }
+
+    /**
+     * Sets the progress.
+     *
+     * @param progress
+     *            the new progress
+     */
+    public void setProgress(int progress) {
+        if (this.progress != progress) {
+            this.progress = progress;
+            if (!CALLED_FROM_ANGLE) {
+                int newPercent = (this.progress * 100) / this.maxProgress;
+                int newAngle = (newPercent * 360) / 100 ;
+                this.setAngle(newAngle);
+                this.setProgressPercent(newPercent);
+            }
+            mListener.onProgressChange(this, this.getProgress());
+            CALLED_FROM_ANGLE = false;
+        }
+    }
+
+    /**
+     * Gets the progress percent.
+     *
+     * @return the progress percent
+     */
+    public int getProgressPercent() {
+        return progressPercent;
+    }
+
+    /**
+     * Sets the progress percent.
+     *
+     * @param progressPercent
+     *            the new progress percent
+     */
+    public void setProgressPercent(int progressPercent) {
+        this.progressPercent = progressPercent;
+    }
+
+    /**
+     * Sets the ring background color.
+     *
+     * @param color
+     *            the new ring background color
+     */
+    public void setRingBackgroundColor(int color) {
+        circleRing.setColor(color);
+    }
+
+    /**
+     * Sets the back ground color.
+     *
+     * @param color
+     *            the new back ground color
+     */
+    public void setBackGroundColor(int color) {
+        innerColor.setColor(color);
+    }
+
+    /**
+     * Sets the progress color.
+     *
+     * @param color
+     *            the new progress color
+     */
+    public void setProgressColor(int color) {
+        circleColor.setColor(color);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see android.view.View#onTouchEvent(android.view.MotionEvent)
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        boolean up = false;
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                moved(x, y, up);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                moved(x, y, up);
+                break;
+            case MotionEvent.ACTION_UP:
+                up = true;
+                moved(x, y, up);
+                break;
+        }
+        return true;
+    }
+
+    /**
+     * Moved.
+     *
+     * @param x
+     *            the x
+     * @param y
+     *            the y
+     * @param up
+     *            the up
+     */
+    private void moved(float x, float y, boolean up) {
+        float distance = (float) Math.sqrt(Math.pow((x - cx), 2) + Math.pow((y - cy), 2));
+        if (distance < outerRadius + adjustmentFactor && distance > innerRadius - adjustmentFactor && !up) {
+            IS_PRESSED = true;
+
+            markPointX = (float) (cx + outerRadius * Math.cos(Math.atan2(x - cx, cy - y) - (Math.PI /2)));
+            markPointY = (float) (cy + outerRadius * Math.sin(Math.atan2(x - cx, cy - y) - (Math.PI /2)));
+
+            float degrees = (float) ((float) ((Math.toDegrees(Math.atan2(x - cx, cy - y)) + 360.0)) % 360.0);
+            // and to make it count 0-360
+            if (degrees < 0) {
+                degrees += 2 * Math.PI;
+            }
+
+            setAngle(Math.round(degrees));
+            invalidate();
+
+        } else {
+            IS_PRESSED = false;
+            invalidate();
+        }
+
+    }
+
+    /**
+     * Gets the adjustment factor.
+     *
+     * @return the adjustment factor
+     */
+    public float getAdjustmentFactor() {
+        return adjustmentFactor;
+    }
+
+    /**
+     * Sets the adjustment factor.
+     *
+     * @param adjustmentFactor
+     *            the new adjustment factor
+     */
+    public void setAdjustmentFactor(float adjustmentFactor) {
+        this.adjustmentFactor = adjustmentFactor;
+    }
+
+    /**
+     * To display seekbar
+     */
+    public void ShowSeekBar() {
+        SHOW_SEEKBAR = true;
+    }
+
+    /**
+     * To hide seekbar
+     */
+    public void hideSeekBar() {
+        SHOW_SEEKBAR = false;
+    }
+}

+ 8 - 0
metronome/src/main/res/drawable/shape_page_main_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:angle="1"
+        android:endColor="@color/color_10a0a0a0"
+        android:startColor="@color/color_102dc7aa" />
+
+</shape>

+ 3 - 1
metronome/src/main/res/drawable/shape_speed_mark.xml

@@ -2,6 +2,8 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
     <gradient android:startColor="#59E5D5"
         android:angle="1"
-        android:endColor="@color/color_2dc7aa"/>
+        android:endColor="#2dc7aa"/>
     <corners android:radius="4dp"/>
+    <size android:width="6dp"
+        android:height="16dp"/>
 </shape>

+ 96 - 15
metronome/src/main/res/layout/activity_metronome_layout.xml

@@ -4,20 +4,31 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="@drawable/shape_page_main_bg"
     tools:context=".MetronomeActivity">
 
-    <include layout="@layout/common_toolbar_layout" />
+    <include
+        android:id="@+id/toolbar_include"
+        layout="@layout/common_toolbar_layout" />
 
     <ImageView
-        android:visibility="gone"
         android:id="@+id/iv_plate"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
+        android:layout_width="300dp"
+        android:layout_height="300dp"
         android:layout_marginTop="65dp"
         android:src="@mipmap/bg_metronome"
+        android:visibility="visible"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/toolbar" />
+        app:layout_constraintTop_toBottomOf="@+id/toolbar_include" />
+
+    <com.cooleshow.metronome.widget.CircularSeekBar
+        android:layout_width="220dp"
+        android:layout_height="220dp"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_plate"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_plate"
+        app:layout_constraintRight_toRightOf="@+id/iv_plate"
+        app:layout_constraintTop_toTopOf="@+id/iv_plate" />
 
     <TextView
         android:id="@+id/tv_speed"
@@ -27,6 +38,7 @@
         android:textColor="@color/color_333333"
         android:textSize="@dimen/sp_40"
         android:textStyle="bold"
+        android:text="136"
         app:layout_constraintBottom_toBottomOf="@+id/iv_plate"
         app:layout_constraintLeft_toLeftOf="@+id/iv_plate"
         app:layout_constraintRight_toRightOf="@+id/iv_plate"
@@ -35,20 +47,89 @@
 
 
     <ImageView
-        app:layout_constraintCircleRadius="55dp"
-        app:layout_constraintCircleAngle="320"
-        app:layout_constraintCircle="@+id/tv_speed"
         android:id="@+id/iv_note"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:src="@mipmap/icon_quarter_note"
+        app:layout_constraintCircle="@+id/tv_speed"
+        app:layout_constraintCircleAngle="320"
+        app:layout_constraintCircleRadius="55dp" />
+
+
+    <ImageView
+        android:id="@+id/iv_reduce"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="11dp"
+        android:src="@mipmap/icon_metronome_bt_bg"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_plate"
+        app:layout_constraintRight_toLeftOf="@+id/iv_beat_value"
+        app:layout_constraintTop_toBottomOf="@+id/iv_plate" />
+
+    <ImageView
+        app:layout_constraintRight_toRightOf="@+id/iv_reduce"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_reduce"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_reduce"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce"
+        android:src="@mipmap/icon_reduce_symbol"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"/>
 
-    <View
-        app:layout_constraintCircleRadius="98dp"
-        app:layout_constraintCircleAngle="270"
-        app:layout_constraintCircle="@+id/iv_plate"
-        android:background="@drawable/shape_speed_mark"
-        android:layout_width="18dp"
-        android:layout_height="6dp"/>
+    <ImageView
+        android:id="@+id/iv_beat_value"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@mipmap/icon_beat_value_bg"
+        app:layout_constraintLeft_toRightOf="@+id/iv_reduce"
+        app:layout_constraintRight_toLeftOf="@+id/iv_add"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce" />
+
+    <ImageView
+        android:id="@+id/iv_add"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@mipmap/icon_metronome_bt_bg"
+        app:layout_constraintLeft_toRightOf="@+id/iv_beat_value"
+        app:layout_constraintRight_toRightOf="@+id/iv_plate"
+        app:layout_constraintTop_toTopOf="@+id/iv_reduce" />
+
+    <ImageView
+        app:layout_constraintRight_toRightOf="@+id/iv_add"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_add"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_add"
+        app:layout_constraintTop_toTopOf="@+id/iv_add"
+        android:src="@mipmap/icon_add_symbol"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:includeFontPadding="false"
+        android:textColor="@color/color_1a1a1a"
+        android:textSize="@dimen/sp_16"
+        android:text="6/4"
+        app:layout_constraintBottom_toBottomOf="@+id/iv_beat_value"
+        app:layout_constraintLeft_toLeftOf="@+id/iv_beat_value"
+        app:layout_constraintRight_toRightOf="@+id/iv_beat_value"
+        app:layout_constraintTop_toTopOf="@+id/iv_beat_value"
+        tools:text="6/4" />
 
+    <TextView
+        android:id="@+id/tv_play"
+        android:layout_width="0dp"
+        android:layout_height="45dp"
+        android:layout_marginStart="53dp"
+        android:layout_marginTop="101dp"
+        android:layout_marginEnd="53dp"
+        android:background="@drawable/shape_2dc7aa_24dp"
+        android:elevation="10dp"
+        android:gravity="center"
+        android:text="播放"
+        android:textColor="@color/white"
+        android:textSize="@dimen/sp_16"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/iv_beat_value" />
 </androidx.constraintlayout.widget.ConstraintLayout>

二進制
metronome/src/main/res/mipmap-xhdpi/icon_add_symbol.png


二進制
metronome/src/main/res/mipmap-xhdpi/icon_beat_value_bg.png


二進制
metronome/src/main/res/mipmap-xhdpi/icon_metronome_bt_bg.png


二進制
metronome/src/main/res/mipmap-xhdpi/icon_reduce_symbol.png


二進制
metronome/src/main/res/mipmap-xxhdpi/icon_add_symbol.png


二進制
metronome/src/main/res/mipmap-xxhdpi/icon_beat_value_bg.png


二進制
metronome/src/main/res/mipmap-xxhdpi/icon_metronome_bt_bg.png


二進制
metronome/src/main/res/mipmap-xxhdpi/icon_reduce_symbol.png