Android custom view and custom attribute, android custom View
This is the content in Android UI Fundamentals.
Create a custom View
To create a custom UI component, you must first inherit a View class.
First, create a simple custom view to display a cross line.
The first thing to do is to create a CrossView class that inherits the View.
public CrossView(Context context, AttributeSet attrs) { super(context, attrs); }
The second parameter of the constructor is used to pass XML parameters. We will discuss it later. Next we will rewrite two basic methods:onMeasure
AndonDraw
.
OnMeasure
System CallonMeasure
Method to determine the size of the view and its sub-view. The two parameter types areint
But these two parameters are not common numbers, but twoMeasureSpec
,MeasureSpec
It is a combination of a pattern and an integer, which is implemented as an integer. The pattern value has the following situations:
Mode |
Explanation |
UNSPECIFIED |
The parent view has no restrictions on this view. It can be of any size. |
AT_MOST |
This view can be smaller than or equalMeasureSpec Any size of medium size |
EXACTLY |
The parent view must beMeasureSpec Specified size |
When you create a custom view and override itonMeasure
Method, you must correctly handle each situation, get the corresponding size, and then mustonMeasure
CallingsetMeasureDimensions
Method, the parameter is the size you decide, if not called, an exception will be thrown.
Below is the rewriteonMeasure
Method code.
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(calculateMeasure(widthMeasureSpec), calculateMeasure(heightMeasureSpec)); }
Note thatcalculateMeasure
The method is defined by ourselves. Next we will complete this method.
We first define a default size of 100, in the unit of dp (I'm not sure if it's dp.).
private static final int DEFAULT_SIZE = 100;
Multiply the pixel density of the device to obtain the actual displayed pixel value.
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
Then we need to get the mode and size from MeasureSpec.
int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);
Next, based onspecMode
To determineresult
What is the value.
MeasureSpec.UNSPECIFIED
At this time, the parent control has no requirements on the size of the custom view, so I will use the default size as the result, that is
int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density);
MeasureSpec.AT_MOST
In this case, the parent control cannot exceed the specified size value. In this case, select the minimum value and the default value. In either case, this method is valid.
result = Math.min(specSize, result);
MeasureSpec.EXACTLY
In this case, the parent control requires that the child view be of the given size.result
Just like it
result = specSize;
Based on the above discussion, our method code is as follows:
private static final int DEFAULT_SIZE = 100; private int calculateMeasure(int measureSpec) { int result = (int) (DEFAULT_SIZE * getResources().getDisplayMetrics().density); int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else if (specMode == MeasureSpec.AT_MOST) { result = Math.min(specSize, result); } return result; }
OnDraw
It is called when the view should draw its contentonDraw
Method. CreatePaint
Object, which handles things such as color and text size.
PassCrossView
To createPaint
Object
private Paint mPaint; public CrossView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(0xffff0000); }
The above code is createdPaint
Object, and set the anti-aliasing and color.
Next rewriteonDraw
Method. The template is as follows,canvas.save()
Andcanvas.restore()
I will not explain it, but it will not affect the subsequent understanding.
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); // code goes here canvas.restore(); }
We scale the canvas Based on The View Size, so that we can use a floating point number between 0 and 1 as the coordinates of the draw line.
private static final float[] mPoints = {0.5f, 0f, 0.5f, 1f, 0f, 0.5f, 1f, 0.5f}; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.scale(getWidth(), getHeight()); canvas.drawLines(mPoints, mPaint); canvas.restore(); }
We add our custom control to the xml of the activity.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <com.shaw.uitest.CrossView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <com.shaw.uitest.CrossView android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>
Run the command to see the picture at the beginning of the article.
Add custom attributes to the custom View
With the custom view, we want it to be configured through the custom XML Attribute. To do this, we need to declare the attribute and then add a new namespace in the XML layout, finally, process the AttributeSet object that is passed to the custom view constructor.
Declare attributes
Create an attrs. xml file under the res/values/directory (which can be another name) and add the following content to it:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="cross"> <attr name="android:color" /> <attr name="rotation" format="string" /> </declare-styleable></resources>
declare-styleable
The element has a name attribute used to reference custom attributes in the Code. Each custom attribute usesattr
Element to declare,attr
The element has two attributes: name and format. name is used for reference and format represents its data type. If the default system attribute is used, format is not required, if you try to define a different format for an existing property, the project cannot be built. declared on the outer layerattr
Otherdeclare-styleable
Reuse is the same as using system attributes, for example:
<?xml version="1.0" encoding="utf-8"?><resources> <attr name="test" format="string" /> <declare-styleable name="foo"> <attr name="test" /> </declare-styleable> <declare-styleable name="bar"> <attr name="test" /> </declare-styleable></resources>
You can also create custom values for attributes, such
<attr name="enum_attr"> <enum name="value1" value="1" /> <enum name="value2" value="2" /></attr><attr name="flag_attr"> <flag name="flag1" value="0x01" /> <flag name="flag2" value="0x02" /></attr>
enum
Andflag
All must be integers. The difference is thatflag
Available|
For exampleandroid:gravity
The value is flag.
Use attributes in XML
To use new attributes in our XML, we must first declare the namespace for the view. In fact, we often see the namespace declaration. For example, we often see the namespace declaration in the xml file of the activity.
xmlns:android="http://schemas.android.com/apk/res/android"
This namespace declares all keywordsandroid
All attributes starting with android can be found in the android package. to use custom attributes, You need to declare a new namespace with the new package name. Next, add a new namespace for the CrossView attributes and add the relevant xml configuration in the Custom View:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:crossview="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <com.shaw.uitest.CrossView android:layout_width="wrap_content" android:layout_height="wrap_content" crossview:rotation="30" android:color="#ff0000ff"/> <com.shaw.uitest.CrossView android:layout_marginTop="10dp" android:layout_width="wrap_content" android:layout_height="wrap_content" crossview:rotation="50" android:color="#ff00ff00"/></LinearLayout>
Allcrossview
Attributes starting with (name can use something else) can be found in res. This is the method required by Gradle.
Use XML attributes in code
InCrossView
The constructorAttributeSet
Object. You can use it to obtain the attributes declared in the XML layout.
UpdateCrossView
And add corresponding functions and member variables:
private float mRotation; public CrossView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setAntiAlias(true); TypedArray arr = getContext().obtainStyledAttributes(attrs, R.styleable.cross); int color = arr.getColor(R.styleable.cross_android_color, Color.BLACK); float rotation = arr.getFloat(R.styleable.cross_rotation, 0f); arr.recycle(); setColor(color); setRotation(rotation); } public void setColor(int color) { mPaint.setColor(color); } public void setRotation(float degree) { mRotation = degree; }
Update simultaneouslyonDraw
Code
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.scale(getWidth(), getHeight()); canvas.rotate(mRotation, 0.5f, 0.5f); canvas.drawLines(mPoints, mPaint); canvas.restore(); }
The center of our rotation is the center of the canvas, rather than the upper left corner.
Run the program as follows: