Android Getting Started tutorial | custom View

Introduction and use of SurfaceView

SurfaceView is a special view in Android system. It has a separate drawing surface, that is, it does not share the same drawing surface with the host window.

Because it has an independent drawing surface, the UI of SurfaceView can be drawn in an independent thread. Because it will not occupy the resources of the main thread, the use of SurfaceView can realize a complex and efficient UI. On the other hand, it will not lead to the lack of timely response to user input. It is more suitable for video playback, picture browsing and games with high picture requirements.

The SurfaceView is quite inside the screen, and the screen opens a hole for it. You can cover it by setting the background color.

One of the reasons for using SurfaceView is the ability to update images in child threads. Reduce the pressure on UI threads.
All methods of SurfaceView and SurfaceHolder.Callback should be called in the UI thread, which is generally the main thread of the application. The various variables to be accessed by the rendering thread should be synchronized. To ensure that the drawing thread only draws while the surface is available.
SurfaceView is mainly controlled by surface holder, which is equivalent to a controller.

Core points
  • View: the screen must be updated in the main thread of the UI, which is used to passively update the screen.
  • SurfaceView: both UI threads and child threads can be. Redraws the picture in a newly started thread and actively updates the picture.

    java.lang.Object
    android.view.View
    android.view.SurfaceView

usage method

Create a class that inherits SurfaceView and implements the SurfaceHolder.Callback interface.

public class MySView extends SurfaceView implements SurfaceHolder.Callback {
    .........
}
Get SurfaceHolder

Get the SurfaceHolder in the constructor. And add a callback.

public MySView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    holder = getHolder();
    holder.addCallback(this);
    // ......
}
Replication method
@Override
public void surfaceCreated(SurfaceHolder holder) {
    Log.d(TAG, "surfaceCreated");
    drawThread = new DrawThread(holder);// Create a drawing thread
    drawThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    Log.d(TAG, "surfaceChanged");
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    drawThread.closeThread();// Destroy thread
    Log.d(TAG, "surfaceDestroyed");
}

When the Activity is not visible, the SurfaceView calls the surfacetested method. The drawing thread is destroyed.

Drawing sub thread

The drawing operation in SurfaceView is carried out in a child thread. Normally, child threads cannot operate the UI. However, we can use the method provided by SurfaceHolder to lock the canvas, release and update the canvas after drawing. The following threads provide stop, resume, and end functions.

class DrawThread extends Thread {

    private SurfaceHolder mmHolder;
    private boolean mmRunning;
    private boolean mmIsPause;

    public DrawThread(SurfaceHolder holder) {
        this.mmHolder = holder;
        mmRunning = true;
    }

    @Override
    public void run() {
        while (mmRunning && !isInterrupted()) {
            if (!mmIsPause) {
                Canvas canvas = null;
                try {
                    synchronized (mmHolder) {
                        canvas = holder.lockCanvas();        // Lock the Canvas to obtain the returned Canvas object Canvas
                        canvas.drawColor(bgSurfaceViewColor);// Set canvas background color
                        // Drawing operations
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (canvas != null) {
                        mmHolder.unlockCanvasAndPost(canvas);// Release the canvas and commit the changes.
                    }
                    pauseThread();
                }
            } else {
                onThreadWait();
            }
        }
    }

    public synchronized void pauseThread() {
        mmIsPause = true;
    }

    /**
 * The thread waits and is not provided to external calls
 */
    private void onThreadWait() {
        try {
            synchronized (this) {
                this.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void resumeThread() {
        mmIsPause = false;
        this.notify();
    }

    public synchronized void closeThread() {
        try {
            mmRunning = false;
            notify();
            interrupt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Used in layout

Remember not to set the background in the layout. The color will directly block the SurfaceView.

<com.rustfisher.fisherandroidchart.MySView
    android:id="@+id/mySurfaceView"
    android:layout_width="match_parent"
    android:layout_height="230dp" />

Android automatically scales the line chart of the upper and lower limits

A polyline that automatically scales the upper and lower limits according to the maximum and minimum values.

  • Inherit View
  • Data is stored using FloatBuffer
  • You can change the size of the display window
  • Axis, polyline and font color can be specified

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.TypedValue;
    import android.view.View;
    
    import java.nio.FloatBuffer;
    
    public class AutoLineChart extends View {
    
      private static final String TAG = "rustApp" + AutoLineChart.class.getSimpleName();
    
      private float yMax = 1.6f;
      private float yMin = -1.0f;
    
      float yAxisZoomLimitMax = 1.6f;
      float yAxisZoomLimitMin = -1.0f; // Reduce the limit value of the y-axis
    
      // Increase / decrease distance when Y axis is automatically scaled
      float axisYPerStep = 0.1f;
    
      // The space between chart lines at the top of the view
      float viewYStart = 2;
      float axisTextSize = 10;
    
      private int onShowPointsCount = 500;  // Number of data currently displayed
      int onShowMinPoints = 100;            // At least the number of data to display
      private int maxPoint = 9000;          // Maximum number of data stores
    
      // Axis line width
      float axisLineWid = 1f;
    
      int dataLineWid = 4;
    
      // Data line color
      private int dataColor = Color.parseColor("#eaffe9");
    
      // Background line color in chart
      private int mainBgLineColor = Color.parseColor("#535353");
    
      // Axis color
      private int axisColor = Color.WHITE;
    
      // Coordinate value font color
      private int axisTextColor = Color.WHITE;
    
      // Background color
      private int viewBgColor = Color.parseColor("#222222");
    
      Rect rectText = new Rect();
    
      private float xStep = 1.0f;
      private float viewWidth;
      private float viewHeight;
      private float botLeftXOnView = 0; // x coordinate of the lower left point of the chart in view
      private float botLeftYOnView = 0;
      private float originYToBottom = 20; // The distance from the chart origin to the bottom of the view
    
      private FloatBuffer dataBuffer;
    
      private Paint bgPaint;
      private Paint linePaint;
    
      public AutoLineChart(Context context) {
          this(context, null);
      }
    
      public AutoLineChart(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
    
      public AutoLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(context);
      }
    
      public int getMaxPoint() {
          return maxPoint;
      }
    
      public void setOnShowPointsCount(int onShowPointsCount) {
          this.onShowPointsCount = onShowPointsCount;
      }
    
      public int getOnShowPointsCount() {
          return onShowPointsCount;
      }
    
      public int getOnShowMinPoints() {
          return onShowMinPoints;
      }
    
      public void addData(float data) {
          dataBuffer.put(data);
          if (dataBuffer.position() > (dataBuffer.capacity() * 2 / 3)) {
              float[] bufferArr = dataBuffer.array();
              System.arraycopy(bufferArr, dataBuffer.position() - maxPoint, bufferArr, 0, maxPoint);
              dataBuffer.position(maxPoint);
    //            Log.d(TAG, "move current data to buffer start position" + dataBuffer);
          }
          invalidate();
      }
    
      private void init(Context context) {
          dataBuffer = FloatBuffer.allocate(3 * maxPoint); // Allocate 3x space
          bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
          bgPaint.setStrokeWidth(axisLineWid);
          bgPaint.setStyle(Paint.Style.STROKE);
          bgPaint.setColor(mainBgLineColor);
          linePaint.setStrokeWidth(dataLineWid);
          linePaint.setStyle(Paint.Style.STROKE);
          linePaint.setColor(dataColor);
    
          botLeftXOnView = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, context.getResources().getDisplayMetrics());
          originYToBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
          viewYStart = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
          axisLineWid = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
          axisTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, context.getResources().getDisplayMetrics());
      }
    
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          super.onSizeChanged(w, h, oldw, oldh);
          viewWidth = getWidth();
          viewHeight = getHeight();
          botLeftYOnView = viewHeight - originYToBottom;
      }
    
      @Override
      protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          canvas.drawColor(viewBgColor);
          xStep = (viewWidth - botLeftXOnView) / (onShowPointsCount - 1);
          float maxData = 0.1f;
          float minData = 0;
    
          int dataStartIndexInBuffer = 0; // Starting subscript of data in buffer
          if (dataBuffer.position() > onShowPointsCount) {
              dataStartIndexInBuffer = dataBuffer.position() - onShowPointsCount;
          }
          float[] bufferArr = dataBuffer.array();
          for (int i = dataStartIndexInBuffer; i < dataBuffer.position(); i++) {
              if (bufferArr[i] < minData) {
                  minData = bufferArr[i];
              } else if (bufferArr[i] > maxData) {
                  maxData = bufferArr[i];
              }
          }
    
          zoomYAxis(maxData, minData);
          drawBgLines(canvas);
          drawWave(canvas, dataStartIndexInBuffer);
      }
    
      // Scale Y axis
      private void zoomYAxis(float maxData, float minData) {
          if (maxData < yAxisZoomLimitMax) {
              yMax = yAxisZoomLimitMax;
          } else if (maxData < yMax) {
              while (maxData < yMax) {
                  yMax -= axisYPerStep;
              }
              yMax += axisYPerStep;
          } else if (maxData > yMax) {
              while (maxData > yMax) {
                  yMax += axisYPerStep;
              }
          }
    
          if (minData > yAxisZoomLimitMin) {
              yMin = yAxisZoomLimitMin;
          } else if (minData > yMin) {
              while (minData > yMin) {
                  yMin += axisYPerStep;
              }
              yMin -= axisYPerStep;
          } else if (minData < yMin) {
              yMin -= axisYPerStep;
          }
      }
    
      private void drawBgLines(Canvas canvas) {
          // Draw coordinate axis
          bgPaint.setStyle(Paint.Style.FILL);
          bgPaint.setStrokeWidth(axisLineWid);
          bgPaint.setTextSize(axisTextSize);
          bgPaint.setTextAlign(Paint.Align.RIGHT);
    
          for (float y = 0; y <= yMax; y += 0.5) {
              drawYAxis(canvas, y);
          }
          for (float y = 0; y >= yMin; y -= 0.5) {
              drawYAxis(canvas, y);
          }
          bgPaint.setColor(axisColor);
          canvas.drawLine(botLeftXOnView, viewYStart / 2, botLeftXOnView, botLeftYOnView + viewYStart / 2, bgPaint);
    //        canvas.drawLine(botLeftXOnView, botLeftYOnView, viewWidth, botLeftYOnView, bgPaint); // x axis
      }
    
      private void drawYAxis(Canvas canvas, float axisYValue) {
          final float yDataRange = yMax - yMin;
          final float yAxisRangeOnView = botLeftYOnView - viewYStart;
          float aY = botLeftYOnView - (axisYValue - yMin) / yDataRange * yAxisRangeOnView;
          bgPaint.setColor(axisColor);
          canvas.drawLine(botLeftXOnView - 20, aY, botLeftXOnView, aY, bgPaint);
    
          String axisText = String.valueOf(axisYValue);
          bgPaint.getTextBounds(axisText, 0, axisText.length(), rectText); // Gets the width and height of the text
          canvas.drawText(axisText, botLeftXOnView - rectText.width() / 2, aY + rectText.height() / 2, bgPaint);
    
          bgPaint.setColor(mainBgLineColor);
          canvas.drawLine(botLeftXOnView, aY, viewWidth, aY, bgPaint);
      }
    
      private void drawWave(Canvas canvas, int dataStartIndexInBuffer) {
          final float yDataRange = yMax - yMin;
          final float yAxisRangeOnView = botLeftYOnView - viewYStart;
          final float yDataStep = yAxisRangeOnView / yDataRange;
    
          float[] dataArr = dataBuffer.array();
          for (int i = dataStartIndexInBuffer; i < dataBuffer.position() - 1; i++) {
              canvas.drawLine(botLeftXOnView + (i - dataStartIndexInBuffer) * xStep, getYL(dataArr[i], yDataStep),
                      botLeftXOnView + (i - dataStartIndexInBuffer + 1) * xStep, getYL(dataArr[i + 1], yDataStep),
                      linePaint);
          }
      }
    
      private float getYL(final float yData, float yDataStep) {
          return botLeftYOnView - (yData - yMin) * yDataStep;
      }
    }

Custom interview questions

1. Tell me about the drawing process of View?

Reference answer:

The workflow of View mainly refers to the three processes of measure, layout and draw, namely measurement, layout and drawing,

  • measure determines the measured width / height of the View;
  • layout determines the final width / height of the View and the positions of the four vertices;
  • draw draws the View on the screen;

The drawing process of View follows the following steps:

  • Draw background   background.draw(canvas)
  • Draw yourself (onDraw)
  • Draw children (dispatchDraw)
  • Draw decoration (ondrawscallbars)

2. What is motionevent? How many events are included? Under what conditions?

Reference answer:

MotionEvent is a series of events generated when a finger touches the screen. Typical event types are as follows:

  • ACTION_DOWN: your finger just touched the screen
  • ACTION_MOVE: move your finger on the screen
  • ACTION_UP: the moment when the finger is released from the screen
  • ACTION_ Cancel: triggered when the finger remains pressed and transfers from the current control to the outer control

Normally, a finger touching the screen will trigger a series of click events. Consider the following situations:

  • Click the screen and release it. The event sequence is: DOWN → UP
  • Click the screen to slide for a while and then release. The event sequence is DOWN → MOVE →..... → MOVE → UP
3. Describe the View event delivery and distribution mechanism?

Reference answer:

The essence of View event distribution is the process of distributing MotionEvent events. That is, when a MotionEvent occurs, the system passes the click event to a specific View

Transmission sequence of click events: Activity (Window) → ViewGroup → View

The event distribution process is completed by three methods:

  • dispatchTouchEvent: used to distribute events. If the event can be passed to the current View, this method must be called. The returned result is affected by the onTouchEvent method of the current View and the dispatchTouchEvent method of the subordinate View, indicating whether to consume the current event
  • onInterceptTouchEvent: call inside the above method to intercept the event. This method is only available in ViewGroup, but not in View (excluding ViewGroup). Once intercepted, the onTouchEvent of the ViewGroup is executed to process the event in the ViewGroup instead of distributing it to the View. It is called only once, and the returned result indicates whether to intercept the current event
  • onTouchEvent: invoked in the dispatchTouchEvent method to handle clicking events and return results to indicate whether the current event is consumed.
4. How to resolve the event conflict of View? An example of development?

Reference answer:

Common event conflicts in development include the sliding conflict between ScrollView and RecyclerView, and the simultaneous sliding of RecyclerView embedded in the same direction

Handling rules for sliding conflicts:

  • For the sliding conflict caused by the inconsistency between the external sliding direction and the internal sliding direction, who can intercept the event can be judged according to the sliding direction.
  • For sliding conflicts caused by the consistency of the external sliding direction and the internal sliding direction, you can specify when to let the external View intercept events and when to let the internal View intercept events according to business requirements.
  • The nesting of the above two cases is relatively complex. You can also find a breakthrough in business according to your needs.

Implementation method of sliding conflict:

  • External interception method: all click events are intercepted by the parent container first. If the parent container needs this event, it will be intercepted, otherwise it will not be intercepted. Specific method: you need to override the onInterceptTouchEvent method of the parent container to intercept it internally.
  • Internal interception method: the parent container does not intercept any events, but passes all events to the child container. If the child container needs this event, it will be consumed directly, otherwise it will be handed over to the parent container for processing. Specific method: it needs to be combined with the requestDisallowInterceptTouchEvent method.
5. What is the difference between scrollto() and scollBy()?

Reference answer:

scrollTo is called internally by scollBy, which is a relative sliding based on the current position; scrollTo is absolute sliding. Therefore, if the scrollTo method is called multiple times with the same input parameters, the initial position of the View is unchanged, so the effect of View scrolling will only occur once
Both can only slide the View content, not the View itself. You can use Scroller to have the effect of excessive sliding

6. How does the scroller realize the elastic sliding of View?

Reference answer:

  • In motionevent.action_ When the up event is triggered, the startScroll() method is called. This method does not perform the actual sliding operation, but records the sliding related quantity (sliding distance and sliding time)
  • Then call the invalidate/postInvalidate() method to request View redrawing, resulting in the execution of the View.draw method
  • When View is redrawn, it will call the computeScroll method in the draw method, and computeScroll will go to Scroller to get the current scrollX and scrollY. Then the sliding is realized by scrollTo method; Then, the postInvalidate method is called for the second redrawing. Like the previous process, this repeatedly causes the view to continuously slide in a small range, and multiple small range slides form an elastic slide until the whole slide is completed

Android zero foundation tutorial video reference

Tags: Android

Posted on Thu, 02 Dec 2021 19:31:51 -0500 by lucy