android 仿全民k歌 线谱乐谱音高图

全民k歌大家都不陌生吧,在嗨歌时有一个线谱样式的动画效果是不是很吸引人呢。

 效果似乎很复杂,感觉上非自定义view莫属了,然而如何处理滑动、如何处理颜色、如何处理多段线条、如何处理数据变化......等都搞好了准备写的时候才发现————一个星期过去了......?

其实如果把每条线都当做简单的自定义view你会发现:就是一个RecyclerView+一条线而已(由于rv涉及到复用、重绘,当自己自定义时如果使用不当会出现各种问题,对于新手可以使用ScrollView+自定义View的实现方式,这样只要一次性初始化完遇到刷新调用invalidate就行了,不需要复用和重绘,数百个自定义的线只会比rv多5M内存。思路同下,具体实现就相对简单多了,可以自己试试)

 

思路:一个一直滑动不可拖动的rv+可以变颜色的自定义view

由于代码不算太多(强忍不说)直接贴出成品吧:

public class KGeActivity extends BaseActivity {@BindView(R.id.fl_KGeXian)FrameLayout mFl;@BindView(R.id.view_KGeXian_Xian)View mViewXian;RecyclerView mRv;private BaseAdapterRvList<BaseViewHolder, LineData> mAdapter;private StartAnimat mAnimat = new StartAnimat();//滑动动画private int mRvWidths;//rv的总长度,计算得来private int mViewMargin = 300;//分割线距左边的位置/*** 音乐总时长*/private int mMusicTime = 100_000;/*** 声谱对应控件的信息*/private ArrayList<LineData> mList = new ArrayList<>();@Overrideprotected int getLayouRes() {//等同于setContentViewreturn R.layout.activity_k_ge;}//等同于onCreate@Overrideprotected void initData() {//由于普通操作无法完全屏蔽事件,此处直接重写rv拦截全部事件mRv = new RecyclerView(this) {@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {return true;}};mFl.addView(mRv, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);mRv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));//初始化adaptermAdapter = new BaseAdapterRvList<BaseViewHolder, LineData>(this) {//等同于bind@Overrideprotected void onBindVH(BaseViewHolder baseViewHolder, int i, LineData lineData) {XianView xv = (XianView) baseViewHolder.itemView;xv.setData(lineData);}//等同于creat@NonNull@Overrideprotected BaseViewHolder onCreateVH(ViewGroup viewGroup, LayoutInflater layoutInflater) {XianView xv = new XianView(getActivity());xv.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));return new BaseViewHolder(xv);}};//添加一个偏移的headerView nullView = new View(this);nullView.setLayoutParams(new RecyclerView.LayoutParams(mViewMargin, 1));mAdapter.addHeaderView(nullView);mRv.setAdapter(mAdapter);mViewXian.post(new Runnable() {@Overridepublic void run() {ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mViewXian.getLayoutParams();params.leftMargin = mViewMargin;mViewXian.setLayoutParams(params);}});//添加模拟数据LineData ld1 = new LineData();//开始空白ld1.lineLength = 1000;ld1.noData = true;mList.add(ld1);for (int i = 0; i < 100; i++) {LineData xd = new LineData();xd.lineY = i % 5 * 50;xd.lineLength = (i % 5 + 1) * 50;xd.noData = i % 8 == 0;mList.add(xd);}LineData ld2 = new LineData();//结束空白ld2.lineLength = 1000;ld2.noData = true;mList.add(ld2);mRvWidths = 0;for (LineData lineData : mList) {mRvWidths += lineData.lineLength;}mAdapter.setListAndNotifyDataSetChanged(mList);//等同于刷新数据//开启滑动mAnimat.start();//随机k歌匹配suiJi();}/*** 随机生成匹配数据*/private void suiJi() {new Thread() {@Overridepublic void run() {super.run();while (true) {try {sleep((long) (Math.random() * 1000));//模拟匹配失败} catch (InterruptedException e) {e.printStackTrace();}final long now1 = System.currentTimeMillis();if (now1 - mAnimat.mStartTime >= mMusicTime) {return;//结束}//模拟匹配成功final int oneTimeLength = 50;for (int i = 0; i < (int) (Math.random() * 40) + 20; i++) {try {sleep(oneTimeLength);} catch (InterruptedException e) {e.printStackTrace();}final long now2 = System.currentTimeMillis();runOnUiThread(new Runnable() {@Overridepublic void run() {successSing(now2, oneTimeLength);//告诉主线程,有匹配成功的数据来了}});}}}}.start();}/*** 用户某段唱成功了** @param endTime    结束时间* @param timeLength 持续时间*/private void successSing(long endTime, int timeLength) {//唱对的这段在rv的位置=rv总长度*时间比例int startWidth = (int) (mRvWidths * (endTime - timeLength - mAnimat.mStartTime) / mMusicTime);int endWidth = (int) (mRvWidths * (endTime - mAnimat.mStartTime) / mMusicTime);int currentWidth = 0;//当前正在遍历item的起始点for (int i = 0; i < mList.size(); i++) {LineData lineData = mList.get(i);int lineEnd = currentWidth + lineData.lineLength;if (startWidth >= currentWidth && startWidth < lineEnd) {//相交,成功的在右侧部分或被包含if (endWidth > lineEnd) {//相交于右侧addKSizeInfo(lineData.kSizeInfo, startWidth - currentWidth, lineData.lineLength);} else {//整个被包含addKSizeInfo(lineData.kSizeInfo, startWidth - currentWidth, endWidth - currentWidth);}mAdapter.notifyDataSetChanged();//notifyItemChanged局部刷新有闪动} else if (currentWidth >= startWidth && currentWidth < endWidth) {//相交,成功的在左侧部分或包含整个if (lineEnd > endWidth) {//相交于左侧addKSizeInfo(lineData.kSizeInfo, 0, endWidth - currentWidth);} else {//包含整段addKSizeInfo(lineData.kSizeInfo, 0, lineData.lineLength);}mAdapter.notifyDataSetChanged();//notifyItemChanged局部刷新有闪动} else if (endWidth < currentWidth) {//遍历过头了break;}currentWidth = lineEnd;//结束继续下一个循环}}/*** 合并里面的集合*/private void addKSizeInfo(List<int[]> kSizeInfo, int start, int end) {if (kSizeInfo.size() > 0) {int[] ints = kSizeInfo.get(kSizeInfo.size() - 1);if (ints[1] - start >= -1) {//重合就合并成一个ints[1] = end;} else {kSizeInfo.add(new int[]{start, end});}} else {kSizeInfo.add(new int[]{start, end});}}/*** 根据音乐时间和list数据均匀滑动*/class StartAnimat {private long mStartTime;//启动时间private int mLastX;//当前滑动的长度private Runnable mRun = new Runnable() {@Overridepublic void run() {if (isFinishing() || !mRv.canScrollHorizontally(1)) {Utils.toast("结束");return;}double now = System.currentTimeMillis();int nowX = (int) (mRvWidths * (now - mStartTime) / mMusicTime);mRv.scrollBy(nowX - mLastX, 0);//rv只有bymLastX = nowX;ViewCompat.postOnAnimation(mRv, mRun);//循环移动}};/*** 开启滑动*/public void start() {mStartTime = System.currentTimeMillis();mLastX = 0;ViewCompat.postOnAnimation(mRv, mRun);}}@Overrideprotected void setListener() {}public static class XianView extends View implements ViewInterface {//线高private int mLineHeight = 10;private Paint mPaint;private LineData mData;public XianView(Context context) {this(context, null, 0);}public XianView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public XianView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initData();initAttrs(attrs);}@TargetApi(Build.VERSION_CODES.LOLLIPOP)public XianView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);initData();initAttrs(attrs);}@Overridepublic void initData() {//此处要inflate,不需要可以删掉黄油刀ButterKnife.bind(this);//注册黄油刀mPaint = new Paint();mPaint.setStyle(Paint.Style.FILL_AND_STROKE);mPaint.setStrokeWidth(mLineHeight);}/*** 简单使用,高度直接写死,具体多高自行判断*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mData != null && mData.lineLength > 0) {//有宽度直接设置super.onMeasure(MeasureSpec.makeMeasureSpec(mData.lineLength, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Utils.dip2px(getContext(), 100), MeasureSpec.EXACTLY));} else {//没宽度直接是0super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Utils.dip2px(getContext(), 100), MeasureSpec.EXACTLY));}}@Overridepublic void initAttrs(@Nullable AttributeSet attrs) {}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (mData == null || mData.noData) {return;}mPaint.setColor(0xffdddddd);//灰int h_2 = mLineHeight / 2;canvas.drawLine(h_2, mData.lineY + h_2, mData.lineLength + h_2, mData.lineY + h_2, mPaint);if (mData.kSizeInfo != null) {mPaint.setColor(0xffff00ff);//红for (int[] kSize : mData.kSizeInfo) {canvas.drawLine(kSize[0] + h_2, mData.lineY + h_2, kSize[1] + h_2, mData.lineY + h_2, mPaint);}}}/*** 设置或刷新数据*/public void setData(LineData data) {if (mData != null && mData.lineLength == data.lineLength) {//宽度不变只需要重绘即可mData = data;invalidate();} else {//宽度改变需要重新加载布局mData = data;requestLayout();}}/*** 设置线的厚度*/public void setLineHeight(int heightPx) {mLineHeight = heightPx;mPaint.setStrokeWidth(mLineHeight);}}public static class LineData {/*** 线距上的距离*/public int lineY;/*** 线的最大长度*/public int lineLength;/*** 用户k歌时匹配正确的信息{开始位置,结束位置}*/public List<int[]> kSizeInfo = new ArrayList<>();/*** 空白数据,不需要唱时为true,{@link #lineLength}为等待的长度*/public boolean noData = false;}
}

使用的adapter见这篇:https://blog.csdn.net/weimingjue/article/details/88190755

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/c_fff"android:gravity="center_horizontal"android:orientation="vertical"><FrameLayoutandroid:id="@+id/fl_KGeXian"android:layout_width="match_parent"android:layout_height="match_parent"><!--margin代码修改--><Viewandroid:id="@+id/view_KGeXian_Xian"android:layout_width="1dp"android:layout_height="100dp"android:layout_marginLeft="1dp"android:background="@color/c_w_333"/></FrameLayout>
</LinearLayout>

效果图

 是不是感觉很简单呢?(刚填完黑洞的博主轻松的向大家挥手)

坑已经帮大家填完了,具体暂停操作、数据处理等细节就自行解决吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/71135.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Guitar Pro8.1升级版本新功能支持编写简谱

很多人在听到Guitar Pro这个名词时&#xff0c;本能反应就是跟吉他有关的软件吧&#xff0c;但是具体是什么样子&#xff0c;有什么功能我们却不一定知道的那么详细&#xff0c;下面呢&#xff0c;我们就来详细的介绍下Guitar Pro这款软件。 Guitar Pro是初学作曲&#xff0c;特…

钢琴转谱技术(MP3转MIDI)

代码(by 字节跳动孔大佬)&#xff1a; https://github.com/qiuqiangkong/piano_transcription_inference 使用&#xff1a; 1.安装package pip install piano_transcription_inference2.下载预训练模型 https://zenodo.org/record/4034264 3.运行代码 from piano_transcri…

midi转简谱_音乐小技巧分享:五线谱转简谱与MIDI的快速方法

把五线谱转成简谱或者MIDI,一个一个音符的手打校对是非常浪费时间的。无意中我研究出了一个方法,通过几个操作来快速地实现五线谱转制成简谱或者MIDI,现在分享给大家。 最常见的转换需求一般是以下这几种情况: 1、喜欢弹钢琴没有什么基础,而对五线谱反应很慢的朋友,有了简…

ChatGPT与软件架构(2) - 基于Obsidian和GPT实现解决方案架构自动化

磨刀不误砍柴工&#xff0c;良好的工具可以有效提高效率。本文介绍基于Obsidian和GPT打造架构知识库的思路&#xff0c;为架构师提供整理、分享、原文: Solution Architecture Automation With Obsidian and GPT Stas Parechyn Unsplash 本文介绍的ArchVault是一个旨在帮助架构…

uniapp 实现微信聊天效果 阻止input失焦

前景&#xff1a;需要开发一个聊天系统&#xff0c;界面需要和微信一样&#xff0c;输完内容直接点发送内容&#xff0c;内容发送完成&#xff0c;但input不失焦&#xff0c;发送可以用input的confirm事件执行&#xff0c;也可以是在别的dom上绑定发送。遇到两个难点 点发送in…

开发者选项看html,手机怎么打开开发者选项?开发者模式开启方法

在手机的设置中隐藏着一个开发者选项&#xff0c;进入其中可以使用一些开发者要用到的设置&#xff0c;也可以通过设置进行硬件加速等操作&#xff0c;下面就教大家如何进入手机开发者模式。 1、经过对比&#xff0c;发现各品牌的手机打开开发者选项的方法都大同小异&#xff0…

35+大龄程序员从焦虑到收入飙升:我的搞钱副业分享。

37岁大龄程序员&#xff0c;一度觉得自己的职场生涯到头了。既没有晋升和加薪的机会&#xff0c;外面的公司要么接不住我的薪资&#xff0c;要么就是卷得不行&#xff0c;无法兼顾工作和家庭&#xff0c;感觉陷入了死局…… 好在我又重新振作起来&#xff0c;决定用副业和兼职填…

毕业论文html代码查重吗,毕业论文中的代码内容重复了怎么办? 毕业论文代码重复率高...

毕业论文中的代码内容重复了怎么办&#xff1f; 毕业论文代码重复率高 发布时间&#xff1a;2021-04-17 09:00:09 作者&#xff1a;知网小编 多理科的学生在写毕业论文的时候&#xff0c;可能会涉及到论文中代码的内容。例如&#xff0c;在计算机、设计等相关专业领域&#xff…

三星被曝出现漏洞,手机会随机发送照片给别人

随着手机在人们的生活中越来越密不可分&#xff0c;通过一部手机获得用户的隐私数据就成了很简单的事情。近期&#xff0c;部分搭载升降式镜头的手机&#xff0c;会在没有开启镜头的状况下弹出镜头&#xff0c;被用户认为有监控的嫌疑&#xff1b;此外三星部分机型也出现了漏洞…

继英伟达后,三星也遭勒索攻击,190GB数据和源代码遭泄密

3 月 6 日消息&#xff0c;被黑客Lapsus$ 团队攻击事件迎来了新的进展。 据显示统计&#xff0c;在这次网络攻击中&#xff0c;Lapsus$ 从英伟达获取到的数据超过 了1TB &#xff0c;包括原理图、驱动程序和固件细节。 还有 71355 名员工的电子邮件地址和 NTLM 密码等敏感工作…

如何获取铁粉

忽然发现我的铁粉从100变成了540&#xff0c;分享下我的经验&#xff0c;我觉得可能是我的机器人经常互动的问题&#xff0c;结合自己的看法和平台大佬的想法一些进行了梳理&#xff1a; 在当今社交媒体时代&#xff0c;吸引和保留铁粉&#xff08;忠实粉丝&#xff09;对于个…

【改BUG】项目遇到的奇葩bug

问题 今天&#xff0c;我的下级代理联系我说&#xff0c;我们的平台&#xff0c;应用服务批量导入后&#xff0c;用户密码含有“0”的都不显示&#xff0c;例如密码是“07121239”但是平台只显示“7121239”&#xff0c;今天做了下排查&#xff0c;本文仅记录一下今天排查的经…

跨越语言的艺术:Weblogic序列化漏洞与IIOP协议

0x01 概述 Weblogic 的序列化漏洞主要依赖于 T3 和 IIOP 协议&#xff0c;这两种协议在通信交互的过程中存在如跨语言、网络传输等方面的诸多问题&#xff0c;会给漏洞的检测和利用带来许多不便。在白帽汇安全研究院的理念中&#xff0c;漏洞检测和利用是一项需要创造性的工作…

chatgpt赋能python:Python波动方程介绍:掌握物理模型与实际应用

Python波动方程介绍&#xff1a;掌握物理模型与实际应用 在物理学中&#xff0c;波动方程是一种描述波动现象的数学模型。而在工程学中&#xff0c;波动方程也被广泛应用于声波、电磁波和弹性波等领域。Python是一种高效且易于学习的编程语言&#xff0c;因此被广泛用于模拟和…

【uniapp】实现买定离手小游戏

前言 最近玩了一个小游戏&#xff0c;感觉挺有意思&#xff0c;打算放进我的小程序【自动化小助手】里面&#xff0c;“三张押一张&#xff0c;专押花姑娘&#xff01;”&#xff0c;从三张卡牌&#xff0c;挑选一张&#xff0c;中奖后将奖励进行发放&#xff0c;并且创建下一…

如何写出高质量代码:特征、编程实践技巧和软件工程方法论

一、 前言 在当今的软件开发行业中&#xff0c;写出高质量代码是每个开发者都应该追求的目标。高质量代码不仅能提升我们自身的编程水平和工作效率&#xff0c;还能减少代码维护和管理的难度&#xff0c;为项目的长期发展奠定坚实的基础。然而&#xff0c;要写出高质量代码并不…

国考省考行测:细节理解,对错判断,要素查找,问什么,找什么,对比分析

国考省考行测&#xff1a;细节理解&#xff0c;对错判断&#xff0c;要素查找&#xff0c;问什么&#xff0c;找什么&#xff0c;对比分析 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要…

想去银行测试?那这套题目你必须要会

一、根据题目要求写出具体LINUX操作命令 1、分别写出一种查看主机IP地址、CPU使用率、内存使用率的命令。 ifconfig top 2、进入/wls/applogs目录&#xff0c;显示该目录下所有文件详细信息&#xff0c;并按照文件变更时间排序 cd /wls/applogs ls -lt 3、在后台运行目录下…

【银行测试】必看的四类题型:这可是最经典的一套题目了

目录&#xff1a;导读 一、根据题目要求写出具体LINUX操作命令 二、JMETER题目 三、根据题目要求写出具体SQL语句 四、测试案例设计题 金三银四面试面对大厂面试官提问&#xff0c;如何回答&#xff1a;花3天背完这100道软件测试面试题&#xff01;银行测试的offer还不是手…

政考网:公务员考试拿不到高分,因为你的刷题姿势不对

众所周知&#xff0c;公务员考试竞争是十分激烈的&#xff0c;如果你想成功上岸&#xff0c;首先就要拿到一个比较优秀的笔试分值&#xff0c;这样你才有进行到面试的资格。但是又有很多同学经常说&#xff0c;自己也付出了不少的努力&#xff0c;为什么却拿不到高分呢。关于这…