Android custom view tutorial

Source: Internet
Author: User
Tags drawtext

Android custom view tutorial
Android UI elements are based on views (a single element on the screen) and ViewGroup (a set of elements). Android has many built-in components and la s, such as buttons, TextView, and RelativeLayout. During app development, we need custom view components to meet our needs. Override the View behavior by inheriting the Child class of the View or View, such as onDraw or onTouchEvent. Creating a fully custom component creating a custom component mainly involves the following five aspects: Drawing: Controlling the rendering of the view, the onDraw override method is usually used to implement Interaction (Interaction): controls the Interaction between users and views, such as OnTouchEvent and gestures size (Measurement): controls the dimension of View content, overwrite the onMeasure method attribute (Attributes): defines the view attribute in XML, and uses TypedArray to obtain the attribute value Persistence (Persistence): Save and restore when the configuration changes, here, we use onSaveInstanceState and onRestoreInstanceState to illustrate how to change the shape (square, circle, and triangle) when creating a graph that allows users to click ). As follows: To define the View class, we create a ShapeSelectorView to inherit the self-View and implement the necessary constructor, as shown below:

public class ShapeSelectorView extends View { // We must provide a constructor that takes a Context and an AttributeSet. // This constructor allows the UI to create and edit an instance of your view. public ShapeSelectorView(Context context, AttributeSet attrs) {super(context, attrs);   }}

 

Add view to layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"   xmlns:app="http://schemas.android.com/apk/res-auto"   xmlns:tools="http://schemas.android.com/tools"   android:layout_width="match_parent"   android:layout_height="match_parent" >   <com.codepath.example.customviewdemo.ShapeSelectorViewandroid:id="@+id/shapeSelector"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_alignParentRight="true"android:layout_alignParentTop="true"android:layout_alignParentLeft="true" /></RelativeLayout>

 

Next, we define a namespace app, which allows Android to automatically parse without specifying a specific package name. You can use XML to configure attributes and styles in the custom property view, for example, we want users to select the shape color and whether to display the shape name. For example, we want the view to be configured as follows:
<com.codepath.example.customviewdemo.ShapeSelectorView   app:shapeColor="#7f0000"   app:displayShapeName="true"   android:id="@+id/shapeSelector"   ... />

 

To be able to define shapeColor and displayShapeName, We need to configure IN res/values/attrs. xml:
<?xml version="1.0" encoding="utf-8"?><resources>  <declare-styleable name="ShapeSelectorView">      <attr name="shapeColor" format="color" />      <attr name="displayShapeName" format="boolean" />  </declare-styleable></resources>

 

For each attribute you want to customize, You need to define the attr node. Each node has the name and format attributes. The format attribute is the type of the expected value, such as color, dimension, boolean, integer, float, etc. Once attributes are defined, you can use them like using built-in attributes. The only difference is that your custom attributes belong to different namespaces, you can define the namespace in the layout of the Root View, generally you just need to specify: http://schemas.android.com/apk/res/ <package_name>, but you can use
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"   xmlns:tools="http://schemas.android.com/tools"   xmlns:app="http://schemas.android.com/apk/res-auto"   android:layout_width="match_parent"   android:layout_height="match_parent" >   <com.codepath.example.customviewdemo.ShapeSelectorView  app:shapeColor="#7f0000"  app:displayShapeName="true"  android:id="@+id/shapeSelector"  android:layout_width="wrap_content"  android:layout_height="wrap_content"  android:layout_above="@+id/btnSelect"  android:layout_alignParentLeft="true"  android:layout_below="@+id/tvPrompt" /></RelativeLayout>

 

The shapeColor and displayShapeName attributes are defined before the application Custom Attributes. We need to extract these two attribute values for use in the Custom view. You can use the TypedArray and obtainStyledAttributes methods to complete them, as follows:
public class ShapeSelectorView extends View { private int shapeColor; private boolean displayShapeName; public ShapeSelectorView(Context context, AttributeSet attrs) {   super(context, attrs);   setupAttributes(attrs); } private void setupAttributes(AttributeSet attrs) {   // Obtain a typed array of attributes   TypedArray a = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.ShapeSelectorView, 0, 0);   // Extract custom attributes into member variables   try {     shapeColor = a.getColor(R.styleable.ShapeSelectorView_shapeColor, Color.BLACK);     displayShapeName = a.getBoolean(R.styleable.ShapeSelectorView_displayShapeName, false);   } finally {     // TypedArray objects are shared and must be recycled.     a.recycle();   } }}

 

Next, we will add some getter and setter methods:
public class ShapeSelectorView extends View { // ... public boolean isDisplayingShapeName() {   return displayShapeName; } public void setDisplayingShapeName(boolean state) {   this.displayShapeName = state;   invalidate();   requestLayout(); } public int getShapeColor() {   return shapeColor; } public void setShapeColor(int color) {   this.shapeColor = color;   invalidate();   requestLayout(); }}

 

You may need to redraw a view when its attributes change. You need to call invalidate () and requestLayout () to refresh the display. Drawing suppose we want to draw a rectangle using the previous attribute. All the drawing is executed in the onDraw method, and the Canvas object is used for drawing, as shown below:
public class ShapeSelectorView extends View { // ... private int shapeWidth = 100; private int shapeHeight = 100; private int textXOffset = 0; private int textYOffset = 30; private Paint paintShape; // ... public ShapeSelectorView(Context context, AttributeSet attrs) {   super(context, attrs);   setupAttributes(attrs);   setupPaint(); } @Override protected void onDraw(Canvas canvas) {   super.onDraw(canvas);   canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);   if (displayShapeName) {     canvas.drawText("Square", shapeWidth + textXOffset, shapeHeight + textXOffset, paintShape);   } } private void setupPaint() {      paintShape = new Paint();     paintShape.setStyle(Style.FILL);     paintShape.setColor(shapeColor);     paintShape.setTextSize(30);  }}

 

This Code draws a Graph Based on the shapeColor set in the XML, and determines whether to display the graph name based on the displayShapeName attribute. The result is as follows: for more painting tutorials, refer to Custom 2D Drawing Tutorial calculation dimensions. To better understand the width and height of a Custom view, we need to define the onMeasure method, this method determines the width and height of a view based on its content. Here the width and height are determined by the shape and the text below, as shown below:
public class ShapeSelectorView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   // Defines the extra padding for the shape name text   int textPadding = 10;   int contentWidth = shapeWidth;      // Resolve the width based on our minimum and the measure spec   int minw = contentWidth + getPaddingLeft() + getPaddingRight();   int w = resolveSizeAndState(minw, widthMeasureSpec, 0);      // Ask for a height that would let the view get as big as it can   int minh = shapeHeight + getPaddingBottom() + getPaddingTop();   if (displayShapeName) { minh += textYOffset + textPadding;   }   int h = resolveSizeAndState(minh, heightMeasureSpec, 0);      // Calling this method determines the measured width and height   // Retrieve with getMeasuredWidth or getMeasuredHeight methods later   setMeasuredDimension(w, h); }}

 

Width and height are discussed based on MeasureSpec. A MeasureSpec encapsulates the layout requirements that the parent layout passes to the Child layout. Each MeasureSpec represents a set of width and height requirements. A MeasureSpec consists of the size and mode. It has three modes: UNSPECIFIED (not specified). The parent element does not apply any constraint to the child element, and the child element can get any desired size. EXACTLY (completely ), the exact size of the parent element determines the size of the child element. The child element is limited to a given boundary while the size of the child element is ignored. AT_MOST (at most), the child element reaches the specified size at most. The resolveSizeAndState () method returns an appropriate value based on the desired size and MeasureSpec of the view. Finally, you need to call the setMeasureDimension () method to take effect. If you want to change the shape after clicking different shapes, you need to add the custom logic in the onTouchEvent method:
public class ShapeSelectorView extends View { // ... private String[] shapeValues = { "square", "circle", "triangle" }; private int currentShapeIndex = 0; // Change the currentShapeIndex whenever the shape is clicked @Override public boolean onTouchEvent(MotionEvent event) {   boolean result = super.onTouchEvent(event);   if (event.getAction() == MotionEvent.ACTION_DOWN) {     currentShapeIndex ++;     if (currentShapeIndex > (shapeValues.length - 1)) {currentShapeIndex = 0;     }     postInvalidate();     return true;   }   return result; }}

 

Now, no matter when the view is clicked, the subscript of the selected shape changes. After the postInvalisate () method is called, a different shape is displayed. Next, update onDraw () method to implement the logic of changing the shape:
public class ShapeSelectorView extends View { // ... @Override protected void onDraw(Canvas canvas) {   super.onDraw(canvas);   String shapeSelected = shapeValues[currentShapeIndex];   if (shapeSelected.equals("square")) {     canvas.drawRect(0, 0, shapeWidth, shapeHeight, paintShape);     textXOffset = 0;   } else if (shapeSelected.equals("circle")) {     canvas.drawCircle(shapeWidth / 2, shapeHeight / 2, shapeWidth / 2, paintShape);     textXOffset = 12;   } else if (shapeSelected.equals("triangle")) {     canvas.drawPath(getTrianglePath(), paintShape);     textXOffset = 0;   }   if (displayShapeName) {     canvas.drawText(shapeSelected, 0 + textXOffset, shapeHeight + textYOffset, paintShape);   } } protected Path getTrianglePath() {   Point p1 = new Point(0, shapeHeight), p2 = null, p3 = null;   p2 = new Point(p1.x + shapeWidth, p1.y);   p3 = new Point(p1.x + (shapeWidth / 2), p1.y - shapeHeight);   Path path = new Path();   path.moveTo(p1.x, p1.y);   path.lineTo(p2.x, p2.y);   path.lineTo(p3.x, p3.y);   return path; } // ...}

 

Now, each click will display a different shape. The result is as follows: Next, add a method to obtain the shape:
public class ShapeSelectorView extends View { // ... // Returns selected shape name public String getSelectedShape() {   return shapeValues[currentShapeIndex]; }}

 

Save the view status when the configuration changes (such as screen rotation) The view needs to save their status. You can implement the onSaveInstanceState () and onRestoreInstanceState () Methods to save and restore the view status, as follows:
public class ShapeSelectorView extends View { // This is the view state for this shape selector private int currentShapeIndex = 0; @Override public Parcelable onSaveInstanceState() {   // Construct bundle   Bundle bundle = new Bundle();   // Store base view state   bundle.putParcelable("instanceState", super.onSaveInstanceState());   // Save our custom view state to bundle   bundle.putInt("currentShapeIndex", this.currentShapeIndex);   // ... store any other custom state here ...   // Return the bundle   return bundle; } @Override public void onRestoreInstanceState(Parcelable state) {   // Checks if the state is the bundle we saved   if (state instanceof Bundle) {     Bundle bundle = (Bundle) state;     // Load back our custom view state     this.currentShapeIndex = bundle.getInt("currentShapeIndex");     // ... load any other custom state here ...     // Load base view state back     state = bundle.getParcelable("instanceState");   }   // Pass base view state on to super   super.onRestoreInstanceState(state); }}

 

Once you implement these save and restore logics, your view will be automatically saved when the phone configuration changes.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.