rendering

preface

Recently, I was working on a radio project and needed to draw an FM scale. At the beginning, I considered the existing open source libraries, but later found that none of them satisfied the requirements of UI brother, so I decided to draw one myself. The Demo effect is shown above. It mainly contains large, medium and small scale lines of three lengths, part of the scale integer value and an indicator. This is the perfect implementation of an FM scale. Here is a general introduction to the specific approach. Those of you who just want to see the code can go straight to Github

Began to draw

I implement the custom View by inheriting the View and overriding the related classes. The most important thing is to implement three related methods:

  • OnMeasure () : Measures how much space the View needs
  • OnDraw () : Draw various shapes
  • OnTouchEvent () : Handling of touch events

Override onMeasure ()

Overrides onMeasure() and calls its parent onMeasure() :

  • The RulerView’s layout_width and layout_height properties match_parent or wrap_content display size is determined by the parent container control.
  • RulerView is set to a fixed value and is displayed as that value.
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(setMeasureWidth(widthMeasureSpec), setMeasureHeight(heightMeasureSpec));
    }

    private int setMeasureHeight(int spec) {
        int mode = MeasureSpec.getMode(spec);
        int size = MeasureSpec.getSize(spec);
        int result = Integer.MAX_VALUE;
        switch (mode) {
            case MeasureSpec.AT_MOST:
                size = Math.min(result, size);
                break;
            case MeasureSpec.EXACTLY:
                break;
            default:
                size = result;
                break;
        }
        return size;
    }

    private int setMeasureWidth(int spec) {
        int mode = MeasureSpec.getMode(spec);
        int size = MeasureSpec.getSize(spec);
        int result = Integer.MAX_VALUE;
        switch (mode) {
            case MeasureSpec.AT_MOST:
                size = Math.min(result, size);
                break;
            case MeasureSpec.EXACTLY:
                break;
            default:
                size = result;
                break;
        }
        return size;
    }
Copy the code

instructions

MeasureSpec. GetSize () resolves the MeasureSpec value to the parent container width or height.

MeasureSpec. GetMode () will get three type int values are: MeasureSpec. EXACTLY MeasureSpec. AT_MOST, MeasureSpec. UNSPECIFIED.

  • MeasureSpec.UNSPECIFIED, so you can set it to any size.
  • MeasureSpec.AT_MOST:RulerView can be any size, but there is an upper limit.
  • MeasureSpec.EXACTLY: The parent container determines a size for MeasureExampleView, and MeasureExampleView size must be within the limits of the parent container.

Rewrite the ontouch ()

First we need to initialize the brush

private Paint mLinePaint;// scale brush
private Paint mTextPaint;// indicates the number brush
private Paint mRulerPaint;// indicate the line brush

private void init(a) {
        mLinePaint = new Paint();
        mLinePaint.setColor(getResources().getColor(R.color.grey));
        / / anti-aliasing
        mLinePaint.setAntiAlias(true);
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(1);

        mTextPaint = new Paint();
        mTextPaint.setColor(getResources().getColor(R.color.grey));
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setStrokeWidth(2);
        mTextPaint.setTextSize(24);

        mRulerPaint = new Paint();
        mRulerPaint.setAntiAlias(true);
        mRulerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mRulerPaint.setColor(getResources().getColor(R.color.ruler_line));
        mRulerPaint.setStrokeWidth(3);
    }
Copy the code

Start drawing:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        // Draw the scale line
        for (int i = min; i <= max; i++) {
            if (i % 10= =0) {
                canvas.drawLine(20.0.20.140, mLinePaint);

                String text = i / 10 + "";
                Rect rect = new Rect();
                float txtWidth = mTextPaint.measureText(text);
                mTextPaint.getTextBounds(text, 0, text.length(), rect);
                if (i / 10 % 2= =1 && i / 10! =107) {
                    canvas.drawText(text, 20 - txtWidth / 2.72 + rect.height() + 74, mTextPaint);
                }
                if (i / 10= =108) {
                    canvas.drawText(text, 20 - txtWidth / 2.72 + rect.height() + 74, mTextPaint); }}else if (i % 5= =0) {
                canvas.drawLine(20.30.20.110, mLinePaint);
            } else {
                canvas.drawLine(20.54.20.86, mLinePaint);
            }
            canvas.translate((float) 8.0);
        }
        canvas.restore();

        // Draw an indicator line
        canvas.drawLine(position, 0, position, 140, mRulerPaint);
        mTextPaint.setTextSize(24);
    }
Copy the code

The code above draws three scale lines, scale numbers, and indicator lines of different lengths. In this way we completed the scale drawing. But having a scale isn’t enough, we also need to rewrite onTouchEvent to respond to click and swipe events. If we need to get the values of the scale when sliding, we also need to define the corresponding pair of listening interfaces.

Rewrite the onTouchEvent ()

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                float x = event.getX();
                if (x < MIN_POSITION) {
                    setPosition(MIN_POSITION);
                } else if (x > MAX_POSITION) {
                    setPosition(MAX_POSITION);
                } else {
                    setPosition((int) x);
                }
                // Move the bar
                if(mMove ! =null) {
                    mMove.onMove(Double.parseDouble(String.format("%.1f", getFmChannel())));
                }
                Log.d("TAG"."position:" + position);
                Log.d("TAG"."channel:" + getFmChannel());
            case MotionEvent.ACTION_CANCEL:
                // Stop only at 0.1(scale)
                setFmChanel(Double.parseDouble(String.format("%.1f", getFmChannel())));
                Log.d("After we stop"."channel:" + Double.parseDouble(String.format("%.1f", getFmChannel())));
                break;
            default:}return true;
    }
    
    public void setPosition(int i) {
        position = i;
        invalidate();
    }

    public void setFmChanel(double fmChanel) {
        int temp = (int) ((fmChanel - 87) * 80) + 20;
        setPosition(temp);
    }

    public double getFmChannel(a) {
        return ((position - 20.0) / 80.0 + 87.0);
    }

Copy the code

So our scale is a sliding indicator scale. I had a problem using the scale in ViewPager: I couldn’t slide the scale smoothly. This is due to a collision with the parent control’s sliding event, which can be resolved by overwriting the dispatchTouchEvent method as follows:

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // Resolve slider conflicts between scale and viewPager
        // When sliding the scale, tell the parent control not to intercept the event and hand it to the child view
        getParent().requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }
Copy the code

If we need to monitor the value of the scale slide in real time, we need to set the corresponding monitoring interface. The code is as follows:

  /** * define the listening interface */
    public interface OnMoveActionListener {
        void onMove(double x);
    }

    /** * Sets a listener */ for each interface
    public void setOnMoveActionListener(OnMoveActionListener move) {
        mMove = move;
    }
Copy the code

The result is a scale that slides indicators, listens to scale values in real time, and jumps to specific values.

conclusion

The realization of the whole scale mainly includes the scale line related elements drawing and sliding event processing. Scale line drawing looks troublesome, in fact, as long as you clear your mind, the corresponding position of the corresponding length of the line can be drawn. The scalability of the scale mentioned this time is poor, the students who need it can be modified on the basis of the use.

Github:github.com/gs666/Ruler… Welcome to issue and star ~

Nuggets homepage: juejin.cn/user/284079… Welcome to pay attention ~