全民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>

效果图

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

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

android 仿全民k歌 线谱乐谱音高图相关推荐

  1. android 耳返解决方案,vivo又出招手机音频领域:联合全民K歌发布首个安卓实时耳返方案...

    原标题:vivo又出招手机音频领域:联合全民K歌发布首个安卓实时耳返方案 通常我们到vivo手机,一般大家都会都会说的是其拍照方面的表现,其实并不然,vivo对于音乐方面也是非常重视的,可以vivo的 ...

  2. 仿制慕课网app实现斗鱼,全民k歌视频引导页(ViewVideoViewPaper)炫酷效果

    在几个月前,我第一次玩全民k歌,下载完app,它弹出来的引导页吸引了我,不像以前的引导页一样千篇一律,而是用了视频的方式,用一种动态的方式来实现.在今天,我突然又想起了这个效果,就抽出了一点时间在网上 ...

  3. 全民k歌下载|全民k歌app下载

    前年跳槽到广东,开始新环境,也要创业,没有了原来的圈子,应酬基本没有了,整天为项目忙的鸡飞狗跳.上个月偶然发现了全民k歌这款软件,还不禁赞叹现在科技这么厉害了,都可以不花钱唱歌了..很土是吧.. 全民 ...

  4. 怎样把音乐保存到计算机,全民K歌怎么将歌曲保存到电脑

    首先我以全民K歌为例,把作品分享到我的QQ好友,由此会得到一个链接 然后我们选择刚才发送的好友,打开这个链接 打开之后这里会有缓冲进度,等待缓冲完成 然后我们打开浏览器的"internet选 ...

  5. 来自腾讯的开源跨端开发框架,全民K歌和王者营地都在用

    项目名称:Hippy 项目作者:腾讯开源 开源许可协议:Apache-2.0 项目地址:https://gitee.com/Tencent/Hippy 项目简介 Hippy 是一个新生的跨端开发框架, ...

  6. 全民K歌误删歌曲位置

    在全民K歌上发了首歌,结果删掉动态发现歌也没了······ 以黑鲨手机为例: 1.打开文件,设置隐藏文件夹为可见 2.进入路径:Android/data/com.tencent.karaoke/fil ...

  7. 全民K歌内存篇2——虚拟内存浅析

    <全民K歌内存篇1--线上监控与综合治理> <全民K歌内存篇2--虚拟内存浅析> <全民K歌内存篇3--native内存分析与监控> 一.简介 在多任务操作系统中, ...

  8. 全民K歌直播推荐算法实践

    导读:直播是社交娱乐app的综合性变现工具,如何培养用户的心智,高效的建立用户和主播的多种连接 ( 点击.观看.关注.常看.常打赏 ) 是直播生态的重要问题之一.为了解决这个问题,各大平台所使用的方法 ...

  9. 全民K歌内容挖掘与召回

    分享嘉宾:timmyqiu 腾讯音乐 应用研究 编辑整理:郭真继 出品平台:DataFunTalk 导读:推荐系统一般分为两部分,召回阶段和排序阶段.召回阶段是从全量数据中挑选出用户可能感兴趣的一部分 ...

最新文章

  1. python爬虫requests-Python爬虫(三)Requests库
  2. #中regex的命名空间_Python命名空间实例解析
  3. 数据挖掘提分三板斧!
  4. boost::mpl模块实现find相关的测试程序
  5. [BUUCTF-pwn]——rip
  6. selenium之如何使用cssSelector定位页面元素
  7. 读jQuery源码释疑笔记
  8. CA周记 - 用 Visual Studio Code 做基于 .NET MAUI 跨平台移动应用开发
  9. java 线程开销_多线程的线程开销
  10. Android接收系统广播
  11. python爬虫可以做哪些好玩的地方_如何快速的找到好玩的旅游景点信息?Python爬虫帮你轻松解决...
  12. 航海王启航服务器维护要多久,《航海王启航》7月1日服务器更新维护公告
  13. 从头学计量-SPSS攻克常见信度检验1
  14. 用C++(附源码)制作一款功能强大,自动偷菜QQ农场辅助!
  15. Mac下禁用开机自启软件
  16. linux 帝国cms 刷新,帝国cms怎么自动刷新网站首页?(帝国CMS自动刷新首页的方法)...
  17. 计算机ms高级应用科目一 科目二考什么,驾考提前知 | 科目一、科目二、科目三、科目四都考什么?...
  18. An Auto-tuning Framework for Autonomous Vehicles
  19. 【434.a + b】
  20. CCC3.0学习笔记_认证和隐私保护

热门文章

  1. MQTT协议数据包结构
  2. 求助大神:用C#编辑计算器,怎么实现连加、连除功能啊!!!!!
  3. 中国通信服务上半年经营收入实现人民币538.34亿元
  4. 【智慧城市】时空云+空间规划,开启“智慧徐圩”新篇章
  5. Android Android应用资源 | 学习笔记
  6. Docker 部署 Flask
  7. Appium appium+python+jenkins 一套自动化测试框架搭建
  8. 关于默认网关不可用的问题
  9. php为什么二次开发,PHP二次开发容易吗?
  10. 应用 matlab二维图形 于版图设计