• Android custom View AirConditionerView hacking


    package com.example.arc.view;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.RectF;
    import android.graphics.SweepGradient;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.View.MeasureSpec;
    import android.widget.Toast;
    
    /**
     *                 Android custom View AirConditionerView hacking
     *
     * 声明:
     *     本人在知乎看到Android非常漂亮的自定义View文章后,因为对其绘图部分运作机制好
     * 奇,于是叫程梦真帮忙一起分析其中代码运作机制,花了两个半小时才将其整体hacking完。
     *
     *                                              2016-1-1 深圳 南山平山村 曾剑锋
     *
     *
     * 一、程序来源:
     *     1. 这种ui谁能给点思路?
     *         https://m.zhihu.com/question/38598212#answer-26400956
     *     2. github:mutexliu/ZhihuAnswer
     *         https://github.com/mutexliu/ZhihuAnswer
     *
     * 二、参考文档:
     *     1. 为什么安卓android变量命名多以小写"m"开头 ?
     *         http://www.01yun.com/mobile_development_question/20130303/194172.html
     *     2. 自定义View之onMeasure()
     *         http://blog.csdn.net/pi9nc/article/details/18764863
     *     3. MeasureSpec介绍及使用详解
     *         http://www.cnblogs.com/slider/archive/2011/11/28/2266538.html
     *     4. SweepGradient扫描渲染
     *         http://blog.csdn.net/q445697127/article/details/7867506
     *     5. SweepGradient
     *         http://developer.android.com/reference/android/graphics/SweepGradient.html
     *     6. 覆写onLayout进行layout,含自定义ViewGroup例子
     *         http://blog.csdn.net/androiddevelop/article/details/8108970
     *     7. View.MeasureSpec
     *         http://developer.android.com/reference/android/view/View.MeasureSpec.html#getMode(int)
     *
     * 三、绘图的三个步骤:
     *     1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
     *     2. protected void onLayout(boolean changed, int left, int top, int right, int bottom);
     *         View中不需要实现。
     *     3. protected void onDraw(Canvas canvas);
     *          
     */
    public class AirConditionerView extends View {
        // 设置当前的温度
        // 这个值只能在16-28度之间,下面的private void checkTemperature(float t)方法中有限制
        private float mTemperature = 24f;
        // 画圆的paint
        private Paint mArcPaint;
        // 画上线的paint
        private Paint mLinePaint;
        // 写字的paint
        private Paint mTextPaint;
    
        // 这里是将控件宽度分为600份,mMinSize代表其中一份
        private float mMinSize;
        // 设置空心边框的宽度,其实就是圆弧的宽度
        private float mGapWidth;
        // 内圆半径
        private float mInnerRadius;
        // 外圆半径
        private float mRadius;
        // 中线点
        private float mCenter;
    
        // 圆弧矩形
        private RectF mArcRect;
        // 渐变渲染
        private SweepGradient mSweepGradient;
    
        // 接下来的三个构造函数是固定的写法,最后调用自己定义的的init方法
        public AirConditionerView(Context context) {
            super(context, null);
        }
    
        public AirConditionerView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public AirConditionerView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init();
        }
    
        private void init(){
            // Paint.Style.FILL              : 填充内部
            // Paint.Style.FILL_AND_STROKE    : 填充内部和描边
            // Paint.Style.STROKE            : 仅描边
    
            // 画圆弧的笔
            mArcPaint = new Paint();
            mArcPaint.setStyle(Paint.Style.STROKE);
            // 设置抗锯齿
            mArcPaint.setAntiAlias(true);
    
            // 画圆弧上的线的笔
            mLinePaint = new Paint();
            mLinePaint.setStyle(Paint.Style.STROKE);
            mLinePaint.setAntiAlias(true);
            // 设置笔的颜色
            mLinePaint.setColor(0xffdddddd);
    
            // 写字的笔
            mTextPaint = new Paint();
            mTextPaint.setAntiAlias(true);
            mTextPaint.setColor(0xff64646f);
        }
    
        private void initSize(){
            //寬度
            mGapWidth = 56*mMinSize;
            //内圆半径
            mInnerRadius = 180*mMinSize;
            mCenter = 300*mMinSize;
            //外圆半径
            mRadius = 208*mMinSize;
    
            // 包含圆弧的矩形
            mArcRect = new RectF(mCenter-mRadius, mCenter-mRadius, mCenter+mRadius, mCenter+mRadius);
    
            // 设置渐变色、渐变位置
            /**
             * A subclass of Shader that draws a sweep gradient around a center point.
             * 
             * Parameters
             * cx            The x-coordinate of the center
             * cy            The y-coordinate of the center
             * colors        The colors to be distributed between around the center. There must be at least 2 colors in the array.
             * positions    May be NULL. The relative position of each corresponding color in the colors array, beginning with 0 and ending with 1.0. If the values are not monotonic, the drawing may produce unexpected results. If positions is NULL, then the colors are automatically spaced evenly.
             */
            int[] colors = {
                0xFFE5BD7D,0xFFFAAA64,
                0xFFFFFFFF, 0xFF6AE2FD,
                0xFF8CD0E5, 0xFFA3CBCB,
                0xFFBDC7B3, 0xFFD1C299, 
                0xFFE5BD7D, 
            };
            float[] positions = {0,1f/8,2f/8,3f/8,4f/8,5f/8,6f/8,7f/8,1};
            mSweepGradient = new SweepGradient(mCenter, mCenter, colors , positions);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            /*****************   变色弧形部分  ************************/
            // draw arc 設置空心邊框的寬度
            // Set the width for stroking. Pass 0 to stroke in hairline mode. Hairlines always draws a single pixel independent of the canva's matrix.
            mArcPaint.setStrokeWidth(mGapWidth);
            int gapDegree = getDegree();
            // 绘制梯度渐变  
            // Set or clear the shader object.
            mArcPaint.setShader(mSweepGradient);
            //画渐变色弧形
            // Draw the specified arc, which will be scaled to fit inside the specified oval.
            // If the start angle is negative or >= 360, the start angle is treated as start angle modulo 360.
            // If the sweep angle is >= 360, then the oval is drawn completely. Note that this differs slightly from SkPath::arcTo, which treats the sweep angle modulo 360. If the sweep angle is negative, the sweep angle is treated as sweep angle modulo 360
            // The arc is drawn clockwise. An angle of 0 degrees correspond to the geometric angle of 0 degrees (3 o'clock on a watch.)
            // Parameters
            //    oval            The bounds of oval used to define the shape and size of the arc
            //    startAngle    Starting angle (in degrees) where the arc begins
            //    sweepAngle    Sweep angle (in degrees) measured clockwise
            //    useCenter        If true, include the center of the oval in the arc, and close it if it is being stroked. This will draw a wedge
            //    paint            The paint used to draw the arc
            canvas.drawArc(mArcRect, -225, gapDegree + 225, false, mArcPaint);
            
            
            /*****************   白色弧形部分  ************************/
            mArcPaint.setShader(null);
            mArcPaint.setColor(Color.WHITE);
            
            //画渐变色弧形
            canvas.drawArc(mArcRect, gapDegree, 45 - gapDegree, false, mArcPaint);
    
            // draw line
            /*****************   画线部分  ************************/
            mLinePaint.setStrokeWidth(mMinSize*1.5f);
            // 将圆等分成120份,每份占360度的3度
            for(int i = 0; i<120; i++){
                // (75-45)*3 = 30*3 = 90,不绘直线部分占90度,正好符合空白区
                if(i<=45 || i >= 75){ 
                    float top = mCenter-mInnerRadius-mGapWidth;
                    // 2度分成15格
                    if(i%15 == 0){
                        top = top - 20*mMinSize;
                    }
                    // 绘制垂直线
                    canvas.drawLine(mCenter, mCenter - mInnerRadius, mCenter, top, mLinePaint);
                }
                /**
                 * Preconcat the current matrix with the specified rotation.
                 * 
                 * Parameters
                 * degrees    The amount to rotate, in degrees
                 * px    The x-coord for the pivot point (unchanged by the rotation)
                 * py    The y-coord for the pivot point (unchanged by the rotation)
                 *
                 * 坐标系旋转3度,不是已经绘制的图形旋转3度
                 */
                canvas.rotate(3,mCenter,mCenter);
            }
    
            // draw text
            /*****************   弧形外部显示温度度数文字 部分  ************************/
            mTextPaint.setTextSize(mMinSize*30);
            mTextPaint.setTextAlign(Paint.Align.CENTER);
            for(int i = 16; i<29; i+=2){
                /**
                 * 计算文字在圆弧边缘的位置,用到三角函数计算。
                 */
                float r = mInnerRadius+mGapWidth + 40*mMinSize;
                float x = (float) (mCenter + r*Math.cos((26-i)/2*Math.PI/4));
                float y = (float) (mCenter - r*Math.sin((26-i)/2*Math.PI/4));
                canvas.drawText(""+i, x, y - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
            }
        }
    
        private int getDegree(){
            checkTemperature(mTemperature);
            return -225 + (int)((mTemperature-16)/12*90+0.5f)*3;
        }
        /*****************   用来控制事件的三个方法  ************************/
        private void checkTemperature(float t){
            if(t<16 || t > 28){
                throw new RuntimeException("Temperature out of range");
            }
        }
    
        public void setTemperature(float t){
            checkTemperature(t);
            mTemperature = t;
            // To force a view to draw, call invalidate().
            invalidate();
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // 获取触摸位置
            float x = event.getX();
            float y = event.getY();
    
            // 计算触摸点到中心点的距离,判断该点是否在圆弧之内
            double distance = Math.sqrt((x - mCenter) * (x - mCenter) + (y - mCenter) * (y - mCenter));
            if(distance < mInnerRadius || distance > mInnerRadius + mGapWidth){
                return false;
            }
            // 判断是否在空白区
            double degree = Math.atan2(-(y-mCenter),x-mCenter);
            if(-3*Math.PI/4<degree && degree < -Math.PI/4){
                return false;
            }
            // 计算角度,并转换为对应的温度,设置当前温度,设置温度之后会自动对View进行重绘
            if(degree < -3*Math.PI/4){
                degree = degree + 2*Math.PI;
            }
            float t = (float) (26 - degree*8/Math.PI);
            setTemperature(t);
    
            return true;
        }
        /*****************   为了获得mMinSize  ************************/
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int desiredWidth = Integer.MAX_VALUE;
    
            /**
             * Extracts the mode from the supplied measure specification.
             * 
             * Parameters
             *     measureSpec    the measure specification to extract the mode from
             * Returns
             *     UNSPECIFIED, AT_MOST or EXACTLY
             */
            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;
                //Toast.makeText(getContext(), "MeasureSpec.EXACTLY", 0).show();
            } else if (widthMode == MeasureSpec.AT_MOST) {
                width = Math.min(desiredWidth, widthSize);
                //Toast.makeText(getContext(), "MeasureSpec.AT_MOST"+desiredWidth+" "+widthSize, 0).show();
            } else {
                //Be whatever you want
                width = desiredWidth;
                //Toast.makeText(getContext(), "MeasureSpec.UNSPECIFIED", 0).show();
            }
            mMinSize = width/600f;
            
            int size = width;
            initSize();
            //Measure Height
            if (heightMode == MeasureSpec.EXACTLY) {
                //Must be this size
                height = heightSize;
            } else if (heightMode == View.MeasureSpec.AT_MOST) {
                //Can't be bigger than...
                height = Math.min(size, heightSize);
            } else {
                //Be whatever you want
                height = size;
            }
    
            //MUST CALL THIS
            /** 
             * This method must be called by onMeasure(int, int) to store the measured width and measured height. Failing to do so will trigger an exception at measurement time.
             * 
             * Parameters
             *     measuredWidth    The measured width of this view. May be a complex bit mask as defined by MEASURED_SIZE_MASK and MEASURED_STATE_TOO_SMALL.
             *     measuredHeight    The measured height of this view. May be a complex bit mask as defined by MEASURED_SIZE_MASK and MEASURED_STATE_TOO_SMALL.
             */
            setMeasuredDimension(width, height);
        }
    
    }

     

  • 相关阅读:
    linux Mint 安装apache2
    linux Mint 安装tomcat8
    linux Mint wine安装qq,桌面快捷键配置
    linux Mint mysql 安装
    卸载linux Mint自带jdk并安装最新jdk
    linux Mint截图软件 Shutter
    linux git安装及配置(包括更新)
    linux安装wine
    百度地图用ip获取当前位置的经纬度(高精度)
    mysql 索引和视图
  • 原文地址:https://www.cnblogs.com/zengjfgit/p/5093504.html
Copyright © 2020-2023  润新知