展示样式:
/** * 温度曲线 (趋势)图 可滑动的单曲线 */ public class FortyFiveTempCurveView extends View { float moveX; float moveY; Context mContext; /** * 重要参数,两点之间分为几段描画,数字愈大分段越多,描画的曲线就越精细. */ private static final int STEPS = 12; List<Integer> points_x; List<Integer> points_y; private static final String TAG = "Today24HourView"; private int ITEM_SIZE = 45; // 45天数据 private int ITEM_WIDTH = 20; //每个Item的宽度 private static final int MARGIN_LEFT_ITEM = 30; //左边预留宽度 private static final int MARGIN_RIGHT_ITEM = 40; //右边预留宽度 private static final int MARGIN_TOP_ITEM = 20; //上边 预留宽度 private static final int windyBoxAlpha = 80; private static final int windyBoxMaxHeight = 60; private static final int windyBoxMinHeight = 20; private static final int windyBoxSubHight = windyBoxMaxHeight - windyBoxMinHeight; //底部 日期字的高度 private static final int bottomTextHeight = 60; private int mHeight, mWidth; private int tempBaseTop; //温度折线的上边Y坐标 private int tempBaseBottom; //温度折线的下边Y坐标 private Paint bitmapPaint, windyBoxPaint, linePaint, pointPaint, dashLinePaint, curveLinePaint; private TextPaint textPaint; private List<HourItem> listItems; private int maxScrollOffset = 0;//滚动条最长滚动距离 private int scrollOffset = 0; //滚动条偏移量 private int currentItemIndex = 0; //当前滚动的位置所对应的item下标 private int currentWeatherRes = -1; //线的高度 private int lineHeight; Canvas curCanvas; private int maxTemp = 98; private int minTemp = 5; /* private int maxWindy = 5; private int minWindy = 2;*/ private static final int TEMP[] = {22, 23, 23, 23, 23, 21, 23, 23, 28, 22, 21, 21, 22, 22, 23, 23, 24, 24, 29, 25, 25, 26, 25, 24}; /*private static final int WINDY[] = {2, 2, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 4, 2, 2, 2, 3, 3, 3, 5, 5, 5};*/ private List<WeatherDayDetailVo> weatherDayList; /*private static final int WEATHER_RES[] = {R.mipmap.w0, R.mipmap.w1, R.mipmap.w3, -1, -1 , R.mipmap.w5, R.mipmap.w7, R.mipmap.w9, -1, -1 , -1, R.mipmap.w10, R.mipmap.w15, -1, -1 , -1, -1, -1, -1, -1 , R.mipmap.w18, -1, -1, R.mipmap.w19};*/ public FortyFiveTempCurveView(Context context, List<WeatherDayDetailVo> weatherList) { this(context, null, weatherList); } public FortyFiveTempCurveView(Context context, AttributeSet attrs, List<WeatherDayDetailVo> weatherList) { this(context, attrs, 0, weatherList); } public FortyFiveTempCurveView(Context context, AttributeSet attrs, int defStyleAttr, List<WeatherDayDetailVo> weatherList) { super(context, attrs, defStyleAttr); this.mContext = context; init(weatherList); } private void init(List<WeatherDayDetailVo> weatherList) {//WeatherAirQuality //同步item的个数 ITEM_SIZE = weatherList.size(); ITEM_WIDTH = (ScreenUtils.getScreenWidth(mContext) - MARGIN_LEFT_ITEM - MARGIN_RIGHT_ITEM) / ITEM_SIZE; mWidth = MARGIN_LEFT_ITEM + MARGIN_RIGHT_ITEM + ITEM_SIZE * ITEM_WIDTH; mHeight = DensityUtil.dip2px(getContext(), 140 + MARGIN_TOP_ITEM); //暂时先写死 lineHeight = mHeight / 3 * 2; tempBaseTop = (mHeight - bottomTextHeight) / 4; tempBaseBottom = (mHeight - bottomTextHeight) * 2 / 3; points_x = new LinkedList<>(); points_y = new LinkedList<>(); initPaint(); this.weatherDayList = weatherList; initHourItems(); } private void initPaint() { pointPaint = new Paint(); pointPaint.setColor(getResources().getColor(R.color.color_FF39A124)); //渐变色 pointPaint.setAntiAlias(true); pointPaint.setTextSize(2); linePaint = new Paint(); linePaint.setColor(getResources().getColor(R.color.color_989a9f)); linePaint.setAntiAlias(true); linePaint.setStyle(Paint.Style.STROKE); linePaint.setStrokeWidth(3); curveLinePaint = new Paint(); curveLinePaint.setColor(getResources().getColor(R.color.color_thirty_white)); curveLinePaint.setAntiAlias(true); curveLinePaint.setStyle(Paint.Style.STROKE); curveLinePaint.setStrokeWidth(3); dashLinePaint = new Paint(); dashLinePaint.setColor(new Color().WHITE); PathEffect effect = new DashPathEffect(new float[]{5, 5, 5, 5}, 1); dashLinePaint.setPathEffect(effect); dashLinePaint.setStrokeWidth(3); dashLinePaint.setAntiAlias(true); dashLinePaint.setStyle(Paint.Style.STROKE); windyBoxPaint = new Paint(); windyBoxPaint.setTextSize(1); windyBoxPaint.setColor(new Color().WHITE); windyBoxPaint.setAlpha(windyBoxAlpha); windyBoxPaint.setAntiAlias(true); textPaint = new TextPaint(); textPaint.setTextSize(DisplayUtil.sp2px(getContext(), 12)); textPaint.setColor(getResources().getColor(R.color.color_white)); textPaint.setAntiAlias(true); bitmapPaint = new Paint(); bitmapPaint.setAntiAlias(true); } // 45 天曲线 通过 最高温的 值进行计算 private void initHourItems() { listItems = new ArrayList<>(); if (weatherDayList == null) { return; } //获取当前的空气质量最大值,最小值 maxTemp = getMaxAirQualityScore(weatherDayList); minTemp = getMinAirQualityScore(weatherDayList); for (int i = 0; i < weatherDayList.size(); i++) { int left = MARGIN_LEFT_ITEM + i * ITEM_WIDTH; int right = left + ITEM_WIDTH - 1; int top = (int) (mHeight - bottomTextHeight + (maxTemp - weatherDayList.get(i).getMaxTemperature() /*WINDY[i]*/) * 1.0 / (maxTemp - minTemp) * windyBoxSubHight - windyBoxMaxHeight); int bottom = mHeight - bottomTextHeight; Rect rect = new Rect(left, top, right, bottom); Point point = calculateTempPoint(left, right, weatherDayList.get(i).getMaxTemperature(), i); HourItem hourItem = new HourItem(); hourItem.windyBoxRect = rect; //DateUtil.timeStamp2Date(model.getEpochTime(), "MM-dd") // hourItem.time = airQualityList.get(i).getDataText() + ":00"; hourItem.time = DateUtil.timeStamp2Date(weatherDayList.get(i).getEpochTime(), "MM-dd"); hourItem.windy = weatherDayList.get(i).getMaxTemperature(); hourItem.epochTime = weatherDayList.get(i).getEpochTime(); hourItem.temperature = weatherDayList.get(i).getMaxTemperature(); hourItem.tempPoint = point; hourItem.qualityLevel = "优"; Log.i("===========", "添加数据" + i + "hourItem.time" + hourItem.time); listItems.add(hourItem); } } private Point calculateTempPoint(int left, int right, int temp, int i) { double minHeight = tempBaseTop; double maxHeight = tempBaseBottom; //与竖线保持一致 int leftX = i * ITEM_WIDTH + (right - left) / 2; //取出垂直Y的值 double tempY = maxHeight - (temp - minTemp) * 1.0 / (maxTemp - minTemp) * (maxHeight - minHeight); // Point point = new Point((left + right) / 2 - (int) (ITEM_WIDTH * 1.5), (int) tempY + DensityUtil.dip2px(mContext, 15));//修改错位的方法 , 左边移动 两个 ITEM_WIDTH Point point = new Point(leftX, (int) tempY + DensityUtil.dip2px(mContext, 15));//修改错位的方法 , 左边移动 两个 ITEM_WIDTH return point; } /** * 获取空气质量最大值 * * @param airQualityList * @return */ private int getMaxAirQualityScore(List<WeatherDayDetailVo> airQualityList) { if (airQualityList != null) { return Collections.max(airQualityList, new AirQualityScoreMaxComparator()).getMaxTemperature(); } return 0; } /** * 获取空气质量最小值 * * @param airQualityList * @return */ private int getMinAirQualityScore(List<WeatherDayDetailVo> airQualityList) { if (airQualityList != null) { return Collections.min(airQualityList, new AirQualityScoreMaxComparator()).getMaxTemperature(); } return 0; } private static class AirQualityScoreMaxComparator implements Comparator<WeatherDayDetailVo> { @Override public int compare(WeatherDayDetailVo o1, WeatherDayDetailVo o2) { //比较的是最高温度 if (o1.getMaxTemperature() == o2.getMaxTemperature()) { return 0; } else if (o1.getMaxTemperature() > o2.getMaxTemperature()) { return 1; } else { return -1; } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mWidth, mHeight); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.i("==========", "onDRAW"); this.curCanvas = canvas; List<Point> pointLisss = new ArrayList<>(); for (HourItem listItem : listItems) { pointLisss.add(listItem.tempPoint); } if (pointLisss != null && pointLisss.size() > 0) { //画曲线 drawCurve(canvas, pointLisss); } //for循环里面一个个画出来的 for (int i = 0; i < listItems.size(); i++) { Rect rect = listItems.get(i).windyBoxRect; Point point = listItems.get(i).tempPoint; //画风力的box和提示文字 onDrawBox(canvas, rect, i); //画底部时间 onDrawTextTime(canvas, i); //画温度的点 onDrawTemp(canvas, i); } //底部水平的白线 linePaint.setColor(getResources().getColor(R.color.color_twenty_white)); canvas.drawLine(0, mHeight - bottomTextHeight, mWidth - MARGIN_RIGHT_ITEM - MARGIN_LEFT_ITEM, mHeight - bottomTextHeight, linePaint); } /** * 温度相关 背景以及文字 * * @param canvas * @param i */ private void onDrawTemp(Canvas canvas, int i) { HourItem item = listItems.get(i); Point point = item.tempPoint; pointPaint.setColor(getResources().getColor(R.color.color_892A59)); //画圆点 画绿色的圆点 // canvas.drawCircle(point.x, point.y, DensityUtil.dip2px(getContext(), 4.3f)/*12*/, pointPaint); if (currentItemIndex == i) { //计算提示文字的运动轨迹 int Y = getTempBarY(); //画天气 /* int res = findCurrentRes(i); if (res != -1) { Drawable drawTemp = ContextCompat.getDrawable(getContext(), res); drawTemp.setBounds(getScrollBarX() + ITEM_WIDTH / 2 + (ITEM_WIDTH / 2 - DisplayUtil.dip2px(getContext(), 18)) / 2, Y - DisplayUtil.dip2px(getContext(), 23), getScrollBarX() + ITEM_WIDTH - (ITEM_WIDTH / 2 - DisplayUtil.dip2px(getContext(), 18)) / 2, Y - DisplayUtil.dip2px(getContext(), 5)); drawTemp.draw(canvas); }*/ //修改 温度 为单位数时候的 字长,温度为个位数的,长度设置为2位数 String tempSizeWidth = (item.temperature < 10 ? 11 : item.temperature) + ""; String str = item.qualityLevel + tempSizeWidth + ""; float contentWidth = textPaint.measureText(str); //画出温度提示 int offset = ITEM_WIDTH / 2; /* if (res == -1) offset = ITEM_WIDTH;*/ Rect targetRect = new Rect(getScrollBarX(), Y - DisplayUtil.dip2px(getContext(), 24) , getScrollBarX() + (int) contentWidth, Y - DisplayUtil.dip2px(getContext(), 4)); Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt(); int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2; textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(getResources().getColor(R.color.white)); } } private int findCurrentRes(int i) { if (listItems.get(i).res != -1) return listItems.get(i).res; for (int k = i; k >= 0; k--) { if (listItems.get(k).res != -1) return listItems.get(k).res; } return -1; } //画底部风力的BOX private void onDrawBox(Canvas canvas, Rect rect, int i) { // 新建一个矩形 RectF boxRect = new RectF(rect); HourItem item = listItems.get(i); if (i == currentItemIndex) {//选中 windyBoxPaint.setAlpha(255); //选中时候的矩形 //canvas.drawRoundRect(boxRect, 4, 4, windyBoxPaint); canvas.drawLine((rect.right - rect.left) / 2 + (ITEM_WIDTH * i), rect.bottom, (rect.right - rect.left) / 2 + (ITEM_WIDTH * i), rect.bottom - lineHeight, windyBoxPaint); //画出box上面的风力提示文字 Rect targetRect = new Rect(getScrollBarX(), rect.top - DisplayUtil.dip2px(getContext(), 20) , getScrollBarX() + ITEM_WIDTH, rect.top - DisplayUtil.dip2px(getContext(), 0)); Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt(); int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2; textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(getResources().getColor(R.color.white)); // canvas.drawText("风力" + item.windy + "级", targetRect.centerX(), baseline, textPaint); String des = DateUtil.getMonth(item.epochTime) + "月" + DateUtil.getDay(item.epochTime) + "日 " + item.temperature + "°"; Drawable selectBg = ContextCompat.getDrawable(getContext(), R.drawable.textview_tianchong_black_forty); //hour_24_float int textWidth = ((int) textPaint.measureText(des)); int textHeight = textPaint.getFontMetricsInt().bottom - textPaint.getFontMetricsInt().top; //限制 左侧 不滑出当前 view if ((rect.right - rect.left) / 2 + (ITEM_WIDTH * i) < 4 * ITEM_WIDTH) { selectBg.setBounds(0, rect.bottom - lineHeight - textHeight, textWidth + 40, rect.bottom - lineHeight); selectBg.draw(canvas); //画字 canvas.drawText(des, (textWidth + 40) / 2, rect.bottom - lineHeight - (textHeight / 4), textPaint); return; } //右侧 限制 if ((rect.right - rect.left) / 2 + (ITEM_WIDTH * i) > 34 * ITEM_WIDTH) { int widthLeft = (rect.right - rect.left) / 2 + (ITEM_WIDTH * 34); // selectBg.setBounds(widthLeft, rect.bottom - lineHeight - textHeight,//192 widthLeft + textWidth + 40, rect.bottom - lineHeight); selectBg.draw(canvas); //画字 canvas.drawText(des, widthLeft + (textWidth + 40) / 2, rect.bottom - lineHeight - (textHeight / 4), textPaint); return; } //正常 selectBg.setBounds(targetRect.centerX() - textWidth / 2 - 20, rect.bottom - lineHeight - textHeight, targetRect.centerX() - textWidth / 2 + textWidth + 20, rect.bottom - lineHeight); selectBg.draw(canvas); //画字 canvas.drawText(des, targetRect.centerX(), rect.bottom - lineHeight - (textHeight / 4), textPaint); } else { //未选中 windyBoxPaint.setAlpha(windyBoxAlpha); // canvas.drawRoundRect(boxRect, 4, 4, windyBoxPaint); canvas.drawLine((rect.right - rect.left) / 2 + (ITEM_WIDTH * i), rect.bottom, (rect.right - rect.left) / 2 + (ITEM_WIDTH * i), rect.bottom - lineHeight, windyBoxPaint); } } /** * 设置当前下标 选中时候的样式 * * @param currentItemIndex */ public void setCurrentItemIndex(int currentItemIndex) { this.maxScrollOffset = ScreenUtils.getScreenWidth(mContext) - MARGIN_LEFT_ITEM - MARGIN_RIGHT_ITEM; // this.maxScrollOffset = maxScrollOffset; scrollOffset = currentItemIndex * ITEM_WIDTH; this.currentItemIndex = currentItemIndex; invalidate(); } private void drawCurve(Canvas canvas, List<Point> points) { Path curvePath = new Path(); points_x.clear(); points_y.clear(); for (int i = 0; i < points.size(); i++) { points_x.add(points.get(i).x); points_y.add(points.get(i).y); } List<Cubic> calculate_x = calculate(points_x); List<Cubic> calculate_y = calculate(points_y); curvePath.moveTo(calculate_x.get(0).eval(0), calculate_y.get(0).eval(0)); for (int i = 0; i < calculate_x.size(); i++) { for (int j = 1; j <= STEPS; j++) { float u = j / (float) STEPS; curvePath.lineTo(calculate_x.get(i).eval(u), calculate_y.get(i) .eval(u)); } } canvas.drawPath(curvePath, curveLinePaint); } /** * 计算 贝塞尔曲线的 点 * * @param x * @return */ private List<Cubic> calculate(List<Integer> x) { int n = x.size() - 1; float[] gamma = new float[n + 1]; float[] delta = new float[n + 1]; float[] D = new float[n + 1]; int i; /* * We solve the equation [2 1 ] [D[0]] [3(x[1] - x[0]) ] |1 4 1 | |D[1]| * |3(x[2] - x[0]) | | 1 4 1 | | . | = | . | | ..... | | . | | . | | 1 4 * 1| | . | |3(x[n] - x[n-2])| [ 1 2] [D[n]] [3(x[n] - x[n-1])] * * by using row operations to convert the matrix to upper triangular and * then back sustitution. The D[i] are the derivatives at the knots. */ gamma[0] = 1.0f / 2.0f; for (i = 1; i < n; i++) { gamma[i] = 1 / (4 - gamma[i - 1]); } gamma[n] = 1 / (2 - gamma[n - 1]); delta[0] = 3 * (x.get(1) - x.get(0)) * gamma[0]; for (i = 1; i < n; i++) { delta[i] = (3 * (x.get(i + 1) - x.get(i - 1)) - delta[i - 1]) * gamma[i]; } delta[n] = (3 * (x.get(n) - x.get(n - 1)) - delta[n - 1]) * gamma[n]; D[n] = delta[n]; for (i = n - 1; i >= 0; i--) { D[i] = delta[i] - gamma[i] * D[i + 1]; } /* now compute the coefficients of the cubics */ List<Cubic> cubics = new LinkedList<Cubic>(); for (i = 0; i < n; i++) { Cubic c = new Cubic(x.get(i), D[i], 3 * (x.get(i + 1) - x.get(i)) - 2 * D[i] - D[i + 1], 2 * (x.get(i) - x.get(i + 1)) + D[i] + D[i + 1]); cubics.add(c); } return cubics; } //绘制底部时间 private void onDrawTextTime(Canvas canvas, int i) { //此处的计算是为了文字能够居中 Rect rect = listItems.get(i).windyBoxRect; Rect targetRect = new Rect(rect.left, rect.bottom, rect.right, rect.bottom + bottomTextHeight); Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt(); int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2; textPaint.setTextAlign(Paint.Align.CENTER); textPaint.setColor(getResources().getColor(R.color.color_fifty_white)); String text = listItems.get(i).time; //45天数据不固定 ,如果大于45天 取45天 ,小于45天取最后一个 int dis = listItems.size() > 45 ? 44 : (listItems.size() - 1); Log.i("=========", "text :" + text + " i :" + i + " dis " + dis + " listItems.size() :" + (listItems.size()) + weatherDayList.size()); if (i % 15 == 0) { //画底部日期 int widthDis = (i == 0 ? DensityUtil.dip2px(mContext, 20) : DensityUtil.dip2px(mContext, 4)); canvas.drawText(text, widthDis + ITEM_WIDTH * i, baseline, textPaint); } else if (i == dis) { int textWidth = ((int) textPaint.measureText("02-02")); canvas.drawText(text, mWidth - textWidth - MARGIN_RIGHT_ITEM, baseline, textPaint); } } public void drawLeftTempText(Canvas canvas, int offset) { //画最左侧的文字 textPaint.setTextAlign(Paint.Align.LEFT); canvas.drawText(maxTemp + "°", DisplayUtil.dip2px(getContext(), 6) + offset, tempBaseTop, textPaint); canvas.drawText(minTemp + "°", DisplayUtil.dip2px(getContext(), 6) + offset, tempBaseBottom, textPaint); } //设置scrollerView的滚动条的位置,通过位置计算当前的时段 public void setScrollOffset(int offset, int maxScrollOffset) { Log.i("===============", "的 offset" + offset + " max:" + maxScrollOffset); this.maxScrollOffset = maxScrollOffset; scrollOffset = offset; int index = calculateItemIndex(offset); currentItemIndex = index; invalidate(); } //通过滚动条偏移量计算当前选择的时刻 private int calculateItemIndex(int offset) { // Log.d(TAG, "maxScrollOffset = " + maxScrollOffset + " scrollOffset = " + scrollOffset); int x = getScrollBarX(); int sum = MARGIN_LEFT_ITEM - ITEM_WIDTH / 2; for (int i = 0; i < ITEM_SIZE; i++) { sum += ITEM_WIDTH; if (x < sum) return i; } return ITEM_SIZE - 1; } private int getScrollBarX() { if (maxScrollOffset == 0) { return 3; } int x = (ITEM_SIZE - 1) * ITEM_WIDTH * scrollOffset / maxScrollOffset; x = x + MARGIN_LEFT_ITEM; return x; } //计算温度提示文字的运动轨迹 private int getTempBarY() { int x = getScrollBarX(); int sum = MARGIN_LEFT_ITEM; Point startPoint = null, endPoint; int i; for (i = 0; i < ITEM_SIZE; i++) { sum += ITEM_WIDTH; if (x < sum) { startPoint = listItems.get(i).tempPoint; break; } } if (i + 1 >= ITEM_SIZE || startPoint == null) return listItems.get(ITEM_SIZE - 1).tempPoint.y; endPoint = listItems.get(i + 1).tempPoint; Rect rect = listItems.get(i).windyBoxRect; int y = (int) (startPoint.y + (x - rect.left) * 1.0 / ITEM_WIDTH * (endPoint.y - startPoint.y)); return y; } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: moveX = event.getX(); moveY = event.getY(); break; case MotionEvent.ACTION_MOVE: int moveDis = (int) (event.getX() - moveX); //动起来 setScrollOffset(((int) event.getX()), ScreenUtils.getScreenWidth(mContext) - MARGIN_LEFT_ITEM - MARGIN_RIGHT_ITEM); break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; } return true; } }
可滑动选中当前温度,自定义View;曲线
猜你喜欢
转载自blog.csdn.net/yanxiangxue/article/details/105728148
今日推荐
周排行