1. What is a custom View?

Definition 1.

In the Android system, the application interface is View, the interface is composed of a View, the Android SDK provides developers with a variety of views, such as: TextView to display text, ImageView to display pictures, ListView to display list data and so on . But in the development want to achieve a broken line statistics chart, at this time the system will not meet the demand, need developers to customize the view to achieve.

2. How to implement it

Custom View is by inheriting View or View subclass, and in the new class to achieve the corresponding processing logic (rewrite the corresponding method), in order to achieve the desired effect.

3. View Frame structure

It can be seen from the flowchart that View is the parent class of all views, the implementation of a View is to inherit the View, and it can be seen that the View is divided into two categories, View and ViewGroup, with this problem we go to see what the difference is between View and ViewGroup.

2. Why custom View

In development, developers often customize views for four main reasons:

  1. Let the interface have a specific display style, effect;
  2. Allow controls to interact in a special way;
  3. Optimize layout;
  4. Encapsulation;

2.1 Make the interface have a specific display style and effect

In development, the Android SDK provides a lot of controls, but sometimes those controls don’t meet business needs. For example, if you want to use a line chart to show a group of data, then if the View provided by the system can not be achieved, can only be achieved by custom View.

2.2 Allow controls to interact in a special way

The Controls provided by the Android SDK have their own specific way of interacting, but sometimes the default way of interacting with the controls does not meet the needs of the business. For example, if a developer wants to scale an image in an ImageView, the system-provided ImageView cannot be used. Instead, the developer can customize the ImageView.

2.3 Optimizing layout

Sometimes, some layouts if the control provided by the system to achieve quite complex, need a variety of nesting, although the final can also achieve the desired effect, but the performance is very poor, at this time you can customize the View to reduce the nesting level, optimize the layout.

2.4 packaging

Some controls may be used in more than one place, such as the bottom Tab in most apps. Frequently used controls like this can be wrapped with a custom View to be used in more than one place.

3. How to customize the View?

Before we know how to customize a View, we need to know what a custom View contains. A custom View consists of three parts

  1. Layout (view layout)
  2. Draw (View drawing)
  3. Touch feedback (view click events)

In the layout phase we need to know the size and position of the View, in the drawing phase we need to know the contents of the View, touch feedback we need to get the click event response of the View.

The layout stage includes two processes: measure and layout. In addition, the drawing and layout stage of view supports the drawing and touch feedback of view. Only when the position of view is determined can we draw the view and set the touch feedback of view

In custom View and custom ViewGroup, although the overall layout and drawing process is the same, but in detail, custom View and custom ViewGroup is not the same, so, the next two types of discussion:

  1. Custom View layout, drawing process
  2. Customize ViewGroup layout and drawing process

3.1 Custom View layout and drawing process

3.2Custom View the most basic flow chart

The normal way to inherit from a View is onDraw here

3.3Constructor (get custom parameters)

In the constructor, we mainly do some initialization and get our own custom attribute parameters (if any). If any custom attributes are used, we can get their values from the AttributeSet object attrs

public LineChartView(Context context) {
    super(context);
}

public LineChartView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
}
Copy the code

There are four constructor overloads to choose from, whether inheriting a ViewGroup or View. The four parameters were added after API21.

The third argument in a three-argument constructor is the default Style, which is the default Style in the current Application or Activity Theme, and only applies when explicitly called

When we’re writing a custom View, we usually have to worry about the constructor with one or two arguments

Custom View measurement phase

Two methods are executed during the measurement phase of the View. (During the measurement phase, the parent View passes in the parent View’s size requirements by calling the View’s measure() method. After that, the View’s measure() method does some preloading and optimization, then calls the View’s onMeasure() method and passes in the parent View’s size requirements via the onMeasure() method. In custom views, the onMeasure() method needs to be overridden only if the View size needs to be changed. The onMeasure() method does the logical processing according to the business requirements and finally tells the parent View its desired size by calling the setMeasuredDimension() method) :

  • measure()
  • onMeasure()

Measure() : scheduling method, mainly do some pre – and optimization work, and finally call onMeasure() method to perform the actual measurement work;

OnMeasure () : a method used to perform actual measurement tasks, mainly to measure the size and position of View. In the onMeasure() method of a custom View, the View calculates its desired size based on its characteristics and the parent View’s size requirements, and informs the parent View of its desired size through the setMeasuredDimension() method.

OnMeasure () calculates the expected size of the View as follows:

  1. Calculate the expected size of the View by referring to the parent View’s size requirements and actual business requirements:
  • Parsing widthMeasureSpec;
  • Parsing heightMeasureSpec;
  • The expected size of the View is obtained by calling resolveSize().
  1. Save the View’s desired size with setMeasuredDimension() (in fact, setMeasuredDimension() tells the parent View its desired size);

OnMeasure (View size)

The measure process is divided into different situations. If it is only an original View, the measure method will complete its measurement process; if it is a ViewGroup, it will not only complete its own measurement process, but also iterate to call the measure methods of all child elements.

MeasureSpec class MeasureSpec specifies a 32-bit int value, with the highest 2 bits representing SpecMode and the lowest 30 bits representing SpecSize. SpecMode refers to the measurement mode, and SpecSize refers to the size of the specification in a certain measurement mode.

There are three types of specmodes, each of which has a special meaning

  • UNSPECIFIED

The parent container does not have any restrictions on the View and can be as large as it wants, which is generally used within the system.

  • EXACTLY

The parent container has already detected the exact size required by the View, and the final size of the View is the value specified by SpecSize. She corresponds to match_parent and the specific value in LayoutParams

  • AT_MOST

The parent container specifies an available size, SpecSize. The View size cannot be larger than this value. She corresponds to wrAP_content in LayoutParams.

OnSizeChanged (Determine the size of View)

This function is called when the view size changes. Normally, the size of a View can be determined in onMeasure. However, the size of a View is not only controlled by the View itself, but also affected by the parent control, so it is best to use the onSizeChanged callback provided by the system to determine the size of the View.

Custom View layout stage

Layout () : Saves the actual size of the View. The setFrame() method is called to hold the View’s actual size, onSizeChanged() is called to notify the developer that the View’s size has changed, and the onLayout() method is finally called to make the child View layout (if any). The onLayout() method of a custom View is an empty implementation because there are no child views in the custom View.

OnLayout () : An empty implementation that does nothing because it has no child views. In the case of a ViewGroup, the onLayout() method calls the layout() method of the child views, passing them the size of the child views, and letting the child views save their size. So, in a custom View, you don’t have to override this method, but in a custom ViewGroup, you do.

Custom View drawing stage

In the drawing phase of the View a method is executed — draw(), draw() is the overall scheduling method for the drawing phase, Method drawBackground(), method onDraw(), method dispatchDraw(), method onDrawForeground();

  • draw()

The draw () : The general scheduling method in the drawing stage, in which the method drawBackground(), the method onDraw(), the method dispatchDraw() and the method onDrawForeground() are called;

DrawBackground () : The method used to draw the background, which cannot be overridden. The background can only be set or modified using an XML layout file or setBackground().

OnDraw () : a method to draw the body content of a View. Normally, when customizing a View, only this method can be implemented;

DispatchDraw () : method for drawing child Views. Like the onLayout() method, it is an empty implementation in a custom View that does nothing. But in a custom ViewGroup, it calls viewGroup.drawChild (), and in viewGroup.drawChild () it calls view.draw () for each child View to draw itself;

OnDrawForeground () : Method used to draw foreground View, that is, when you want to draw something over the body content, it can be done in this method.

Note: In Android, everything is drawn sequentially, with the first drawing overwritten by the second.

Customize ViewGroup layout and drawing process

Custom ViewGroup measurement phase

As with custom views, two methods are executed during the measurement phase of a custom ViewGroup:

  • measure()
  • onMeasure()

Measure() : scheduling method, mainly do some pre – and optimization work, and finally call onMeasure() method to perform the actual measurement work;

OnMeasure () : The actual method to perform the measurement task is different from that of a custom View. In the onMeasure() method of a custom ViewGroup, the ViewGroup recursively calls the measure() method of its child View. Measure () to measure the size of a ViewGroup’s child View. To their own size requirements and their own available space to calculate their own size requirements of the sub-view) passed in, the sub-view is measured, and the measurement results are temporarily saved for use in the layout stage. After measuring the actual size of the child View, the ViewGroup calculates its desired size based on the actual size of the child View and informs the parent View (the parent View of the ViewGroup) of its desired size using the setMeasuredDimension() method.

The specific process is as follows:

  1. Before running, the developer writes in XML the size requirements for ViewGroup and ViewGroup subviews layout_xxx;
  2. A ViewGroup in its onMeasure() method, According to the developer in XML written on the size of the ViewGroup child View requirements, their parent View (ViewGroup parent View) on their own size requirements and their own available space to calculate their own child View size requirements, And call the measure() of each sub-view to pass in the size requirements of the ViewGroup to the sub-view to measure the size of the sub-view;
  3. After the child View calculates the desired size (in the onMeasure() method of the ViewGroup, the ViewGroup recursively calls the measure() method of each child View, The child View will inform the parent View (ViewGroup) of its desired size by calling setMeasuredDimension() in its onMeasure() method, obtain the actual size and position of the child View, and temporarily store the calculated results. For use in the layout phase;
  4. The ViewGroup calculates its desired size based on the size and position of the child View and informs the parent View of its desired size using the setMeasuredDimension() method. (resolveSize()); (resolveSize()); (resolveSize()); (resolveSize()); In this way, the expected size of the ViewGroup is more consistent with the size requirements of the parent View of the ViewGroup.

Custom ViewGroup layout stage

As with custom views, two methods are executed during the layout phase of a custom ViewGroup:

  • layout()
  • onLayout()

Layout () : Saves the actual size of the ViewGroup. The setFrame() method is called to hold the ViewGroup’s actual size, onSizeChanged() is called to notify the developer that the ViewGroup’s size has changed, and the onLayout() method is finally called to lay out the child View;

OnLayout () : ViewGroup recursively calls the layout() method of each child View, passing the actual size and position of the child View calculated in the measurement stage to the child View, allowing the child View to save its own actual size and position.

Custom ViewGroup drawing phase

As with custom views, a method called draw() is executed during the drawing phase of a custom ViewGroup. Draw () is the general scheduling method of the drawing stage, Method drawBackground(), method onDraw(), method dispatchDraw(), method onDrawForeground();

  • draw()

The draw () : The general scheduling method in the drawing stage, in which the method drawBackground(), the method onDraw(), the method dispatchDraw() and the method onDrawForeground() are called;

In ViewGroup, you can also override the main drawing method onDraw(), the subview method dispatchDraw(), and the foreground drawing method onDrawForeground(). But for the most part, custom viewgroups don’t need to override any drawing methods. Because normally, a ViewGroup’s role is to be a container, a transparent container, and it’s just to hold child views.

4. Practice

Broken line statistics, you can drag the vertical line, and prompt the current point value

1. Declaration and acquisition of custom attributes

<? The XML version = "1.0" encoding = "utf-8"? > <resources> <declare-styleable name="LineChartView"> <attr name="horizontal_dotted_color" format="reference|color" /> <attr name="horizontal_color" format="reference|color" /> <attr name="vertical_color" format="reference|color" /> </declare-styleable> </resources> TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView); horizontal_color = typedArray.getColor(R.styleable.LineChartView_horizontal_color, Color.BLUE); horizontal_dotted_color = typedArray.getColor(R.styleable.LineChartView_horizontal_dotted_color, Color.GRAY); vertical_color = typedArray.getColor(R.styleable.LineChartView_vertical_color, Color.RED); typedArray.recycle();Copy the code

2. Rewrite the ontouch

View to draw, first to rewrite the onDraw method, in the drawing process, we need to use two key objects:

  1. Paint: Use the brush object to set the Settings of the brush such as the color, the thickness of the line, etc.
  2. Path: Sets the start and end positions of drawing points.
  3. Canvas: The Canvas can be drawn using drawPath to bind Paint to Path.
//Canvas: Canvas @override protected void onDraw(Canvas Canvas) {super.ondraw (Canvas); }Copy the code

For example the use of

Private Paint textPaint() {Paint textPaint = new Paint(); textPaint.setColor(horizontal_color); textPaint.setStrokeWidth(3); textPaint.setTextSize(30); return textPaint; Private Paint dottedPaint() {Paint dottedPaint = new Paint(); dottedPaint.setAntiAlias(true); dottedPaint.setStyle(Paint.Style.STROKE); dottedPaint.setStrokeWidth(2); dottedPaint.setColor(horizontal_dotted_color); return dottedPaint; Paint dottedPaint = dottedPaint(); PathEffect pathEffect = new DashPathEffect(new float[]{5, 5, 5, 5}, 2); dottedPaint.setPathEffect(pathEffect); Private void drawLineX(Canvas Canvas, Paint textPaint, Paint dottedPaint) {for (int I = 0; i < lineNumY; I ++) {if (lineNumY -1 == I) {Paint Paint = new Paint(); Path path = new Path(); paint.setColor(Color.BLUE); paint.setStrokeWidth(3); paint.setStyle(Paint.Style.STROKE); paint.setAntiAlias(true); path.moveTo(marginLeftRight, marginTopBottom + (i * meanHeight)); path.lineTo(getWidth() - marginLeftRight, marginTopBottom + (i * meanHeight)); canvas.drawPath(path, paint); paint.reset(); } else {// draw line graph x dotted linePath linePath = new Path(); linePath.moveTo(marginLeftRight, marginTopBottom + (i * meanHeight)); linePath.lineTo(getWidth() - marginLeftRight, marginTopBottom + (i * meanHeight)); canvas.drawPath(linePath, dottedPaint); } String s = (((lineNumY - 1) -i) * meanValue) + ""; float method = textPaint.measureText(s); canvas.drawText(s, marginLeftRight - method - 10, marginTopBottom + (i * meanHeight) + 10, textPaint); } dottedPaint.reset(); Private Paint brokenLinePaint() {Paint brokenLinePaint = new Paint(); brokenLinePaint.setColor(vertical_color); brokenLinePaint.setStrokeWidth(3); brokenLinePaint.setStyle(Paint.Style.STROKE); brokenLinePaint.setAntiAlias(true); return brokenLinePaint; Private void drawLineChart(Canvas Canvas, brokenLinePaint) {coordbeans.clear (); Path pathChart = new Path(); //lintNumX for (int I = 0; i < lintNumX; I ++) {if (I == 0) {// The starting point of the line pathchart.moveto (marginLeftRight, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i]); } pathChart.lineTo(marginLeftRight + meanWidth * i, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i] / meanVH); //marginTopBottom + ((lineNumY - 1) * meanHeight) - math[i%6]) canvas.drawCircle(marginLeftRight + meanWidth * i, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i] / meanVH, 3, brokenLinePaint); coordBeans.add(new CoordBean(marginLeftRight + meanWidth * i, marginTopBottom + ((lineNumY - 1) * meanHeight) - axisDataY[i] / meanVH, axisDataY[i])); } canvas.drawPath(pathChart, brokenLinePaint); }Copy the code

This is the process of drawing a line chart

Now let’s see, when we touch the screen how do we get the touch points of the broken lines

  • So I’m going to overwrite onTouchEvent and then I’m going to focus on that
  • Finger down action motionEvent.action_down
  • Finger movement action motionEvent.action_move
Override public Boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {// Press case MotionEvent.ACTION_DOWN: downX = event.getX(); downY = event.getY(); For (int I = 0; int I = 0; int I = 0; i < coordBeans.size(); i++) { if (Math.abs(downX - coordBeans.get(i).getCoordX()) < paddingPath / 2 && Math.abs(downY - coordBeans.get(i).getCoordY()) < paddingPath / 2) { isClick = true; isClickIndex = i; scrollX = coordBeans.get(i).getCoordX(); invalidate(); showDetails(isClickIndex); break; } } return true; ACTION_MOVE: float x = event.getx (); // Move case motionEvent.action_move: float x = event.getx (); float y = event.getY(); Log. I ("onTouchEvent", x + "-- "+ y); log. I ("onTouchEvent", x + "--" + y) if (x >= startX && x <= endX && y >= startY && y <= endY) { CoordBean coorBean = getCoorBean(x); if (coorBean ! = null) { invalidate(); showDetails(isClickIndex); } } return true; } return super.onTouchEvent(event); }Copy the code

Hover prompt

private void showDetails(int index) { if (mPopWin ! = null) mPopWin.dismiss(); TextView tv = new TextView(getContext()); tv.setTextColor(Color.WHITE); tv.setBackgroundColor(Color.RED); tv.setPadding(20, 0, 20, 0); tv.setGravity(Gravity.CENTER); tv.setText(coordBeans.get(index).getClickCoord() + ""); mPopWin = new PopupWindow(tv, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mPopWin.setBackgroundDrawable(new ColorDrawable(0)); mPopWin.setFocusable(false); Int xoff = (int) (coordbeans.get (index).getCoordx () -0.5 * paddingPath); int yoff = (int) (coordBeans.get(index).getCoordY() - paddingPath); Log.i("showDetails", coordBeans.get(index).getCoordX() + "---" + coordBeans.get(index).getCoordY()); mPopWin.showAsDropDown(this, xoff, yoff - getHeight()); mPopWin.update(); }Copy the code

The above is the basic idea and process of custom broken line statistics chart

details

  • Invalidate () functions, usage scenarios, and precautions
  • View (ViewGroup) event distribution

conclusion

The custom view consists of three main parts

  1. Draw the draw
  2. Layout of the layout
  3. Touch time event

Layout determines the size of the view, which is the basis for drawing and setting touch events below the view. A complete custom view is indispensable