这里主要实现一个音乐播放器的进度条,效果如下
最初不是这个效果就是一个简单的进度条所以版本1就直接
SeekBar
处理了.这里需要处理的是随着播放进度的长度颜色值也逐渐改变,因此就采用了自定义view
来处理这个需求.
思路
- 一个
background
表示总长度 - 一个
secondaryProgress
表示缓冲进度 - 一个
progress
表示当前进度
过程
需要在
xml
布局文件中使用并且需要使用自定义的属性,因此需要实现三个参数的构造方法.- 一个参数的构造方法 当不需要使用xml声明或者不需要使用inflate动态加载时候,实现此构造函数即可
- 两个参数的构造方法 当需要在xml中声明此控件,则需要实现此构造函数。并且在构造函数中把自定义的属性与控件的数据成员连接起来
- 三个参数的构造方法 接受一个style资源
这里三个方法都实现了
public ColorSeekBar(Context context) { this(context, null); } public ColorSeekBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ColorSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaintAndAttribute(context, attrs, defStyleAttr); }
根据布局文件的配置完成相关属性的初始化配置
主要是
end
start
line_height
三个属性<com.multimedia.widget.ColorSeekBar android:id="@+id/playing_seek" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_centerVertical="true" app:end="100" app:line_height="4dp" app:start="0"/>
获取到
xml
文件中的配置之后进行初始化设置private void initPaintAndAttribute(Context context, AttributeSet attributeSet, int defStyleAttr) { TypedArray array = context .obtainStyledAttributes(attributeSet, R.styleable.ColorSeekBar, defStyleAttr, 0); int attrCount = array.getIndexCount(); for (int i = 0; i < attrCount; i++) { int currentAttr = array.getIndex(i); switch (currentAttr) { case R.styleable.ColorSeekBar_start://开始值 start = array.getInt(currentAttr, defaultStart); break; case R.styleable.ColorSeekBar_end://结束值 end = array.getInt(currentAttr, defaultEnd); break; case R.styleable.ColorSeekBar_line_height: lineHeight = array.getDimension(currentAttr, defaultLineHeight); break; } } max = end - start; array.recycle(); mPaint = new Paint(); }
完成初始化之后就是根据数据源来动态绘制三个层面的进度了,在
onDraw()
方法中进行绘制刷新.@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); paddingLeft = getPaddingLeft(); paddingTop = getPaddingTop(); paddingRight = getPaddingRight(); paddingBottom = getPaddingBottom(); int contentWidth = getWidth() - paddingLeft - paddingRight; int contentHeight = getHeight() - paddingTop - paddingBottom; sWidth = contentWidth; sHeight = lineHeight; sLeft = paddingLeft; sRight = sLeft + contentWidth; sTop = contentHeight / 2 - sHeight / 2; sBottom = contentHeight / 2 + sHeight / 2; x = sWidth * scale + paddingLeft; y = contentHeight / 2; //上面是处理绘制进度条的相关位置点坐标 drawBackground(canvas); drawProgress(canvas); drawThumb(canvas); }
绘制背景
绘制背景就比较简单就是一个圆角的长方形
/** * 背景 * * @param canvas */ private void drawBackground(Canvas canvas) { RectF bgRectf = new RectF(paddingLeft + sWidth * scale, sTop, sRight, sBottom); mPaint.reset(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(Color.GRAY); canvas.drawRoundRect(bgRectf, 4, 4, mPaint); }
绘制进度
绘制进度是这个自定义
view
的核心点,因为需要色值随进度值的增加而加深,所以这里使用了LinearGradient
,然后给paint
设置Shader
即可./** * 进度 * * @param canvas */ private void drawProgress(Canvas canvas) { RectF progressRectf = new RectF(sLeft, sTop, x, sBottom); LinearGradient linearGradient = new LinearGradient(sLeft, sTop, paddingLeft + sWidth * scale, sHeight, new int[]{ Color.parseColor("#005EFF") , Color.parseColor("#B81671FF") , Color.parseColor("#7CE4FE")}, null, Shader.TileMode.MIRROR); mPaint.reset(); mPaint.setStyle(Paint.Style.FILL); mPaint.setAntiAlias(true); mPaint.setShader(linearGradient); canvas.drawRoundRect(progressRectf, 4, 4, mPaint); }
绘制快进快退按钮
另外需要一个快进快退的效果,需要注意的是绘制的位置以及结果位置值的确定.
/** * 拖动 * * @param canvas */ private void drawThumb(Canvas canvas) { RectF thumbRectf = new RectF(x - 5, y - 10, x + 5, y + 10); mPaint.reset(); mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(Color.parseColor("#50A5F0")); mPaint.setAntiAlias(true); canvas.drawRect(thumbRectf, mPaint); }
这里就需要对触摸事件进行处理
@Override public boolean onTouchEvent(MotionEvent event) { move = true; this.x = event.getX(); scale = (x - paddingLeft) / sWidth; if (x < paddingLeft) { scale = 0.0f; } else if ((x > (getWidth() - paddingRight))) { scale = 1.0f; } this.progress = (int) (this.max * scale + this.start); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: if (onStateChangeListener != null) { onStateChangeListener.OnChangeListener(this, progress); } invalidate(); break; case MotionEvent.ACTION_MOVE: invalidate(); break; } return true; }
功能已经完成 向外抛出接口供使用方法
/*********** * 根据需求向外提供接口 * 及方法 ************************************/ public interface OnStateChangeListener { void OnChangeListener(View view, float progress); } public void setOnStateChangeListener(OnStateChangeListener stateChangeListener) { this.onStateChangeListener = stateChangeListener; } /** * 设置进度 * * @param progress */ public void setProgress(int progress) { if (progress < this.start) { progress = this.start; } else if (progress > this.end) { progress = end; } this.progress = progress; this.scale = (this.progress - this.start) / (float) this.max; invalidate(); } public int getProgress() { return this.progress; } public void setMax(int max) { this.max = max; } public void setValueSize(int start, int end) { this.start = start; this.end = end; this.max = this.end - this.start; } public void addProgress() { setProgress(progress + 1); } public void lessProgress() { setProgress(progress - 1); }