A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 大山哥哥 黑马粉丝团   /  2016-12-31 18:44  /  1510 人查看  /  3 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

现在自定义控件越来越多,我们现在来写一个选择百分比的自定义控件,效果图如下:

从图片来看,这个view由中间数据展示部分、可以滑动的箭头、另个实心圆、固定百分比对应的点和默认百分比组成,接下来我们就一步步实现这个效果。
首先需要在attrs.xml中定义需要的参数(内圆颜色、外圆颜色、箭头颜色…….):
[AppleScript] 纯文本查看 复制代码
<declare-styleable name="ProgressDashboardView">
    <attr name="inner_cir_color" format="color"/>
    <attr name="inner_cir_radius" format="dimension"/>
    <attr name="outer_cir_color" format="color"/>
    <attr name="outer_cir_radius" format="dimension"/>
    <attr name="arrow_color" format="color"/>
    <attr name="outer_circle_line_color" format="color"/>
    <attr name="outer_circle_line_radius" format="dimension"/>
    <attr name="outer_circle_line_text_size" format="dimension"/>
    <attr name="inner_circle_value_text_size" format="dimension"/>
    <attr name="outer_circle_line_text_color" format="color"/>
    <attr name="inner_circle_value_text_color" format="color"/>
    <attr name="outer_circle_persent_point_color" format="color"/>
    <attr name="outer_circle_persent_point_select_color" format="color"/>
</declare-styleable>

然后在构造函数中获取并初始化各部分用到的画笔,这样我们在布局文件中就可以直接使用这些参数了,代码如下:
[AppleScript] 纯文本查看 复制代码
public ProgressDashboardView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ProgressDashboardView, defStyleAttr, 0);
    inner_cir_color = typedArray.getColor(R.styleable.ProgressDashboardView_inner_cir_color, Color.parseColor(default_inner_cir_color));
    inner_cir_radius = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_inner_cir_radius, 30);
    outer_cir_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_cir_color, Color.parseColor(default_out_cir_color));
    outer_cir_radius = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_outer_cir_radius, 35);
    arrow_color = typedArray.getColor(R.styleable.ProgressDashboardView_arrow_color, Color.parseColor(default_arrow_color));
    outer_circle_line_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_line_color, Color.parseColor(default_out_cir_line_color));
    outer_circle_line_radius = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_outer_circle_line_radius, 50);
    outer_circle_line_text_size = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_outer_circle_line_text_size, 22);
    inner_circle_value_text_size = typedArray.getDimensionPixelSize(R.styleable.ProgressDashboardView_inner_circle_value_text_size, 22);
    outer_circle_line_text_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_line_text_color, Color.parseColor(default_outer_circle_line_text_color));
    inner_circle_value_text_color = typedArray.getColor(R.styleable.ProgressDashboardView_inner_circle_value_text_color, Color.parseColor(default_inner_circle_value_text_color));
    outer_circle_persent_point_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_persent_point_color, Color.parseColor(default_outer_circle_persent_point_color));
    outer_circle_persent_point_select_color = typedArray.getColor(R.styleable.ProgressDashboardView_outer_circle_persent_point_select_color, Color.parseColor(default_outer_circle_persent_point_select_color));

    //外半圆线画笔
    out_cir_line_paint = new Paint();
    out_cir_line_paint.setStyle(Paint.Style.STROKE);
    out_cir_line_paint.setStrokeWidth(dip2px(getContext(),2));
    out_cir_line_paint.setAntiAlias(true);
    out_cir_line_paint.setColor(outer_circle_line_color);
    //内圆画笔
    inner_cir_paint = new Paint();
    inner_cir_paint.setStyle(Paint.Style.FILL);
    inner_cir_paint.setAntiAlias(true);
    inner_cir_paint.setColor(inner_cir_color);
    //外圆画笔
    outer_cir_paint = new Paint();
    outer_cir_paint.setStyle(Paint.Style.FILL);
    outer_cir_paint.setAntiAlias(true);
    outer_cir_paint.setColor(outer_cir_color);

    //百分比点画笔
    persent_point_paint = new Paint();
    persent_point_paint.setStrokeWidth(dip2px(getContext(), 4));
    persent_point_paint.setStyle(Paint.Style.FILL);
    persent_point_paint.setAntiAlias(true);
    persent_point_paint.setColor(outer_circle_line_color);

    //外圆环线对应的百分比画笔
    persent_default_paint = new TextPaint();
    persent_point_paint.setStyle(Paint.Style.FILL);
    persent_point_paint.setAntiAlias(true);
    persent_default_paint.setColor(outer_circle_line_color);
    persent_default_paint.setTextSize(outer_circle_line_text_size);

    //内圆百分比值对应的画笔
    persent_result_paint = new TextPaint();
    persent_result_paint.setStyle(Paint.Style.FILL);
    persent_result_paint.setAntiAlias(true);
    persent_result_paint.setColor(inner_circle_value_text_color);
    persent_result_paint.setTextSize(inner_circle_value_text_size);

    //箭头画笔
    arrow_paint = new Paint();
    arrow_paint.setColor(arrow_color);
    arrow_paint.setStyle(Paint.Style.FILL);
    arrow_paint.setAntiAlias(true);


}
好了,参数定义好了,我们开始画这个效果图,先画固定不变的部分,首先是中间两个实心圆,既然是画圆,就需要圆心和半径,半径已经定义了,圆心哪来呢,简单起见直接取控件中心点,如下:
[AppleScript] 纯文本查看 复制代码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int desiredWidth = dip2px(getContext(), 250);
    int desiredHeight = dip2px(getContext(), 250);
    /** 处理wrapcontent*****************************************************************/
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    int width;
    int height;
    //Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
        //Must be this size
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        width = Math.min(desiredWidth, widthSize);
    } else {
        //Be whatever you want
        width = desiredWidth;
    }
    //Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
        //Must be this size
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        //Can't be bigger than...
        height = Math.min(desiredHeight, heightSize);
    } else {
        //Be whatever you want
        height = desiredHeight;
    }
    circleX = width / 2;
    circleY = height / 2;
    //MUST CALL THIS
    setMeasuredDimension(width, height);
}
现在圆心和半径都有了,实现两个圆的代码如下:
[AppleScript] 纯文本查看 复制代码
private void drawInnerCircle(Canvas canvas) {
    canvas.drawCircle(circleX, circleY, outer_cir_radius, outer_cir_paint);
    canvas.drawCircle(circleX, circleY, inner_cir_radius, inner_cir_paint);

}
两个实心圆画好了,现在来画50%60%70%80%90%100%对应的点:
[AppleScript] 纯文本查看 复制代码
private void drawPersentPoint(Canvas canvas, int currentjiaodu) {
    float pointRadius = (outer_circle_line_radius - outer_cir_radius) / 2 + outer_cir_radius;
    for (int i = 0; i < 6; i++) {
        int jiaodu = 36 * i;
        if (Math.abs(currentjiaodu - jiaodu) < 3) {
            persent_point_paint.setColor(outer_circle_persent_point_select_color);
        }
        int radius = dip2px(getContext(), 2);
        if (jiaodu < 90) {
            canvas.drawCircle(circleX - (float) (pointRadius * Math.cos(Math.toRadians(jiaodu))), circleY - (float) (pointRadius * Math.sin(Math.toRadians(jiaodu))), radius, persent_point_paint);
        } else if (jiaodu == 90) {
            canvas.drawCircle(circleX, circleY - pointRadius, radius, persent_point_paint);
        } else if (jiaodu > 90) {
            canvas.drawCircle(circleX + (float) (pointRadius * Math.cos(Math.toRadians(180 - jiaodu))), circleY - (float) (pointRadius * Math.sin(Math.toRadians(180 - jiaodu))), radius, persent_point_paint);
        }

        persent_point_paint.setColor(outer_circle_persent_point_color);

    }

}
然后是外层圆环,代码比较简单:
[AppleScript] 纯文本查看 复制代码
private void drawOuterCirLine(Canvas canvas) {

    float left = circleX - outer_circle_line_radius;
    float top = circleY - outer_circle_line_radius;
    float right = circleX + outer_circle_line_radius;
    float bottom = circleY + outer_circle_line_radius;
    RectF rectF = new RectF(left, top, right, bottom);
    canvas.drawArc(rectF, -180, 180, false, out_cir_line_paint);
}
然后是固定的50%60%70%80%90%100%这几个值:
[AppleScript] 纯文本查看 复制代码
private void drawPersentDefaultValue(Canvas canvas) {
    for (int i = 0; i < 6; i++) {
        int jiaodu = 36 * i;
        float X = 0;
        float Y = 0;
        if (jiaodu < 90) {
            X = circleX - (float) (outer_circle_line_radius * Math.cos(Math.toRadians(jiaodu))) - persent_default_paint.measureText("50%");
            Y = circleY - (float) (outer_circle_line_radius * Math.sin(Math.toRadians(jiaodu)));
        } else if (jiaodu == 90) {
            X = circleX;
            Y = circleY - outer_circle_line_radius;
        } else if (jiaodu > 90) {
            X = circleX + (float) (outer_circle_line_radius * Math.cos(Math.toRadians(180 - jiaodu)));
            Y = circleY - (float) (outer_circle_line_radius * Math.sin(Math.toRadians(180 - jiaodu)));
        }
        canvas.drawText(50 + i * 10 + "%", X, Y, persent_default_paint);
    }
}
静态部分已经画完,接下来就要画动态部分(箭头和中间数值部分),箭头的位置和中间数值部分都是根据角度变化而变化的,假设50%的点对应的角度为0度,100%的点对应的角度为180,则每个百分点对应的角度为3.6度,假设当前的角度为jiaodu,现在开始画箭头,箭头其实就是三个点连成的线,我们知道canvas有一个方法canvas. drawPath(Path path, Paint paint)画一个闭合的回路,方法知道了,具体代码如下:
[AppleScript] 纯文本查看 复制代码
private void drawArrow(Canvas canvas, int jiaodu) {
    int triangleJiaodu = 5;
    Path path = new Path();
    if (jiaodu < 90) {
        path.moveTo(circleX - (float) (outer_cir_radius * Math.cos(Math.toRadians(jiaodu))), circleY - (float) (outer_cir_radius * Math.sin(Math.toRadians(jiaodu))));
        //左点
        path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu - triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu - triangleJiaodu))));

        //判断画右点
        if ((jiaodu + triangleJiaodu) < 90) {
            path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu + triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu + triangleJiaodu))));
        } else if ((jiaodu + triangleJiaodu) == 90) {
            path.lineTo(circleX, circleY - inner_cir_radius);

        } else if ((jiaodu + triangleJiaodu) > 90) {
            path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - (jiaodu + triangleJiaodu)))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu - triangleJiaodu))));
        }
    } else if (jiaodu == 90) {
        path.moveTo(circleX, circleY - outer_cir_radius);
        //左点
        path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu + triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu + triangleJiaodu))));
        //右点
        path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - (jiaodu + triangleJiaodu)))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu - triangleJiaodu))));
    } else if (jiaodu > 90) {
        path.moveTo(circleX + (float) (outer_cir_radius * Math.cos(Math.toRadians(180 - jiaodu))), circleY - (float) (outer_cir_radius * Math.sin(Math.toRadians(180 - jiaodu))));

        //判断画左点
        if ((jiaodu - triangleJiaodu) < 90) {
            path.lineTo(circleX - (float) (inner_cir_radius * Math.cos(Math.toRadians(jiaodu - triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(jiaodu - triangleJiaodu))));
        } else if ((jiaodu - triangleJiaodu) == 90) {
            path.lineTo(circleX, circleY - inner_cir_radius);
        } else if ((jiaodu - triangleJiaodu) > 90) {
            path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - jiaodu + triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu + triangleJiaodu))));

        }
        path.lineTo(circleX + (float) (inner_cir_radius * Math.cos(Math.toRadians(180 - jiaodu - triangleJiaodu))), circleY - (float) (inner_cir_radius * Math.sin(Math.toRadians(180 - jiaodu - triangleJiaodu))));

    }

    path.close();
    canvas.drawPath(path, arrow_paint);
}
箭头的重点在于三个点的计算,因为三个点对应的角度都可能为90度的情况,所以要判断计算。
箭头这样就画完了,还剩中间结果百分比,这个其实就是画一个textview,根据角度算出值即可,代码如下:
[AppleScript] 纯文本查看 复制代码
private void drawPersentValue(Canvas canvas, int jiaodu) {

    canvas.drawText((50 + (int) (jiaodu / 3.6)) + "%", circleX - persent_result_paint.measureText("70%") / 2, circleY + persent_result_paint.measureText("00") / 2, persent_result_paint);
}
箭头和中间的百分比都是根据角度来画的,那这个角度是怎么来的呢,我们实现的功能为手指可以波动箭头,手指移动到哪,箭头就旋转到对应的位置,所以我们需要确定手机移动到的位置,重写onTouchEvent
方法,MotionEvent.ACTION_MOVE事件中不断计算角度的值,然后不断重绘,代码如下:
[AppleScript] 纯文本查看 复制代码
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN: 
                startY = event.getY();
                if (startY >= circleY) {
                    startY = circleY;
                }
            case MotionEvent.ACTION_MOVE:
                float endX = event.getX();
                float endY = event.getY();
                if (endY >= circleY) {
                    endY = circleY;
                }
                jiaodu = (int) calAngle(circleX - outer_cir_radius, circleY, event.getX(), endY);
                if (changeValueListener != null) {
                    changeValueListener.onValueChange((int) (50 + jiaodu / 3.6));
                }
                invalidate();
                startY = endY;
            case MotionEvent.ACTION_UP: 
                break;
            default:
                break;

        }
        return true;
    }
因为在程序用运用的时候我们是需要获取到当前百分比的值得,写一个回调方法,当手指滑动时不停的回调该方法。
[AppleScript] 纯文本查看 复制代码
public void setOnChangeValueListener(ChangeValueListener changeValueListener) {
    this.changeValueListener = changeValueListener;
}

public interface ChangeValueListener {
    void onValueChange(int value);
}
控件这就写完了,然后就是使用过程了,首先布局文件:
[AppleScript] 纯文本查看 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <com.hobby.progressdashboardviewdemo.ProgressDashboardView
        android:id="@+id/progressDashboardView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        app:arrow_color="@color/txt_color"
        app:inner_cir_radius="40dp"
        app:inner_circle_value_text_size="18sp"
        app:outer_cir_radius="50dp"
        app:outer_circle_line_radius="80dp"
        app:outer_circle_line_text_size="16sp" />
    <TextView
        android:id="@+id/tv_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/txt_color"
        android:layout_gravity="center"
        android:textSize="18sp" />
</LinearLayout>
然后在Activity中获取百分(设置value变化监听):
[AppleScript] 纯文本查看 复制代码
progressDashboardView.setOnChangeValueListener(changeValueListener);

ProgressDashboardView.ChangeValueListener changeValueListener = new ProgressDashboardView.ChangeValueListener() {
    @Override
    public void onValueChange(int value) {
        tvValue.setText(value + "%");
    }
};

游客,如果您要查看本帖隐藏内容请回复

3 个回复

倒序浏览
回复 使用道具 举报
谢谢分享
回复 使用道具 举报
多谢分享
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马