//自定义类

public class GramophoneView extends View {/*** 尺寸计算设计说明:* 1、唱片有两个主要尺寸:中间图片的半径、黑色圆环的宽度。* 黑色圆环的宽度 = 图片半径的一半。* 2、唱针分为“手臂”和“头”,手臂分两段,一段长的一段短的,头也是一段长的一段短的。* 唱针四个部分的尺寸求和 = 唱片中间图片的半径+黑色圆环的宽度* 唱针各部分长度 比例——长的手臂:短的手臂:长的头:短的头 = 8:4:2:1* 3、唱片黑色圆环顶部到唱针顶端的距离 = 唱针长的手臂的长度。*/private int halfMeasureWidth;// 绘制唱片相关变量// 中间图片默认半径private static final int DEFAULT_PICTURE_RADIUS = 400;// 唱片旋转默认速度,其实是通过每次旋转叠加的角度来控制速度private static final float DEFAULT_DISK_ROTATE_SPEED = 0.3f;private int pictureRadius;    // 中间图片的半径private int ringWidth;        // 黑色圆环宽度private float diskRotateSpeed;// 唱片旋转速度private Paint discPaint;      // 唱片画笔private Path clipPath;        // 裁剪图片的路径private Bitmap bitmap;        // 图片private Rect srcRect;         // 图片被裁减范围private Rect dstRect;         // 图片被绘制范围// 绘制唱针相关变量private static final int PLAY_DEGREE = -15;  // 播放状态时唱针的旋转角度private static final int PAUSE_DEGREE = -45; // 暂停状态时唱针的旋转角度private int smallCircleRadius = 20;          // 唱针顶部小圆半径private int bigCircleRadius;    // 唱针顶部大圆半径private int longArmLength;      // 唱针手臂,较长那段的长度private int shortArmLength;     // 唱针手臂,较短那段的长度private int longHeadLength;     // 唱针的头,较长那段的长度private int shortHeadLength;    // 唱针的头,较短那段的长度private Paint needlePaint;      // 唱针画笔// 状态控制相关变量private boolean isPlaying;            // 是否处于播放状态private int needleDegreeCounter;      // 唱针旋转角度计数器private float diskDegreeCounter;      // 唱片旋转角度计数器public GramophoneView(Context context) {this(context, null);}public GramophoneView(Context context, AttributeSet attrs) {super(context, attrs);// 读取xml文件属性TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GramophoneView);pictureRadius = (int) typedArray.getDimension(R.styleable.GramophoneView_picture_radius, DEFAULT_PICTURE_RADIUS);diskRotateSpeed = typedArray.getFloat(R.styleable.GramophoneView_disk_rotate_speed, DEFAULT_DISK_ROTATE_SPEED);Drawable drawable = typedArray.getDrawable(R.styleable.GramophoneView_src);if (drawable == null) {bitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.gramophone_view_default_picture);} else {bitmap = ((BitmapDrawable) drawable).getBitmap();}typedArray.recycle();// 初始化唱片变量ringWidth = pictureRadius >> 1;discPaint = new Paint();discPaint.setColor(Color.MAGENTA);discPaint.setStyle(Paint.Style.STROKE);discPaint.setStrokeWidth(ringWidth);srcRect = new Rect();dstRect = new Rect();setBitmapRect(srcRect, dstRect);clipPath = new Path();clipPath.addCircle(0, 0, pictureRadius, Path.Direction.CW);diskDegreeCounter = 0;// 初始化唱针变量bigCircleRadius = smallCircleRadius << 1;shortHeadLength = (pictureRadius + ringWidth) / 15;longHeadLength = shortHeadLength << 1;shortArmLength = longHeadLength << 1;longArmLength = shortArmLength << 1;needlePaint = new Paint();needleDegreeCounter = PAUSE_DEGREE;}/*** 根据加载的图片资源尺寸和设置的唱片中间图片直径,* 为canvas.drawBitmap()方法设置源Rect和目标Rect,* 以宽度为例,假设图片资源宽度为width,唱片中间图片直径为diameter* 如果width <= diameter,则截取宽度为整张图片宽度。* 如果width > diameter,则截取宽度为图片资源横向中间长度为diameter的区域。* 高度的截取方法与宽度相同。** @param src 源矩形* @param dst 目标矩形*/private void setBitmapRect(Rect src, Rect dst) {
//        这种处理方式意义好像不大,暂时注释
//        int bitmapWidth = bitmap.getWidth();
//        int bitmapHeight = bitmap.getHeight();
//        // 唱片里的图片直径,也就是唱片里的图片的外接正方形边长
//        int diameter = pictureRadius<<1;
//        // 图片宽度小于唱片图片直径
//        if(bitmapWidth <= diameter){
//            src.left = 0;
//            src.right = bitmapWidth;
//        } else {
//            src.left = (bitmap.getWidth()-diameter)/2;
//            src.right = bitmap.getWidth()/2+diameter;
//        }
//        // 图片高度小于唱片图片直径
//        if(bitmapHeight <= diameter){
//            src.top = 0;
//            src.bottom = bitmapHeight;
//        } else {
//            src.top = (bitmap.getHeight()-diameter)/2;
//            src.bottom = bitmap.getHeight()/2+diameter;
//        }src.set(0, 0, bitmap.getWidth(), bitmap.getHeight());dst.set(-pictureRadius, -pictureRadius, pictureRadius, pictureRadius);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);/*** wrap_content属性下View的宽高设计:* 宽度:等于唱片直径,即图片半径+圆环宽度求和再乘以2。* 高度:等于唱片直径+唱针较长的手臂*/int width = (pictureRadius + ringWidth) * 2;int height = (pictureRadius + ringWidth) * 2 + longArmLength;int measuredWidth = resolveSize(width, widthMeasureSpec);int measuredHeight = resolveSize(height, heightMeasureSpec);setMeasuredDimension(measuredWidth, measuredHeight);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);halfMeasureWidth = getMeasuredWidth() >> 1;drawDisk(canvas);drawNeedle(canvas);if (needleDegreeCounter > PAUSE_DEGREE) {invalidate();}}// 绘制唱片(胶片)private void drawDisk(Canvas canvas) {diskDegreeCounter = diskDegreeCounter % 360 + diskRotateSpeed;drawDisk(canvas, diskDegreeCounter);}// 绘制旋转了制定角度的唱片private void drawDisk(Canvas canvas, float degree) {// 绘制圆环,注意理解平移的圆心距离和圆环半径是怎么计算的canvas.save();canvas.translate(halfMeasureWidth, pictureRadius + ringWidth + longArmLength);canvas.rotate(degree);canvas.drawCircle(0, 0, pictureRadius + ringWidth / 2, discPaint);// 绘制图片canvas.clipPath(clipPath);canvas.drawBitmap(bitmap, srcRect, dstRect, discPaint);canvas.restore();}// 绘制唱针private void drawNeedle(Canvas canvas) {// 由于PLAY_DEGREE和PAUSE_DEGREE之间的差值是30,所以每次增/减值应当是30的约数即可if (isPlaying) {if (needleDegreeCounter < PLAY_DEGREE) {needleDegreeCounter += 3;}} else {if (needleDegreeCounter > PAUSE_DEGREE) {needleDegreeCounter -= 3;}}drawNeedle(canvas, needleDegreeCounter);}// 绘制旋转了指定角度的唱针private void drawNeedle(Canvas canvas, int degree) {// 移动坐标到水平中点canvas.save();canvas.translate(halfMeasureWidth, 0);// 绘制唱针手臂needlePaint.setStrokeWidth(10);needlePaint.setColor(Color.parseColor("#C0C0C0"));// 绘制第一段臂canvas.rotate(degree);canvas.drawLine(0, 0, 0, longArmLength, needlePaint);// 绘制第二段臂canvas.translate(0, longArmLength);canvas.rotate(-30);canvas.drawLine(0, 0, 0, shortArmLength, needlePaint);// 绘制唱针头// 绘制第一段唱针头canvas.translate(0, shortArmLength);needlePaint.setStrokeWidth(10);canvas.drawLine(0, 0, 0, longHeadLength, needlePaint);// 绘制第二段唱针头canvas.translate(0, longHeadLength);needlePaint.setStrokeWidth(25);canvas.drawLine(0, 0, 0, shortHeadLength, needlePaint);canvas.restore();// 两个重叠的圆形canvas.save();canvas.translate(halfMeasureWidth, 0);needlePaint.setStyle(Paint.Style.FILL);needlePaint.setColor(Color.parseColor("#C0C0C0"));canvas.drawCircle(0, 0, bigCircleRadius-30, needlePaint);needlePaint.setColor(Color.parseColor("#8A8A8A"));canvas.drawCircle(0, 0, smallCircleRadius-20, needlePaint);canvas.restore();}/*** 设置是否处于播放状态** @param isPlaying true:播放,false:暂停*/public void setPlaying(boolean isPlaying) {this.isPlaying = isPlaying;invalidate();}/*** 获取播放状态** @return true:播放,false:暂停*/public boolean getPlaying() {return isPlaying;}/*** 获取图片半径** @return 图片半径*/public int getPictureRadius() {return pictureRadius;}/*** 设置图片半径** @param pictureRadius 图片半径*/public void setPictureRadius(int pictureRadius) {this.pictureRadius = pictureRadius;}/*** 获取唱片旋转速度** @return 唱片旋转速度*/public float getDiskRotateSpeed() {return diskRotateSpeed;}/*** 设置唱片旋转速度** @param diskRotateSpeed 旋转速度*/public void setDiskRotateSpeed(float diskRotateSpeed) {this.diskRotateSpeed = diskRotateSpeed;}/*** 设置图片资源id** @param resId 图片资源id*/public void setPictureRes(int resId) {bitmap = BitmapFactory.decodeResource(getContext().getResources(), resId);setBitmapRect(srcRect, dstRect);invalidate();}
}

//attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><!--TickView(打钩小动画)的属性--><declare-styleable name="TickView"><!--选中时圆的颜色--><attr name="checked_color" format="color"/><!--是否选中--><attr name="checked" format="boolean"/><!--圆半径--><attr name="radius" format="dimension"/></declare-styleable><!--GramophoneView(唱片机)属性--><declare-styleable name="GramophoneView"><!--图片半径--><attr name="picture_radius" format="dimension"/><!--图片资源id--><attr name="src" format="reference"/><!--唱片旋转速度--><attr name="disk_rotate_speed" format="float"/></declare-styleable>
</resources>

//布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.bwie.rikao17.MainActivity"><com.bwie.rikao17.GramophoneViewandroid:id="@+id/gramophone_view"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"app:picture_radius="100dp"app:disk_rotate_speed="0.2"app:src="@drawable/asd"/><Buttonandroid:layout_marginTop="20px"android:layout_marginLeft="50px"android:layout_marginRight="50px"android:id="@+id/btn_play_pause"android:text="播放"android:layout_width="match_parent"android:layout_height="wrap_content" />
</LinearLayout>

//应用

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);final GramophoneView gramophoneView = (GramophoneView)findViewById(R.id.gramophone_view);final Button button = (Button)findViewById(R.id.btn_play_pause);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if(gramophoneView.getPlaying()){button.setText("点击播放");}else{button.setText("点击暂停");}gramophoneView.setPlaying(!gramophoneView.getPlaying());}});}
}

Android自定义View——仿网易云音乐留声机效果相关推荐

  1. 自定义View实现网易云音乐留声机效果(代码区)

    //绘制旋转了指定角度的唱针private void drawNeedle(Canvas canvas,int degree){//移动坐标到水平中点canvas.save();canvas.tran ...

  2. Android自定义View分享——仿网易云音乐留声机效果

    写在前面 这是笔者自学习自定义View以来,分享的第五篇效果,之前分享过一篇动态时钟效果的自定义View,如果有兴趣的可以看看: Android自定义View分享--一个时钟 之前的博客笔者一般都会说 ...

  3. android bitmap转图片_带你用Android自定义View实现网易云音乐宇宙尘埃特效

    作者:Mlx, 链接:https://juejin.im/post/6871049441546567688 前言 前段时间,女朋友用网易云音乐的时候看到一个宇宙尘埃特效,说很好看,想要让我给她开VIP ...

  4. Android自定义view之网易云推荐歌单界面

    系列文章目录 Android自定义view之网易云推荐歌单界面 文章目录 系列文章目录 前言 一.实现 1.自定义一个圆角图片控件(也可直接使用第三方框架) 2.进行布局摆设 3.图片切换动画效果 二 ...

  5. Android漂亮的音乐歌词控件,仿网易云音乐滑动效果

    前言: 项目有个音乐播发器功能,实现音乐在线播放,同时需要带有歌词显示功能.网上也找过,在github找到勉强能用的控件,只是效果还是差强人意,不是特别好.于是趁有空的时间,参考了网上的部分demo, ...

  6. android 高仿 探探卡片滑动,Android自定义View仿探探卡片滑动效果

    Android自定义View仿探探卡片滑动这种效果网上有很多人已经讲解了实现思路,大多都用的是RecyclerView来实现的,但是我们今天来换一种实现思路,只用一个自定义的ViewGroup来搞定这 ...

  7. 自定义View之网易云音乐听歌识曲水波纹动画

    先上效果图 点击中间的按钮后,像外发散水波纹,再次点击水波纹消失. 实现原理 当点击按钮后,我们隔一段时间执行一个RippleCircleView的动画,动画包括扩大和透明度,通过PropertyVa ...

  8. Android 仿网易云音乐App

    因为工作实在是有点忙,所以还没完成成品,就先挂到GitHub上.日后慢慢更新啦. 项目地址 GitHub地址,希望大佬们点个star GitHub仿网易云音乐App 效果展示 注:因为视频太模糊,每日 ...

  9. android仿网易云音乐、即时通讯、bilibili、沙漏动画等源码

    Android精选源码 android仿网易云音乐安卓版源码 android开源即时通讯,实时传讯IM源码 android类似淘宝的商品详情页源码 android面向.艺术家.设计师等创意类作品源码 ...

最新文章

  1. 数据连接linux网络编程之TCP/IP基础(四):TCP连接的建立和断开、滑动窗口
  2. python中关于sqlite3数据库更新数据的使用
  3. 伊利诺伊大学厄巴纳-香槟分校
  4. 21天学通python电子版-小数据池,深浅拷贝,集合+菜中菜
  5. X86嵌入式主板在IOT网关产品的应用
  6. OpenGL开发学习指南二(glfw+glad)
  7. 邀请合作如何表达_适时表达想法 才有利于彼此的合作
  8. php笔记之-laravel-Redis hash
  9. html5动态切换class,uni-app v-for循环遍历 动态切换class、动态切换style
  10. 服务器iis的作用,IIS是什么 IIS服务组件有什么作用
  11. Android视频录制之NV21和NV12
  12. 关于vba word的一些用法
  13. ppt复制切片器_听说你还不会用切片器?比筛选好用100倍,小白也能学会!
  14. 浅谈视频编解码器的工作原理和应用领域
  15. 计算机系统与外部交换信息主要通过显示器,微机系统与外部交换信息主要通过什么设备...
  16. 【多任务CTR】阿里ESMM:Entire Space Multi-Task Model: An Effective Approach for Estimating Post-Click Conve
  17. php a标签加nofollow,Z-Blog给文章所有的站外a链接添加nofollow的方法
  18. 计算机绘画社团活动教案,电脑绘画社团课教师教案.doc
  19. 这几年我看过的书,力荐书单(含技术和非技术)
  20. 谈一谈对JS闭包的理解

热门文章

  1. MATLAB 跳出循环
  2. JS 实现鼠标指向图片时图片放大的效果
  3. php中socket的使用,php中socket的用法详解,phpsocket详解_PHP教程
  4. 用java编写cs游戏_Java CS训练小游戏
  5. “法拉第笼”的笑话背后,是欧美民众无处安放的新闻信任感
  6. 50行代码复制多级文件夹--Java //正常人推荐Ctrl+c/v 手动狗头
  7. 小程序开发与网页开发的区别
  8. 最炫楼宇,今天做了这件最“拉风的事”……
  9. 8.利用通道差异抠取人物头发与玩具
  10. html中美化右侧滑动条,美化css滚动条样式,就这么简单