实现一个彩虹色进度条功能,不说明具体用途大家应该能猜到。想找别人造的轮子,但是没有合适的,所以决定自己实现一个。
相关知识
android 自定义view
lineargradient 线性渐变
实现步骤
自定义view
自定义一个tmcview类继承view
重写两个构造方法。构造方法一共有4个,这里边重写两个
重写ongsizechanged方法,用来获取控件宽、高,来计算内部组件尺寸。
重写ondraw方法,里边要描画背景drawbackground,分段数据drawsection,和seekbar图片drawimage。
import android.content.context; import android.graphics.canvas; import android.util.attributeset; import android.view.view; import androidx.annotation.nullable; import java.util.list; public class tmcview extends view { public tmcview(context context) { super(context); } public tmcview(context context, @nullable attributeset attrs) { super(context, attrs); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawbackground(canvas); drawsection(canvas); drawimage(canvas); } /** * 画背景 * @param canvas 画布 */ private void drawbackground(canvas canvas) { } /** * 画分段数据 * @param canvas 画布 */ private void drawsection(canvas canvas) { } /** * 画图片 * @param canvas 画布 */ private void drawimage(canvas canvas) { } /** * 更新view * @param total 总长度 * @param rest 剩余长度 * @param sections 分段数据 */ public void updateview(int total, int rest, list<sectiondata> sections){ }
实现几个重构方法
标注:
整体宽度为图片宽度44px
背景条宽度30px,外边距7px,圆角15px
带颜色的条宽度20xp,外边距5px,圆角15px
自定义view的坐标轴是以view的左上角位置为原点,向右为x轴正方向,向下为y轴正方向
在视图中用rectf创建背景,和颜色条,并在onsizechanged中设置尺寸
public class tmcview extends view { private final rectf backgroundrectf = new rectf(); private final rectf colorrectf = new rectf(); public tmcview(context context) { super(context); } public tmcview(context context, @nullable attributeset attrs) { super(context, attrs); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); //设置矩形 left top right bottom 左上右下点的值 按照标注计算得出 backgroundrectf.set(7, 0, 37, h); colorrectf.set(12, 5, 32, h-5); } ... }
绘制背景条
实现drawbackground方法,画背景需要一根画笔paint 为了避免重复创建,声明为成员变量
public class tmcview extends view { /*背景画笔*/ private final paint backpaint = new paint(paint.anti_alias_flag); private final rectf backgroundrectf = new rectf(); private final rectf colorrectf = new rectf(); public tmcview(context context) { super(context); } public tmcview(context context, @nullable attributeset attrs) { super(context, attrs); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); //设置矩形 left top right bottom 左上右下点的值 按照标注计算得出 backgroundrectf.set(7, 0, 37, h); colorrectf.set(12, 5, 32, h-5); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawbackground(canvas); drawsection(canvas); drawimage(canvas); } /** * 画背景 * @param canvas 画布 */ private void drawbackground(canvas canvas) { backpaint.setstyle(paint.style.fill); backpaint.setantialias(true); //抗锯齿 backpaint.setcolor(color.parsecolor("#ffffff")); //画圆角矩形,15为圆角的角度 canvas.drawroundrect(backgroundrectf, 15, 15, backpaint); } ... }
布局中加入tmcview
<com.bigxuan.tesapp.view.tmcview android:id="@+id/tmc" android:layout_width="44px" android:layout_height="690px" android:layout_marginend="1230px" android:layout_marginbottom="100px" app:layout_constraintbottom_tobottomof="parent" app:layout_constraintend_toendof="parent" />
绘制颜色条
实现drawsection方法,这里要用到线性渐变lineargradient
lineargradient 简单说,指定每一段的颜色和位置百分比,就能实现每一段显示不同颜色。
但它默认是渐变色,要想不变就在每一段的开始和结束位置都设置相同的颜色。
再创建一个画笔 paint,画颜色条
public class tmcview extends view { private final paint backpaint = new paint(paint.anti_alias_flag); private final paint colorpaint = new paint(paint.anti_alias_flag); private final rectf backgroundrectf = new rectf(); private final rectf colorrectf = new rectf(); public tmcview(context context) { super(context); } public tmcview(context context, @nullable attributeset attrs) { super(context, attrs); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); //设置矩形 left top right bottom 左上右下点的值 按照标注计算得出 backgroundrectf.set(7, 0, 37, h); colorrectf.set(12, 5, 32, h-5); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawbackground(canvas); drawsection(canvas); drawimage(canvas); } /** * 画背景 * @param canvas 画布 */ private void drawbackground(canvas canvas) { backpaint.setstyle(paint.style.fill); backpaint.setantialias(true); //抗锯齿 backpaint.setcolor(color.parsecolor("#ffffff")); //画圆角矩形,15为圆角的角度 canvas.drawroundrect(backgroundrectf, 15, 15, backpaint); } /** * 画分段数据 * @param canvas 画布 */ private void drawsection(canvas canvas) { int[] colorarray = { color.parsecolor("#ff0000"), color.parsecolor("#ff0000"), color.parsecolor("#00ff00"), color.parsecolor("#00ff00"), color.parsecolor("#0000ff"), color.parsecolor("#0000ff") }; float[] positionarray = { 0f, 0.2f, 0.2f, 0.6f, 0.6f, 1f }; colorpaint.setstyle(paint.style.fill); colorpaint.setantialias(true); //指定渐变色方向x轴方向不变,沿y方向渐变,渐变范围为 颜色条高度 colorpaint.setshader(new lineargradient(0, 0, 0, colorrectf.bottom, colorarray, positionarray, shader.tilemode.repeat)); canvas.drawroundrect(colorrectf,15, 15, colorpaint); } ... }
绘制进度图片
app加入图片资源,根据资源id获取bitmap对象,绘制。
需要注意的是,坐标轴的顶点在左上角,绘制图片时也是以图片左上顶点位置做定位,图片位置是view的高度减掉图片的高度,才能显示在正确位置。
public class tmcview extends view { private context context; private final paint backpaint = new paint(paint.anti_alias_flag); private final paint colorpaint = new paint(paint.anti_alias_flag); private final rectf backgroundrectf = new rectf(); private final rectf colorrectf = new rectf(); /*图片资源id*/ private int imgresid; /*图片位置*/ private int imgpos; public tmcview(context context) { super(context); } public tmcview(context context, @nullable attributeset attrs) { super(context, attrs); this.context = context; this.imgresid = r.drawable.icon_seekbar_day; } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); //设置矩形 left top right bottom 左上右下点的值 按照标注计算得出 backgroundrectf.set(7, 0, 37, h); colorrectf.set(12, 5, 32, h-5); imgpos = h - 44; // 设置一个初始位置 } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawbackground(canvas); drawsection(canvas); drawimage(canvas); } /** * 画背景 * @param canvas 画布 */ private void drawbackground(canvas canvas) { backpaint.setstyle(paint.style.fill); backpaint.setantialias(true); //抗锯齿 backpaint.setcolor(color.parsecolor("#ffffff")); //画圆角矩形,15为圆角的角度 canvas.drawroundrect(backgroundrectf, 15, 15, backpaint); } /** * 画分段数据 * @param canvas 画布 */ private void drawsection(canvas canvas) { int[] colorarray = { color.parsecolor("#ff0000"), color.parsecolor("#ff0000"), color.parsecolor("#00ff00"), color.parsecolor("#00ff00"), color.parsecolor("#0000ff"), color.parsecolor("#0000ff") }; float[] positionarray = { 0f, 0.2f, 0.2f, 0.6f, 0.6f, 1f }; colorpaint.setstyle(paint.style.fill); colorpaint.setantialias(true); //指定渐变色方向x轴方向不变,沿y方向渐变,渐变范围为 颜色条高度 colorpaint.setshader(new lineargradient(0, 0, 0, colorrectf.bottom, colorarray, positionarray, shader.tilemode.repeat)); canvas.drawroundrect(colorrectf,15, 15, colorpaint); } /** * 画图片 * @param canvas 画布 */ private void drawimage(canvas canvas) { bitmap bitmap = initbitmap(imgresid); canvas.save(); canvas.translate(0, 0); canvas.drawbitmap(bitmap, 0, imgpos, null); canvas.restore(); } /** * 通过资源id 创建bitmap * @param resid 资源id * @return */ private bitmap initbitmap(int resid) { bitmap originalbmp = bitmapfactory.decoderesource(context.getresources(), resid); return bitmap.createscaledbitmap(originalbmp, 44, 44, true); } ... }
公共方法
暴露方法用来更新进度updateview
定义一个图片有效的运动距离为view高度减掉图片高度,函数参数为总距离和剩余距离,计算百分比后乘以有效运动距离得出图片描画位置。最后调用invalidate方法刷新view
import android.content.context; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.graphics.canvas; import android.graphics.color; import android.graphics.lineargradient; import android.graphics.paint; import android.graphics.rectf; import android.graphics.shader; import android.util.attributeset; import android.view.view; import androidx.annotation.nullable; import com.navinfo.tesapp.r; import java.util.list; public class tmcview extends view { private context context; private final paint backpaint = new paint(paint.anti_alias_flag); private final paint colorpaint = new paint(paint.anti_alias_flag); private final rectf backgroundrectf = new rectf(); private final rectf colorrectf = new rectf(); /*图片资源id*/ private int imgresid; /*图片位置*/ private int imgpos; /*图片有效的运动距离*/ private int imgvalidheight; public tmcview(context context) { super(context); } public tmcview(context context, @nullable attributeset attrs) { super(context, attrs); this.context = context; this.imgresid = r.drawable.icon_seekbar_day; } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); //设置矩形 left top right bottom 左上右下点的值 按照标注计算得出 backgroundrectf.set(7, 0, 37, h); colorrectf.set(12, 5, 32, h-5); imgvalidheight = h - 44; imgpos = h - 44; // 设置一个初始位置 } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); drawbackground(canvas); drawsection(canvas); drawimage(canvas); } /** * 画背景 * @param canvas 画布 */ private void drawbackground(canvas canvas) { backpaint.setstyle(paint.style.fill); backpaint.setantialias(true); //抗锯齿 backpaint.setcolor(color.parsecolor("#ffffff")); //画圆角矩形,15为圆角的角度 canvas.drawroundrect(backgroundrectf, 15, 15, backpaint); } /** * 画分段数据 * @param canvas 画布 */ private void drawsection(canvas canvas) { int[] colorarray = { color.parsecolor("#ff0000"), color.parsecolor("#ff0000"), color.parsecolor("#00ff00"), color.parsecolor("#00ff00"), color.parsecolor("#0000ff"), color.parsecolor("#0000ff") }; float[] positionarray = { 0f, 0.2f, 0.2f, 0.6f, 0.6f, 1f }; colorpaint.setstyle(paint.style.fill); colorpaint.setantialias(true); //指定渐变色方向x轴方向不变,沿y方向渐变,渐变范围为 颜色条高度 colorpaint.setshader(new lineargradient(0, 0, 0, colorrectf.bottom, colorarray, positionarray, shader.tilemode.repeat)); canvas.drawroundrect(colorrectf,15, 15, colorpaint); } /** * 画图片 * @param canvas 画布 */ private void drawimage(canvas canvas) { bitmap bitmap = initbitmap(imgresid); canvas.save(); canvas.translate(0, 0); canvas.drawbitmap(bitmap, 0, imgpos, null); canvas.restore(); } /** * * @param resid * @return */ private bitmap initbitmap(int resid) { bitmap originalbmp = bitmapfactory.decoderesource(context.getresources(), resid); return bitmap.createscaledbitmap(originalbmp, 44, 44, true); } /** * 更新view * @param total 总长度 * @param rest 剩余长度 * @param sections 分段数据 */ public void updateview(int total, int rest, list<sectiondata> sections){ float percent = (1f * rest) / total; //防止溢出 if(percent < 0){ return; } imgpos = (int)(percent * imgvalidheight); invalidate(); } }
updateview方法还有第三个参数,是用来传出颜色条不同段的颜色和百分比数据的。根据此数据来更新颜色条。这里需要根据业务不同自己实现,我这里就不写了。
总结
大家可能看出来了,这个视图是用来展示导航中不同路段交通情况和当前车辆进度用的。自定义view中可以在构造方法中获取一些自定义属性,像背景条和颜色条的边距、圆角这些都可以定义到xml中,因为只适配一种屏幕尺寸所以也没有做多尺寸适配。以前也没有做过,这次记录下来。
发表评论