Share the idea that custom controls, pure is individual experience. First, the renderings from the artist girl.
And then the screenshot of the real machine.
This time I’m going to share my ideas, not the actual code implementation.
1. Functional analysis
Display 0~100 data, the number of data is uncertain, horizontal can slide, click will appear bubbles. This is a typical line chart requirement.
2. Confirmation of details effect
Background color, horizontal background color, vertical background color, unit line, broken line gradient background, vertex radian, click on bubble, click on vertex circle, corresponding unit line highlight, corresponding abscess highlight.
3. Draw ideas
① Determine the size of the control
Control width spread across the screen, height can be customized. But this is all set up through XML, and the controls themselves need no special processing. But there’s an important point here. Is the size of the font, font size cannot be constant, can be scaled according to the size of the control. You can even scale with the number of words you’re going to draw.
② Determine the coordinates of key components
Key coordinates can be customized view coordinates basically unchanged, such as the horizontal and vertical position. The positions of other components are drawn according to the positions of key components. After the control size is determined, the position of key components can be roughly determined. For example, in this line chart, the Y-axis position of the abscissa is the 12th cell after the control height is divided into 12.5 cells, and the X-axis position of the ordinate is the last cell after the control width is divided into 10.5 cells.
③ Determine the variables
The determination of variables is critical, and some variables can be set externally to improve the applicability of controls. Other variables are factors of animation, or sliding factors. The variables of this control are the number of horizontal cells, and the horizontal coordinate of the curve (used for sliding).
④ Determine the drawing sequence
The last one will block out the previous one, so decide in what order you want to draw it. There is also the timing of the drawing triggered by the event.
Understand the API and data structure used when drawing
You need to know what APIS to use to draw specific effects, like how to draw curves, how to draw gradients, etc. The structure of the data is also determined at this point.
⑥ Draw fixed state
My personal habit is to draw the fixed state first, that is, do not consider the animation or touch events, just draw the key state of the control appearance, of course, use the above mentioned variable drawing. Doing this is like doing keyframes in animation.
⑦ Change variable
Change the value of variables, so that each fixed state connected, timely call lifting drawing, so that the control moved up.
⑧ Touch event processing
Modify variables based on touch events. The prerequisite, of course, is a good understanding of Android touch event handling.
⑨ The artist has a look
Come out and show it to the designers to see if they missed anything. Or what details need to be fixed.
Attending to optimize
Optimize interface effect, increase animation, optimize memory and rendering efficiency, adapt resolution.
Use 4.
Use in actual projects, continuous optimization.
Finally, code sharing, students can use this effect to modify their own.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.CornerPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.allrun.arsmartelevatorformanager.R;
import com.allrun.arsmartelevatorformanager.util.DPUnitUtil;
import java.util.List;
/** * Created by GrenndaMi on 2017/4/5. */
public class PPChart extends View {
Context mContext;
Paint mPaint;
private int mXDown, mLastX;
// The shortest sliding distance
int a = 0;
float startX = 0;
float lastStartX = 0;// The leftmost X coordinate of the current control after lifting the finger
float cellCountW = 9.5 f;// The width of a screen to display the number of grids
float cellCountH = 12.5 f;// The height of the entire control will display the number of cells
float cellH, cellW;
float topPadding = 0.25 f;
PathEffect mEffect = new CornerPathEffect(20);// Smooth the transition Angle
int state = -100;
int lineWidth;
public void setData(List<dataObject> data) {
this.data = data;
state = -100;
postInvalidate();
}
List<dataObject> data;
public PPChart(Context context) {
super(context);
mContext = context;
}
public PPChart(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
a = DPUnitUtil.px2dip(context, ViewConfiguration.get(context).getScaledDoubleTapSlop());
setClickable(true);
lineWidth = DPUnitUtil.dip2px(mContext, 1);
}
public PPChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
}
private void initPaint(a) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
// Line color
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
initPaint();
cellH = getHeight() / cellCountH;
cellW = getWidth() / cellCountW;
// Draw the bottom background
mPaint.setColor(0xff44b391);
canvas.drawRect(0, (((int) cellCountH - 1) + topPadding) * cellH, getWidth(), cellCountH * cellH, mPaint);
if (data == null || data.size() == 0) {
return;
}
DrawAbscissaLines(canvas);
DrawOrdinate(canvas);
//------------ ends with this background ---------------
canvas.saveLayer(0.0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);
DrawDataBackground(canvas);
canvas.restore();
canvas.saveLayer(0.0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);
DrawDataLine(canvas);
canvas.restore();
canvas.saveLayer(0.0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);
DrawAbscissa(canvas);
canvas.restore();
showPop(canvas);
if (state == -100) { gotoEnd(); }}// Draw the abscissa
private void DrawOrdinate(Canvas canvas) {
mPaint.reset();
float i = 0.5 f;
for (dataObject tmp : data) {
mPaint.setColor(0xffb4e1d3);
mPaint.setTextSize(getWidth() / cellCountW / 3.2 f);
dataObject tmp2 = getDataByX(mLastX);
// The selected item needs to be deepened
if(tmp2 ! =null && tmp2.getHappenTime().equals(tmp.getHappenTime()) && state == MotionEvent.ACTION_UP && Math.abs(mLastX - mXDown) < a) {
mPaint.setColor(0xffffffff);
} else {
mPaint.setColor(0xffb4e1d3);
}
String str1 = tmp.getHappenTime().split("-") [0];
canvas.drawText(str1,
startX + cellW * i - mPaint.measureText(str1) / 2,
(((int) cellCountH - 1) + topPadding + cellCountH) / 2 * cellH,
mPaint);
mPaint.setTextSize(getWidth() / cellCountW / 3.5 f);
String str2 = tmp.getHappenTime().split("-") [1] + "." + tmp.getHappenTime().split("-") [2];
canvas.drawText(str2,
startX + cellW * i - mPaint.measureText(str2) / 2,
(((int) cellCountH - 1) + topPadding + cellCountH) / 2 * cellH - 1.5 f * (mPaint.ascent() + mPaint.descent()),
mPaint);
// Draw a vertical background line
mPaint.setColor(0xff92dac4);
canvas.drawLine(startX + cellW * i,
topPadding * cellH,
startX + cellW * i,
(topPadding + 10.5 f) * cellH,
mPaint);
i++;
}
mPaint.setColor(0xffb4e1d3);
mPaint.setTextSize(getWidth() / cellCountW / 3f);
canvas.drawText("end",
startX + cellW * i - mPaint.measureText("end") / 2,
(((int) cellCountH - 1) + topPadding + cellCountH) / 2 * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
}
// Draw the vertical coordinate
public void DrawAbscissaLines(Canvas canvas) {
mPaint.setColor(0xff92dac4);
// Draw a background line
canvas.drawLine(0,
topPadding * cellH,
cellW * 9.5 f,
topPadding * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 1) * cellH,
cellW * 9.5 f,
(topPadding + 1) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 2) * cellH,
cellW * 9.5 f,
(topPadding + 2) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 3) * cellH,
cellW * 9.5 f,
(topPadding + 3) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 4) * cellH,
cellW * 9.5 f,
(topPadding + 4) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 5) * cellH,
cellW * 9.5 f,
(topPadding + 5) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 6) * cellH,
cellW * 9.5 f,
(topPadding + 6) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 7) * cellH,
cellW * 9.5 f,
(topPadding + 7) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 8) * cellH,
cellW * 9.5 f,
(topPadding + 8) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 9) * cellH,
cellW * 9.5 f,
(topPadding + 9) * cellH,
mPaint);
canvas.drawLine(0,
(topPadding + 10) * cellH,
cellW * 9.5 f,
(topPadding + 10) * cellH,
mPaint);
}
// Draw the vertical coordinate
public void DrawAbscissa(Canvas canvas) {
mPaint.reset();
mPaint.setColor(mContext.getResources().getColor(R.color.colorPrimary));
// Draw the background 9.51 = 10-0.5 (I start) + 0.01 (show the last line)
canvas.drawRect(cellW * ((int) cellCountW - 0.5 f + 0.01 f), 0, cellW * ((int) cellCountW + 1), 11.2 f * cellH, mPaint);
mPaint.setColor(0xffb6e6d7);
mPaint.setTextSize(getWidth() / cellCountW / 3);
canvas.drawText("100%",
cellW * (int) cellCountW - mPaint.measureText("100%") / 2,
topPadding * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
canvas.drawText("80%",
cellW * (int) cellCountW - mPaint.measureText("80%") / 2,
(topPadding + 2) * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
canvas.drawText("60%",
cellW * (int) cellCountW - mPaint.measureText("60%") / 2,
(topPadding + 4) * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
canvas.drawText("40%",
cellW * (int) cellCountW - mPaint.measureText("40%") / 2,
(topPadding + 6) * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
canvas.drawText("20%",
cellW * (int) cellCountW - mPaint.measureText("20%") / 2,
(topPadding + 8) * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
canvas.drawText("0%",
cellW * (int) cellCountW - mPaint.measureText("0%") / 2,
(topPadding + 10) * cellH - (mPaint.ascent() + mPaint.descent()) / 2,
mPaint);
}
// Draw a gradient background
private void DrawDataBackground(Canvas canvas) {
if (data == null || data.size() == 0) {
return;
}
LinearGradient lg = new LinearGradient(getWidth() / 2, topPadding * cellH, getWidth() / 2, (topPadding + 10) * cellH, 0xaaffffff.0xaa61ccab, Shader.TileMode.CLAMP);
mPaint.setShader(lg);
float i = 0.5 f;
Path path = new Path();
// The starting and ending points should be drawn twice more to prevent rounded corners
path.moveTo(startX + cellW * i, (topPadding + 10) * cellH);
path.lineTo(startX + cellW * i, (topPadding + 10) * cellH);
path.lineTo(startX + cellW * i, getHByValue(data.get(0).getNum()));
for (dataObject tmp : data) {
path.lineTo(startX + cellW * i, getHByValue(tmp.getNum()));
i++;
}
path.lineTo(startX + cellW * (i -1), getHByValue(data.get(data.size()-1).getNum()));
path.lineTo(startX + cellW * (i - 1), (topPadding + 10) * cellH -1);
path.lineTo(startX + cellW * (i - 1), (topPadding + 10) * cellH);
path.close();
mPaint.setPathEffect(mEffect);
canvas.drawPath(path, mPaint);
}
// Draw data line
public void DrawDataLine(Canvas canvas) {
float i = 0.5 f;
mPaint.reset();
mPaint.setStrokeWidth(lineWidth);
mPaint.setColor(0xffffffff);
Path path = new Path();
path.moveTo(startX + cellW * i -1, getHByValue(data.get(0).getNum()));
path.lineTo(startX + cellW * i, getHByValue(data.get(0).getNum()));
for (dataObject tmp : data) {
path.lineTo(startX + cellW * i, getHByValue(tmp.getNum()));
i++;
}
path.lineTo(startX + cellW * (i -1), getHByValue(data.get(data.size()-1).getNum()));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setPathEffect(mEffect);
canvas.drawPath(path, mPaint);
}
// Display data bubbles
private void showPop(Canvas canvas) {
/ / click
if (state == MotionEvent.ACTION_UP && Math.abs(mLastX - mXDown) < a) {
dataObject data = getDataByX(mLastX);
if (data == null) {
return;
}
initPaint();
// The selected line
mPaint.setColor(0xaaffffff);
canvas.drawLine(getXBykey(data.getHappenTime()), getHByValue(data.getNum()), getXBykey(data.getHappenTime()), (topPadding + 10f) * cellH, mPaint);
// Draw bubble background
mPaint.setColor(0xffffffff);
mPaint.setTextSize(getWidth() / cellCountW / 3f);
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
RectF r;
// The bubble is 0.5 grid height away from the vertex, the bubble height is 1.5 times the height of the text. Width 1.6 times the width of the text (0.8+0.8)
float left = getXBykey(data.getHappenTime()) - mPaint.measureText(data.getNum() + "%") * 0.8 f;
if(left < 0 ){
left = 0;
}
float right = left + 2 * mPaint.measureText(data.getNum() + "%") * 0.8 f;
if (data.getNum() >= 10) {
r = new RectF(left,
getHByValue(data.getNum()) + 0.5 f * cellH,
right,
getHByValue(data.getNum()) + 0.5 f * cellH + 1.5 f * (fontMetrics.bottom - fontMetrics.top));
} else {
r = new RectF(left,
getHByValue(data.getNum()) - 0.5 f * cellH - 1.5 f * (fontMetrics.bottom - fontMetrics.top),
right,
getHByValue(data.getNum()) - 0.5 f * cellH);
}
// Draw the text on the bubble
canvas.drawRoundRect(r, 90.90, mPaint);
mPaint.setColor(0xff414141);
float baseline = (r.bottom + r.top - fontMetrics.bottom - fontMetrics.top) / 2;
canvas.drawText(data.getNum() + "%",
(r.left+ r.right)/2 - mPaint.measureText(data.getNum() + "%") / 2f,
baseline, mPaint);
// Draw the circle on the line
mPaint.setStrokeWidth(lineWidth * 2);
mPaint.setColor(0xff49c29d);
canvas.drawCircle(getXBykey(data.getHappenTime()), getHByValue(data.getNum()), lineWidth * 5, mPaint);
mPaint.setColor(0xffffffff);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(getXBykey(data.getHappenTime()), getHByValue(data.getNum()), lineWidth * 5, mPaint); mPaint.setStrokeWidth(lineWidth); }}// Touch processing
@Override
public boolean onTouchEvent(MotionEvent event) {
if (data == null || data.size() == 0) {
return super.onTouchEvent(event);
}
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
/ / press
mXDown = (int) event.getRawX();
state = MotionEvent.ACTION_DOWN;
break;
case MotionEvent.ACTION_MOVE:
/ / move
mLastX = (int) event.getRawX();
if (Math.abs(lastStartX - mXDown) < a) {
break;
}
// Slide limit
if (lastStartX + mLastX - mXDown > 0.5 f * cellW || lastStartX + mLastX - mXDown + cellW * (data.size() + 0.5 f) < cellW * (cellCountW - 1)) {
break;
}
state = MotionEvent.ACTION_MOVE;
startX = lastStartX + mLastX - mXDown;
postInvalidate();
break;
case MotionEvent.ACTION_UP:
/ / lift
lastStartX = startX;
state = MotionEvent.ACTION_UP;
postInvalidate();
break;
default:
break;
}
return super.onTouchEvent(event);
}
// Get the nearest point by the coordinates
private dataObject getDataByX(int pointX) {
float i = 0.5 f;
dataObject result = null;
for (dataObject tmp : data) {
float x = startX + cellW * i;
if (Math.abs(x - pointX) < cellW / 2) {
result = tmp;
return result;
}
i++;
}
return result;
}
private float getHByValue(float value) {
return (topPadding + 10) * cellH - (cellH * 10) * value / 100;
}
// Get the x-coordinate of the point from the x-coordinate text
private float getXBykey(String key) {
float i = 0.5 f;
for (dataObject tmp : data) {
if (tmp.getHappenTime().equals(key)) {
return startX + cellW * i;
}
i++;
}
return 0;
}
// Display the most recent data on the right
public void gotoEnd(a) {
if (data == null || data.size() == 0) {
return;
}
if (data.size() < cellCountW - 1) {
startX = 0;
lastStartX = startX;
postInvalidate();
return;
}
startX = -(cellW) * (data.size() - cellCountW + 1); lastStartX = startX; postInvalidate(); }}Copy the code
The data structure used
public class dataObject {
String happenTime;
float num;
public dataObject(String happenTime, float num) {
this.happenTime = happenTime;
this.num = num;
}
public String getHappenTime() {
return happenTime;
}
public void setHappenTime(String happenTime) {
this.happenTime = happenTime;
}
public float getNum() {
return num;
}
public void setNum(float num) {
this.num = num; }}Copy the code
XML layout
<com.allrun.arsmartelevatorformanager.widget.PPChart
android:id="@+id/chart"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:background="@color/colorPrimary" />Copy the code
Set the data in the code