仿酷狗歌词的滚动效果
2016-06-21 13:49
525 查看
先上图:
我只是做了个假的效果,真正做的时候需要根据当前歌曲的进度判断歌词扫描的进度;
原理是:1. 自定义一个歌词的view,用来控制每行歌词的扫描进度
2.自定义一个viewGroup,控制歌词的上下滚动
3.通过延时消息控制1和2的交替运行
4.每次滚动,都判断最上面一行有没有到顶部,到顶部则隐藏之
activity的布局就是一个fFrameLayout包裹一个2中定义的自定义ViewGroup,就不贴出来了; activity代码如下:
下面是控制滚动的ViewGroup代码,滚动的原理是Scroller:
public class KrcListView extends LinearLayout {
最后是歌词的view代码:
我只是做了个假的效果,真正做的时候需要根据当前歌曲的进度判断歌词扫描的进度;
原理是:1. 自定义一个歌词的view,用来控制每行歌词的扫描进度
2.自定义一个viewGroup,控制歌词的上下滚动
3.通过延时消息控制1和2的交替运行
4.每次滚动,都判断最上面一行有没有到顶部,到顶部则隐藏之
activity的布局就是一个fFrameLayout包裹一个2中定义的自定义ViewGroup,就不贴出来了; activity代码如下:
public class LrcActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_lrc); findViews(); initData(); //首先第一句歌词需要展示在中间 scrollToCenter(); //开始滚动歌词 startScroll(); } private void startScroll() { Message message = Message.obtain(); message.what = ACTION_SWIPE; handler.sendMessage(message); } private void scrollToCenter() { krcListView.post(new Runnable() { @Override public void run() { krcListView.scrollToCenter(); } }); } private void initData() { KrcView krcView = null; for(int i=0;i<30;i++){ krcView = new KrcView(strList[i],this); krcListView.addView(krcView); } } private void findViews() { krcListView = (KrcListView) findViewById(R.id.krcListView); } private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case ACTION_SWIPE: post(swipeRunnable); break; case ACTION_SCROLL: post(scrollRunnable); break; case ACTION_REMOVE_TOP: krcListView.setTopInvisible(index); break; case ACTION_SCROLL_CENTER: krcListView.scrollToCenter(); break; } } }; private Runnable scrollRunnable = new Runnable() { @Override public void run() { index++; //遍历每行歌词的画笔颜色 krcListView.setChildPaint(index); if(index >= krcListView.getChildCount()){ //滚动到最后一行时,恢复到第一行的初始状态 Message message = Message.obtain(); message.what = ACTION_SCROLL_CENTER; handler.sendMessage(message); }else{ if(index >= KrcListView.MAX_SIZE / 2 - 1){ //隐藏最上面一行 Message message1 = Message.obtain(); message1.what = ACTION_REMOVE_TOP; handler.sendMessageDelayed(message1, 500); } //滚动一行 krcListView.scrollSingleHeight(); } //发送扫描的消息 Message message = Message.obtain(); message.what = ACTION_SWIPE; handler.sendMessage(message); } }; private Runnable swipeRunnable = new Runnable() { @Override public void run() { index = index >= krcListView.getChildCount() ? 0 : index; //找到当前选中的那行,并以每次百分之5的进度扫描 KrcView krcView = (KrcView)krcListView.getChildAt(index); krcView.swipeProgress(swipeProgress); krcView.invalidate(); Message message = Message.obtain(); if(swipeProgress >= 1.0f){ //如果该行扫描结束,则开始滚动 swipeProgress = 0.0f; message.what = ACTION_SCROLL; handler.sendMessage(message); }else{ //如果该行没有扫描结束,则进度加0.05,继续扫描 swipeProgress += 0.05f; message.what = ACTION_SWIPE; handler.sendMessageDelayed(message,100); } } }; private int index = 0; private float swipeProgress = 0.0f; private KrcListView krcListView; private static final int ACTION_SWIPE = 1; private static final int ACTION_SCROLL = 2; private static final int ACTION_REMOVE_TOP = 3; private static final int ACTION_SCROLL_CENTER = 4; private static final String[] strList = new String[]{ "对这个世界如果你有太多的抱怨", "跌倒了就不敢继续往前走", "为什麽人要这麽的脆弱 堕落", "请你打开电视看看", "多少人为生命在努力勇敢的走下去", "我们是不是该知足", "珍惜一切 就算没有拥有", "还记得你说家是唯一的城堡", "随着稻香河流继续奔跑", "微微笑 小时候的梦我知道", "不要哭让萤火虫带着你逃跑", "乡间的歌谣永远的依靠", "回家吧 回到最初的美好", "陈湘制作QQ:123154129", "不要这麽容易就想放弃", "就像我说的", "追不到的梦想", "换个梦不就得了", "为自己的人生鲜艳上色", "先把爱涂上喜欢的颜色", "笑一个吧 功成名就不是目的", "让自己快乐快乐这才叫做意义", "童年的纸飞机", "现在终於飞回我手里", "所谓的那快乐", "赤脚在田里追蜻蜓追到累了", "偷摘水果被蜜蜂给叮到怕了", "谁在偷笑呢", "我靠着稻草人吹着风唱着歌睡着了", "哦 哦 午后吉它在虫鸣中更清脆 " };
下面是控制滚动的ViewGroup代码,滚动的原理是Scroller:
public class KrcListView extends LinearLayout {
public KrcListView(Context context) { this(context,null,0); } public KrcListView(Context context, AttributeSet attrs) { this(context, attrs,0); } public KrcListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScroller = new Scroller(context); init(); } public void scrollToCenter() { //滚动到中心,使第一行的歌词居中 scrollTo(0, -getHeight() / 2 + getHeight() / (2 * MAX_SIZE)); int childCount = getChildCount(); int height = getHeight(); for (int i = 0; i < childCount; i++) { KrcView krcView = (KrcView) getChildAt(i); //设置每行歌词的高度 krcView.getLayoutParams().height = height / MAX_SIZE; //滚动到中心时,所有歌词可见(因为滚动一行之后,顶部的歌词会不可见,所以重置一下) if(krcView.getVisibility() != VISIBLE) krcView.setVisibility(VISIBLE); if(krcView.getNoCurrentAlpha() < 255){ krcView.setNoCurrentAlpha(1.0f); krcView.invalidate(); } } } @Override public void computeScroll() { super.computeScroll(); if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } } private void init() { setOrientation(VERTICAL); } public void setChildPaint(int index) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { KrcView krcView = (KrcView) getChildAt(i); krcView.isCurrent = index == i; int distance = Math.abs(index - i); //离当前歌词距离越远,画笔越透明 float alpha = 1.0f - 0.05f * distance; krcView.setNoCurrentAlpha(alpha); krcView.invalidate(); } } public void scrollSingleHeight() { if(getChildCount()==0) return; int childHeight = getChildAt(0).getHeight(); mScroller.startScroll(0,getScrollY(),0,childHeight,1000); invalidate(); } /**设置顶部的歌词不可见 **/ public void setTopInvisible(int index) { int topIndex = index-5; if(topIndex >= 0){ getChildAt(topIndex).setVisibility(INVISIBLE); } } private Scroller mScroller; public static final int MAX_SIZE = 11;
最后是歌词的view代码:
public class KrcView extends View { public KrcView(String krc, Context context) { super(context); this.context = context; lineStr = krc; init(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int height = getHeight(); //根据父布局给他的高度,适配字体大小 float textSize = height*0.62f; noCurrentPaint.setTextSize(textSize); currentPaint.setTextSize(textSize); currentPassPaint.setTextSize(textSize); //计算字体高度,ondraw时drawText用到,详细可见我之前的博客 Paint.FontMetrics fontMetrics = currentPassPaint.getFontMetrics(); textHeight = (int) Math.ceil(fontMetrics.bottom - fontMetrics.top); bottomY = fontMetrics.bottom; textWidth = (int) currentPassPaint.measureText(lineStr); } public void setNoCurrentAlpha(float alpha) { noCurrentPaint.setAlpha((int) (alpha*256)); } public int getNoCurrentAlpha() { return noCurrentPaint.getAlpha(); } public void swipeProgress(float swipeProgress) { isCurrent = true; progress = swipeProgress; } private void init() { //初始化非当前歌词的画笔 noCurrentPaint = new TextPaint(); noCurrentPaint.setAntiAlias(true); noCurrentPaint.setColor(Color.parseColor("#ffffff")); noCurrentPaint.setTextAlign(Paint.Align.CENTER); //初始化当前歌词的画笔 currentPaint = new TextPaint(); currentPaint.setAntiAlias(true); currentPaint.setColor(Color.parseColor("#ffffff")); currentPaint.setTextAlign(Paint.Align.CENTER); //初始化当前歌词扫描的画笔(黄色) currentPassPaint = new TextPaint(); currentPassPaint.setAntiAlias(true); currentPassPaint.setColor(Color.parseColor("#ffffff00")); currentPassPaint.setTextAlign(Paint.Align.CENTER); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //如果是当前的歌词,则需要画两边,一遍是普通的,一遍是扫描的 if(isCurrent){ canvas.drawText(lineStr, getWidth() / 2, getHeight() / 2 + textHeight / 2 - bottomY, currentPaint); canvas.save(); canvas.clipRect(0, 0, getWidth() / 2 - textWidth / 2 + textWidth * progress, getHeight()); canvas.drawText(lineStr,getWidth()/2,getHeight() / 2 + textHeight / 2-bottomY,currentPassPaint); canvas.restore(); }else{ canvas.drawText(lineStr,getWidth()/2,getHeight() / 2 + textHeight / 2-bottomY,noCurrentPaint); } } private String lineStr; private int textHeight; private int textWidth; private float bottomY = 0; private TextPaint currentPaint; private TextPaint currentPassPaint; private TextPaint noCurrentPaint; private float progress = 0.0f; public boolean isCurrent = true; private Context context; }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories