前言
网易云音乐是一款非常优秀的音乐播放器,尤其是播放界面,使用唱盘机风格,显得格外古典优雅。笔者出于学习与挑战的想法,思考播放界面背后的实现原理,并写了一个小程序。
笔者尽可能地去模仿官方的视觉、交互效果,其中包括了唱盘与唱针切换时的细节处理、背景渐变等。本文将会分享一些视觉效果实现的方法以及设计思想,但难免有错漏之处。若读者发现有错误的地方或者更好的实现方法,请留言回复,希望与大家共同进步。
1 源码地址
需要源码的朋友,可以到github中自行下载。Android开发技术进阶群;701740775。加群的朋友麻烦备注一下csdn
github;https://github.com/AchillesLzg/jianshu-neteasedisc
2 本文内容
- 项目结构介绍
- 解决加载大图OOM问题
- 生成圆图最简单的方法
- 使用LayerDrawable进行图片合成
- 实现背景毛玻璃效果
- 使用LayerDrawable与属性动画,实现背景切换时渐变效果
- 遇到复杂的场景,应该如何编写代码
- 配合Service、本地广播进行音乐播放
- 结束语
3 项目结构介绍
项目结构介绍包括以下内容:
- 主界面布局设计
- 唱盘布局设计
- 动态布局
- 唱盘控件DiscView对外接口及方法
- 音乐状态控制时序图
3.1主界面布局设计
主界面布局从上到下可以划分几大区域,如图3-1所示:
图 3-1 主界面布局
标题栏
使用ToolBar实现,字体可能需要自定义。
唱盘区域
唱盘区域包括唱盘、唱针、底盘、以及实现切换的ViewPager等控件,该布局比较复杂,本案例使用自定义控件实现唱盘区域。
时长显示区域
使用RelativeLayout作为根布局,进度条使用SeekBar实现。
播放控制区域
比较简单,使用LinearLayout作为根布局。
另外,主界面使用RelativeLayout作为根布局。
3.2 唱盘布局设计
唱盘区域由控件DiscView实现,以RelativeLayout为根布局,子控件包括:底盘、唱针、ViewPager等。其中,底盘和唱针均用ImageView实现,然后使用ViewPager加载ImageView实现唱片的切换。如图3-2所示。
图 3-2 唱盘区域布局
唱盘布局代码如下所示:
<?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">
<!--底盘-->
<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"
/>
<!--唱针-->
<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>
3.3 动态布局
到这里,读者可能有些好奇,上述布局中并没有指定控件的宽高、边距等参数,那如何保证控件显示在正确的位置?我们没有网易云音乐的设计图,因此不能得知官方的布局参数,那该怎么办呢?其实有个笨方法,我们可以打开网易云音乐的播放界面并截图,然后手动去量需要的高度、边距等参数。
截图量到控件的宽高、边距等数值,除以截图的宽或高,得到控件参数比例。使用时,我们根据手机的屏幕宽高,乘以对应的比例,就能得到该屏幕尺寸下的控件宽高、边距。
当然,这种动态布局肯定会消耗更多性能,但不失为没有办法中的办法。
相关控件参数比例,笔者统一放在DisplayUtil.java文件中,代码如下:
public class DisplayUtil {
/*手柄起始角度*/
public static final float ROTATION_INIT_NEEDLE = -30;
/*截图屏幕宽高*/
private static final float BASE_SCREEN_WIDTH = (float) 1080.0;
private static final float BASE_SCREEN_HEIGHT = (float) 1920.0;
/*唱针宽高、距离等比例*/
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);
/*唱盘比例*/
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);
/*专辑图片比例*/
public static final float SCALE_MUSIC_PIC_SIZE = (float) (533.0 / BASE_SCREEN_WIDTH);
/*设备屏幕宽度*/
public static int getScreenWidth(Contet contet) {
return contet.getResources().getDisplayMetrics().widthPiels;
}
/*设备屏幕高度*/
public static int getScreenHeight(Contet contet) {
return contet.getResources().getDisplayMetrics().heightPiels;
}
}
例如需要设置唱盘底盘的顶部外边距,我们先获得该比例,然后乘上当前屏幕高度,得到具体数值,最后通过LayoutParams类进行动态设置。
int marginTop = (int) (DisplayUtil.SCALE_DISC_MARGIN_TOP * mScreenHeight);
RelativeLayout.LayoutParams layoutParams = (LayoutParams) mDiscBlackground.getLayoutParams();
layoutParams.setMargins(0, marginTop, 0, 0);
3.4 DiscView对外接口及方法
唱盘控件DiscView提供一个接口IPlayInfo,代码如下:
public interface IPlayIn