自定义View_自定义SeekBar实现梯形渐变效果

前几天,朋友找我帮忙实现一个这样的效果,如下:
自定义View_自定义SeekBar实现梯形渐变效果
文章图片
当时我看到这个效果图,想了一下大概的思路如下:

1、自定义View继承SeekBar,这样方便使用SeekBar的一些自带功能,比如触摸事件及回调等; 2、蓝色和灰色的进度条,自己实现,用Path这个类来绘制; 3、圆形的滑块使用自带属性android:thumb=""添加一张图片即可。

按照这个思路最终还是实现了这样的一个效果,效果动图如下:
自定义View_自定义SeekBar实现梯形渐变效果
文章图片
雏形
我们先使用SeekBar控件,滑块使用自定义的图片,进度条置空@null,待会进度条效果我们自己来实现,代码及预览图如下:
自定义View_自定义SeekBar实现梯形渐变效果
文章图片
分析
OK,到目前为止就需要我们自己来完成进度条的绘制了,绘制之前我们先分析一下该如何绘制。因为滑块有一定的宽度,假设是30dp,那么当滑块位于起始位置的时候,它的中心位置距离左边界的距离就是15dp,那么我们在绘制进度条的时候就是从这个位置开始的,也就是说起始位置x坐标是在半个滑块宽度的位置。终点位置同理,为什么要这样呢?因为如果不考虑这个因素从0坐标开始绘制的话将是这样的效果,两边多出来的很丑有木有?
自定义View_自定义SeekBar实现梯形渐变效果
文章图片
所以,我们需要考虑这个细节问题,让起始位置和终点位置都对齐滑块的中心位置。这样,假如SeekBar控件的宽度是width,那么进度条的宽度就是width-滑块宽度。
初始化
初始化画笔等对象:
setMax(MAX_VALUE); //获取滑块宽度的一半 sliderHalf = (float) (mContext.getResources().getDimension(R.dimen.sliderWidth) / 2.0); paintDefault = new Paint(); paintDefault.setAntiAlias(true); paintDefault.setColor(ContextCompat.getColor(mContext, R.color.seekBarBackgroundColor)); paintDefault.setStyle(Paint.Style.FILL); paintFore = new Paint(); paintFore.setAntiAlias(true); paintFore.setColor(ContextCompat.getColor(mContext, R.color.seekBarForegroundColor)); paintFore.setStyle(Paint.Style.FILL);

初始化Path及相关坐标:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mViewWidth = w; mViewHeight = h; //获取预设值 leftHeight = mContext.getResources().getDimension(R.dimen.leftHeight); rightHeight = mContext.getResources().getDimension(R.dimen.rightHeight); //计算进度条底部坐标 float bottomY = mViewHeight / 2 + rightHeight / 2; //初始化背景色Path对象 pathDefault.moveTo(sliderHalf, bottomY); pathDefault.lineTo(mViewWidth - sliderHalf, bottomY); pathDefault.lineTo(mViewWidth - sliderHalf, bottomY - rightHeight); pathDefault.lineTo(sliderHalf, bottomY - leftHeight); pathDefault.close(); }

绘制
//计算进度条底边的y坐标 float bottomY = mViewHeight / 2 + rightHeight / 2; //计算当前progress对应的长度(减去初始高度leftHeight) float topHeight = (rightHeight - leftHeight) * getProgress() / getMax(); //计算当前progress对应的右边顶点y坐标 float rightTop = bottomY - topHeight - leftHeight; //基准线每次切换的平均间隔 float dis = (float) (sliderHalf / (getMax() / 2.0)); //计算当前progress对应的像素,即x坐标,这个坐标是未校准的,不是一定处在滑块的中间位置 float unCalibrationX = mViewWidth * getProgress() / getMax(); //计算出每次需要校准的距离,从sliderHalf长度像素逐次减少 float calibration = sliderHalf - getProgress() * dis; float px = unCalibrationX + calibration; //根据当前progress初始化Path对象 pathCurrent.reset(); pathCurrent.moveTo(sliderHalf, bottomY); pathCurrent.lineTo(px, bottomY); pathCurrent.lineTo(px, rightTop); pathCurrent.lineTo(sliderHalf, bottomY - leftHeight); pathCurrent.close(); //绘制底色背景 canvas.drawPath(pathDefault, paintDefault); //绘制进度 canvas.drawPath(pathCurrent, paintFore);

完整代码:
MySeekBar:
public class MySeekBar extends SeekBar {private Context mContext; /** * SeekBar最大值 */ private static final int MAX_VALUE = https://www.it610.com/article/100; /** * 背景色画笔 */ private Paint paintDefault; /** * 进度条画笔 */ private Paint paintFore; /** * 左边初始高度 */ private float leftHeight; /** * 右边最大高度 */ private float rightHeight; /** * 背景色Path对象 */ private Path pathDefault = new Path(); /** * 进度条Path对象 */ private Path pathCurrent = new Path(); /** * SeekBar宽度 */ private int mViewWidth; /** * SeekBar高度 */ private int mViewHeight; /** * 滑块一半的宽度 */ private float sliderHalf; public MySeekBar(Context context) { super(context); mContext = context; init(); }public MySeekBar(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init(); }public MySeekBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; init(); }/** * 初始化 */ private void init() { setMax(MAX_VALUE); //获取滑块宽度的一半 sliderHalf = (float) (mContext.getResources().getDimension(R.dimen.sliderWidth) / 2.0); paintDefault = new Paint(); paintDefault.setAntiAlias(true); paintDefault.setColor(ContextCompat.getColor(mContext, R.color.seekBarBackgroundColor)); paintDefault.setStyle(Paint.Style.FILL); paintFore = new Paint(); paintFore.setAntiAlias(true); paintFore.setColor(ContextCompat.getColor(mContext, R.color.seekBarForegroundColor)); paintFore.setStyle(Paint.Style.FILL); }@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mViewWidth = w; mViewHeight = h; //获取预设值 leftHeight = mContext.getResources().getDimension(R.dimen.leftHeight); rightHeight = mContext.getResources().getDimension(R.dimen.rightHeight); //计算进度条底部坐标 float bottomY = mViewHeight / 2 + rightHeight / 2; //初始化背景色Path对象 pathDefault.moveTo(sliderHalf, bottomY); pathDefault.lineTo(mViewWidth - sliderHalf, bottomY); pathDefault.lineTo(mViewWidth - sliderHalf, bottomY - rightHeight); pathDefault.lineTo(sliderHalf, bottomY - leftHeight); pathDefault.close(); }@Override protected synchronized void onDraw(Canvas canvas) { /** * 因为SeekBar在滑动的整个过程中,基准线是有一个渐变的过程的,这个宽度就是滑块的宽度 * 在滑块从0开始到(getMax() / 2.0)的次数内,也就是走完一半的距离时,基准线刚好对准滑块的中间位置 * 利用这个规律,来校准滑块的位置,使其不管滑到什么位置,都可以计算出其基准线的x坐标 */////基准线每次切换的平均间隔 //float dis = (float) (sliderHalf / (getMax() / 2.0)); ////计算当前progress对应的像素,即x坐标,这个坐标是未校准的,不是一定处在滑块的中间位置 //float unCalibrationX = mViewWidth * getProgress() / getMax(); ////计算出每次需要校准的距离,从sliderHalf长度像素逐次减少 //float calibration = sliderHalf - getProgress() * dis; //float px = unCalibrationX + calibration; ////绘制基准线 //canvas.drawLine(px, 0, px, mViewHeight, paintFore); //绘制背景和进度 drawPath(canvas); //super绘制滑块 super.onDraw(canvas); }/** * 绘制进度和背景 * * @param canvas */ private void drawPath(Canvas canvas) { //计算进度条底边的y坐标 float bottomY = mViewHeight / 2 + rightHeight / 2; //计算当前progress对应的长度(减去初始高度leftHeight) float topHeight = (rightHeight - leftHeight) * getProgress() / getMax(); //计算当前progress对应的右边顶点y坐标 float rightTop = bottomY - topHeight - leftHeight; //基准线每次切换的平均间隔 float dis = (float) (sliderHalf / (getMax() / 2.0)); //计算当前progress对应的像素,即x坐标,这个坐标是未校准的,不是一定处在滑块的中间位置 float unCalibrationX = mViewWidth * getProgress() / getMax(); //计算出每次需要校准的距离,从sliderHalf长度像素逐次减少 float calibration = sliderHalf - getProgress() * dis; float px = unCalibrationX + calibration; //根据当前progress初始化Path对象 pathCurrent.reset(); pathCurrent.moveTo(sliderHalf, bottomY); pathCurrent.lineTo(px, bottomY); pathCurrent.lineTo(px, rightTop); pathCurrent.lineTo(sliderHalf, bottomY - leftHeight); pathCurrent.close(); //绘制底色背景 canvas.drawPath(pathDefault, paintDefault); //绘制进度 canvas.drawPath(pathCurrent, paintFore); } }

MainActivity:
public class MainActivity extends AppCompatActivity {@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); }private void init() { final TextView mTextView = findViewById(R.id.textView); final SeekBar mSeekBar = findViewById(R.id.seekbar); //更新初始值 updateText(mTextView, 0); mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { //监听回调,开始更新 updateText(mTextView, progress); }@Override public void onStartTrackingTouch(SeekBar seekBar) {}@Override public void onStopTrackingTouch(SeekBar seekBar) {} }); }/** * 更新控件 * * @param textView * @param progress */ private void updateText(TextView textView, int progress) { textView.setText(String.valueOf(progress)); } }

另外,还有一些尺寸等参数如下:
3dp20dp30dp40dp

【自定义View_自定义SeekBar实现梯形渐变效果】时间匆忙,很多细节没有展开来讲,如果有人需要可以来和我一起讨论~

    推荐阅读