炫酷的空调调节器控件

来源:互联网 发布:古罗马 实力 知乎 编辑:程序博客网 时间:2024/04/28 14:41

介绍

最近工作有一些闲暇时间,本身项目并不是自己的,看着控件挺有意思的,而且效果市面上比较少见.抱着学习态度,撸了这个View.

转载请标明出处:
http://blog.csdn.net/u012401802/article/details/78543322

来自卡斯迪奥-北京的博客

github下载地址:https://github.com/liujiayu5566/AirConditionerView

看一下UI提供的效果


先看一下最终效果

代码实现

1.res/values/attr.xml

自定义属性: #

<?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="CirqueView">    <attr name="temperature_min" format="integer">10</attr>    <attr name="temperature_max" format="integer">30</attr>    <attr name="time_left" format="integer">10</attr>    <attr name="time_right" format="integer">30</attr> </declare-styleable></resources>

简单介绍一下:
temperature_min: 温度范围最小值.
temperature_max: 温度范围最大值.
time_left: 时间范围左侧数值.
time_right: 时间范围右侧数值.

CirqueView

2.获取自定义属性

public CirqueView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    this.context = context;    initPaint();    startAnim();    TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CirqueView, defStyleAttr, 0);    minTxt = ta.getInteger(R.styleable.CirqueView_temperature_min, 10);    maxTxt = ta.getInteger(R.styleable.CirqueView_temperature_max, 30);    timeMinTxt = ta.getInteger(R.styleable.CirqueView_time_left, 10);    timeMaxTxt = ta.getInteger(R.styleable.CirqueView_time_right, 30);    ta.recycle();    timeInitial = 165;}

3.重写onMeasure方法

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));}private int measure(int measureSpec) {    int result;    int specMode = MeasureSpec.getMode(measureSpec);    int specSize = MeasureSpec.getSize(measureSpec);    if (specMode == MeasureSpec.EXACTLY) {        result = specSize;    } else {        //这样,当时用wrap_content时,View就获得一个默认值200px,而不是填充整个父布局。        result = DensityUtil.dip2px(context, 200);        if (specMode == MeasureSpec.AT_MOST) {            result = Math.min(result, specSize);        }    }    return result;}

很通用的代码.

4.重写onSizeChanged方法

@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    radius = (getWidth() / 2) - DensityUtil.dip2px(context, 20);    oval.set(getWidth() / 2 - radius - defaultValue, getHeight() / 2 - radius - defaultValue,            getWidth() / 2 + radius + defaultValue, getHeight() / 2 + radius + defaultValue);    mSweepGradient = new SweepGradient(getWidth() / 2, getHeight() / 2, colors, floats);    cirque.setShader(mSweepGradient);}

radius计算半径,oval.set()设置绘制的范围,设置渐变颜色.

5.重写onDraw方法

@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //背景圆以及背景虚化    canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius + DensityUtil.dip2px(context, 5), circularBackground);    canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, circularPaint);    //弧形背景    canvas.drawArc(oval, 195, 150, false, cirqueBackground);//小弧形    canvas.drawArc(oval, 15, 150, false, cirqueBackground);//小弧形    //温度绘制弧形    if (mCurrentAngle != 0) {        canvas.drawArc(oval, 195, mCurrentAngle, false, cirque);//小弧形    }    //时间绘制弧形    if (mTimeCurrentAngle != 0) {        canvas.drawArc(oval, 165, -mTimeCurrentAngle, false, timecirque);//小弧形    }    //文字以及图标绘制    int v = maxTxt - minTxt;    int timeV = timeMaxTxt - timeMinTxt;    text = Math.round(mCurrentAngle / (150f / v)) + minTxt + "℃";    timeText = Math.round(mTimeCurrentAngle / (150f / timeV)) + timeMinTxt + "min";    textPaint.setColor(colors[(int) (mCurrentAngle / (15 + 0.5f))]);    canvas.drawText(text, getWidth() / 2 - textPaint.measureText(text) / 2, getHeight() / 2 - DensityUtil.dip2px(context, 5), textPaint);    canvas.drawText(timeText, getWidth() / 2 - timeTextPaint.measureText(timeText) / 2, getHeight() / 2 + DensityUtil.dip2px(context, 20), timeTextPaint);    canvas.drawText(timeMinTxt + "", getWidth() / 2 - radius - defaultValue - timeTextPaint.measureText(timeMinTxt + "") * 1 / 3 + value, getHeight() / 2 + DensityUtil.dip2px(context, 17), unitPaint);    canvas.drawText(timeMaxTxt + "", getWidth() / 2 + radius + defaultValue - timeTextPaint.measureText(timeMaxTxt + "") * 1 / 3 - value, getHeight() / 2 + DensityUtil.dip2px(context, 17), unitPaint);    canvas.drawBitmap(snowflake, getWidth() / 2 - radius - defaultValue - snowflake.getWidth() / 2 + value, getHeight() / 2 - DensityUtil.dip2px(context, 17), bitmapPaint);    canvas.drawBitmap(sun, getWidth() / 2 + radius + defaultValue - sun.getWidth() / 2 - value, getHeight() / 2 - DensityUtil.dip2px(context, 17), bitmapPaint);    //刻度绘制竖线    linePaint.setColor(colors[(int) (mCurrentAngle / (15 + 0.5f))]);    canvas.rotate(mCurrentAngle + 15f, getWidth() / 2, getHeight() / 2);    canvas.drawLine(getWidth() / 2 - radius - defaultValue - DensityUtil.dip2px(context, 1), getHeight() / 2 - DensityUtil.dip2px(context, 1), getWidth() / 2 - radius * 3 / 4, getHeight() / 2, linePaint);    canvas.rotate(-mTimeCurrentAngle - 30f - mCurrentAngle, getWidth() / 2, getHeight() / 2);    canvas.drawLine(getWidth() / 2 - radius - defaultValue - DensityUtil.dip2px(context, 1), getHeight() / 2 + DensityUtil.dip2px(context, 1), getWidth() / 2 - radius * 3 / 4, getHeight() / 2, timeLinePaint);    if (txtFinishListener != null) {  //监听        txtFinishListener.onFinish(text, timeText);    }}

onDraw方法中,绘制整个View.
第5行根据UI图背景边缘有蓝色的虚化效果,绘制一个比背景大5dp的实体圆,通过Paint.setMaskFilter(new BlurMaskFilter(30, BlurMaskFilter.Blur.NORMAL))方法实现边缘虚幻.
第23/32行,计算温度展示以及圆弧垂直直线的颜色,加入0.5f防止越界.

6.重写onTouchEvent方法

 @Overridepublic boolean onTouchEvent(MotionEvent event) {    /*获取点击位置的坐标*/    float Action_x = event.getX();    float Action_y = event.getY();    /*根据坐标转换成对应的角度*/    float get_x0 = Action_x - getWidth() / 2;    float get_y0 = Action_y - getHeight() / 2;    switch (event.getAction()) {        case MotionEvent.ACTION_DOWN:            if (Action_y < getHeight() / 2) { //温度                tag = true;            } else {//时间                tag = false;            }            break;        case MotionEvent.ACTION_MOVE:            if (tag) { //温度                /*02:左上角区域*/                if (get_x0 <= 0 & get_y0 <= 0) {                    float tan_x = get_x0 * (-1);                    float tan_y = get_y0 * (-1);                    double atan = Math.atan(tan_y / tan_x);                    mCurrentAngle = (int) Math.toDegrees(atan);                }                /*03:右上角区域*/                if (get_x0 >= 0 & get_y0 <= 0) {                    float tan_x = get_x0;                    float tan_y = get_y0 * (-1);                    double atan = Math.atan(tan_x / tan_y);                    mCurrentAngle = (int) Math.toDegrees(atan) + 90f;                }                if (Math.abs(mCurrentAngle) <= 1 || (get_x0 <= 0 & get_y0 >= 0))                    mCurrentAngle = 0;                if (Math.abs(mCurrentAngle) >= 149 || (get_x0 >= 0 & get_y0 >= 0))                    mCurrentAngle = 150;            } else if (!tag) { //时间                /*01:左下角区域*/                if (get_x0 <= 0 & get_y0 >= 0) {                    float tan_x = get_x0 * (-1);                    float tan_y = get_y0;                    double atan = Math.atan(tan_x / tan_y);                    mTimeCurrentAngle = -(int) Math.toDegrees(atan) + 90f;                }                /*04:右下角区域*/                if (get_x0 >= 0 & get_y0 >= 0) {                    float tan_x = get_x0;                    float tan_y = get_y0;                    double atan = Math.atan(tan_y / tan_x);                    mTimeCurrentAngle = -(int) Math.toDegrees(atan) + 180f;                }                if (Math.abs(mTimeCurrentAngle) <= 1 || (get_x0 <= 0 & get_y0 <= 0))                    mTimeCurrentAngle = 0;                if (Math.abs(mTimeCurrentAngle) >= 149 || (get_x0 >= 0 & get_y0 <= 0))                    mTimeCurrentAngle = 150;            }            break;        case MotionEvent.ACTION_POINTER_UP:            break;    }    /*得到点的角度后进行重绘*/    invalidate();    return true;}

onTouchEvent方法控制触摸事件.
但是原理很简单,简单看一下.
第4-8行:计算触摸点的位置,get_x0<0在触摸点控件左侧,反之在控件右侧.通过这种方式,将整个控件根据圆心分为4个区域.
第10行:根据按下操作,确定控制上下哪个方向.
第17行:计算滑动位置的角度.注意:滑动范围150°不能超出范围,以及滑动另一区域,也需要做对应处理.(34-37行,55-58行)

调用方法

CirqueView mCv = (CirqueView) findViewById(R.id.cv);//        mCv.setTemperaturemin(-30, 30); //设置温度范围  默认10-30//        mCv.setTime(0, 60); //设置时间范围  默认10-30    mCv.setDefault(27, 22);  //添加默认数据--注:不能超出范围    mCv.setTxtFinishListener(new CirqueView.txtFinishListener() {        @Override        public void onFinish(String temperature, String time) {            Util.showToast(MainActivity.this, temperature + "//" + time);        }    });

回调返回String类型数值.

总结

完成时间大概是3天时间,之前对自定义View这块很薄弱,相关API记忆不是很深,查了一些相关博客,对自己有很大帮助.还是多撸撸代码成长很大(撸代码上班时间过的非常快^_^!).

相关博客:
http://blog.csdn.net/rhljiayou/article/details/7212620
http://blog.csdn.net/yanbober/article/details/50577855?locationNum=1&fps=1

原创粉丝点击