自定义View实战
PS:上一篇从0开始学自定义View有博友给我留言说要看实战,今天我特意写了几个例子,供大家参考,所画的图案加上动画看着确实让人舒服,喜欢的博友可以直接拿到自己的项目中去使用,由于我这个写的是demo,代码格式写的有些乱,所以,要自己封装一下才可以使用,当然你如果真的不想封装,可以直接使用,也可以给我留言,我封装好放在github上供大家参考,也会做成依赖让大家直接添加即可
先上图再分析
可以看出图中有三种样式
- 第一种是普通的一个label,使用场景:商品过期,促销等展示。
- 第二种是圆形进度条, 使用场景:下载文件进度,加载视频进度,耗电量进度.....
- 第三种是条形进度条, 使用场景:滑动调值,手机音效大小...
上面的三种,均是demo,考虑使用场景并不完善,比如说第三种条形进度条还可以加上刻度,滑动到两边需要判断越界等。那就先拿第三个来吧
条形进度条-可拖动
分析:我们想要做一个类似的控件,需要考虑的问题不只是眼睛看的到的,看不到的就好比我只能点击小红球才可以滑动,我点击其他区域是不能有任何操作的,这个时候就要判断手指down的时候是否落在了小球上。
- 线条 : 渐变颜色,线帽格式,长度,宽度设置,父布局宽高格式设置格式设置,子view宽高格式设置
- 球 :颜色,起始位置和终止位置要在线上,尺寸
在做之前我们先一个一个知识点解析,首先是线的渐变颜色,单独拿出
/** * 设置进度圆环颜色(支持渐变色) * * @param colorArray 渐变色集合 */ private int[] mColorArray; // 圆环渐变色 public void setProgColor(@ColorRes int[] colorArray) { if (colorArray == null || colorArray.length < 2) return; mColorArray = new int[colorArray.length]; for (int index = 0; index < colorArray.length; index++) mColorArray[index] = ContextCompat.getColor(getContext(), colorArray[index]); paint.setShader(new LinearGradient(0, 0, getMeasuredWidth(),0 , mColorArray, new float[]{0,.3F,.6F,.9F}, Shader.TileMode.MIRROR)); invalidate(); }
我们可以看到我封装成了一个方法,通过paint.setShader进行着色,方法传入的是LinearGradient对象,我们看源码,解释参数
/** * Create a shader that draws a linear gradient along a line. * * @param x0 The x-coordinate for the start of the gradient line * @param y0 The y-coordinate for the start of the gradient line * @param x1 The x-coordinate for the end of the gradient line * @param y1 The y-coordinate for the end of the gradient line * @param colors The colors to be distributed along the gradient line * @param positions May be null. The relative positions [0..1] of * each corresponding color in the colors array. If this is null, * the the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode */ public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int colors[], @Nullable float positions[], @NonNull TileMode tile) { if (colors.length < 2) { throw new IllegalArgumentException("needs >= 2 number of colors"); } if (positions != null && colors.length != positions.length) { throw new IllegalArgumentException("color and position arrays must be of equal length"); } mType = TYPE_COLORS_AND_POSITIONS; mX0 = x0; mY0 = y0; mX1 = x1; mY1 = y1; mColors = colors.clone(); mPositions = positions != null ? positions.clone() : null; mTileMode = tile; }
参数
- x0,y0着色的起始位置
- x1,y1终止位置
- colors区域内着色的颜色集
- positions区域内部划分模块,逐一着色,如:区域1-100划分为4块,第一块1-25红色,26-50蓝色..。系统会默认在两种颜色不一样的情况下进行颜色过度渲染,达到渐变的效果,所以我们不用担心出现红蓝划分明显的情况。
- TileMode 模式选择
- CLAMP:当图片小于绘制尺寸时要进行边界拉伸来填充
- REPEAT:当图片小于绘制尺寸时重复平铺
- MIRROR:当图片小于绘制尺寸时镜像平铺
好了,下面我们就先画线和红点,如果有看不懂的博友,可以先看上一篇从0开始学自定义View。
public void init(){ paint = new Paint(); paint.setStrokeWidth(20);//画笔宽度 paint.setStyle(Paint.Style.FILL);//填充类型 paint.setAntiAlias(true); paint.setStrokeCap(Paint.Cap.ROUND);//线帽,半圆 paintCircle2 = new Paint(); paintCircle2.setColor(Color.RED); paintCircle2.setStrokeWidth(5); paintCircle2.setStyle(Paint.Style.FILL); paintCircle2.setAntiAlias(true); }
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = MeasureSpec.getSize(widthMeasureSpec); heigth = MeasureSpec.getSize(heightMeasureSpec); Log.e("heigth -- width ",heigth+" -- "+width); circleX=width/2;//初始化红点坐标位置 circleY=20; }
int[] colors={R.color.colorPrimary,R.color.colorAccent,R.color.color_environment_serious,R.color.color_environment_mild}; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); setProgColor(colors); //绘制一条线,线帽为半圆 canvas.drawLine(50,20,width-50,20,paint); canvas.drawCircle(circleX,circleY,15,paintCircle2); }
到这里,线和点就已经做好了,只是静态的,下面是如何拖动,就要在onTouchEvent方法中去写了,代码都已经添加了注释 Math.abs(dhx)<50&&Math.abs(dhy)<50 是证明down的坐标点和原始球的坐标点相差范围在50内,如果小球在屏幕200,200的位置,而我们手指down的点在800,800,那么相差如此巨大,肯定不是我们想要的结果,所以,我们就认为down的坐标减去球的坐标差值最小(50内)才是我们想要的结果,这个时候我们再设置小球move的坐标(让小球跟随手指移动)。
/** * 判断所按压的坐标和红点坐标的关系 * 如果手指按在了红点的下方,那么down-红点y坐标的绝对值如果等于或者小于半径,也就是说目前按压的就是红点, * * */ private boolean clickCircle(int downHeigth,int downWidth){ int dhy = downHeigth - circleY; int dhx = downWidth - circleX; //证明按压的是原点 if(Math.abs(dhx)<50&&Math.abs(dhy)<50){ return true; } return false; } @Override public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } boolean isFlag=false; @Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) event.getX(); int rawY = (int) event.getY();//获取到视图坐标,想对于外部viewgroup来说。 Log.e("raw",rawX+" --- "+rawY); Log.e("rawwww",circleX+" --- "+circleY); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //如果是手指down在了红点上,move时可重绘 if(clickCircle(rawY,rawX)) { circleX = rawX; isFlag=true; }else{ isFlag=false; } break; case MotionEvent.ACTION_MOVE: if(isFlag){ circleX = rawX; invalidate(); } break; case MotionEvent.ACTION_UP: isFlag=false;//抬起手指,状态恢复 break; } return true;//自己消费 }
下一篇是圆形进度条