近来公司事前较少,再来找几个有意思的View练练手,下面是原生华为天气预报界面: 
下面是仿的UI交互效果:

**思路: 1.平移画布到View中心,先绘制一个圆弧,绘制中间文字 2.旋转画布,绘制小短线,同时绘制中间的温度和下边的图片 3.确定0摄氏度的位置,确定每日温差之间共覆盖多少角度 4.算出最小温度的起始角,根据cos,sin计算坐标,绘制温度数字。 5.算出最小温度的短线索引,加粗变色绘制。**
1.首先在onMeasure中保证View宽高。
@Override PRotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wrap_Len = 600; int width = measureDimension(wrap_Len, widthMeasureSpec); int height = measureDimension(wrap_Len, heightMeasureSpec); int len=Math.min(width,height); //保证是一个正方形 setMeasuredDimension(len,len); } public int measureDimension(int defaultSize, int measureSpec){ int result; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if(specMode == MeasureSpec.EXACTLY){ result = specSize; }else{ result = defaultSize; //UNSPECIFIED if(specMode == MeasureSpec.AT_MOST){ result = Math.min(result, specSize); } } return result; }然后开始平移画布,绘制一个圆弧 mWidth=getWidth(); mHeight=getHeight(); radius=(mWidth-getPaddingLeft()-getPaddingRight())/2;//半径 canvas.translate(mWidth/2,mHeight/2); private void drawArcView(Canvas canvas) { RectF mRect=new RectF(-radius,-radius,radius,radius); //canvas.drawRect(mRect,mArcPaint); canvas.drawArc(mRect,startAngle,sweepAngle,false,mArcPaint); }效果如图: 
2.旋转画布,绘制小短线 以一定的角度旋转画布,循环画出短线
private void drawLine(Canvas canvas) { canvas.save(); float angle = (float)sweepAngle/count;//刻度间隔 canvas.rotate(-270+startAngle);//将起始刻度点旋转到正上方 for(int i=0;i<=count;i++){ if(i==0 || i==count){ mLinePaint.setStrokeWidth(1); mLinePaint.setColor(Color.WHITE); canvas.drawLine(0,-radius,0,-radius+40,mLinePaint); }else if(i>=getStartLineIndex(minTemp,maxTemp) && i<=getEndLineIndex(minTemp,maxTemp)){ mLinePaint.setStrokeWidth(3); mLinePaint.setColor(getRealColor(minTemp,maxTemp)); canvas.drawLine(0,-radius,0,-radius+30,mLinePaint); }else { mLinePaint.setStrokeWidth(2); mLinePaint.setColor(Color.WHITE); canvas.drawLine(0,-radius,0,-radius+30,mLinePaint); } canvas.rotate(angle);//逆时针旋转 } canvas.restore(); }效果如图:
然后绘制中间的温度和下边的图片,并注释掉绘制圆环的方法
private void drawTextBitmapView(Canvas canvas) { mTextPaint.setTextSize(144); canvas.drawText(currentTemp+"°",0,0+getTextPaintOffset(mTextPaint),mTextPaint); canvas.drawBitmap(bitmap,0-bitmap.getWidth()/2,radius-bitmap.getHeight()/2-30,null); }其中getTextPaintOffset()函数改变文字的Y坐标偏移量
public float getTextPaintOffset(Paint paint){ Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); return -fontMetrics.descent+(fontMetrics.bottom-fontMetrics.top)/2; }效果如图:
3.然后确定0摄氏度在圆环中的位置,设为230度的位置为0度
计算圆环中230度的X,y坐标。 //根据角获得X坐标 R*cos&+getTextPaintOffset(mTextPaint)-off private float getCosX(int Angle){ return (float) (radius*Math.cos(Angle*Math.PI/180))+getTextPaintOffset(mTextPaint); } private float getSinY(int Angle){ return (float)(radius*Math.sin(Angle*Math.PI/180))+getTextPaintOffset(mTextPaint); } 因为平移的坐标系,此时要根据象限决定偏移量的正负
private float getRealCosX(int Angle,int off,boolean outoff){ if(!outoff){ off=-off; } if(getCosX(Angle)<0){ return getCosX(Angle)-off; }else{ return getCosX(Angle)+off; } } private float getRealSinY(int Angle,int off,boolean outoff){ if(!outoff){ off=-off; } if(getSinY(Angle)<0){ return getSinY(Angle)-off; }else{ return getSinY(Angle)+off; } }有了正确的X,Y坐标即可绘制出文字。有了0摄氏度的位置就能确定当天温度范围的具体角。
//根据当天温度范围获得扇形开始角。 private int getStartAngle(int minTemp,int maxTemp){ int startFgAngle=0; if(minTemp>=maxTemp){ Log.e("ws","getStartAngle---?fail"); return startFgAngle; } if(minTemp<=0){ startFgAngle=ocAngle - (0 - minTemp)*fgAngle/(maxTemp-minTemp); }else{ startFgAngle=ocAngle+(minTemp-0)*fgAngle/(maxTemp-minTemp); } //边界 start if(startFgAngle<=startAngle){//如果开始角小于startAngle,防止过边界 startFgAngle=startAngle+10; }else if((startFgAngle+fgAngle)>=(startAngle+sweepAngle)){//如果结束角大于(startAngle+sweepAngle) startFgAngle =startAngle+sweepAngle-20-fgAngle; } //边界 end return startFgAngle; }求出具体角后就可以确定X,Y坐标了
private void drawTempLineView(Canvas canvas) { mTextPaint.setTextSize(24); canvas.drawText("0°C",getRealCosX(ocAngle,offset,true),getRealSinY(ocAngle,offset,true),mTextPaint);//固定0度的位置 int startTempAngle=getStartAngle(minTemp,maxTemp); /* if(startTempAngle<=startAngle){//如果开始角小于startAngle,防止过边界 startTempAngle=startAngle+10; }else if((startTempAngle+fgAngle)>=(startAngle+sweepAngle)){//如果结束角大于(startAngle+sweepAngle) startTempAngle =startAngle+sweepAngle-20-fgAngle; }*/ canvas.drawText(minTemp + "°", getRealCosX(startTempAngle, offset,true), getRealSinY(startTempAngle, offset,true), mTextPaint); canvas.drawText(maxTemp + "°", getRealCosX(startTempAngle+fgAngle, offset,true), getRealSinY(startTempAngle+fgAngle, offset,true), mTextPaint); // int circleAngle = startTempAngle+(currentTemp-minTemp)*fgAngle/(maxTemp-minTemp); // mPointPaint.setColor(getRealColor(minTemp,maxTemp)); //canvas.drawCircle(getRealCosX(circleAngle,50,false),getRealSinY(circleAngle,50,false),7,mPointPaint); }
然后就是要绘制这段温度范围的线颜色,以及当前温度的点 绘制点:
int circleAngle = startTempAngle+(currentTemp-minTemp)*fgAngle/(maxTemp-minTemp); mPointPaint.setColor(getRealColor(minTemp,maxTemp)); canvas.drawCircle(getRealCosX(circleAngle,50,false),getRealSinY(circleAngle,50,false),7,mPointPaint);绘制当天温度范围的线,即在drawLine方法中加上
else if(i>=getStartLineIndex(minTemp,maxTemp) && i<=getEndLineIndex(minTemp,maxTemp)){ mLinePaint.setStrokeWidth(3); mLinePaint.setColor(getRealColor(minTemp,maxTemp)); canvas.drawLine(0,-radius,0,-radius+30,mLinePaint);//根据当天温度范围获取开始短线的索引 private int getStartLineIndex(int minTemp,int maxTemp){ return (getStartAngle(minTemp,maxTemp)-startAngle)/(sweepAngle/count); } private int getEndLineIndex(int minTemp,int maxTemp){ return (getStartAngle(minTemp,maxTemp)-startAngle)/(sweepAngle/count)+fgAngle/(sweepAngle/count); } //根据温度返回颜色值 public int getRealColor(int minTemp,int maxTemp){ if(maxTemp<=0){ return Color.parseColor("#00008B");//深海蓝 }else if(minTemp<=0 && maxTemp>0){ return Color.parseColor("#4169E1");//黄君兰 }else if(minTemp>0 && minTemp<15 ){ return Color.parseColor("#40E0D0");//宝石绿 }else if(minTemp>=15 && minTemp<25){ return Color.parseColor("#00FF00");//酸橙绿 }else if(minTemp>=25 &&minTemp<30){ return Color.parseColor("#FFD700");//金色 }else if(minTemp>=30){ return Color.parseColor("#CD5C5C");//印度红 } return Color.parseColor("#00FF00");//酸橙绿; }最终效果图 

import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.text.TextPaint;import android.util.AttributeSet;import android.util.Log;import android.view.View;/** * Created by ws on 2017/2/25. */public class NowHwWeatherView extends View { private Paint mArcPaint; private Paint mLinePaint; private Paint mTextPaint; private Paint mPointPaint; private float mWidth; private float mHeight; private float radius;//半径 private int startAngle;//圆弧开始角 private int sweepAngle;//圆弧总角度数 private int count;//圆弧被分的份数 private int currentTemp;//当前温度 private int maxTemp; private int minTemp; private Bitmap bitmap; private int ocAngle;//0度初始角 private int fgAngle;//总覆盖的角 private int offset; public NowHwWeatherView(Context context) { this(context,null); } public NowHwWeatherView(Context context, AttributeSet attrs) { this(context, attrs,0); } public NowHwWeatherView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { initPaint(); startAngle=120; sweepAngle=300; count=60;//刻度份数 currentTemp=26; maxTemp=27; minTemp=20; bitmap= BitmapFactory.decodeResource(context.getResources(), R.drawable.w16); ocAngle=230; fgAngle=90; offset=22; } private void initPaint() { mArcPaint=new Paint(); mArcPaint.setColor(Color.WHITE); mArcPaint.setStrokeWidth(2); mArcPaint.setStyle(Paint.Style.STROKE); mArcPaint.setAntiAlias(true); mLinePaint=new Paint(); mLinePaint.setColor(Color.WHITE); mLinePaint.setStrokeWidth(2); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setAntiAlias(true); mTextPaint=new TextPaint(); mTextPaint.setColor(Color.WHITE); mTextPaint.setStrokeWidth(4); mTextPaint.setAntiAlias(true); mTextPaint.setTextAlign(Paint.Align.CENTER); mTextPaint.setTextSize(144); mPointPaint=new Paint(); mPointPaint.setColor(Color.WHITE); mPointPaint.setStrokeWidth(2); mPointPaint.setStyle(Paint.Style.FILL); mPointPaint.setAntiAlias(true); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int wrap_Len = 600; int width = measureDimension(wrap_Len, widthMeasureSpec); int height = measureDimension(wrap_Len, heightMeasureSpec); int len=Math.min(width,height); //保证是一个正方形 setMeasuredDimension(len,len); } public int measureDimension(int defaultSize, int measureSpec){ int result; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if(specMode == MeasureSpec.EXACTLY){ result = specSize; }else{ result = defaultSize; //UNSPECIFIED if(specMode == MeasureSpec.AT_MOST){ result = Math.min(result, specSize); } } return result; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mWidth=getWidth(); mHeight=getHeight(); radius=(mWidth-getPaddingLeft()-getPaddingRight())/2;//半径 canvas.translate(mWidth/2,mHeight/2); //drawArcView(canvas);//画圆环 drawLine(canvas);//画短线 drawTextBitmapView(canvas);//画中间的温度和下边的图片 drawTempLineView(canvas);//画动态温度 } private void drawTempLineView(Canvas canvas) { mTextPaint.setTextSize(24); //canvas.drawText("0°C",getRealCosX(ocAngle,offset,true),getRealSinY(ocAngle,offset,true),mTextPaint);//固定0度的位置 int startTempAngle=getStartAngle(minTemp,maxTemp); /* if(startTempAngle<=startAngle){//如果开始角小于startAngle,防止过边界 startTempAngle=startAngle+10; }else if((startTempAngle+fgAngle)>=(startAngle+sweepAngle)){//如果结束角大于(startAngle+sweepAngle) startTempAngle =startAngle+sweepAngle-20-fgAngle; }*/ canvas.drawText(minTemp + "°", getRealCosX(startTempAngle, offset,true), getRealSinY(startTempAngle, offset,true), mTextPaint); canvas.drawText(maxTemp + "°", getRealCosX(startTempAngle+fgAngle, offset,true), getRealSinY(startTempAngle+fgAngle, offset,true), mTextPaint); int circleAngle = startTempAngle+(currentTemp-minTemp)*fgAngle/(maxTemp-minTemp); mPointPaint.setColor(getRealColor(minTemp,maxTemp)); canvas.drawCircle(getRealCosX(circleAngle,50,false),getRealSinY(circleAngle,50,false),7,mPointPaint); } private void drawArcView(Canvas canvas) { RectF mRect=new RectF(-radius,-radius,radius,radius); //canvas.drawRect(mRect,mArcPaint); canvas.drawArc(mRect,startAngle,sweepAngle,false,mArcPaint); } private void drawLine(Canvas canvas) { canvas.save(); float angle = (float)sweepAngle/count;//刻度间隔 canvas.rotate(-270+startAngle);//将起始刻度点旋转到正上方 for(int i=0;i<=count;i++){ if(i==0 || i==count){ mLinePaint.setStrokeWidth(1); mLinePaint.setColor(Color.WHITE); canvas.drawLine(0,-radius,0,-radius+40,mLinePaint); }else if(i>=getStartLineIndex(minTemp,maxTemp) && i<=getEndLineIndex(minTemp,maxTemp)){ mLinePaint.setStrokeWidth(3); mLinePaint.setColor(getRealColor(minTemp,maxTemp)); canvas.drawLine(0,-radius,0,-radius+30,mLinePaint); }else { mLinePaint.setStrokeWidth(2); mLinePaint.setColor(Color.WHITE); canvas.drawLine(0,-radius,0,-radius+30,mLinePaint); } canvas.rotate(angle);//逆时针旋转 } canvas.restore(); } private void drawTextBitmapView(Canvas canvas) { mTextPaint.setTextSize(144); canvas.drawText(currentTemp+"°",0,0+getTextPaintOffset(mTextPaint),mTextPaint); canvas.drawBitmap(bitmap,0-bitmap.getWidth()/2,radius-bitmap.getHeight()/2-30,null); } public float getTextPaintOffset(Paint paint){ Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); return -fontMetrics.descent+(fontMetrics.bottom-fontMetrics.top)/2; } //根据角获得X坐标 R*cos&+getTextPaintOffset(mTextPaint)-off private float getCosX(int Angle){ return (float) (radius*Math.cos(Angle*Math.PI/180))+getTextPaintOffset(mTextPaint); } private float getSinY(int Angle){ return (float)(radius*Math.sin(Angle*Math.PI/180))+getTextPaintOffset(mTextPaint); } //根据象限加一个偏移量 private float getRealCosX(int Angle,int off,boolean outoff){ if(!outoff){ off=-off; } if(getCosX(Angle)<0){ return getCosX(Angle)-off; }else{ return getCosX(Angle)+off; } } private float getRealSinY(int Angle,int off,boolean outoff){ if(!outoff){ off=-off; } if(getSinY(Angle)<0){ return getSinY(Angle)-off; }else{ return getSinY(Angle)+off; } } //根据当天温度范围获得扇形开始角。 private int getStartAngle(int minTemp,int maxTemp){ int startFgAngle=0; if(minTemp>=maxTemp){ Log.e("ws","getStartAngle---?fail"); return startFgAngle; } if(minTemp<=0){ startFgAngle=ocAngle - (0 - minTemp)*fgAngle/(maxTemp-minTemp); }else{ startFgAngle=ocAngle+(minTemp-0)*fgAngle/(maxTemp-minTemp); } //边界 start if(startFgAngle<=startAngle){//如果开始角小于startAngle,防止过边界 startFgAngle=startAngle+10; }else if((startFgAngle+fgAngle)>=(startAngle+sweepAngle)){//如果结束角大于(startAngle+sweepAngle) startFgAngle =startAngle+sweepAngle-20-fgAngle; } //边界 end return startFgAngle; } //根据当天温度范围获取开始短线的索引 private int getStartLineIndex(int minTemp,int maxTemp){ return (getStartAngle(minTemp,maxTemp)-startAngle)/(sweepAngle/count); } private int getEndLineIndex(int minTemp,int maxTemp){ return (getStartAngle(minTemp,maxTemp)-startAngle)/(sweepAngle/count)+fgAngle/(sweepAngle/count); } //根据温度返回颜色值 public int getRealColor(int minTemp,int maxTemp){ if(maxTemp<=0){ return Color.parseColor("#00008B");//深海蓝 }else if(minTemp<=0 && maxTemp>0){ return Color.parseColor("#4169E1");//黄君兰 }else if(minTemp>0 && minTemp<15 ){ return Color.parseColor("#40E0D0");//宝石绿 }else if(minTemp>=15 && minTemp<25){ return Color.parseColor("#00FF00");//酸橙绿 }else if(minTemp>=25 &&minTemp<30){ return Color.parseColor("#FFD700");//金色 }else if(minTemp>=30){ return Color.parseColor("#CD5C5C");//印度红 } return Color.parseColor("#00FF00");//酸橙绿; } public void setBitmap(Bitmap mBitmap){ this.bitmap=mBitmap; invalidate(); }}