/
VSView.java
391 lines (356 loc) · 14.9 KB
/
VSView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
package com.d.lib.ui.view.vs;
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.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.d.lib.common.util.DimenUtils;
import com.d.lib.ui.view.R;
import java.text.DecimalFormat;
/**
* Use:
* VSItem vsItemA = new VSItem("A", true); // true: 已选中, false: 未选中
* VSItem vsItemB = new VSItem("B", false);
* vsView.setCompareA(vsItemA)
* vsView.setCompareB(vsItemB)
* vsView.setPercent(0.85f, true); // param1: 若为50%请传-1; param2: false为只更值不刷新,true为更值并刷新view
* <p>
* Created by D on 2017/2/28.
*/
public class VSView extends View {
/**
* Math.pow(...) is very expensive, so avoid calling it and create it
* yourself.
*/
private static final int POW_10[] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
};
private int mWidth; // View的宽度
private int mHeight; // View的高度
private float mPadding; // 对比条距两端间距
private float mHPercent; // 对比条高度
private float mRadius; // 对比条的圆角矩形弧度
private Rect mRect;
private RectF mRectF;
private Paint mPaintA; // A类颜色的画笔
private Paint mPaintB; // B类颜色的画笔
private Paint mPaintTxt; // 仅用于绘制文字的画笔
private Path mPath; // 通用路径
private float mMargin; // 两圆与对比条的垂直间距
private float mMarginTxt; // 文字与圆的水平间距
private OnVSClickListener mListener; // Listener
private float mPercent = 0.5f; // 对比A项所占百分比 范围0-1
private int mDIndex = -1; // ActionDown按压时的位置
private int mUIndex = 0; // ActionUp松开时的位置
private int mTouchSlop; // 最小视为移动距离
private float mDX, mDY; // ActionDown的坐标(dx,dy)
private boolean mDInvaild; // 标志位,点击是否有效(true有效: 点中了圆, false无效: 未点中圆)
private float mSpaceHalf; // 对比条,中间空隙的一半宽度
private float mTanW; // 对比条,中间空隙的偏斜宽度
private Bitmap mBitmapA; // 对比A项正常图片
private Bitmap mBitmapAP; // 对比A项按压图片
private Bitmap mBitmapAS; // 对比A项选中图片
private Bitmap mBitmapAU; // 对比A项未选中图片
private Bitmap mBitmapB; // 对比B项正常图片
private Bitmap mBitmapBP; // 对比B项按压图片
private Bitmap mBitmapBS; // 对比B项选中图片
private Bitmap mBitmapBU; // 对比B项未选中图片
private Rect mRectBp; // 仅用于图片Rect
private VSItem mItemA; // 左项
private VSItem mItemB; // 右项
public VSView(Context context) {
this(context, null);
}
public VSView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public VSView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
/**
* format a number properly with the given number of digits
*
* @param number the number to format
* @param digits the number of digits
*/
public static String formatDecimal(double number, int digits) {
number = roundNumber((float) number, digits);
StringBuffer a = new StringBuffer();
for (int i = 0; i < digits; i++) {
if (i == 0)
a.append(".");
a.append("0");
}
DecimalFormat nf = new DecimalFormat("###,###,###,##0" + a.toString());
String formatted = nf.format(number);
return formatted;
}
public static float roundNumber(float number, int digits) {
try {
if (digits == 0) {
int r0 = (int) Math.round(number);
return r0;
} else if (digits > 0) {
if (digits > 9)
digits = 9;
StringBuffer a = new StringBuffer();
for (int i = 0; i < digits; i++) {
if (i == 0)
a.append(".");
a.append("0");
}
DecimalFormat nf = new DecimalFormat("#" + a.toString());
String formatted = nf.format(number);
return Float.valueOf(formatted);
} else {
digits = -digits;
if (digits > 9)
digits = 9;
int r2 = (int) (number / POW_10[digits] + 0.5);
return r2 * POW_10[digits];
}
} catch (NumberFormatException e) {
e.printStackTrace();
return number;
}
}
private void init(Context context) {
int textSize = DimenUtils.dp2px(context, 13);
mPadding = DimenUtils.dp2px(context, 2);
mHPercent = DimenUtils.dp2px(context, 3);
mTanW = DimenUtils.dp2px(context, 0.5f);
mSpaceHalf = DimenUtils.dp2px(context, 1);
mMargin = DimenUtils.dp2px(context, 4);
mMarginTxt = DimenUtils.dp2px(context, 6);
mRadius = DimenUtils.dp2px(context, 13.5f);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
mBitmapA = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapAP = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapAS = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapAU = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapB = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapBP = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapBS = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mBitmapBU = BitmapFactory.decodeResource(getResources(), R.drawable.lib_ui_view_vs_icon);
mRectBp = new Rect();
mRect = new Rect();
mRectF = new RectF();
mPath = new Path();
int colorTxt = Color.parseColor("#7c838a");
mPaintTxt = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintTxt.setTextSize(textSize);
mPaintTxt.setTextAlign(Paint.Align.LEFT);
mPaintTxt.setColor(colorTxt);
mPaintTxt.setAlpha(0xbf);
mPaintA = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintA.setColor(Color.parseColor("#E3542B"));
mPaintB = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaintB.setColor(Color.parseColor("#4ABC00"));
mItemA = new VSItem("", false);
mItemB = new VSItem("", false);
calcPercent();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mHeight == 0 || mWidth == 0) {
return;
}
if (mPercent == 0) {
drawRoundRect(canvas, mRect, mRectF, mPaintB, mHPercent / 2,
mPadding, mHeight - mHPercent, mWidth - mPadding, mHeight);
} else if (mPercent == 1) {
drawRoundRect(canvas, mRect, mRectF, mPaintA, mHPercent / 2,
mPadding, mHeight - mHPercent, mWidth - mPadding, mHeight);
} else {
drawRoundRect(canvas, mRect, mRectF, mPaintA, mHPercent / 2,
mPadding, mHeight - mHPercent, mPadding + mHPercent, mHeight);
drawRoundRect(canvas, mRect, mRectF, mPaintB, mHPercent / 2,
mWidth - mHPercent - mPadding, mHeight - mHPercent, mWidth - mPadding, mHeight);
float offset = (mWidth - mPadding * 2 - mHPercent) * mPercent + mPadding + mHPercent / 2;
if (offset < mPadding + mHPercent + mSpaceHalf + mTanW) {
// 限定最小值
offset = mPadding + mHPercent + mSpaceHalf + mTanW;
} else if (offset > mWidth - mPadding - mHPercent - mSpaceHalf - mTanW) {
// 限定最大值
offset = mWidth - mPadding - mHPercent - mSpaceHalf - mTanW;
}
float left = mPadding + mHPercent / 2;
float right = offset - mSpaceHalf;
drawPath(canvas, mPath, mPaintA,
left, left, right + mTanW, right - +mTanW,
mHeight - mHPercent, mHeight, mHeight - mHPercent, mHeight);
left = offset + mSpaceHalf;
right = mWidth - mPadding - mHPercent / 2;
drawPath(canvas, mPath, mPaintB,
left + mTanW, left - mTanW, right, right,
mHeight - mHPercent, mHeight, mHeight - mHPercent, mHeight);
}
float cy = mHeight - mHPercent - mMargin - mRadius;
drawBitmap(canvas, cy);
float textHeight = DimenUtils.getTextHeight(mPaintTxt);
mPaintTxt.setTextAlign(Paint.Align.LEFT);
canvas.drawText(mItemA.mainText + " " + mItemA.percent, mRadius * 2 + mMarginTxt, cy + textHeight / 2, mPaintTxt);
mPaintTxt.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(mItemB.mainText + " " + mItemB.percent, mWidth - mRadius * 2 - mMarginTxt, cy + textHeight / 2, mPaintTxt);
}
private void drawBitmap(Canvas canvas, float cy) {
Bitmap bpA = mBitmapA, bpB = mBitmapB;
if (!mItemA.isChecked && !mItemB.isChecked) {
// A、B项都未选中
if (mDInvaild) {
if (mDIndex == 0) {
bpA = mBitmapAP;
bpB = mBitmapB;
} else if (mDIndex == 1) {
bpA = mBitmapA;
bpB = mBitmapBP;
}
}
} else {
if (mItemA.isChecked) {
bpA = mBitmapAS;
bpB = mBitmapBU;
} else {
bpA = mBitmapAU;
bpB = mBitmapBS;
}
}
mRectBp.set(0, (int) (cy - mRadius), (int) (mRadius * 2), (int) (cy + mRadius));
canvas.drawBitmap(bpA, null, mRectBp, null);
mRectBp.set((int) (mWidth - mRadius * 2), (int) (cy - mRadius), mWidth, (int) (cy + mRadius));
canvas.drawBitmap(bpB, null, mRectBp, null);
}
private void drawPath(Canvas canvas, Path path, Paint paint,
float ltX, float lbX, float rtX, float rbX,
float ltY, float lbY, float rtY, float rbY) {
path.reset();
path.moveTo(ltX, ltY);
path.lineTo(lbX, lbY);
path.lineTo(rbX, rbY);
path.lineTo(rtX, rtY);
path.close();
canvas.drawPath(path, paint);
}
/**
* Draw圆角矩形
*/
private void drawRoundRect(Canvas canvas, Rect rect, RectF rectF, Paint paint, float rxy,
float left, float top, float right, float bottom) {
rect.set((int) left, (int) top, (int) right, (int) bottom);
rectF.set(rect);
canvas.drawRoundRect(rectF, rxy, rxy, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(mWidth, mHeight);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float eX = event.getX();
float eY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDX = event.getX();
mDY = event.getY();
float cline = mHeight - mHPercent - mMargin;
if (eX >= 0 && eX <= mRadius * 2 && eY >= cline - mRadius * 2 && eY <= cline) {
mDIndex = 0;
mDInvaild = true; // 点击有效
} else if (eX >= mWidth - mRadius * 2 && eX <= mWidth && eY >= cline - mRadius * 2 && eY <= cline) {
mDIndex = 1;
mDInvaild = true; // 点击有效
} else {
mDIndex = -1;
mDInvaild = false; // 点击无效
}
invalidate();
return true;
case MotionEvent.ACTION_MOVE:
if (Math.abs(eX - mDX) > mTouchSlop || Math.abs(eY - mDY) > mTouchSlop) {
mDInvaild = false; // 点击无效
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
if (mDInvaild) {
float clineF = mHeight - mHPercent - mMargin;
if (eX >= 0 && eX <= mRadius * 2 && eY >= clineF - mRadius * 2 && eY <= clineF) {
mUIndex = 0;
} else if (eX >= mWidth - mRadius * 2 && eX <= mWidth && eY >= clineF - mRadius * 2 && eY <= clineF) {
mUIndex = 1;
}
if (mUIndex == mDIndex) {
if (mListener != null) {
if (mUIndex == 0) {
mListener.onItemClick(mUIndex, mItemA);
} else if (mUIndex == 1) {
mListener.onItemClick(mUIndex, mItemB);
}
}
}
}
// Reset
mDX = 0;
mDY = 0;
mDIndex = -1;
mDInvaild = false;
invalidate();
return true;
case MotionEvent.ACTION_CANCEL:
// Reset
mDX = 0;
mDY = 0;
mDIndex = -1;
mDInvaild = false;
invalidate();
break;
}
return super.onTouchEvent(event);
}
private void calcPercent() {
if (mPercent == -1) {
mItemA.percent = mItemB.percent = "";
mPercent = 0.5f;
} else {
mItemA.percent = formatDecimal(mPercent * 100, 2) + "%";
mItemB.percent = formatDecimal(100 - mPercent * 100, 2) + "%";
}
}
public VSView setCompareA(VSItem item) {
this.mItemA = item;
return this;
}
public VSView setCompareB(VSItem item) {
this.mItemB = item;
return this;
}
/**
* 动态刷新-设置对比项A所占百分比并重新绘制-格式为0.00
*/
public void setPercent(float percentA, boolean isRefresh) {
mPercent = percentA;
calcPercent();
if (isRefresh) {
invalidate();
}
}
public void setOnVSClickListener(OnVSClickListener l) {
this.mListener = l;
}
public interface OnVSClickListener {
void onItemClick(int index, VSItem item);
}
}