Android音樂播放器開發(fā)小記——功能實現(xiàn)2

項目源碼
https://github.com/dogmeng/littleyunmusic
第二部分 自定義控件的實現(xiàn)
主要有主頁滑動條MoveLine,播放頁面PlayRoundView,歌詞頁面LrcView,及一些簡單的自定義輸入框LoginEditText和自定義圓形或圓角矩形CircleImageView的實現(xiàn).
MoveLine和PlayRoundView的實現(xiàn)過程都用到了貝塞爾曲線(二階和三階),相關(guān)文章也很多,這里不再一一說明.重點介紹控件的實現(xiàn)思想.
MoveLine:隨手指滑動的距離,由弧形變直線再變弧形.也就是說,滑動距離影響控制弧形的三個點的位置.
如圖,弧形部分即為moveline

圖片發(fā)自簡書App

圖片發(fā)自簡書App

圖片發(fā)自簡書App

圖片發(fā)自簡書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);     
    }
//由外界傳入,開始位置,結(jié)束位置,和y軸的縮放比例
    public void setPosition(float startX,float endX,float speed){
        this.startX = startX;
        this.endX = endX;
        this.controlY = height*speed;
        invalidate();
    }

因為本項目中的滑動是有頁面中的viewpager控制的,所以就在viewpager的滑動監(jiān)聽中,來設(shè)置這幾個參數(shù)

    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:當(dāng)前頁面 arg1:當(dāng)前頁面偏移百分比 arg2:當(dāng)前頁面偏移的像素位置
            //右滑從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 突變?yōu)?,position為小的,突變?yōu)榇蟮?            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:整體是三個圓形的疊加,圓形的一半沿著隨機的方向向外擴展,到一定距離后,出現(xiàn)不規(guī)則分布的小點點.在這里,圓形外擴的參數(shù)由屬性動畫來控制,然后對canvas進行隨機旋轉(zhuǎn),這樣坐標(biāo)參數(shù)就比較簡單,容易控制.


圖片發(fā)自簡書App

首先初始化畫筆和圓形坐標(biāo)

    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;
        //初始化圓形坐標(biāo)
        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);  
//設(shè)置小點點出現(xiàn)的區(qū)域
        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);
//移動坐標(biāo)中心到圓心     
        canvas.translate(defaultwidth/2, width/2+defaultwidth/8);
        canvas.save();
//旋轉(zhuǎn)坐標(biāo)軸
        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) {
//根據(jù)屬性動畫提供的move值,進行動態(tài)繪制
        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:歌詞滾動控件,因目前未實現(xiàn)網(wǎng)路下載功能,所以歌詞暫且用測試數(shù)據(jù)代替,來演示效果.


圖片發(fā)自簡書App

歌詞的滾動來自兩個方面的控制,一是播放時,自動滾動到當(dāng)前播放行,這個可以用一個handler每隔1秒發(fā)送message來檢測當(dāng)前播放時間和歌詞所在行時間,如果匹配,就設(shè)置當(dāng)前行為中心行.另一個是用手指滑動來設(shè)置當(dāng)前行.onDraw的重點在于從中心行開始繪制,然后畫上半部分和下半部分,這樣可以避免繪制無用的頁面外的行.繪制過程中根據(jù)滑動距離,不斷移動橫坐標(biāo)的位置.當(dāng)移動到下一行后,重置橫坐標(biāo)位置,然后繼續(xù)繪制.整個過程就是這樣反復(fù)進行.具體如下:

@Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
//重置數(shù)據(jù)
            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);
//當(dāng)移動距離大于兩行之間的高度時,重新設(shè)置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為負(fù),下移         
                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);
        }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市稿茉,隨后出現(xiàn)的幾起案子妻枕,更是在濱河造成了極大的恐慌,老刑警劉巖贫途,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寞钥,死亡現(xiàn)場離奇詭異盾剩,居然都是意外死亡蚁廓,警方通過查閱死者的電腦和手機访圃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來相嵌,“玉大人腿时,你說我怎么就攤上這事》贡觯” “怎么了批糟?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長看铆。 經(jīng)常有香客問我徽鼎,道長,這世上最難降的妖魔是什么弹惦? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任否淤,我火速辦了婚禮,結(jié)果婚禮上肤频,老公的妹妹穿的比我還像新娘叹括。我一直安慰自己算墨,他們只是感情好宵荒,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著净嘀,像睡著了一般报咳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挖藏,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天暑刃,我揣著相機與錄音,去河邊找鬼膜眠。 笑死岩臣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宵膨。 我是一名探鬼主播架谎,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辟躏!你這毒婦竟也來了谷扣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤捎琐,失蹤者是張志新(化名)和其女友劉穎会涎,沒想到半個月后裹匙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡末秃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年概页,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛔溃。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡绰沥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贺待,到底是詐尸還是另有隱情徽曲,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布麸塞,位于F島的核電站秃臣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏哪工。R本人自食惡果不足惜奥此,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雁比。 院中可真熱鬧稚虎,春花似錦、人聲如沸偎捎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茴她。三九已至寻拂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丈牢,已是汗流浹背祭钉。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留己沛,地道東北人慌核。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像申尼,于是被迫代替她去往敵國和親垮卓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容