一起制作简易唱片播放器

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/50789666
本文出自:【顾林海的博客】

扯蛋篇

这几由于智齿又长了出来,痛了好几天还没好,吃饭又不能吃,加上公司里面操蛋的体制,感觉人活着真累,昨天晚上回到住处,我朋友让我帮忙写个唱片播放器,效果大致上如下:

于是乎,利用一个早上做了下,比较粗糙,毕竟帮别人做的,随便做做得了,当然有很多地方投机取巧了。

原理篇

在做之前看看我们得需要以下素材:

一起制作简易唱片播放器_第1张图片

额!!!这个,这个ic_launcher忽律。

现在我们分析这个播放器的几个播放步骤:

  1. 当我们杆放到碟子上时,碟子需要旋转
  2. 在碟子旋转时,向上或是向下滑动淡出(PS:这里面我为了方便,放了两层的碟子View,这样上面滑出时,底下的唱片还是显示的)
  3. 当我们拨动杆时,杆逐渐移动到唱片上,再次触摸杆时,杆回到原来的位置(PS:这里有个问题,就是杆移动到唱片上时,触摸事件其实还是在原来的位置,这里我们在唱片右下角放了一个透明的View,嘿嘿,投机取巧嘛,也是说,我在原来的位置只处理向右移动的操作,这个透明的View只处理杆归位的操作)

讲了这么多,先看看布局吧

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" >

    <FrameLayout  android:layout_width="260dp" android:layout_height="match_parent" android:layout_centerInParent="true" >

        <FrameLayout  android:id="@+id/fl_dvd" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center" >

            <FrameLayout  android:id="@+id/fl_dvd1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" >

                <ImageView  android:id="@+id/iv_background1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/turntable_bg_anim_highlight" />

                <ImageView  android:id="@+id/iv_center1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/default_album" />
            </FrameLayout>

            <FrameLayout  android:id="@+id/fl_dvd2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" >

                <ImageView  android:id="@+id/iv_background2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/turntable_bg_anim_highlight" />

                <ImageView  android:id="@+id/iv_center2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/default_album" />
            </FrameLayout>
        </FrameLayout>

        <ImageView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/turntable_bg" />

        <ImageView  android:id="@+id/iv_pole" android:layout_width="50sp" android:layout_height="180sp" android:layout_gravity="right|center" android:src="@drawable/stylus" />

        <View  android:id="@+id/id_pole" android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center" android:layout_marginLeft="65dp" android:layout_marginTop="45dp" />
    </FrameLayout>

    <FrameLayout  android:layout_width="match_parent" android:layout_height="match_parent" >
    </FrameLayout>

</RelativeLayout>

这里的id为 fl_dvd1 的FrameLayout是底部的唱片View, id为 fl_dvd2的FrameLayout是上面用来做移出动画的唱片View,id为 id_pole的View就是唱片右下角透明的View,旋转的是id 为 fl_dvd的View。

首先定义一个继承LinearLayout的BaseRecordView类:

public abstract class BaseRecordView extends LinearLayout {}

接着重写三个构造器:

public BaseRecordView(Context context) {
        this(context, null);
}

public BaseRecordView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
}

public BaseRecordView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mContext = context;
        init();
        initAnimation();
        initEvent();
}

init方法里是载入我们刚定义的布局,并初始化控件:

/** * 初始化View * * @param context */
    private void init() {
        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
        LayoutInflater.from(mContext).inflate(R.layout.record_layout, this);
        fl_dvd = (FrameLayout) findViewById(R.id.fl_dvd);// 两层 碟片的容器
        fl_dvd2 = (FrameLayout) findViewById(R.id.fl_dvd2);// 移出唱片动画的View
        iv_background2 = (ImageView) findViewById(R.id.iv_background2);// 黑色的唱片(移出唱片动画的View)
        iv_center2 = (ImageView) findViewById(R.id.iv_center2);// 中间的圆心图案(移出唱片动画的View)
        iv_pole = (ImageView) findViewById(R.id.iv_pole);// 杆
        mPole = findViewById(R.id.id_pole);// 唱片右下角透明的View
    }

mTouchSlop 拿到的是当面屏幕滑动的最小距离。

第一步先让整个唱片旋转,也就是 fl_dvd 这个View进行旋转,可以通过Animation进行播放动画,先配置我们的动画文件:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <rotate  android:duration="2400" android:fillAfter="true" android:fillBefore="false" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:repeatCount="-1" android:toDegrees="359" />

</set>

通过设置repeatCount属性为-1,使唱片无限旋转。定义唱片旋转的动画。

/** * 碟片的播放动画 */
private void initDVDAnimation() {
    mDVDLin = new LinearInterpolator();
    mDVDAnimtion = AnimationUtils.loadAnimation(mContext, R.anim.dvd_anim);
    mDVDAnimtion.setInterpolator(mDVDLin);
    mDVDAnimtion.setFillAfter(true);
    mDVDAnimtion.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
    });
}

杆的拨动到唱片上的效果,是将我们的图片顺时针旋转20度,那么杆的复位是逆时针旋转20度,定义我们的杆拨打的动画文件:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <rotate  android:duration="300" android:fromDegrees="0" android:pivotX="45%" android:pivotY="10%" android:repeatCount="0" android:toDegrees="20" />

</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <rotate  android:duration="300" android:fromDegrees="0" android:pivotX="45%" android:pivotY="10%" android:repeatCount="0" android:toDegrees="20" />

</set>

老样子初始化我们杆的动画:

/** * 杆子启动动画 */
private void initPoleAnimation() {
    mPoleAnimtion = AnimationUtils.loadAnimation(mContext, R.anim.pole_anim);
    mPoleLin = new LinearInterpolator();
    mPoleAnimtion.setFillAfter(true);
    mPoleAnimtion.setInterpolator(mPoleLin);
    mPoleAnimtion.setAnimationListener(new AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            /* * 杆子启动完毕,说明碟子应该要转动了 */
            isStart = true;
            isPole = true;
            startDVDAnimation();
            mIRecordListener.start();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });

}

/** * 杆复位动画 */
private void initPoleStopAnimation() {
    mPoleAnimtion = AnimationUtils.loadAnimation(mContext,R.anim.pole_rest_anim);
    mPoleLin = new LinearInterpolator();
    mPoleAnimtion.setFillAfter(true);
    mPoleAnimtion.setInterpolator(mPoleLin);
    mPoleAnimtion.setAnimationListener(new AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            /* * 杆子复位,说明已经停止播放或者播放完毕 */
            isPole = false;
            isStart = false;
            stopDVDAnimation();
            mIRecordListener.stop();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });

}

上面在杆放到唱片上后,调用startDVDAnimation方法进行唱片的旋转,并通过接口回调通知我们的当前处于播放状态;当杆复位时调用stopDVDAnimation方法进行唱片的停止,并通过接口回调通知我们的当前处于停止状态。

接着添加我们杆的触摸事件,注意还有我们投机取巧的透明View的触摸事件:

/** * 杆触摸事件 */
iv_pole.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            if (!isStart) {
                /* * 当停止播放时,拨动杆,播放 */
                isPole = true;
                startPoleAnimation();
            }
            break;
        default:
            break;
        }
        return true;
    }
});

/** * 虚拟位置的触摸 */
mPole.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            if (isStart) {
                /* * 当播放时,拨动杆,停止 */
                isPole = false;
                stopPoleAnimation();
            }
            break;
        default:
            break;
        }
        return true;
    }
});

通过触摸杆和透明的View来决定是否进行杆的复位还是移动到唱片上。

最后是唱片本身触摸的事件处理:

/** * 碟片的触摸事件 */
private OnTouchListener mOnTouchListener = new OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
    int oldY = (int) event.getX();
    int newY = (int) event.getY();
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        oldY = (int) event.getY();
        break;
    case MotionEvent.ACTION_MOVE:
        newY = (int) event.getY();
        stopDVDAnimation();
        break;
    case MotionEvent.ACTION_UP:
        if (isStart && oldY - newY > mTouchSlop && (newY < oldY)) {
            up();
        } else if (isStart && newY - oldY > mTouchSlop && (newY > oldY)) {
            down();
        } else if (isPole) {
            startDVDAnimation();
        }
        break;
    default:
        break;
    }
    return true;
}};

这里面是滑动时的唱片停止处理,以及向上向下滑动时的处理,小于滑动最小距离并离开屏幕时,唱片重新旋转。

最后给出up和down的方法:

/** * 碟片向上切歌动画 */
private void initDVDUpAnimation() {
    mUpAnimation = AnimationUtils.loadAnimation(mContext, R.anim.up_anim);
    mUpAnimation.setAnimationListener(new AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {
            mIRecordListener.up();
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            fl_dvd2.setVisibility(View.VISIBLE);
            startDVDAnimation();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });
}

/** * 碟片向下切歌动画 */
private void initDVDDownAnimation() {
    mDownAnimation = AnimationUtils.loadAnimation(mContext,R.anim.down_anim);
    mDownAnimation.setAnimationListener(new AnimationListener() {

        @Override
        public void onAnimationStart(Animation animation) {
            mIRecordListener.down();
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            fl_dvd2.setVisibility(View.VISIBLE);
            startDVDAnimation();

        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    });

}

完整的代码:

package com.example.recordproject.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.example.recordproject.R;
import com.example.recordproject.widget.interf.IRecordListener;

/** * 唱片 * * @author Linhai Gu * */
public abstract class BaseRecordView extends LinearLayout {

    /** * 上下文 */
    protected Context mContext;
    /** * 碟片 */
    private FrameLayout fl_dvd;
    /** * 碟片上面 */
    private FrameLayout fl_dvd2;
    /** * 碟片动画 */
    private Animation mDVDAnimtion;
    /** * 碟片播放动画的速度匀速 */
    private LinearInterpolator mDVDLin;

    /** * 杆 */
    private ImageView iv_pole;
    /** * 杆动画 */
    private Animation mPoleAnimtion;
    /** * 杆播放动画的速度匀速 */
    private LinearInterpolator mPoleLin;
    /** * 向上切歌 */
    private Animation mUpAnimation;
    /** * 向下切歌 */
    private Animation mDownAnimation;

    /** * 滑动的最小距离 */
    private int mTouchSlop;
    /** * 浮层碟片 */
    private ImageView iv_center2;
    private ImageView iv_background2;
    private View mPole;
    /** * 是否正在播放 */
    private boolean isStart = false;
    /** * 杆是否在碟片上 */
    private boolean isPole = false;

    private IRecordListener mIRecordListener;

    protected abstract void setRecordListener(IRecordListener listener);

    protected abstract void start();

    protected abstract void stop();

    public BaseRecordView(Context context) {
        this(context, null);
    }

    public BaseRecordView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BaseRecordView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.mContext = context;
        init();
        initAnimation();
        initEvent();
    }

    /** * 初始化View * * @param context */
    private void init() {
        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
        LayoutInflater.from(mContext).inflate(R.layout.record_layout, this);
        fl_dvd = (FrameLayout) findViewById(R.id.fl_dvd);// 两层 碟片的容器
        fl_dvd2 = (FrameLayout) findViewById(R.id.fl_dvd2);// 移出唱片动画的View
        iv_background2 = (ImageView) findViewById(R.id.iv_background2);// 黑色的唱片(移出唱片动画的View)
        iv_center2 = (ImageView) findViewById(R.id.iv_center2);// 中间的圆心图案(移出唱片动画的View)
        iv_pole = (ImageView) findViewById(R.id.iv_pole);// 杆
        mPole = findViewById(R.id.id_pole);// 唱片右下角透明的View
    }

    /** * <pre> * {@link #initDVDAnimation}初始化碟片动画 * {@link #initDVDUpAnimation}初始向上切歌的动画 * {@link #initDVDDownAnimation}初始向下切歌的动画 * </pre> */
    private void initAnimation() {
        initDVDAnimation();
        initDVDUpAnimation();
        initDVDDownAnimation();
    }

    /** * 碟片的触摸事件 */
    private OnTouchListener mOnTouchListener = new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int oldY = (int) event.getX();
            int newY = (int) event.getY();
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                oldY = (int) event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                newY = (int) event.getY();
                stopDVDAnimation();
                break;
            case MotionEvent.ACTION_UP:
                if (isStart && oldY - newY > mTouchSlop && (newY < oldY)) {
                    up();
                } else if (isStart && newY - oldY > mTouchSlop && (newY > oldY)) {
                    down();
                } else if (isPole) {
                    startDVDAnimation();
                }
                break;
            default:
                break;
            }
            return true;
        }
    };

    /** * 初始化事件 */
    private void initEvent() {
        /** * 碟片的触摸事件 */
        iv_background2.setOnTouchListener(mOnTouchListener);
        iv_center2.setOnTouchListener(mOnTouchListener);

        /** * 杆触摸事件 */
        iv_pole.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                    if (!isStart) {
                        /* * 当停止播放时,拨动杆,播放 */
                        isPole = true;
                        startPoleAnimation();
                    }
                    break;
                default:
                    break;
                }
                return true;
            }
        });

        /** * 虚拟位置的触摸 */
        mPole.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    break;
                case MotionEvent.ACTION_MOVE:
                    break;
                case MotionEvent.ACTION_UP:
                    if (isStart) {
                        /* * 当播放时,拨动杆,停止 */
                        isPole = false;
                        stopPoleAnimation();
                    }
                    break;
                default:
                    break;
                }
                return true;
            }
        });
    }

    /** * 碟片的播放动画 */
    private void initDVDAnimation() {
        mDVDLin = new LinearInterpolator();
        mDVDAnimtion = AnimationUtils.loadAnimation(mContext, R.anim.dvd_anim);
        mDVDAnimtion.setInterpolator(mDVDLin);
        mDVDAnimtion.setFillAfter(true);
        mDVDAnimtion.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }

    /** * 碟片向上切歌动画 */
    private void initDVDUpAnimation() {
        mUpAnimation = AnimationUtils.loadAnimation(mContext, R.anim.up_anim);
        mUpAnimation.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                mIRecordListener.up();
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                fl_dvd2.setVisibility(View.VISIBLE);
                startDVDAnimation();
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }

    /** * 碟片向下切歌动画 */
    private void initDVDDownAnimation() {
        mDownAnimation = AnimationUtils.loadAnimation(mContext,
                R.anim.down_anim);
        mDownAnimation.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                mIRecordListener.down();
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                fl_dvd2.setVisibility(View.VISIBLE);
                startDVDAnimation();

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
    }

    /** * 杆子启动动画 */
    private void initPoleAnimation() {
        mPoleAnimtion = AnimationUtils
                .loadAnimation(mContext, R.anim.pole_anim);
        mPoleLin = new LinearInterpolator();
        mPoleAnimtion.setFillAfter(true);
        mPoleAnimtion.setInterpolator(mPoleLin);
        mPoleAnimtion.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                /* * 杆子启动完毕,说明碟子应该要转动了 */
                isStart = true;
                isPole = true;
                startDVDAnimation();
                mIRecordListener.start();
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

    }

    /** * 杆复位动画 */
    private void initPoleStopAnimation() {
        mPoleAnimtion = AnimationUtils.loadAnimation(mContext,
                R.anim.pole_rest_anim);
        mPoleLin = new LinearInterpolator();
        mPoleAnimtion.setFillAfter(true);
        mPoleAnimtion.setInterpolator(mPoleLin);
        mPoleAnimtion.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                /* * 杆子复位,说明已经停止播放或者播放完毕 */
                isPole = false;
                isStart = false;
                stopDVDAnimation();
                mIRecordListener.stop();
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

    }

    /** * 播放碟片动画 */
    private void startDVDAnimation() {
        fl_dvd.startAnimation(mDVDAnimtion);
    }

    /** * 暂停碟片动画 */
    private void stopDVDAnimation() {
        fl_dvd.clearAnimation();
        // mDVDAnimtion.cancel();
    }

    /** * 播放杆的动画 */
    protected void startPoleAnimation() {
        initPoleAnimation();
        // mPoleAnimtion.cancel();
        iv_pole.startAnimation(mPoleAnimtion);
    }

    /** * 暂停杆的动画 */
    protected void stopPoleAnimation() {
        // iv_pole.clearAnimation();
        initPoleStopAnimation();
        if (mPoleAnimtion != null) {
            mPoleAnimtion.cancel();
        }
        iv_pole.startAnimation(mPoleAnimtion);
    }

    /** * 向上切歌 */
    private void up() {
        fl_dvd2.startAnimation(mUpAnimation);
    }

    /** * 向下切歌 */
    private void down() {
        fl_dvd2.startAnimation(mDownAnimation);
    }

    /** * 监听 * * @param listener */
    protected void initListener(IRecordListener listener) {
        this.mIRecordListener = listener;
    }
}
package com.example.recordproject.widget;

import com.example.recordproject.widget.interf.IRecordListener;

import android.content.Context;
import android.util.AttributeSet;

public class RecordView extends BaseRecordView {

    public RecordView(Context context) {
        this(context, null);
    }

    public RecordView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RecordView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setRecordListener(IRecordListener listener) {
        super.initListener(listener);
    }

    @Override
    public void start() {
        startPoleAnimation();
    }

    @Override
    public void stop() {
        stopPoleAnimation();
    }

}
package com.example.recordproject.widget.interf;

public interface IRecordListener {

    /** * 开始 */
    public void start();

    /** * 关闭 */
    public void stop();

    /** * 向上切歌 */
    public void up();

    /** * 向下切歌 */
    public void down();

}
package com.example.recordproject;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.example.recordproject.widget.RecordView;
import com.example.recordproject.widget.interf.IRecordListener;

public class MainActivity extends Activity {

    private RecordView mRecordView;
    private int count = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void initViews() {
        mRecordView = (RecordView) findViewById(R.id.mRecordView);
        mRecordView.setRecordListener(new IRecordListener() {

            @Override
            public void stop() {
                Log.e("TAG", "-----停止-----");
            }

            @Override
            public void start() {
                Log.e("TAG", "-----开启-----");
            }

            @Override
            public void up() {
                Log.e("TAG", "-----向上切歌-----");
            }

            @Override
            public void down() {
                Log.e("TAG", "-----向下切歌-----");
            }

        });
    }
}

以下是项目的完整github地址。

github项目源码地址:点击【项目源码】

你可能感兴趣的:(android,动画,播放器,自定义控件,唱片)