今天用自己这几天的理解完全的写了一个自定义VIew,成就感满满
效果
- 大概实现进度条样式功能
- 进度条的颜色,样式,等
- 进度条的拖动,点击回调
- 上一下效果图吧,虽然丑额,但是好歹功能大概实现了
思路
- 先定义出我们这个View需要的属性文件
- 然后在自定义View中,获取属性,测量相关宽高,绘制View
- 然后添加点击事件,或者自定义我们的回调接口,当进度条进度改变等一系列事件的回调接口
- 然后重写onTouchEvent(),处理各种事件,并在适当的时候回调我们的接口方法
代码实现
先来看一下属性文件的内容,连着好几篇了,我就不解释了,直接上代码
<declare-styleable name="MyProgressView">
<attr name="myProgressBackColor" format="color"/>
<attr name="guageModel">
<enum name="Circle" value="1"/>
<enum name="Five_Point_Star" value="2"/>
</attr>
<attr name="guageColor" format="color"/>
<attr name="lineColor" format="color"/>
</declare-styleable>
public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr)
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyProgressView)
for(int i = 0
int attr = array.getIndex(i)
switch (attr){
case R.styleable.MyProgressView_myProgressBackColor:
backColor = array.getColor(attr,Color.GRAY)
break
case R.styleable.MyProgressView_guageColor:
guageColor = array.getColor(attr,Color.RED)
break
case R.styleable.MyProgressView_guageModel:
guageModel = array.getInt(attr,1)
break
case R.styleable.MyProgressView_lineColor:
lineColor = array.getColor(attr,Color.GREEN)
break
}
}
mPaint = new Paint()
}
测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMeasureMode == MeasureSpec.EXACTLY){
width = widthMeasureSize;
}else {
ViewGroup parenGroup = (ViewGroup) getParent();
if(parenGroup != null){
width = parenGroup.getMeasuredWidth();
}
}
if(heightMeasureMode == MeasureSpec.EXACTLY){
height = heightMeasureSize;
}else {
ViewGroup parenGroup = (ViewGroup) getParent();
if(parenGroup != null){
height = parenGroup.getMeasuredHeight()/2/2/2;
}
}
switch (guageModel){
case CIRCLE:
guageRadius = (int) (((float)((height - getPaddingTop() - getPaddingBottom())/2))*0.25);
break;
case FIVE_POINTS_STAR:
break;
default:break;
}
guageMinX = getPaddingLeft() + guageRadius;
guageLineMinX = getPaddingLeft();
guageMaxX = width - getPaddingRight() - guageRadius;
guageLineMaxX = guageMaxX + guageRadius;
setMeasuredDimension(width,height);
}
- 注释写的少,大概说一下,先判断宽高模式,然后如果是wrap_Content的话,就取父布局宽的全部,和高的八分之一,这个自己写的话根据自己的情况而定
- 接下来更新进度条的最大值和最小值,以及那个进度球中心的最大值和最小值
绘制
protected void onDraw(Canvas canvas) {
Rect rect = new Rect();
rect.set(getPaddingLeft(),getPaddingTop(),width - getPaddingRight(),height -getPaddingBottom());
mPaint.setColor(backColor);
canvas.drawRect(rect,mPaint);
if(guageX <= 0){
guageX = guageMinX;
}
int y = (height - getPaddingTop() - getPaddingBottom())/2 + getPaddingTop();
mPaint.setColor(lineColor);
Rect rect1 = new Rect(guageLineMinX,y-height/2/2/2,guageX,y+height/2/2/2);
canvas.drawRect(rect1,mPaint);
rect1.set(guageX,y-height/2/2/2,guageLineMaxX,y+height/2/2/2);
mPaint.setColor(Color.RED);
canvas.drawRect(rect1,mPaint);
mPaint.setColor(guageColor);
switch (guageModel){
case CIRCLE:
canvas.drawCircle(guageX,y,guageRadius,mPaint);
break;
case FIVE_POINTS_STAR:
break;
}
}
添加回调接口
public interface MyProgressListener{
void moveing(MyProgressView progress);
void moveStart(MyProgressView progress);
void moveEnd(MyProgressView progress);
}
- 这里我给每个接口方法都传进来了我们这个VIew自身,以便于在回调的时候可以调用相关方法,比方说,我添加一个可以获取进度的方法
public float getProgress(){
return (guageX - guageMinX)*1.0f/(guageMaxX - guageMinX);
}
public void setProgressChangeListener(MyProgressListener listener){
this.listener = listener;
}
- 然后在onTouchEvent方法中去调用我们的接口方法
public boolean onTouchEvent(MotionEvent event) {
int x = (int) getX();
int y = (int) getY();
int downX;
int downY;
if((x < getPaddingLeft() || x > width - getPaddingRight()) &&
y < getPaddingTop() || y > height - getPaddingBottom()){
return false;
}
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
downY = (int) getY();
if(downX > (guageX - guageRadius) && downX < (guageX + guageRadius)){
canMove = true;
Log.d("--- ","设置canMove为true");
listener.moveStart(this);
}
Log.d("---down == ","guageX = "+guageX + ",guageRadius = "+guageRadius+",downX = "+downX);
Log.d("---canMove == ",canMove+"");
guageX+=10;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if(canMove){
Log.d("---Move == ","正在移动,x = "+event.getX()+",guageX = "+guageX);
int nowX = (int) event.getX();
if(nowX > guageMaxX ){
guageX = guageMaxX;
}else if(nowX < guageMinX){
guageX = guageMinX;
}else {
guageX = nowX;
}
invalidate();
listener.moveing(this);
}
break;
case MotionEvent.ACTION_UP:
listener.moveEnd(this);
canMove = false;
break;
}
return true;
}
var progressView = findViewById<MyProgressView>(R.id.main_MyProgressView)
progressView!!.setProgressChangeListener(object :MyProgressView.MyProgressListener {
override fun moveEnd(progress: MyProgressView?) {
}
override fun moveStart(progress: MyProgressView?) {
}
override fun moveing(progress: MyProgressView?) {
}
})
- 到这里这个定义的View主要功能就完成了,至于一些细节啥东西,可以根据需求自行添加,在这里我主要是叙述自定义View的原理
- 到这里自定义View的原理篇基本就这样了,接下来,我会去学一下自定义Viewgroup,敬请期待吧
- 因个人水平能力有限,各位小伙伴又发现我错误的地方欢迎指出,有什么问题也可以随便提问,谢谢
- 最后再贴一下我的整体代码
public class MyProgressView extends View {
private final static int CIRCLE = 1;
private final static int FIVE_POINTS_STAR = 2;
private int backColor = Color.GRAY;
private int guageColor = Color.RED;
private int guageModel = CIRCLE;
private int lineColor = Color.GREEN;
private MyProgressListener listener;
private Paint mPaint;
private int width;
private int height;
private int guageRadius;
private int guageX = 0;
private int guageMinX;
private int guageMaxX;
private int guageLineMinX;
private int guageLineMaxX;
public MyProgressView(Context context) {
this(context,null);
}
public MyProgressView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyProgressView);
for(int i = 0;i < array.getIndexCount(); i++){
int attr = array.getIndex(i);
switch (attr){
case R.styleable.MyProgressView_myProgressBackColor:
backColor = array.getColor(attr,Color.GRAY);
break;
case R.styleable.MyProgressView_guageColor:
guageColor = array.getColor(attr,Color.RED);
break;
case R.styleable.MyProgressView_guageModel:
guageModel = array.getInt(attr,1);
break;
case R.styleable.MyProgressView_lineColor:
lineColor = array.getColor(attr,Color.GREEN);
break;
}
}
mPaint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMeasureMode == MeasureSpec.EXACTLY){
width = widthMeasureSize;
}else {
ViewGroup parenGroup = (ViewGroup) getParent();
if(parenGroup != null){
width = parenGroup.getMeasuredWidth();
}
}
if(heightMeasureMode == MeasureSpec.EXACTLY){
height = heightMeasureSize;
}else {
ViewGroup parenGroup = (ViewGroup) getParent();
if(parenGroup != null){
height = parenGroup.getMeasuredHeight()/2/2/2;
}
}
switch (guageModel){
case CIRCLE:
guageRadius = (int) (((float)((height - getPaddingTop() - getPaddingBottom())/2))*0.25);
break;
case FIVE_POINTS_STAR:
break;
default:break;
}
guageMinX = getPaddingLeft() + guageRadius;
guageLineMinX = getPaddingLeft();
guageMaxX = width - getPaddingRight() - guageRadius;
guageLineMaxX = guageMaxX + guageRadius;
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
Rect rect = new Rect();
rect.set(getPaddingLeft(),getPaddingTop(),width - getPaddingRight(),height -getPaddingBottom());
mPaint.setColor(backColor);
canvas.drawRect(rect,mPaint);
if(guageX <= 0){
guageX = guageMinX;
}
int y = (height - getPaddingTop() - getPaddingBottom())/2 + getPaddingTop();
mPaint.setColor(lineColor);
Rect rect1 = new Rect(guageLineMinX,y-height/2/2/2,guageX,y+height/2/2/2);
canvas.drawRect(rect1,mPaint);
rect1.set(guageX,y-height/2/2/2,guageLineMaxX,y+height/2/2/2);
mPaint.setColor(Color.RED);
canvas.drawRect(rect1,mPaint);
mPaint.setColor(guageColor);
switch (guageModel){
case CIRCLE:
canvas.drawCircle(guageX,y,guageRadius,mPaint);
break;
case FIVE_POINTS_STAR:
break;
}
}
boolean canMove = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) getX();
int y = (int) getY();
int downX;
int downY;
if((x < getPaddingLeft() || x > width - getPaddingRight()) &&
y < getPaddingTop() || y > height - getPaddingBottom()){
return false;
}
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
downY = (int) getY();
if(downX > (guageX - guageRadius) && downX < (guageX + guageRadius)){
canMove = true;
Log.d("--- ","设置canMove为true");
listener.moveStart(this);
}
Log.d("---down == ","guageX = "+guageX + ",guageRadius = "+guageRadius+",downX = "+downX);
Log.d("---canMove == ",canMove+"");
guageX+=10;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if(canMove){
Log.d("---Move == ","正在移动,x = "+event.getX()+",guageX = "+guageX);
int nowX = (int) event.getX();
if(nowX > guageMaxX ){
guageX = guageMaxX;
}else if(nowX < guageMinX){
guageX = guageMinX;
}else {
guageX = nowX;
}
invalidate();
listener.moveing(this);
}
break;
case MotionEvent.ACTION_UP:
listener.moveEnd(this);
Log.d("----","设置可点击为false" + canMove);
canMove = false;
break;
}
return true;
}
public float getProgress(){
return (guageX - guageMinX)*1.0f/(guageMaxX - guageMinX);
}
public void setProgressChangeListener(MyProgressListener listener){
this.listener = listener;
}
public interface MyProgressListener{
void moveing(MyProgressView progress);
void moveStart(MyProgressView progress);
void moveEnd(MyProgressView progress);
}
}
总结一下
- 在获取父布局宽度的时候一定要用getMeasureWidth()方法,不然还有一个getWidth()这个方法是获取不到宽度的
- 进度条的x值我们需要去给他设置最大最小值
- 最后贴上git地址