最近、プロジェクトの要件に合わせて温度曲線図をカスタマイズする必要があることがわかりました。以前の同僚は製品のニーズをよく理解していなかったため、温度線グラフを2つのビューに分割しました。1つは高温のビューで、もう1つは低温のビューです。このアプローチは、実際には製品のニーズを十分に理解していません。これをなぜ言うのですか。2つのビューに分割されると、交差する点が描画されるのは欠陥があります。どういう意味ですか、写真を見てください。
2つのビューに従ってそれを行う場合、この制限があります。交点がカットされます。そこで、ここでこのカスタムビューを再変更します。
上記の要件を満たしてから、設計を開始します。まず、カスタムビューの汎用性を高めるために、変更される可能性のあるものをいくつか抽出する必要があります。ここでは、非常に一般的に使用される属性の一部のみを示します。残りはカスタマイズする必要があり、自分で追加できます。コードを直接見てください
<declare-styleable name="NewWeatherChartView">
<!--开始的x坐标-->
<attr name="new_start_point_x" format="dimension"/>
<!--两点之间x坐标的间隔-->
<attr name="new_point_x_margin" format="dimension"/>
<!--显示温度的字体大小-->
<attr name="temperature_text_size" format="dimension"/>
<!--圆点的半径-->
<attr name="point_radius" format="dimension"/>
<!--选中天气项,温度字体的颜色-->
<attr name="select_temperature_text_color" format="reference|color"/>
<!--未选中天气项,温度字体的颜色-->
<attr name="unselect_temperature_text_color" format="reference|color"/>
<!--选中天气项,圆点的颜色-->
<attr name="select_point_color" format="reference|color"/>
<!--未选中天气项,圆点的颜色-->
<attr name="unselect_point_color" format="reference|color"/>
<!--连接线的颜色-->
<attr name="line_color" format="reference|color"/>
<!--连接线的类型,可以是实线,也可以是虚线,默认是虚线。0虚线,1实线-->
<attr name="line_type" format="integer"/>
</declare-styleable>
复制代码
public class NewWeatherChartView extends View {
private final static String TAG = "NewWeatherChartView";
private List<WeatherInfo> items;//温度的数据源
//都是可以在XML里面配置的属性,目前项目里面都是用的默认配置。
private int mLineColor;
private int mSelectTemperatureColor;
private int mUnSelectTemperatureColor;
private int mSelectPointColor;
private int mUnselectPointColor;
private int mLineType;
private int mTemperatureTextSize;
private int mPointStartX = 0;
private int mPointXMargin = 0;
private int mPointRadius;
private Point[] mHighPoints; //高温的点的坐标
private Point[] mLowPoints; //低温的点的坐标
//这里是为了方便写代码,多创建了几个画笔,也可以用一个画笔,然后配置不同的属性
private Paint mLinePaint; //用于画线画笔
private Paint mTextPaint; // 用于画小圆点旁边的温度文字的画笔
private Paint mCirclePaint;//用来画小圆点的画笔
private Float mMaxTemperature = Float.MIN_VALUE;//最高温度
private Float mMinTemperature = Float.MAX_VALUE;//最低温度
private Path mPath;//连接线的路径
private DecimalFormat mDecimalFormat;
private int mTodayIndex = -1;//用于判断哪一个被选中
private Context mContext;
...
}
复制代码
上記はいくつかの初期化のことなので、これらの描画方法を考えてみましょう。上記の初期化は、主に線、テキスト、ドットを描画することも説明しています。では、どこから始めるべきでしょうか?1つ目は、点の座標から始めることです。これは、線であろうとテキストであろうと、それらの位置は点に関連しているためです。次に、ポイントの座標を見つけることが最初の仕事です。ポイントの座標を見つける方法、および最初のX座標は何ですか。最初のポイントのX座標は構成に基づいているので、2番目のポイントのX座標はどうでしょうか。、2番目の点のx座標は、最初の点のx座標にX方向のそれらの間の距離を加えたものであり、x方向の距離もプロパティに従って構成されます。したがって、すべてのポイントのx座標を簡単に取得できます。ドットのy座標はどうですか?まず、写真を見てみましょう。
ポイントは残りの高さに均等に分散する必要があります。
残りの高さ=コントロールの高さ-2*テキストの高さ。
ポイントのy座標は
*残りの高さ-((現在の温度-最低温度)/(最高温度-最低温度)残りの高さ)+テキストの高さ
少し複雑に見えますが、数式があればコードは単純です。次に、初期化コードとポイント座標を計算するためのコードを確認する必要があります。
コードは次のように表示されます。
//首先从两个参数的构造函数里面获取各种配置的值
public NewWeatherChartView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NewWeatherChartView);
mPointStartX = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_new_start_point_x, 0);
mPointXMargin = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_new_point_x_margin, 0);
mTemperatureTextSize = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_temperature_text_size, 20);
mPointRadius = (int) typedArray.getDimension(R.styleable.NewWeatherChartView_point_radius, 8);
mSelectPointColor = typedArray.getColor(R.styleable.NewWeatherChartView_select_point_color, context.getResources().getColor(R.color.weather_select_point_color));
mUnselectPointColor = typedArray.getColor(R.styleable.NewWeatherChartView_unselect_point_color, context.getResources().getColor(R.color.weather_unselect_point_color));
mLineColor = typedArray.getColor(R.styleable.NewWeatherChartView_line_color, context.getResources().getColor(R.color.weather_line_color));
mSelectTemperatureColor = typedArray.getColor(R.styleable.NewWeatherChartView_select_temperature_text_color, context.getResources().getColor(R.color.weather_select_temperature_color));
mUnSelectTemperatureColor = typedArray.getColor(R.styleable.NewWeatherChartView_unselect_temperature_text_color, context.getResources().getColor(R.color.weather_unselect_temperature_color));
mLineType = typedArray.getInt(R.styleable.NewWeatherChartView_line_type, 0);
this.mContext = context;
typedArray.recycle();
}
private void initData() {
//初始化线的画笔
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(2);
mLinePaint.setDither(true);
//配置虚线
if (mLineType == 0) {
DashPathEffect pathEffect = new DashPathEffect(new float[]{10, 5}, 1);
mLinePaint.setPathEffect(pathEffect);
}
mPath = new Path();
//初始化文字的画笔
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(sp2px(mTemperatureTextSize));
mTextPaint.setTextAlign(Paint.Align.CENTER);
// 初始化圆点的画笔
mCirclePaint = new Paint();
mCirclePaint.setStyle(Paint.Style.FILL);
mDecimalFormat = new DecimalFormat("0");
for (int i = 0; i < items.size(); i++) {
float highY = items.get(i).getHigh();
float lowY = items.get(i).getLow();
if (highY > mMaxTemperature) {
mMaxTemperature = highY;
}
if (lowY < mMinTemperature) {
mMinTemperature = lowY;
}
if (DateUtil.fromTodayDate(items.get(i).getDate()) == 0) {
mTodayIndex = i;
}
}
float span = mMaxTemperature - mMinTemperature;
//这种情况是为了防止所有温度都一样的情况
if (span == 0) {
span = 6.0f;
}
mMaxTemperature = mMaxTemperature + span / 6.0f;
mMinTemperature = mMinTemperature - span / 6.0f;
mHighPoints = new Point[items.size()];
mLowPoints = new Point[items.size()];
}
public int sp2px(float spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, Resources.getSystem().getDisplayMetrics());
}
public int dip2px(float dpValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, Resources.getSystem().getDisplayMetrics());
}
复制代码
昨夜これらの準備ができたら、onDrawに移動して描画できます。
protected void onDraw(Canvas canvas) {
Logging.d(TAG, "onDraw: ");
if (items == null) {
return;
}
int pointX = mPointStartX; // 开始的X坐标
int textHeight = sp2px(mTemperatureTextSize);//文字的高度
int remainingHeight = getHeight() - textHeight * 2;//除去文字后,剩余的高度
// 计算每一个点的X和Y坐标
for (int i = 0; i < items.size(); i++) {
int x = pointX + mPointXMargin * i;
float highTemp = items.get(i).getHigh();
float lowTemp = items.get(i).getLow();
int highY = remainingHeight - (int) (remainingHeight * ((highTemp - mMinTemperature) / (mMaxTemperature - mMinTemperature))) + textHeight;
int lowY = remainingHeight - (int) (remainingHeight * ((lowTemp - mMinTemperature) / (mMaxTemperature - mMinTemperature))) + textHeight;
mHighPoints[i] = new Point(x, highY);
mLowPoints[i] = new Point(x, lowY);
}
// 画线
drawLine(mHighPoints, canvas);
drawLine(mLowPoints, canvas);
for (int i = 0; i < mHighPoints.length; i++) {
// 画文本度数 例如:3°
String yHighText = mDecimalFormat.format(items.get(i).getHigh());
String yLowText = mDecimalFormat.format(items.get(i).getLow());
int highDrawY = mHighPoints[i].y - dip2px(mPointRadius + 8);
int lowDrawY = mLowPoints[i].y + dip2px(mPointRadius + 8 + sp2px(mTemperatureTextSize));
if (i == mTodayIndex) {
mTextPaint.setColor(mSelectTemperatureColor);
mCirclePaint.setColor(mSelectPointColor);
} else {
mTextPaint.setColor(mUnSelectTemperatureColor);
mCirclePaint.setColor(mUnselectPointColor);
}
canvas.drawText(yHighText + "°", mHighPoints[i].x, highDrawY, mTextPaint);
canvas.drawText(yLowText + "°", mLowPoints[i].x, lowDrawY, mTextPaint);
canvas.drawCircle(mHighPoints[i].x, mHighPoints[i].y, mPointRadius, mCirclePaint);
canvas.drawCircle(mLowPoints[i].x, mLowPoints[i].y, mPointRadius, mCirclePaint);
}
}
private void drawLine(Point[] ps, Canvas canvas) {
Point startp;
Point endp;
mPath.reset();
mLinePaint.setAntiAlias(true);
for (int i = 0; i < ps.length - 1; i++) {
startp = ps[i];
endp = ps[i + 1];
mLinePaint.setColor(mLineColor);
canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mLinePaint);
}
}
复制代码
上記はすべてのキーコードです、もちろん、割り当てコードもあります
public void setData(List<WeatherInfo> list) {
this.items = list;
initData();
}
复制代码
最終的なレンダリングを見てみましょう。
上記は単純な温度マップですが、このマップには最適化できる場所がたくさんあり、属性として抽出できる場所もたくさんあります。たとえば、最適化のポイント、テキストの測定について説明します。上記のコードのテキストの測定は、実際には非常に大まかなものです。よく見ると、テキストと上の線上の点との間の距離が、テキストと下の線上の点との間の距離とは異なることがわかります。これは、上記のテキスト測定が行われなかった結果です。次の図に示すように、ここでテキスト測定の最適化を実行しました。ここの方がはるかに優れていますか?また、多くの場所で最適化できます。この記事は以上です。