开发天气相关软件时候,做了两个自定义View,这里进行记录,由于涉及类较多,这里仅包含核心代码,需要调整后才可以运行,自定义View范围仅包含网格相关UI。需要注意的是横向坐标需要25个数据,来表示一天24小时。最后一个数据表示0点,效果如下:
降雨的效果:
该效果可以通过滑动来更新顶部日期和详细降雨信息。自定义View包含天气图标、网格、横竖坐标
风向效果:
该效果可以通过滑动来更新顶部日期和详细降雨信息。自定义View包含天气图标、网格、横竖坐标
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.special.aspire.weather.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by YM on 2023/7/28.
*/
public class WeatherColumn extends View {
private float sumHeight;//总控件的高度
private float sumWidth;// 总空间的宽度
private Paint linePaint;//网格横线线的画笔
private Paint verticalDividerLinePaint;//网格线中垂直分割线的画笔
private Paint verticalDividerLinePaint2;//网格线中第二条垂直分割线的画笔
private Paint mColumnPaint;//圆柱的画笔
private Path mColumnPath;//圆柱的路径
private Paint textPaint;//文字的画笔
private Paint topImagePaint;//顶部图标的画笔
private List<Float> mPrecipitationList;//降水
private List<String> mDateTimeList;//底部的时间
private List<String> mCodeList;//天气代码
private float oneHeight; //每一个小段所要分成的高
private float oneWidth;//每一个小段所要分成的宽
private float buttomHeiht; //给底部一排日期预留出的时间
private Path baseLinePath;//折线路径
private List<PointF> xyList;//储存定好的坐标点的集合
private List<Integer> topImageList = new ArrayList<>();
private List<Boolean> weatherEnableList = new ArrayList<>();
// Y轴显示的列表 必须是9个
private final List<String> yDataList = new ArrayList<>();
// 设置进来的数据是否有效
// 例:windSpeedIsEnableList false false true true true true true false false
// firstIndicateIndex 为2 secondIndicateIndex = 6
private int beforeIndicateIndex = -1;
private int afterIndicateIndex = -1;
private int ratioY = 25;
private float unitRation = 10;// 不同降雨单位之间的比值
private RainData rainData;
public WeatherColumn(Context context) {
super(context);
initPaint();
initYDataList();
}
public WeatherColumn(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
initYDataList();
}
private void initYDataList() {
ratioY = 17;
if (yDataList.isEmpty()) {
for (int i = 0; i < 17; i++) {
if (i % 2 == 0) {
yDataList.add(i + "");
}
}
}
}
public void setYDataListIn() {
ratioY = 25;
yDataList.clear();
unitRation = 100f;
for (int i = 0; i <= ratioY; i += 5) {
float value = i / unitRation;
yDataList.add(value + "in");
}
invalidate();
}
public void setYDataListMm() {
ratioY = 25;
yDataList.clear();
unitRation = 10f;
for (int i = 0; i <= ratioY; i += 10) {
float value = i / unitRation;
yDataList.add(value + "mm");
}
invalidate();
}
/**
* 设置纵坐标显示刻度
* 必须是9个
*
* @param unit 降雨单位,默认in
*/
public void setYDataListUnit(String unit) {
switch (unit) {
case "mm":
setYDataListMm();
break;
case "in":
setYDataListIn();
break;
}
}
/**
* 初始化画笔
*
* @linpaint 线条画笔
*/
private void initPaint() {
//画线的画笔
linePaint = new Paint();
linePaint.setColor(Color.parseColor("#1AFFFFFF"));
linePaint.setAntiAlias(true);
linePaint.setTextSize(dp2px(getContext(), 12));
linePaint.setStrokeWidth(dp2px(getContext(), 1));
verticalDividerLinePaint = new Paint();
verticalDividerLinePaint.setColor(Color.parseColor("#FF58FFBE"));
verticalDividerLinePaint.setAntiAlias(true);
verticalDividerLinePaint.setStrokeWidth(dp2px(getContext(), 2));
verticalDividerLinePaint2 = new Paint();
verticalDividerLinePaint2.setColor(Color.parseColor("#FF58FFBE"));
verticalDividerLinePaint2.setAntiAlias(true);
verticalDividerLinePaint2.setStrokeWidth(dp2px(getContext(), 2));
//文字的画笔
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setTextSize(dp2px(getContext(), 14));
buttomHeiht = dp2px(35);//线距离底部高度
mColumnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mColumnPaint.setColor(Color.parseColor("#FF58FFBE"));
mColumnPaint.setStrokeWidth(dp2px(getContext(), 2));
mColumnPaint.setStyle(Paint.Style.FILL);
mColumnPath = new Path();
topImagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
sumHeight = getMeasuredHeight();
sumWidth = getMeasuredWidth();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
private void measure() {
String text = "min";
Rect rect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), rect);
// 总共分几个高度
oneHeight = (sumHeight - buttomHeiht) / ratioY;
TOP_MARGIN = rect.height();
oneWidth = (sumWidth - RIGHT_MARGIN - LEFT_WIDTH) / HOR_SIZE;
}
int RIGHT_MARGIN = dp2px(40);
int LEFT_WIDTH = dp2px(15);//距离左边宽度
int TOP_MARGIN = dp2px(13);
private final int HOR_SIZE = 25;
private float topLineY = 0;//网格顶部的Y坐标
private float bottomLineY = 0;//网格底部的Y坐标
/**
* 设置数据
* WindLineData 里的所有集合数量必须是25个 从00点 到24点
*/
public void setWindData(RainData data) {
this.mPrecipitationList = data.getPrecipitation();
this.mDateTimeList = data.getDateTimeList();
this.topImageList = data.getWeatherImageResList();
this.weatherEnableList = data.getWeatherIsEnableList();
this.mCodeList = data.getCodeList();
beforeIndicateIndex = -1;
afterIndicateIndex = -1;
for (int i = 0; i < this.weatherEnableList.size(); i++) {
Boolean thisValue = this.weatherEnableList.get(i);
// 当前为false 下一个为true
if (!thisValue && i != this.weatherEnableList.size() - 1 && this.weatherEnableList.get(i + 1)) {
beforeIndicateIndex = i + 1;
}
// 当前为true 下一个为false
if (thisValue && i != this.weatherEnableList.size() - 1 && !this.weatherEnableList.get(i + 1)) {
afterIndicateIndex = i;
}
}
if (this.mPrecipitationList != null && this.mPrecipitationList.size() > 0 && mDateTimeList != null && mDateTimeList.size() > 0) {
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mPrecipitationList == null || mDateTimeList == null) return;
measure();
toGetXy();//获取x和y的坐标
toDrawColumn(canvas);
drawWindImg(canvas);
drawTouchLine(canvas);
}
private void toGetXy() {
xyList = new ArrayList<>();
for (int i = 0; i < mPrecipitationList.size(); i++) {
// float x = i * (HOR_SIZE * 1.0f / (mPrecipitationList.size() - 1)) * oneWidth;
float x = i * oneWidth;
float prec = mPrecipitationList.get(i) * unitRation;//降雨量为0.01mm的
float y = (sumHeight - (oneHeight * prec));
xyList.add(new PointF(x + LEFT_WIDTH, y - buttomHeiht));
}
}
/**
*/
private void toDrawColumn(Canvas canvas) {
if (xyList == null || xyList.size() == 0) {
return;
}
drawLine(canvas);
List<PointF> NewPoints = new ArrayList<>(xyList);
baseLinePath = new Path();
baseLinePath.moveTo(NewPoints.get(0).x, NewPoints.get(0).y);
for (int i = 1; i < NewPoints.size(); i++) {
PointF p = NewPoints.get(i);
int colorRes = RainUtil.getWeatherColor(mCodeList.get(i),mDateTimeList.get(i));
int color = getResources().getColor(colorRes);
drawColumn(canvas, p, color);
}
//绘制底部文字
for (int i = 0; i < NewPoints.size(); i++) {
float x = NewPoints.get(i).x;
Rect rectf = new Rect();
textPaint.setColor(Color.WHITE);
textPaint.getTextBounds(mDateTimeList.get(i), 0, mDateTimeList.get(i).length(), rectf);
// 只画 00 06 12 18 这几个标志
if (i % 6 == 0 && i != NewPoints.size() - 1) {
canvas.drawText(mDateTimeList.get(i), x - rectf.width() / 2f, sumHeight - dp2px(18), textPaint);//画最下方的字体
}
}
}
private Path touchLinePath = new Path();
/**
* 画触摸线
* @param canvas
*/
private void drawTouchLine(Canvas canvas) {
if (currentTouchX == -1) {
verticalDividerLinePaint.setColor(Color.parseColor("#FF58FFBE"));
canvasDividerLine(canvas);
return;
}
verticalDividerLinePaint.setColor(Color.parseColor("#7E58FFBE"));
canvas.drawLine(currentTouchX,
topLineY,
currentTouchX,
bottomLineY,
verticalDividerLinePaint2);
}
/**
* 网格分割线
* 书写右侧垂直标注动文字
* @param canvas
*/
private void drawLine(Canvas canvas) {
textPaint.setColor(Color.parseColor("#B3FFFFFF"));
int dividerTextStartX = (int) sumWidth - RIGHT_MARGIN;
int startX = 0;
int stopX = 0;
if (xyList.size() != 0) {
startX = (int) xyList.get(0).x;
stopX = (int) xyList.get(xyList.size() - 1).x;
}
// String text = "0";
float ratioHeight = (sumHeight - buttomHeiht) / yDataList.size();
topLineY = buttomHeiht;
bottomLineY = ratioHeight * (yDataList.size() - 1) + buttomHeiht;
//画横线
for (int i = 0; i < yDataList.size(); i++) {//因为横线是从y轴开始画的,开始Y坐标为0,要留出画图片的空间,这里随便取一个值,暂时取buttomHeiht
String text = yDataList.get(yDataList.size() - i - 1);//倒叙取值
float y5 = ratioHeight * i + buttomHeiht;//可绘制高度除于总横线的数量,得到每一份绘制高度,然后乘以i,得到每一条横线的高度
canvas.drawText(text, dividerTextStartX, y5, textPaint);
canvas.drawLine(startX, y5, stopX, y5, linePaint);
}
// 画竖线
for (int i = 0; i < xyList.size(); i++) {
// // 补充最后一条线
if (i % 6 == 0 || i == xyList.size() - 1) { // 一共出4条竖线 一共24个数据
canvas.drawLine(xyList.get(i).x, topLineY, xyList.get(i).x, bottomLineY, linePaint);
}
}
Log.e("YM---->","drawLine-->beforeIndicateIndex:"+beforeIndicateIndex+"--->afterIndicateIndex:"+afterIndicateIndex);
canvasDividerLine(canvas);
}
private void canvasDividerLine(Canvas canvas){
// if (currentTouchX != -1) {//手指滑动过程中,该分割线不显示
// return;
// }
//这里绘制左侧阴影部分与右侧非阴影部分的分割线
if (beforeIndicateIndex != -1) {
canvas.drawLine(xyList.get(beforeIndicateIndex).x, topLineY, xyList.get(beforeIndicateIndex).x, bottomLineY, verticalDividerLinePaint);
}
if (afterIndicateIndex != -1) {
canvas.drawLine(xyList.get(afterIndicateIndex).x, topLineY, xyList.get(afterIndicateIndex).x, bottomLineY, verticalDividerLinePaint);
}
}
public int dp2px(float dp) {
final float scale = this.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
private final Map<Integer, Bitmap> bitmaps = new HashMap<>();
@SuppressLint("SuspiciousIndentation")
public void drawWindImg(Canvas canvas) {
for (int i = 0; i < topImageList.size(); i++) {
if (i % 2 != 0) {
// 隔一个画一个
continue;
}
Bitmap bmp;
if (bitmaps.containsKey(topImageList.get(i))) {
bmp = bitmaps.get(topImageList.get(i));
} else {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bmp = BitmapFactory.decodeResource(getResources(), topImageList.get(i));
bmp = Bitmap.createScaledBitmap(bmp, dp2px(24), dp2px(24), false);
bitmaps.put(topImageList.get(i), bmp);
}
// 有效值才渲染图标
if (i >= beforeIndicateIndex && (afterIndicateIndex == -1 || i <= afterIndicateIndex)) {
canvas.drawBitmap(bmp, xyList.get(i).x - bmp.getWidth() / 2f, 0, topImagePaint);
}
}
}
//画一个顶部为圆角的矩形
public void drawColumn(Canvas canvas, PointF pointF, int color) {
float round = dp2px(4);
float width = dp2px(6);
float startX = pointF.x - oneWidth / 2; //减横向半个位置的宽度,否则会超过画布
float endX = startX + width;
float startY = pointF.y;
float endY = bottomLineY;
mColumnPaint.setColor(color);
if (startY == endY) { //如果开始的Y坐标与结束的Y坐标相同,说明今天是晴天
return;
}else if (startY + round > endY){//开始的Y坐标加上半圆的Y坐标 大于 结束的Y坐标,说明有雨,但是雨不大, 这时候稍微画一点痕迹表示有雨
canvas.drawRoundRect(startX, endY - round, endX, endY, round, round, mColumnPaint);
return;
}
mColumnPath.reset();
mColumnPath.moveTo(startX + round, startY); // 移动到左上角圆角的起点
mColumnPath.lineTo(endX - round, startY); // 绘制顶边
mColumnPath.quadTo(endX, startY, endX, startY + round); // 绘制右上角圆角
mColumnPath.lineTo(startX + width, endY); // 绘制右边
mColumnPath.lineTo(startX, endY); // 绘制底边
mColumnPath.lineTo(startX, startY + round); // 绘制左边
mColumnPath.quadTo(startX, startY, startX + round, startY); // 绘制左上角圆角
mColumnPath.close();
canvas.drawPath(mColumnPath, mColumnPaint);
}
public int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public float dp2px(Resources resources, float dp) {
final float scale = resources.getDisplayMetrics().density;
return dp * scale + 0.5f;
}
public float sp2px(Resources resources, float sp) {
final float scale = resources.getDisplayMetrics().scaledDensity;
return sp * scale;
}
private int getColumnColor(String code){
return R.color.color_4D898989;
}
// private Boolean isTouching = false;
private float currentTouchX = -1;
private float lastTouchX = -1;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (xyList == null) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
return true;
case MotionEvent.ACTION_MOVE:
currentTouchX = event.getX();
if (beforeIndicateIndex != -1) {
currentTouchX = Math.max(xyList.get(beforeIndicateIndex).x, currentTouchX);
}
if (afterIndicateIndex != -1) {
currentTouchX = Math.min(xyList.get(afterIndicateIndex).x, currentTouchX);
}
if (currentTouchX <= xyList.get(0).x) {
currentTouchX = xyList.get(0).x;
} else if (currentTouchX >= xyList.get(xyList.size() - 1).x) {
currentTouchX = xyList.get(xyList.size() - 1).x;
}
touchLinePath.reset();
touchLinePath.moveTo(currentTouchX, (sumHeight - (oneHeight * 0)) - buttomHeiht);
touchLinePath.lineTo(currentTouchX, (sumHeight - (oneHeight * yDataList.size())) - buttomHeiht);
touchLinePath.lineTo(currentTouchX + 3, (sumHeight - (oneHeight * yDataList.size())) - buttomHeiht);
touchLinePath.moveTo(currentTouchX + 3, (sumHeight - (oneHeight * 0)) - buttomHeiht);
touchLinePath.close();
// 限制更新频率
if (Math.abs(lastTouchX - currentTouchX) >= 6) {
lastTouchX = currentTouchX;
if (onTouchPositionChange != null) {
onTouchPositionChange.onChange((currentTouchX - xyList.get(0).x) / (xyList.get(xyList.size() - 1).x - xyList.get(0).x));
}
invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
currentTouchX = -1;
if (onTouchPositionChange != null) {
onTouchPositionChange.onTouchUp();
}
invalidate();
break;
}
// invalidate();
return super.onTouchEvent(event);
}
public static class RainData {
// 天气数据是否可用
private List<Boolean> weatherIsEnableList;
//降水
private List<Float> precipitation;
// 时间
private List<String> dateTimeList;
private List<String> codeList;
// 天气图标
private List<Integer> weatherImageResList;
public List<Boolean> getWeatherIsEnableList() {
return weatherIsEnableList;
}
public List<Float> getPrecipitation() {
return precipitation;
}
public List<String> getDateTimeList() {
return dateTimeList;
}
public List<Integer> getWeatherImageResList() {
return weatherImageResList;
}
public void setWeatherIsEnableList(List<Boolean> weatherIsEnableList) {
this.weatherIsEnableList = weatherIsEnableList;
}
public void setPrecipitation(List<Float> precipitation) {
this.precipitation = precipitation;
}
public void setDateTimeList(List<String> dateTimeList) {
this.dateTimeList = dateTimeList;
}
public void setWeatherImageResList(List<Integer> winImageResList) {
this.weatherImageResList = winImageResList;
}
public List<String> getCodeList() {
return codeList;
}
public void setCodeList(List<String> codeList) {
this.codeList = codeList;
}
}
private OnTouchPositionChange onTouchPositionChange;
public void setOnTouchPositionChange(OnTouchPositionChange onTouchPositionChange) {
this.onTouchPositionChange = onTouchPositionChange;
}
public interface OnTouchPositionChange {
// 进度从0到1 0代表0点 1 代表24点
void onChange(float position);
void onTouchUp();
}
}
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.StringRes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by wangchang on 2019/4/29.
*/
public class WindWaveLine extends View {
private float sumHeight;//总控件的高度
private float sumWidth;// 总空间的宽度
private float maxTime;//最大的时间 用来划分单位的 最小就是20 X1.2是为了给上方和下方预留空间
private Paint linePaint;//网格横线线的画笔
private Paint verticalDividerLinePaint;//网格线中垂直分割线的画笔
private Paint verticalDividerLinePaint2;//网格线中第二条垂直分割线的画笔
private Paint mPaint;//曲线画笔
private Paint circlePaint;//第一个圆点画笔,该圆点不会动
private Paint circlePaint2;//第二个圆点画笔, 长按后会动
private Paint textPaint;//文字的画笔
private Paint topImagePaint;//顶部风向图标的画笔
private List<Float> mWindSpeedList;//风速
private List<String> mDateTimeList;//底部的时间
private float oneHeight; //每一个小段所要分成的高
private float oneWidth;//每一个小段所要分成的宽
private float buttomHeiht; //给底部一排日期预留出的时间
private Path baseLinePath;//折线路径
private float smoothness = 0.27f; //折线的弯曲率
private Paint baseLeftShadow;//折线下的阴影的画笔
private Paint baseShadow;//折线下的阴影的画笔
private List<PointF> xyList;//储存定好的坐标点的集合
private List<Integer> topImageList = new ArrayList<>();
private List<Boolean> windEnableList = new ArrayList<>();
// Y轴显示的列表 必须是9个
private List<String> yDataList = new ArrayList<>();
// 设置进来的数据是否有效
// 例:windSpeedIsEnableList false false true true true true true false false
// firstIndicateIndex 为2 secondIndicateIndex = 6
private int beforeIndicateIndex = -1;
private int afterIndicateIndex = -1;
private WindLWaveineData windLWaveineData;
private String noDataDesc = "";// 左侧无数据的文字描述
public WindWaveLine(Context context) {
super(context);
initPaint();
initYDataList();
}
public WindWaveLine(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
initYDataList();
}
private void initYDataList() {
if (yDataList.isEmpty()) {
for (int i = 0; i < 17; i++) {
if (i % 2 == 0) {
yDataList.add(i + "");
}
}
}
}
public void setYDataListMph() {
yDataList.clear();
for (int i = 0; i <= 40; i += 5) {
yDataList.add(i + "");
}
invalidate();
}
public void setYDataListKmh() {
yDataList.clear();
for (int i = 0; i <= 80; i += 10) {
yDataList.add(i + "");
}
invalidate();
}
/**
* 设置纵坐标显示刻度
* 必须是9个
*
* @param unit 风速单位,默认m/s
*/
public void setYDataListUnit(String unit) {
switch (unit) {
case "km/h":
setYDataListKmh();
break;
case "mph":
setYDataListMph();
break;
}
}
public void setNoDataDesc(String noDataDesc){
this.noDataDesc = noDataDesc;
invalidate();
}
public void setNoDataDesc(@StringRes int noDataDescId){
this.noDataDesc = getResources().getString(noDataDescId);
invalidate();
}
/**
* 初始化画笔
*
* @linpaint 线条画笔
*/
private void initPaint() {
//画线的画笔
linePaint = new Paint();
linePaint.setColor(Color.parseColor("#1AFFFFFF"));
linePaint.setAntiAlias(true);
linePaint.setTextSize(dp2px(getContext(), 12));
linePaint.setStrokeWidth(dp2px(getContext(), 1));
verticalDividerLinePaint = new Paint();
verticalDividerLinePaint.setColor(Color.parseColor("#1AFFFFFF"));
verticalDividerLinePaint.setAntiAlias(true);
verticalDividerLinePaint.setStrokeWidth(dp2px(getContext(), 2));
verticalDividerLinePaint2 = new Paint();
verticalDividerLinePaint2.setColor(Color.parseColor("#FF2D9BFE"));
verticalDividerLinePaint2.setAntiAlias(true);
verticalDividerLinePaint2.setStrokeWidth(dp2px(getContext(), 2));
//文字的画笔
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setAntiAlias(true);
textPaint.setTextSize(dp2px(getContext(), 14));
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(Color.parseColor("#FF3EFFD4"));
circlePaint.setStrokeWidth(dp2px(getContext(), 2));
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint2.setColor(Color.parseColor("#FF0061E2"));
circlePaint2.setStyle(Paint.Style.FILL);
baseLeftShadow = new Paint();
baseLeftShadow.setAntiAlias(true);
baseLeftShadow.setColor((Color.WHITE & 0x40FFFFFF) | 0x10000000);
baseLeftShadow.setStyle(Paint.Style.FILL);
baseShadow = new Paint();
baseShadow.setAntiAlias(true);
baseShadow.setColor((Color.WHITE & 0x40FFFFFF) | 0x10000000);
baseShadow.setStyle(Paint.Style.FILL);
buttomHeiht = dp2px(35);//线距离底部高度
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.parseColor("#FF58FFBE"));
mPaint.setStrokeWidth(dp2px(getContext(), 2));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
// mPaint.setColor(Color.parseColor("#17CAAA"));
topImagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
sumHeight = getMeasuredHeight();
sumWidth = getMeasuredWidth();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
private void measure() {
maxTime = 16;//最大分数为100
for (int i = 0; i < mWindSpeedList.size(); i++) {
if (maxTime <= mWindSpeedList.get(i)) {
maxTime = mWindSpeedList.get(i);
}
}
// if (maxTime < 20) {
// maxTime = 20;
// }
String text = "min";
Rect rect = new Rect();
textPaint.getTextBounds(text, 0, text.length(), rect);
// 1个time几个高度
oneHeight = ((sumHeight - buttomHeiht - 2 * rect.height()) / maxTime);
TOP_MARGIN = rect.height();
oneWidth = (sumWidth - RIGHT_MARGIN - LEFT_WIDTH) / HOR_SIZE;
}
int RIGHT_MARGIN = dp2px(40);
int LEFT_WIDTH = dp2px(13);//距离右边宽度
int TOP_MARGIN = dp2px(13);
// 横向分成100份
private int HOR_SIZE = 100;
/**
* 设置数据
* WindLineData 里的所有集合数量必须是25个 从00点 到24点
*/
public void setWindData(WindLWaveineData windLWaveineData) {
this.mWindSpeedList = windLWaveineData.getWindSpeed();
this.mDateTimeList = windLWaveineData.getDateTimeList();
this.topImageList = windLWaveineData.getWinImageResList();
this.windEnableList = windLWaveineData.getWindSpeedIsEnableList();
beforeIndicateIndex = -1;
afterIndicateIndex = -1;
for (int i = 0; i < this.windEnableList.size(); i++) {
Boolean thisValue = this.windEnableList.get(i);
// 当前为false 下一个为true
if (!thisValue && i != this.windEnableList.size() - 1 && this.windEnableList.get(i + 1)) {
beforeIndicateIndex = i + 1;
}
// 当前为true 下一个为false
if (thisValue && i != this.windEnableList.size() - 1 && !this.windEnableList.get(i + 1)) {
afterIndicateIndex = i;
}
}
if (this.mWindSpeedList != null && this.mWindSpeedList.size() > 0 && mDateTimeList != null && mDateTimeList.size() > 0) {
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mWindSpeedList == null || mDateTimeList == null) return;
maxTime = getMaxTime(mWindSpeedList);
measure();
toGetXy();//获取x和y的坐标
toDrawLine(canvas);
drawWindImg(canvas);
drawTouchLine(canvas);
drawNoDataDesc(canvas);
}
private void toGetXy() {
xyList = new ArrayList<>();
for (int i = 0; i < mWindSpeedList.size(); i++) {
// (HOR_SIZE*1.0f/timeList.size() 分成100份 每份有多长
float x = oneWidth + i * (HOR_SIZE * 1.0f / (mWindSpeedList.size() - 1)) * oneWidth;
float time = mWindSpeedList.get(i);//每点时间
float y = (sumHeight - (oneHeight * time));
xyList.add(new PointF(x + LEFT_WIDTH, y - buttomHeiht));
}
}
// 左侧没有数据的区域画上蒙层
LinearGradient mOverlyShader = new LinearGradient(0, sumHeight - maxTime * oneHeight - dp2px(4),
0,
sumHeight,
new int[]{Color.parseColor("#00F1F9FE"), Color.parseColor("#FFC4E0F2")},
new float[]{0f, 1f},
Shader.TileMode.REPEAT);
/**
* 画线
*/
private void toDrawLine(Canvas canvas) {
if (xyList == null || xyList.size() == 0) {
return;
}
drawLine(canvas);
List<PointF> NewPoints = new ArrayList<>();
NewPoints.addAll(xyList);
float lX = 0;
float lY = 0;
baseLinePath = new Path();
baseLinePath.moveTo(NewPoints.get(0).x, NewPoints.get(0).y);
for (int i = 1; i < NewPoints.size(); i++) {
PointF p = NewPoints.get(i);
PointF firstPointF = NewPoints.get(i - 1);
float x1 = firstPointF.x + lX;
float y1 = firstPointF.y + lY;
PointF secondPointF = NewPoints.get(i + 1 < NewPoints.size() ? i + 1 : i);
lX = (secondPointF.x - firstPointF.x) / 2 * smoothness;
lY = (secondPointF.y - firstPointF.y) / 2 * smoothness;
float x2 = p.x - lX;
float y2 = p.y - lY;
if (y1 == p.y) {
y2 = y1;
}
baseLinePath.cubicTo(x1, y1, x2, y2, p.x, p.y);
}
canvas.save();
// drawLeftShadow(canvas);
drawLeftMask(canvas);
canvas.drawPath(baseLinePath, mPaint);
drawArea(canvas, NewPoints);
canvas.restore();
//绘制底部文字
for (int i = 0; i < NewPoints.size(); i++) {
float x = NewPoints.get(i).x;
float y = NewPoints.get(i).y;
Float time = mWindSpeedList.get(i);
String mThumbText = String.valueOf(time);
// Rect rect = new Rect();
// linePaint.getTextBounds(mThumbText, 0, mThumbText.length(), rect);
// canvas.drawText(mThumbText, x - rect.width() / 2, y - dp2px(6), linePaint);//画最上面字体
// canvas.drawCircle(x, y, dp2px(3), circlePaint);
// canvas.drawCircle(x, y, dp2px(2), circlePaint2);
Rect rectf = new Rect();
textPaint.setColor(Color.WHITE);
textPaint.getTextBounds(mDateTimeList.get(i), 0, mDateTimeList.get(i).length(), rectf);
// 只画 00 06 12 18 这几个标志
if (i % 6 == 0 && i != NewPoints.size() - 1) {
canvas.drawText(mDateTimeList.get(i), x - rectf.width() / 2, sumHeight - dp2px(18), textPaint);//画最下方的字体
}
}
}
//绘制一个单层的遮罩层
private void drawLeftMask(Canvas canvas){
baseLeftShadow.setColor(Color.parseColor("#9A10232F"));
//绘制左侧阴影渐变色
if (beforeIndicateIndex != -1 && afterIndicateIndex != -1) {
canvas.drawRect(xyList.get(0).x, (sumHeight - (oneHeight * 16)) - buttomHeiht,
xyList.get(beforeIndicateIndex).x,
(sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.drawRect(xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)) - buttomHeiht,
xyList.get(xyList.size() - 1).x, (sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.clipRect(xyList.get(beforeIndicateIndex).x, (sumHeight - (oneHeight * 0)) - buttomHeiht,
xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)));
} else if (beforeIndicateIndex != -1) {
canvas.drawRect(xyList.get(0).x, (sumHeight - (oneHeight * 16)) - buttomHeiht, xyList.get(beforeIndicateIndex).x,
(sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.clipRect(xyList.get(beforeIndicateIndex).x, (sumHeight - (oneHeight * 0)) - buttomHeiht,
sumWidth, (sumHeight - (oneHeight * 16)));
} else if (afterIndicateIndex != -1) {
canvas.drawRect(xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)) - buttomHeiht,
xyList.get(xyList.size() - 1).x, (sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.clipRect(0, (sumHeight - (oneHeight * 0)) - buttomHeiht,
xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)));
}
}
private void drawLeftShadow(Canvas canvas){
//绘制左侧阴影渐变色
if (beforeIndicateIndex != -1 && afterIndicateIndex != -1) {
mOverlyShader = new LinearGradient(xyList.get(0).x, 0f, xyList.get(beforeIndicateIndex).x, 0f,
new int[]{Color.parseColor("#00F1F9FE"), Color.parseColor("#FFC4E0F2")},
new float[]{0f, 1f},
Shader.TileMode.REPEAT);
baseLeftShadow.setShader(mOverlyShader);
canvas.drawRect(xyList.get(0).x, (sumHeight - (oneHeight * 16)) - buttomHeiht,
xyList.get(beforeIndicateIndex).x,
(sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
mOverlyShader = new LinearGradient(xyList.get(0).x, 0f, xyList.get(beforeIndicateIndex).x, 0f,
new int[]{Color.parseColor("#FFC4E0F2"), Color.parseColor("#00F1F9FE")},
new float[]{0f, 1f},
Shader.TileMode.REPEAT);
baseLeftShadow.setShader(mOverlyShader);
canvas.drawRect(xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)) - buttomHeiht,
xyList.get(xyList.size() - 1).x, (sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.clipRect(xyList.get(beforeIndicateIndex).x, (sumHeight - (oneHeight * 0)) - buttomHeiht,
xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)));
} else if (beforeIndicateIndex != -1) {
mOverlyShader = new LinearGradient(xyList.get(0).x, 0f, xyList.get(beforeIndicateIndex).x, 0f,
new int[]{Color.parseColor("#00F1F9FE"), Color.parseColor("#FFC4E0F2")},
new float[]{0f, 1f},
Shader.TileMode.REPEAT);
baseLeftShadow.setShader(mOverlyShader);
canvas.drawRect(xyList.get(0).x, (sumHeight - (oneHeight * 16)) - buttomHeiht, xyList.get(beforeIndicateIndex).x,
(sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.clipRect(xyList.get(beforeIndicateIndex).x, (sumHeight - (oneHeight * 0)) - buttomHeiht,
sumWidth, (sumHeight - (oneHeight * 16)));
} else if (afterIndicateIndex != -1) {
mOverlyShader = new LinearGradient(xyList.get(0).x, 0f, xyList.get(beforeIndicateIndex).x, 0f,
new int[]{Color.parseColor("#FFC4E0F2"), Color.parseColor("#00F1F9FE")},
new float[]{0f, 1f},
Shader.TileMode.REPEAT);
baseLeftShadow.setShader(mOverlyShader);
canvas.drawRect(xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)) - buttomHeiht,
xyList.get(xyList.size() - 1).x, (sumHeight - (oneHeight * 0) - buttomHeiht), baseLeftShadow);
canvas.clipRect(0, (sumHeight - (oneHeight * 0)) - buttomHeiht,
xyList.get(afterIndicateIndex).x, (sumHeight - (oneHeight * 16)));
}
}
private Path touchLinePath = new Path();
/**
* 画触摸线
*
* @param canvas
*/
private void drawTouchLine(Canvas canvas) {
if (currentTouchX == -1) {
return;
}
canvas.drawLine(currentTouchX,
(sumHeight - (oneHeight * 0)) - buttomHeiht, currentTouchX,
(sumHeight - (oneHeight * 16)) - buttomHeiht,
verticalDividerLinePaint2);
Path tempPath = new Path();
RectF rectF = new RectF();
boolean isIntersect = tempPath.op(touchLinePath,baseLinePath, Path.Op.INTERSECT);
tempPath.computeBounds(rectF, true);
if (isIntersect) {
canvas.drawCircle(rectF.left, rectF.top, dp2px(3.5f), circlePaint2);
}
}
/**
* 网格分割线
* 书写右侧垂直标注动文字
*
* @param canvas
*/
private void drawLine(Canvas canvas) {
textPaint.setColor(Color.parseColor("#B3FFFFFF"));
int dividerTextStartX = (int) sumWidth - RIGHT_MARGIN + dp2px(12);
//
float y16 = (sumHeight - (oneHeight * 16));
int startX = 0;
int stopX = 0;
if (xyList.size() != 0) {
startX = (int) xyList.get(0).x;
stopX = (int) xyList.get(xyList.size() - 1).x;
}
// String text = "0";
String text = yDataList.get(0);
float y0 = (sumHeight - (oneHeight * 0));
canvas.drawText(text, dividerTextStartX, sumHeight - buttomHeiht, textPaint);
canvas.drawLine(startX, y0 - buttomHeiht, stopX, y0 - buttomHeiht, linePaint);
//60
// text = "2";
text = yDataList.get(1);
float y2 = (sumHeight - (oneHeight * 2));
canvas.drawText(text, dividerTextStartX, y2 - buttomHeiht, textPaint);
canvas.drawLine(startX, y2 - buttomHeiht, stopX, y2 - buttomHeiht, linePaint);
// text = "4";
text = yDataList.get(2);
float y4 = (sumHeight - (oneHeight * 4));
canvas.drawText(text, dividerTextStartX, y4 - buttomHeiht, textPaint);
canvas.drawLine(startX, y4 - buttomHeiht, stopX, y4 - buttomHeiht, linePaint);
// text = "6";
text = yDataList.get(3);
float y6 = (sumHeight - (oneHeight * 6));
canvas.drawText(text, dividerTextStartX, y6 - buttomHeiht, textPaint);
canvas.drawLine(startX, y6 - buttomHeiht, stopX, y6 - buttomHeiht, linePaint);
// text = "8";
text = yDataList.get(4);
float y8 = (sumHeight - (oneHeight * 8));
canvas.drawText(text, dividerTextStartX, y8 - buttomHeiht, textPaint);
canvas.drawLine(startX, y8 - buttomHeiht, stopX, y8 - buttomHeiht, linePaint);
// text = "10";
text = yDataList.get(5);
float y10 = (sumHeight - (oneHeight * 10));
canvas.drawText(text, dividerTextStartX, y10 - buttomHeiht, textPaint);
canvas.drawLine(startX, y10 - buttomHeiht, stopX, y10 - buttomHeiht, linePaint);
// text = "12";
text = yDataList.get(6);
float y12 = (sumHeight - (oneHeight * 12));
canvas.drawText(text, dividerTextStartX, y12 - buttomHeiht, textPaint);
canvas.drawLine(startX, y12 - buttomHeiht, stopX, y12 - buttomHeiht, linePaint);
// text = "14";
text = yDataList.get(7);
float y14 = (sumHeight - (oneHeight * 14));
canvas.drawText(text, dividerTextStartX, y14 - buttomHeiht, textPaint);
canvas.drawLine(startX, y14 - buttomHeiht, stopX, y14 - buttomHeiht, linePaint);
// text = "16";
text = yDataList.get(8);
canvas.drawText(text, dividerTextStartX, y16 - buttomHeiht, textPaint);
canvas.drawLine(startX, y16 - buttomHeiht, stopX, y16 - buttomHeiht, linePaint);
// 画竖线
for (int i = 0; i < xyList.size(); i++) {
// // 补充最后一条线
if (i % 6 == 0 || i == xyList.size() - 1) { // 一共出4条竖线 一共24个数据
canvas.drawLine(xyList.get(i).x, y0 - buttomHeiht, xyList.get(i).x, y16 - buttomHeiht, linePaint);
}
}
if (currentTouchX != -1){//手指滑动过程中,该分割线不显示
return;
}
//这里绘制左侧阴影部分与右侧非阴影部分的分割线
if (beforeIndicateIndex != -1) {
canvas.drawLine(xyList.get(beforeIndicateIndex).x, y0 - buttomHeiht, xyList.get(beforeIndicateIndex).x, y16 - buttomHeiht, verticalDividerLinePaint);
canvas.drawCircle(xyList.get(beforeIndicateIndex).x, xyList.get(beforeIndicateIndex).y, dp2px(1.5f), circlePaint);
}
if (afterIndicateIndex != -1) {
canvas.drawLine(xyList.get(afterIndicateIndex).x, y0 - buttomHeiht, xyList.get(afterIndicateIndex).x, y16 - buttomHeiht, verticalDividerLinePaint);
canvas.drawCircle(xyList.get(afterIndicateIndex).x, xyList.get(afterIndicateIndex).y, dp2px(1.5f), circlePaint);
}
}
/**
* 阴影层叠
* 右侧折线底部阴影渐变
* @param canvas
* @param Points
*/
private void drawArea(Canvas canvas, List<PointF> Points) {
// sumHeight-maxTime*oneHeight-dp2px(4) -dp4是为了调整误差无特殊用处
LinearGradient mShader =
new LinearGradient(0, sumHeight - maxTime * oneHeight - dp2px(4),
0, sumHeight,
new int[]{Color.parseColor("#FF58FFBE"),
Color.parseColor("#0058FFBE")},
new float[]{0f, 1f}, Shader.TileMode.REPEAT);
baseShadow.setShader(mShader);
if (Points.size() > 0 && xyList != null && xyList.size() > 0) {
baseLinePath.lineTo(xyList.get(Points.size() - 1).x, sumHeight - buttomHeiht);
baseLinePath.lineTo(xyList.get(0).x, sumHeight - buttomHeiht);
baseLinePath.close();
canvas.drawPath(baseLinePath, baseShadow);
}
}
public int dp2px(float dp) {
final float scale = this.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
/**
* 取出时间里面的最大的一个用来计算总长度
*
* @param timeList
* @return
*/
public float getMaxTime(List<Float> timeList) {
maxTime = 0;
for (int i = 0; i < timeList.size(); i++) {
if (maxTime < timeList.get(i)) {
maxTime = timeList.get(i);
}
}
if (maxTime <= 10) {
maxTime = 10;
}
// maxTime *= 1.2;
return maxTime;
}
private Map<Integer, Bitmap> bitmaps = new HashMap<>();
@SuppressLint("SuspiciousIndentation")
public void drawWindImg(Canvas canvas) {
for (int i = 0; i < topImageList.size(); i++) {
if (i % 2 != 0) {
// 隔一个画一个
continue;
}
Bitmap bmp;
if (bitmaps.containsKey(topImageList.get(i))) {
bmp = bitmaps.get(topImageList.get(i));
} else {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
bmp = BitmapFactory.decodeResource(getResources(), topImageList.get(i));
bmp = Bitmap.createScaledBitmap(bmp, dp2px(24), dp2px(24), false);
bitmaps.put(topImageList.get(i), bmp);
}
// 有效值才渲染风向图标
if (i >= beforeIndicateIndex && (afterIndicateIndex == -1 || i <= afterIndicateIndex)) {
canvas.drawBitmap(bmp, xyList.get(i).x - bmp.getWidth() / 2f, 0, topImagePaint);
}
}
}
//绘制无数据的描述,该描述会在18点后显示
//当时间为18点之后且没有数据时,显示该描述
private void drawNoDataDesc(Canvas canvas){
if (beforeIndicateIndex < 18) {
return;
}
float firstX = xyList.get(0).x; //00点
float secondX = xyList.get(18).x; //18点
Rect rect = new Rect();
textPaint.getTextBounds(noDataDesc, 0, noDataDesc.length(), rect);
float textWidth = rect.width();
float startX = (secondX - firstX - textWidth) / 2 + firstX ;
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(dp2px(getContext(), 12));
if (noDataDesc != null && !noDataDesc.isEmpty()) {
canvas.drawText(noDataDesc, startX, getHeight() / 2f, textPaint);
}
}
public int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public float dp2px(Resources resources, float dp) {
final float scale = resources.getDisplayMetrics().density;
return dp * scale + 0.5f;
}
public float sp2px(Resources resources, float sp) {
final float scale = resources.getDisplayMetrics().scaledDensity;
return sp * scale;
}
// private Boolean isTouching = false;
private float currentTouchX = -1;
private float lastTouchX = -1;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (xyList == null) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// currentTouchX=-1;
// isTouching=true;
getParent().requestDisallowInterceptTouchEvent(true);
return true;
case MotionEvent.ACTION_MOVE:
currentTouchX = event.getX();
if (beforeIndicateIndex != -1) {
currentTouchX = Math.max(xyList.get(beforeIndicateIndex).x, currentTouchX);
}
if (afterIndicateIndex != -1) {
currentTouchX = Math.min(xyList.get(afterIndicateIndex).x, currentTouchX);
}
if (currentTouchX <= xyList.get(0).x) {
currentTouchX = xyList.get(0).x;
} else if (currentTouchX >= xyList.get(xyList.size() - 1).x) {
currentTouchX = xyList.get(xyList.size() - 1).x;
}
touchLinePath.reset();
touchLinePath.moveTo(currentTouchX, (sumHeight - (oneHeight * 0)) - buttomHeiht);
touchLinePath.lineTo(currentTouchX, (sumHeight - (oneHeight * 16)) - buttomHeiht);
touchLinePath.lineTo(currentTouchX + 3, (sumHeight - (oneHeight * 16)) - buttomHeiht);
touchLinePath.moveTo(currentTouchX + 3, (sumHeight - (oneHeight * 0)) - buttomHeiht);
touchLinePath.close();
// 限制更新频率
if (Math.abs(lastTouchX - currentTouchX) >= 6) {
lastTouchX = currentTouchX;
if (onTouchPositionChange != null) {
onTouchPositionChange.onChange((currentTouchX - xyList.get(0).x) / (xyList.get(xyList.size() - 1).x - xyList.get(0).x));
}
invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
getParent().requestDisallowInterceptTouchEvent(false);
currentTouchX = -1;
if (onTouchPositionChange != null) {
onTouchPositionChange.onTouchUp();
}
invalidate();
break;
}
// invalidate();
return super.onTouchEvent(event);
}
public static class WindLWaveineData {
// 风速数据是否可用
private List<Boolean> windSpeedIsEnableList;
//风速
private List<Float> windSpeed;
// 时间
private List<String> dateTimeList;
// 风向图标
private List<Integer> winImageResList;
public List<Boolean> getWindSpeedIsEnableList() {
return windSpeedIsEnableList;
}
public List<Float> getWindSpeed() {
return windSpeed;
}
public List<String> getDateTimeList() {
return dateTimeList;
}
public List<Integer> getWinImageResList() {
return winImageResList;
}
public void setWindSpeedIsEnableList(List<Boolean> windSpeedIsEnableList) {
this.windSpeedIsEnableList = windSpeedIsEnableList;
}
public void setWindSpeed(List<Float> windSpeed) {
this.windSpeed = windSpeed;
}
public void setDateTimeList(List<String> dateTimeList) {
this.dateTimeList = dateTimeList;
}
public void setWinImageResList(List<Integer> winImageResList) {
this.winImageResList = winImageResList;
}
}
private OnTouchPositionChange onTouchPositionChange;
public void setOnTouchPositionChange(OnTouchPositionChange onTouchPositionChange) {
this.onTouchPositionChange = onTouchPositionChange;
}
public interface OnTouchPositionChange {
// 进度从0到1 0代表0点 1 代表24点
void onChange(float position);
void onTouchUp();
}
}