先上图:
我只是做了个假的效果,真正做的时候需要根据当前歌曲的进度判断歌词扫描的进度;
原理是: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;
}