仿酷狗歌词的滚动效果

先上图:
这里写图片描述

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


}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值