www.jianshu.com/p/c84693096…
Custom View, this article is enough
My CSDN blog synchronous release: custom View, have this one is enough
In order to eliminate blind spots in learning, as much as possible coverage of the Android knowledge of fringes, decided to a custom View to be a slightly more comprehensive usage summary, above content is not, what are the unique place, other basically have great god the blog in this aspect of the content, if you to customize the View very well, Then you don’t have to look down. If you are not familiar with custom View, or you have forgotten a lot of content and want to review it, or you have never used it before, welcome to review this knowledge with me. Maybe my blog is more suitable for you.
1. Customize the View first we need to understand, why custom View? The main reason is that the built-in View of Android system cannot meet our requirements. We need to customize the View we want according to our business needs. Most of the time we just need to rewrite two functions: onMeasure() and onDraw(). OnMeasure is responsible for measuring the size of the current View and onDraw is responsible for drawing the current View. Of course, you have to write at least two constructors:
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Copy the code
For our custom View, we first need to measure the width and height. Why measure width and height? When I first learned to customize View, I couldn’t understand it! Because I thought, I already specified the width and height in the XML file, do I need to get the width and height again in my custom View and set the width and height? Since my custom View is inherited from the View class, the Google team directly in the View class directly get the width and height of the XML Settings, and set in the good? So why does Google make us do this “repetitive work”? Don’t worry, I’ll bring you tea at once
In Android, we learned that in XML layout files, we can write wrap_content or match_parent instead of layout_width and layout_height. The meaning, as we all know, is to set the size to “cover the content” and “fill all the space the parent layout gives us.” These two Settings do not specify the actual size, but the View we draw on the screen must have a specific width and height, and for this reason we have to handle and set the size ourselves. Of course, the View class gives the default processing, but if the default processing of the View class does not meet our requirements, we will have to rewrite the onMeasure function. For example, we want our View to be a square. If we specify wrap_content as the width and height in XML, using the measure method provided by the View class will not satisfy our requirements.
Take a look at the onMeasure function prototype:
Protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) It looks a lot like width and height, and yes, those are the parameters that contain the width and height. What? Contain? Do you need more information? Yes! It also contains the measurement mode, that is, an int integer that contains the measurement mode and size. So how do you put two pieces of information in one number? We know that we have three options for setting the width and height: wrap_content, match_parent, and specifying a fixed size, and there are three measurement modes: UNSPECIFIED, EXACTLY, and AT_MOST, of course, are not mutually exclusive. I’ll cover the three modes in detail later, but the measurement mode is still UNSPECIFIED. With binary, we only need two bits. Because two bits are in the range [0,3] and there’s enough room for four bits. So how does Google put an int with both measurement mode and size information? We know that ints take up 32 bits, but what Google does is to use the first two bits of ints to distinguish between different layout patterns, and the last 30 bits to store size data.
So how do we extract the measurement mode and size from int data? The Android built-in MeasureSpec class MeasureSpec does not need to write the shift << and fetch & operation every time.
int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); Why do we measure patterns when we can get width dimensions from widthMeasureSpec? Would the measurement model be redundant? Note: The size here is not the final size of our View, but the reference size provided by the parent View. Let’s look at the measurement model. What does the measurement model do?
The measurement mode means that the parent of UNSPECIFIED has no restrictions on the current View, AT_MOST The current size is the maximum size that the current View can take What is the relationship between the above measurement mode and wrap_content, match_parent and fixed size in our layout?
Match_parent – > EXACTLY. How do you understand that? Match_parent is using all the free space that the parent View gives us, and the free space that the parent View gives us is determined, which is the size of the integer in this measurement mode.
Wrap_content – > AT_MOST. We want to set the size to wrap our view content, so the size is the parent view gives us as a reference size, as long as it does not exceed this size, the specific size according to our needs to set.
Fixed size (e.g. 100DP) –>EXACTLY. Users specify their own size, we do not have to interfere, of course, with the specified size mainly.
There is too much theory above, so let’s do it and feel the use of onMeasure. Suppose we want to display the current View as a square with the same width and height, and the default width and height is 100 pixels. You can write like this:
private int getMySize(int defaultSize, int measureSpec) { int mySize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); Switch (mode) {case MeasureSpec.UNSPECIFIED: {// if the size is UNSPECIFIED, the defaultSize is set to mySize = defaultSize; break; } case MeasureSpec.AT_MOST: {// mySize = size; break; } case MeasureSpec.EXACTLY: {mySize = size; break; } } return mySize;Copy the code
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMySize(100, widthMeasureSpec); int height = getMySize(100, heightMeasureSpec);
if (width < height) {
height = width;
} else {
width = height;
}
setMeasuredDimension(width, height);
Copy the code
}
Let’s set up the layout
<com.hc.studyview.MyView android:layout_width=”match_parent” android:layout_height=”100dp” android:background=”#ff0000″ />
See the effect of using our own onMeasure function:
If we didn’t override the onMeasure, it would look like this:
The default size is 1.3. Rewrite onDraw and we learned to customize the size, so we will set the size, next is to draw the desired effect. It is easy to draw the desired effect directly on the Canvas, too simple, let’s use a simple example to learn: Suppose we need to implement that our View displays a circle. We have already implemented the same width, height and size above, and proceed:
@override protected void onDraw(Canvas Canvas) {// Call the parent View’s onDraw function, because View implements some basic drawing functions. For example, draw background colors, background images, etc. Super. onDraw(canvas); int r = getMeasuredWidth() / 2; Int centerX = getLeft() + r; int MeasureDHeight () + r; Int centerY = getTop() + r; int centerY = getTop() + r;
Paint paint = new Paint(); paint.setColor(Color.GREEN); DrawCircle (centerX, centerY, r, paint); }Copy the code
If there are some attributes that we want the user to specify and we use hard-coded values only when the user does not specify them, such as the default size above, what if we want the user to specify them in the layout file? That is of course through our custom properties, let the user use our defined properties
First we need to declare a custom property in the res/values/styles. XML file (if not, please create your own) :
<! --> <declare-styleable name="MyView"> <! --> <attr name="default_size" format="dimension" /> </declare-styleable>Copy the code
The next step is to use our custom properties in the layout file
<com.hc.studyview.MyView
android:layout_width="match_parent"
android:layout_height="100dp"
hc:default_size="100dp" />
Copy the code
Note: need to be set inside the root tag (LinearLayout) namespace, the namespace name can literally take, such as hc, values are fixed behind the namespace: “schemas.android.com/apk/res-aut…”
And then the last thing we do is we pull out the value of our custom property in our custom View, and in the constructor, remember there’s an AttributeSet property? It is used to extract properties from the layout:
private int defalutSize; public MyView(Context context, AttributeSet attrs) { super(context, attrs); // The second argument is the tag in our styles. XML file, which is the tag of the property collection, In R file name for R.s tyleable + name TypedArray a = context. ObtainStyledAttributes (attrs, R.s tyleable. MyView);
// The first parameter is the property in the property set, R file name: R.tyleable + attribute set name + underscore + attribute name // The second argument is, if this attribute is not set, Then set the default value defalutSize = A.getDimensionPixelSize (r.starleable. MyView_default_size, 100); // Remember to recycle the TypedArray object a.ricycle ();Copy the code
}
Finally, attach the complete code for MyView:
package com.hc.studyview;
import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View;
/ * *
- Package com.hc.studyview
- Created by HuaChao on 2016/6/3.
*/ public class MyView extends View {
private int defalutSize; public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); // The second argument is the <declare-styleable> tag in the styles. XML file. In R file name for R.s tyleable + name TypedArray a = context. ObtainStyledAttributes (attrs, R.s tyleable. MyView); // The first parameter is the property in the property set, R file name: R.tyleable + attribute set name + underscore + attribute name // The second argument is, if this attribute is not set, Then set the default value defalutSize = A.getDimensionPixelSize (r.starleable. MyView_default_size, 100); // Remember to recycle the TypedArray object a.ricycle (); } private int getMySize(int defaultSize, int measureSpec) { int mySize = defaultSize; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); Switch (mode) {case MeasureSpec.UNSPECIFIED: {// if the size is UNSPECIFIED, the defaultSize is set to mySize = defaultSize; break; } case MeasureSpec.AT_MOST: {// mySize = size; break; } case MeasureSpec.EXACTLY: {mySize = size; break; } } return mySize; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMySize(defalutSize, widthMeasureSpec); int height = getMySize(defalutSize, heightMeasureSpec); if (width < height) { height = width; } else { width = height; } setMeasuredDimension(width, height); } @override protected void onDraw(Canvas Canvas) {// Call the parent View's onDraw function, because View implements some basic drawing functions. For example, draw background colors, background images, etc. Super. onDraw(canvas); int r = getMeasuredWidth() / 2; Int centerX = getLeft() + r; int MeasureDHeight () + r; Int centerY = getTop() + r; int centerY = getTop() + r; Paint paint = new Paint(); paint.setColor(Color.GREEN); DrawCircle (centerX, centerY, r, paint); }Copy the code
}
2 custom ViewGroup custom View process is very simple, just those steps, can customize ViewGroup can not be so simple ~, because it not only to manage their own, but also take into account its child View. We all know that a ViewGroup is a View container that holds the Child View and is responsible for putting the Child View into the specified location. Imagine for a moment, if you were in charge of designing a ViewGroup, how would you design it?
1. First of all, we need to know the size of each child View. Only when we know the size of each child View, we know how big the current ViewGroup should be set to accommodate them.
2. Determine the size of the ViewGroup according to the size of the subview and the function our ViewGroup wants to achieve
3.ViewGroup and subview size calculated, the next is to put it, specific how to put it? It’s up to you to customize it. For example, if you want the child views to be placed next to each other in vertical order, or one on top of the other in chronological order, it’s up to you.
4 already know how to put ah, decided how to put is equivalent to the existing space “divided” into large and small space, each space corresponding to a sub-view, we next is to put the sub-view into the seat, put them in their place.
Now that we have completed the design of the ViewGroup, let’s take a concrete example: put the sub-views side by side in a vertical order from top to bottom, which mimics the vertical layout of the LinearLayout.
First rewrite onMeasure to measure sub-view size and set ViewGroup size:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Measure all child Views, which triggers the onMeasure function for each child View. MeasureChild Is measured on a single view measureChildren(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childCount = getChildCount(); If (childCount == 0) {setMeasuredDimension(0, 0); // If (childCount == 0) {setMeasuredDimension(0, 0); } else {widthMode == MeasureSpec.AT_MOST &&heightMode == MeasureSpec.AT_MOST) {widthMode == MeasureSpec. Int height = getTotleHeight(); int height = getTotleHeight(); int height = getTotleHeight(); int width = getMaxChildWidth(); setMeasuredDimension(width, height); } else if (heightMode == measurespec.at_most) {// If (heightMode == measurespec.at_most) { SetMeasuredDimension (widthSize, getTotleHeight()); } else if (widthMode == MeasureSpec.AT_MOST) {// If (widthMode == MeasureSpec. SetMeasuredDimension (getMaxChildWidth(), heightSize); */ private int getMaxChildWidth() {int childCount = getChildCount(); int maxWidth = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView.getMeasuredWidth() > maxWidth) maxWidth = childView.getMeasuredWidth(); } return maxWidth; **/ private int getTotleHeight() {int childCount = getChildCount(); int height = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); height += childView.getMeasuredHeight(); } return height; }Copy the code
The comments in the code have been written in detail and I won’t go through each line of code. The onMeasure above has measured the sub-view and set its own size. Now let’s put the sub-view ~
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); Int curHeight = t; For (int I = 0; i < count; i++) { View child = getChildAt(i); int height = child.getMeasuredHeight(); int width = child.getMeasuredWidth(); Child.layout (l, curHeight, L + width, curHeight + height); curHeight += height; }}
Set the width and height of our ViewGroup to wrap_content. Add a background to the ViewGroup to make it more visible:
<com.hc.studyview.MyViewGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff9900">
<Button
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="btn" />
</com.hc.studyview.MyViewGroup>
Copy the code
Take a look at the final result
We can also implement the effect of the LinearLayout ourselves
MyViewGroup MyViewGroup MyViewGroup
package com.hc.studyview;
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup;
/ * *
- Package com.hc.studyview
- Created by HuaChao on 2016/6/3.
*/ public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { super(context); }
public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); */ private int getMaxChildWidth() {int childCount = getChildCount(); int maxWidth = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView.getMeasuredWidth() > maxWidth) maxWidth = childView.getMeasuredWidth(); } return maxWidth; **/ private int getTotleHeight() {int childCount = getChildCount(); int height = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); height += childView.getMeasuredHeight(); } return height; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Measure all child Views, which triggers the onMeasure function for each child View. MeasureChild Is measured on a single view measureChildren(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childCount = getChildCount(); If (childCount == 0) {setMeasuredDimension(0, 0); // If (childCount == 0) {setMeasuredDimension(0, 0); } else {widthMode == MeasureSpec.AT_MOST &&heightMode == MeasureSpec.AT_MOST) {widthMode == MeasureSpec. Int height = getTotleHeight(); int height = getTotleHeight(); int height = getTotleHeight(); int width = getMaxChildWidth(); setMeasuredDimension(width, height); } else if (heightMode == measurespec.at_most) {// If (heightMode == measurespec.at_most) { SetMeasuredDimension (widthSize, getTotleHeight()); } else if (widthMode == MeasureSpec.AT_MOST) {// If (widthMode == MeasureSpec. SetMeasuredDimension (getMaxChildWidth(), heightSize); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); Int curHeight = t; for (int i = 0; i < count; i++) { View child = getChildAt(i); int height = child.getMeasuredHeight(); int width = child.getMeasuredWidth(); child.layout(l, curHeight, l + width, curHeight + height); curHeight += height; }}Copy the code
}
This is the end of the study of custom View. Is it so easy to find a custom View?