03 Custom View Directory
-
Three categories: 3.5 categories:
- 1. Inherit from the original control
-
2. Combination View
- 2.1 Customize the custom attributes of VIew.
-
3. Self-drawing control inheriting View
- 3.1 View
- 3.2 ViewGroup
-
The three most important methods of customization are:
- onDraw , onLayout, onMeasure
- Drawing, typesetting sub-layout, measuring the width of custom View
-
Notes: {
- 1. inflate(context, layout, this);
- 2. ObtainStyle.
- 3. Outer Layer and Inner Layer}
java.util.concurrent.TimeoutException: Cannot get spooler! [Exception]: You can no longer update the UI in the main thread using invalidate method, that is, redraw.
3.2 Composite View's custom attributes
-
Code aspect:
-
Step1: Define an attr.xml in values.
- Declare-style leable represents a set of attributes. Name is named as View class name _Style.
-
attr is each attribute, name is the name referenced in the xml layout, format is the attribute type, there are 10 attribute types. Reference is a reference, that is, a picture reference.
- Step1.2: When referencing in the layout file, first define a nameSpace, app, from res followed by res-auto, automatically find
-
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Define a set of attributes --> <declare-styleable name="AddDecreaseView_Style"> <attr name="middle_text_color" format="color"></attr> <attr name="left_image_src" format="reference"></attr> </declare-styleable> </resources>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.bwie.juan_mao.selfview02.view.AddDecreaseView android:id="@+id/adv" android:layout_width="match_parent" android:layout_height="wrap_content" app:left_image_src="@mipmap/ic_launcher" app:middle_text_color="#ff0000" /> </LinearLayout>
-
Step2: Use context.obtainStyledAttributes when initView, introduce a custom attribute group, return a TypedArray, type array
- a.getColor(Styleable, int); just pass in the corresponding value.
- Of course, resources should be released when they are set up.
- And add the corresponding properties set to View.
-
public class AddDecreaseView extends RelativeLayout { private ImageView btnDecrease; private ImageView btnAdd; private TextView txtNum; // 1. Provide an interface public interface OnAdvClickListener { void add(int num); void decrease(int num); } // 2. Provide an interface object private OnAdvClickListener listener; public void setOnAdvClickListener(OnAdvClickListener listener) { this.listener = listener; } public AddDecreaseView(Context context) { this(context, null); } public AddDecreaseView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AddDecreaseView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { // The obtainStyledAttributes method returns an array of one type // the return value is an array of one type. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AddDecreaseView_Style); // Custom declare-style leable name // Name of parent + underscore + name of child int color = a.getColor(R.styleable.AddDecreaseView_Style_middle_text_color, Color.BLACK); // Set a color, getColor int leftImage = a.getResourceId(R.styleable.AddDecreaseView_Style_left_image_src, R.drawable.img_decrease); // Set the image resourceId, // Release resources after use // Release reusable data after use a.recycle(); // The last parameter when introducing a resource file is this View.inflate(context, R.layout.item_add_decrease, this); btnDecrease = findViewById(R.id.btn_decrease); btnAdd = findViewById(R.id.btn_add); txtNum = findViewById(R.id.txt_num); txtNum.setTextColor(color); // Remember to set the set Color or something to the control btnDecrease.setImageResource(leftImage); btnAdd.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { String s = txtNum.getText().toString(); int num = Integer.parseInt(s); num++; txtNum.setText(num + ""); // Callback plus sign method listener.add(num); } }); btnDecrease.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { String s = txtNum.getText().toString(); int num = Integer.parseInt(s); if (num > 0) { num--; } txtNum.setText(num + ""); listener.decrease(num); } }); } }
-
Step1: Define an attr.xml in values.
3.3 Draw a View
- Painting is done with paints and canvas
-
To rewrite the onDraw method
-
In canvas, drawCircle (circle), drawRect (rectangle), drawLine (drawing line) can be drawn.
- DraOval (drawing ellipse), DraAct (sector or arc), DraPath (drawing path), DraText (drawing text), DraBitmap (drawing picture)
- DrawColor
-
// Method of Rewriting Drawing Graphics // Canvas Canvas @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // new comes out with a paint brush Paint paint = new Paint(); paint.setColor(Color.RED); // Anti-aliasing. true's edge is smooth and not rough. paint.setAntiAlias(true); // Setting Drawing Style // The units in the code are all px pixels paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(10); paint.setStyle(Paint.Style.FILL); // Three attributes of style in paint, representing line drawing / filling / filling + adding paint.setStyle(Paint.Style.FILL_AND_STROKE); // Draw a circle, x, y, radius, brush canvas.drawCircle(100, 100, 100, paint); // Draw a rectangle, left, top, right and bottom canvas.drawRect(0, 200, 200, 400, paint); // Line drawing, starting point x, y, ending point x, y, brush canvas.drawLine(0, 0, 200, 200, paint); // Draw ellipse, left, top, right, bottom, brush canvas.drawOval(0, 0, 400, 200, paint); // Reset the brush, reset the original set properties do not take effect, it is equivalent to a new brush. paint.reset(); paint.setColor(Color.GREEN); // Draw a fan or arc. The upper left and lower right are the circle's range. Start Angle starts with the right margin of the circle. SwepAngle is the scanned angle, clockwise. // Use Center for true is to use the space inside the circle. When false, the triangle between the center and radius is removed. canvas.drawArc(0, 0, 200, 200, 180, 180, false, paint); paint.setColor(Color.GRAY); canvas.drawRect(0, 100, 200, 300, paint); canvas.drawRect(0, 0, 200, 200, paint); paint.setColor(Color.GREEN); // Drawing Path Path path = new Path(); path.moveTo(0, 0); path.lineTo(100, 100); path.lineTo(200, 100); path.addArc(0, 0, 200, 200, 0, 90); path.lineTo(100, 200); path.lineTo(0, 200); path.lineTo(0, 0); canvas.drawPath(path, paint); canvas.drawRect(0, 0, 100, 100, paint); paint.setTextSize(30); String text = "hello world"; canvas.drawText(text, 0, 100, paint); // Drawing text, the first parameter is the text to be drawn, the index at the beginning of the start, the index at the end, the index at the end, the index at the beginning, the index at the end, the index at the end, the index at the beginning, the index at the end, and the coordinates at the beginning of the x and y axes. // Coordinates starting at the lower left corner of the text canvas.drawText("hello world", 0, 3, 0, 100, paint); Rect bounds = new Rect(); paint.getTextBounds(text, 0, text.length(), bounds); // Draw a text border and set the text bounds of the brush to Rect canvas.drawRect(bounds, paint); // What is drawn is the border of a text. bounds.width(); // Width and height of text bounds.height(); canvas.drawColor(Color.RED); // Background color of canvas canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher), 0, 0, paint); // Draw a picture canvas.drawRect(0, 0, 290, 290, paint); }
-
In canvas, drawCircle (circle), drawRect (rectangle), drawLine (drawing line) can be drawn.
3.4 Measure the width of the custom View (understand its measurement mode)
- Override the onMeasure method,
-
Code aspect:
- When the width or height of the layout is match_parent or fixed value, his Mode mode Mode is EXACTLY, representing accuracy.
- And wrap_content, Mode mode Mode is AT_MOST, representing the maximum value of the control.
-
/** * Measuring the Width and Height of Custom View * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // Width measurement mode int widthMode = MeasureSpec.getMode(widthMeasureSpec); // Measured width int widthSize = MeasureSpec.getSize(widthMeasureSpec); //Height measurement mode int heightMode = MeasureSpec.getMode(heightMeasureSpec); // Height measured int heightSize = MeasureSpec.getSize(heightMeasureSpec); // Setting the width and height of the final measurement to 99% is the way to set the width and height. setMeasuredDimension(widthSize / 2, heightSize / 2); switch (widthMode) { case MeasureSpec.EXACTLY: Log.i(TAG, "onMeasure: " + "The current measurement mode is the exact value."); break; case MeasureSpec.AT_MOST: Log.i(TAG, "onMeasure: " + "The current measurement mode is the maximum"); break; // Usually not needed case MeasureSpec.UNSPECIFIED: Log.i(TAG, "onMeasure: " + "There's nothing special about it right now."); break; } Log.i(TAG, "onMeasure: The size of the measurement is" + widthSize); // Log.i(TAG, "onMeasure: " + MeasureSpec.EXACTLY); // Log.i(TAG, "onMeasure: " + MeasureSpec.AT_MOST); // Log.i(TAG, "onMeasure: " + MeasureSpec.UNSPECIFIED); // EXACTLY: 1073741824 - Precise value // AT_MOST: -2147483648 - Maximum // UNSPECIFIED: 0 // match_parent // widthMode: 1073741824---------EXACTLY // widthSize: 720 // wrap_content // widthMode: -2147483648----------AT_MOST // SelfView: widthSize: 720 // 200dp // widthMode: 1073741824-----------EXACTLY // widthSize: 300 Log.d(TAG, "widthMode: " + widthMode); Log.d(TAG, "widthSize: " + widthSize); // setMeasuredDimension(200, 1920); }
3.5 How to customize a timer
-
Define a MyTextView, inherit TextView
- Code aspect:
-
public class MyTextView extends TextView { public int num = 1; private Paint paint; private boolean isStart = true; public MyTextView(Context context) { this(context, null); } public MyTextView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { paint = new Paint(); paint.setColor(Color.RED); paint.setAntiAlias(true); paint.setTextSize(100); } private Canvas canvas; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); this.canvas = canvas; canvas.drawText(num + "", 300, 300, paint); } public void add() { num++; // draw(canvas); // Every time invalidate is called, the onDraw method is called again, that is, redrawing. invalidate(); // Running in the main thread UI thread // Inside, another Handler is created, which makes it inefficient. postInvalidate(); } public void start() { isStart = true; new Thread(new Runnable() { @Override public void run() { while (isStart) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num++; // Unable to use invalidate to update UI in sub-threads // invalidate(); // Subthreads need to use postInvalidate when they call onDraw method again postInvalidate(); } } }).start(); } public void stop() { isStart = false; } }
3.6 Rewrite onLayout, inherit ViewGroup
3.6 Ladder Layout
- Inherit ViewGroup and override onMeasure and onLayout methods
- Code aspect:
-
public class LadderView extends ViewGroup { private static final String TAG = "LadderView"; public LadderView(Context context) { this(context, null); } public LadderView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LadderView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // The onDraw method can be invoked in ViewGroup, which is generally not used in ViewGroup. /*@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setColor(Color.RED); paint.setAntiAlias(true); canvas.drawCircle(100, 100, 100, paint); }*/ /** * onMeasure can be invoked when inheriting from ViewGroup and is very important * The width of the ViewGroup is measured. * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); } // Inheritance of methods that must be rewritten from ViewGroup // Layout method @Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) { // Get data for child controls int count = getChildCount(); Log.i(TAG, "count: " + count); measureChildren(0, 0); /** * portrait */ /*int sumHeight = 0; // Loop out each child control for (int j = 0; j < count; j++) { View view = getChildAt(j); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); Log.i(TAG, "The width of the'+i+'view is:'+width'; Log.i(TAG, "The height of the'+i +'view is'+height'; // Upper left and lower right view.layout(0, sumHeight, view.getMeasuredWidth(), sumHeight + view.getMeasuredHeight()); sumHeight += view.getMeasuredHeight(); }*/ /** * transverse */ /*int sumWidth = 0; for (int j = 0; j < count; j++) { View view = getChildAt(j); view.layout(sumWidth, 0, sumWidth + getMeasuredWidth(), view.getMeasuredWidth()); sumWidth = sumWidth + view.getMeasuredWidth(); }*/ /** * Trapezoidal layout */ int sumWidth = 0; int sumHeight = 0; for (int j = 0; j < count; j++) { View view = getChildAt(j); view.layout(sumWidth, sumHeight, sumWidth + view.getMeasuredWidth(), sumHeight + view.getMeasuredHeight()); sumWidth += view.getMeasuredWidth(); sumHeight += view.getMeasuredHeight(); } } }