Custom View & Custom Properties

Keywords: Mobile Android Attribute xml encoding

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);
                    }
                });
            }
        }
        
        ​

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);
        
        }

 

 

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();
            }
        }
    }
     

 

 

 

 

 

 

Posted by andygrant on Sat, 19 Jan 2019 08:42:13 -0800