Android音乐播放器开发小记——功能实现2

项目源码
https://github.com/dogmeng/littleyunmusic
第二部分 自定义控件的实现
主要有主页滑动条MoveLine,播放页面PlayRoundView,歌词页面LrcView,及一些简单的自定义输入框LoginEditText和自定义圆形或圆角矩形CircleImageView的实现.
MoveLine和PlayRoundView的实现过程都用到了贝塞尔曲线(二阶和三阶),相关文章也很多,这里不再一一说明.重点介绍控件的实现思想.
MoveLine:随手指滑动的距离,由弧形变直线再变弧形.也就是说,滑动距离影响控制弧形的三个点的位置.
如图,弧形部分即为moveline

图片发自简书App

图片发自简书App

图片发自简书App

图片发自简书App

在MoveLine的onDraw方法中:

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        mPath.reset();
//渐变
        mShader = new LinearGradient(startX, 0, startX, controlY/2, tabColor, themeColor, Shader.TileMode.CLAMP);
        mPaint.setShader(mShader);
//绘制弧形
        mPath.moveTo(startX, 0);
        mPath.quadTo((endX-startX)/2+startX, controlY, endX, 0);
        canvas.drawPath(mPath, mPaint);     
    }
//由外界传入,开始位置,结束位置,和y轴的缩放比例
    public void setPosition(float startX,float endX,float speed){
        this.startX = startX;
        this.endX = endX;
        this.controlY = height*speed;
        invalidate();
    }

因为本项目中的滑动是有页面中的viewpager控制的,所以就在viewpager的滑动监听中,来设置这几个参数

    class MainViewPagerListener implements ViewPager.OnPageChangeListener{
        private int lastPosition = -1;
        @Override
        public void onPageScrollStateChanged(int arg0) {
            // TODO Auto-generated method stub
            //0:挂起 1:正在滑动 2:滑动完毕
        }

        @Override
        public void onPageScrolled(int arg0, final float arg1, int arg2) {
            // TODO Auto-generated method stub
            //agr0:当前页面 arg1:当前页面偏移百分比 arg2:当前页面偏移的像素位置
            //右滑从1到0,position为小的
            if(arg2!=0&&arg2 < lastPosition){
                if(arg1>0.5f){
                    startX = (int) (sideWidth+ tabWidth*(arg0+1)-(tabWidth*(1-arg1)*2));
                    endX = (int) (sideWidth+ tabWidth*(arg0+2));
                    moveLine.setPosition(startX,endX, (2*arg1-1));
                }else if(arg1<=0.5f){
                    startX = (int) (sideWidth+ tabWidth*arg0);
                    endX = (int) (sideWidth+ tabWidth*(arg0+2)-(tabWidth*(1-2*arg1)));
                    moveLine.setPosition(startX,endX, (1-2*arg1));
                }
            }
            //左滑从0到1 突变为0,position为小的,突变为大的
            if(arg2!=0&&arg2 > lastPosition){
                if(arg1<0.5f){
                    startX =(int) (sideWidth+ tabWidth*arg0);
                    endX =(int) (sideWidth+tabWidth*(arg0+1)+(tabWidth*arg1*2));
                    moveLine.setPosition(startX,endX, (1-2*arg1));
                }else if(arg1>=0.5f){
                    endX = (int)(sideWidth+tabWidth*(arg0+2));
                    startX =(int) (sideWidth+ tabWidth*arg0+(tabWidth*(2*arg1-1)));
                    moveLine.setPosition(startX,endX, (2*arg1-1));
                }
            }
            lastPosition = arg2;
        }

PlayRoundView:整体是三个圆形的叠加,圆形的一半沿着随机的方向向外扩展,到一定距离后,出现不规则分布的小点点.在这里,圆形外扩的参数由属性动画来控制,然后对canvas进行随机旋转,这样坐标参数就比较简单,容易控制.


图片发自简书App

首先初始化画笔和圆形坐标

    private void init(){
        themeColor = ThemeManager.getCurrentColor(context);
//初始化圆形画笔
        mPaint = new Paint();
//初始化小点点画笔
        starPaint = new Paint();
        starPaint.setAntiAlias(true);
        starPaint.setStrokeWidth(10);
        starPaint.setStrokeCap(Cap.ROUND);
        starPaint.setColor(themeColor);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(themeColor);
        mPath = new Path();     
        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        defaultwidth = wm.getDefaultDisplay().getWidth();
        defaultheight = wm.getDefaultDisplay().getHeight()*2/3;

        mRadius = defaultwidth/6;
        //初始化圆形坐标
        p5 = new PointF(mRadius * bezFactor,-mRadius);
        p6 = new PointF(0, -mRadius);
        p7 = new PointF(-mRadius * bezFactor, -mRadius);
        
        p0 = new PointF(0, mRadius);
        p1 = new PointF(mRadius * bezFactor, mRadius);
        p11 = new PointF(-mRadius * bezFactor, mRadius);

        p2 = new PointF(mRadius, mRadius * bezFactor);
        p3 = new PointF(mRadius, 0);
        p4 = new PointF(mRadius, -mRadius * bezFactor);

        p8 = new PointF(-mRadius, -mRadius * bezFactor);
        p9 = new PointF(-mRadius, 0);
        p10 = new PointF(-mRadius, mRadius * bezFactor);  
//设置小点点出现的区域
        starRect = new RectF(mRadius, -mRadius, defaultwidth/4, mRadius);
//初始化小点点的集合
        for(int i = 0;i<16;i++){
            starList.add(new PointF());
        }
    }

在onDraw()中进行绘制:

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
//移动坐标中心到圆心     
        canvas.translate(defaultwidth/2, width/2+defaultwidth/8);
        canvas.save();
//旋转坐标轴
        canvas.rotate(rotate);
//如果到达顶点则显示小点点
        if(showStar){
            for(int i = 0;i<starList.size()/2;i++){
                starPaint.setAlpha((int) (255*move/drag));
                canvas.drawPoint(starList.get(i++).x, starList.get(i++).y, starPaint);
                starPaint.setAlpha((int) (150*move/drag));
                canvas.drawPoint(starList.get(i).x, starList.get(i).y, starPaint); 
            }
        }
//画圆形
        bounce2RightRound(canvas);
        canvas.restore();
    }
    private void bounce2RightRound(Canvas canvas) {
//根据属性动画提供的move值,进行动态绘制
        for(int i = 0;i<3;i++){         
            mPaint.setAlpha(100+50*i);
            mPath.reset();
            mPath.moveTo(p0.x, p0.y-stroke*i);
            mPath.cubicTo(p1.x-stroke*i, p1.y-stroke*i, p2.x-stroke*i+move, p2.y-stroke*i, p3.x -stroke*i+move, p3.y);
            mPath.cubicTo(p4.x-stroke*i+move, p4.y+stroke*i, p5.x-stroke*i, p5.y +stroke*i, p6.x, p6.y +stroke*i);
            mPath.cubicTo(p7.x+stroke*i, p7.y+stroke*i, p8.x+stroke*i, p8.y+stroke*i, p9.x+stroke*i, p9.y);
            mPath.cubicTo(p10.x+stroke*i, p10.y-stroke*i, p11.x+stroke*i, p11.y-stroke*i, p0.x, p0.y-stroke*i);
            mPath.close();
            canvas.drawPath(mPath, mPaint);
        }
    }

属性动画中的move值

    class DragAnimator extends BaseAnimator{
        private float lastValue = 0;
        private int count = 0;
        public DragAnimator(View target, float startValue, float endValue,float thirdValue) {
            super(target, startValue, endValue,thirdValue);
            // TODO Auto-generated constructor stub
        }

        @Override
        protected void doAnim(float animatedValue) {
            // TODO Auto-generated method stub
            move = animatedValue;
            if(move-lastValue<0){
                showStar = true;
            }else{
                showStar = false;
            }
            lastValue = move;
            PlayRoundView.this.invalidate();
        }
    }

LrcView:歌词滚动控件,因目前未实现网路下载功能,所以歌词暂且用测试数据代替,来演示效果.


图片发自简书App

歌词的滚动来自两个方面的控制,一是播放时,自动滚动到当前播放行,这个可以用一个handler每隔1秒发送message来检测当前播放时间和歌词所在行时间,如果匹配,就设置当前行为中心行.另一个是用手指滑动来设置当前行.onDraw的重点在于从中心行开始绘制,然后画上半部分和下半部分,这样可以避免绘制无用的页面外的行.绘制过程中根据滑动距离,不断移动横坐标的位置.当移动到下一行后,重置横坐标位置,然后继续绘制.整个过程就是这样反复进行.具体如下:

@Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//重置数据
            if(isFirst){
                distanceY = 0;
                isFirst = false;
            }
            if(isLrc()){
                if(distanceY>0){
                    isTop = true;
                    if(centerLine ==  currentLrc.size()-1){
                        return false;
                    }                   
                }else if(distanceY<0){
                    isTop = false;
                    if(centerLine == 0){
                        offset = 0;
                        m = 0;
                        return false;
                    }
                }
//总移动距离
                mOffset += distanceY;            
                offset = Math.abs(mOffset);
//中心行高度
                int x = (int) staticLayouts.get(centerLine).getHeight();            
                y =  Math.abs(offset-m);
//当移动距离大于两行之间的高度时,重新设置centerLine和y的值
                if(y -(x+textSize)>=0){                                 
                    if(isTop){
                        m = offset;
                        centerLine = centerLine+1 >= currentLrc.size()-1 ? currentLrc.size()-1:centerLine+1;
                    }else{
                        m = offset;
                        centerLine = centerLine-1 <= 0 ? 0:centerLine-1;
                    }
                    y = 0; 
                }else{
                    if(!isTop){
                        y=-y;
                    }
                }
                invalidate();
            }           
            return true;
        }
@Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.translate(getPaddingLeft(), height/2);
        if(isLrc()){            
                //画中间的 y为正上移,y为负,下移         
                canvas.save();
                canvas.translate(0, -y);
                mContentPaint.setTextSize((float)(textSize*1.1));   
                mContentPaint.setColor(themeColor);
                staticLayouts.get(centerLine).draw(canvas);
                canvas.restore();           
                mContentPaint.setTextSize(textSize);    
                mContentPaint.setColor(getResources().getColor(R.color.toolbarTextColor));
                //画上面的
                if(centerLine>0){
                    int num = centerLine -1;
                    int top = 0;
                    while(top<height/2&&num>=0){
                        canvas.save();
                        top += 120+staticLayouts.get(num).getHeight();
                        canvas.translate(0, -top-y);
                        staticLayouts.get(num--).draw(canvas);
                        canvas.restore();
                    }                   
                }
                //画下面的
                if(centerLine<currentLrc.size()-1){
                    Log.i("下面的centerLine", centerLine+"");
                    int num1 = centerLine;
                    int bottom = 0;
                    while(bottom<height/2&&num1<currentLrc.size()-1){
                        canvas.save();
                        bottom += 120+staticLayouts.get(num1).getHeight();
                        canvas.translate(0, bottom-y);
                        staticLayouts.get(++num1).draw(canvas);
                        canvas.restore();
                    }
                }

        }else{
            textSize  = 70;
            mContentPaint.setTextSize(textSize);    
            mContentPaint.setTextAlign(Align.CENTER);
            mContentPaint.setColor(themeColor);
            canvas.drawText("暂无歌词", 0, 0, mContentPaint);
        }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容