Android Custom View: Imitate Lofter Picture Loading (1)

Keywords: Android network xml github

Preface

Recently, the company has given a requirement to show the loading progress when the picture is loaded. As it happens, I usually prefer to browse some pictures with Lofter, so I have an idea, just do a control that imitates Lofter picture loading.

Take a look at the rendering first.


Overall effect of Lofter Image View

Perhaps the speed of the above network is too fast to see any effect. The following chart should be able to see clearly.



LofterImageView Control Effect

Analysis

Let's not say anything. First look at the screenshots of Lofter loaded images and carefully analyze what we should do if we do it.



Lofter Load Image Analysis.png
  • First show the loading progress box
  • Then the image is loaded to support zooming.
  • Support multi-graph sliding Preview

There are only three steps, that's all; this article will first analyze the implementation of the load schedule box, that is, to write the Lofter ProgressView control.

Custom ProgressBar

The old rule, first take a closer look at Lofter's Progress Bar, pictured above


Lofter's Progress Bar Analysis

The ProgressBar control consists of three parts

  • Background: A rounded rectangle
  • Progress bar: an arc
  • Percentage: Pure text, nothing special

So, let's do it step by step.

First, in order to unify the code, two private methods (obtaining custom configuration initAttrs() and initializing brush initProSettings()) are defined and executed in the constructor.

 public LofterProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //Get the custom configuration first
        initAttrs(context, attrs);
        //Re-initialization related configuration
        initProSettings();
    }
Step 1 Draws Rounded Rectangular Background

Background Here I define three configurations, width, color, and radius of rounded corners.

mBgWidth = typedArray.getDimensionPixelSize(R.styleable.ProgressView_bgWidth, 100);
mBgColor = typedArray.getColor(R.styleable.ProgressView_bgColor, Color.WHITE);
mBgCornerRadius = typedArray.getDimensionPixelSize(R.styleable.ProgressView_bgCornerRadius, 20);

Initialization brushes are nothing special, just color and fill modes.

mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setColor(mBgColor);
mBgPaint.setStyle(Paint.Style.FILL);

The next step is to draw a rectangle, but the premise is to know exactly where the rectangle is to be drawn, that is to say, to know the location of coordinates, it is not recommended to create objects in onDraw() and onMeasure(), so we create mBgRect member variables in LofterProgressView control, and assign this to mBgRect in initProSettings() method.

mBgRect = new RectF();

In onMeasure(), we just set the coordinates of the rectangle.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //Location of the Center
    centerX = getMeasuredWidth() / 2;
    centerY = getMeasuredHeight() / 2;

    //Calculate and set the position of bg rounded rectangle
    mBgRect.set(centerX - (mBgWidth / 2), centerY - (mBgWidth / 2), centerX + (mBgWidth / 2), centerY + (mBgWidth / 2));
    ...
}

Finally, drawing RoundRect () is used in onDraw(), and the rectangle coordinates, the radius of the xy axis of the rounded corner and the brush are passed in to draw the rounded rectangle background.

//Draw background, rounded rectangle
canvas.drawRoundRect(mBgRect, mBgCornerRadius, mBgCornerRadius, mBgPaint);

The background effect map comes out.



LofterProgressView_bg.png
Step 2 Draws Progress Bar Arc

Look at the figure below. There are two steps in drawing an arc:

  • Ring Background (static, zero progress status display)
  • Progress Circle (Dynamics)



    Arc Drawing Analysis of Progress Bar

Here I define four configuration items, radius, static ring color, progress bar arc color and width of progress bar arc.

mInnerRadius = typedArray.getDimensionPixelSize(R.styleable.ProgressView_innerRadius, 50);
mEdgeColor = typedArray.getColor(R.styleable.ProgressView_edgeColor, Color.RED);
mRingColor = typedArray.getColor(R.styleable.ProgressView_ringColor, Color.BLUE);
mRingWidth = typedArray.getDimensionPixelSize(R.styleable.ProgressView_ringWidth, 10);

Initialization Brush

//Static Ring Background Brush
mEdgePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mEdgePaint.setColor(mEdgeColor);
mEdgePaint.setStrokeCap(Paint.Cap.ROUND);
mEdgePaint.setStrokeWidth(mRingWidth);
mEdgePaint.setStyle(Paint.Style.STROKE);
//Dynamic Progress Bar Arc Brush
mPercentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPercentPaint.setColor(mPercentColor);
mPercentPaint.setStyle(Paint.Style.FILL);
mPercentPaint.setTextSize(mPercentSize);

Let's draw a static ring first. It's relatively simple.

 //Draw a static circle, which corresponds to the display when the progress is zero.
canvas.drawCircle(centerX, centerY, mInnerRadius, mEdgePaint);

Second, we need to draw progress arc. To draw an arc, we need to use its corresponding tangent rectangle, which is exactly the rectangle enclosing the circle where the arc is located. In the code, RectF is the class. Therefore, we need to determine the coordinates of the rectangle in onMeasure().

//A rectangle with an arc circumscribed (in)
private RectF mOval;

/**
 * Initialization configuration
 */
private void initProSettings(){
    ...
    mOval = new RectF();
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    centerX = getMeasuredWidth() / 2;
    centerY = getMeasuredHeight() / 2;

    ...
    //Calculate the position of the tangent rectangle
    mOval.left = (centerX - mInnerRadius);
    mOval.top = (centerY - mInnerRadius);
    mOval.right = centerX + mInnerRadius;
    mOval.bottom = centerY + mInnerRadius;
}

The tangential rectangle only determines the circle in which the arc is located, but how much the arc should be drawn (or how many parts of the whole circle the arc should occupy). Here we need to calculate the sweeping angle of the arc.


/**
 * mPercent Download progress
 * Round, 360 degrees a week
 * Here the angle swept by the arc must be multiplied by 360.
 */
progress = (float) mPercent / 100 * 360;

The final step is to draw the progress.

//Draw progress bar circles
//The second parameter refers to the starting point of the arc, which is zero in the direction of 3 o'clock, so let's write - 90 here.
//The third parameter, true, means to connect the arc to the center of the circle, that is, to draw a pie in a sector, where we don't need to set it to false.
canvas.drawArc(mOval, -90, progress, false, mRingPaint);

The above rendering (I changed the color of the progress bar for more visibility)


Arc effect
Step 3 Draws Percentage Text

The text defines two configuration items, color and size.

mPercentColor = typedArray.getColor(R.styleable.ProgressView_percentColor, Color.GRAY);
mPercentSize = typedArray.getDimensionPixelSize(R.styleable.ProgressView_percentSize, 30);

Initialization Brush

//Brushes in fonts
mPercentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPercentPaint.setColor(mPercentColor);
mPercentPaint.setStyle(Paint.Style.FILL);
mPercentPaint.setTextSize(mPercentSize);

//Calculate the height of the font
Paint.FontMetrics fm = mPercentPaint.getFontMetrics();
mTextHeight = (int) Math.ceil(fm.descent - fm.ascent);

Draw fonts (first adjust the position of the text so that the text is in the middle)

//Calculate font width
mTextWidth = mPercentPaint.measureText(percentText, 0, percentText.length());
 //Percentage of drawing
canvas.drawText(percentText, centerX - mTextWidth / 2, centerY + mTextHeight / 4, mPercentPaint);

Finally, that's the whole picture.


LofterProgressView control
Step 4 exposes the interface to the outside world

Just pay attention to redrawing here.

/**
 * Set percentages directly from the outside
 *
 * @param percent Percentage
 */
 public void setPercent(int percent) {
     this.mPercent = percent;
     //Repaint
     postInvalidate();
 }
Finally, simulate the progress display

xml configuration

<chengang.library.widget.LofterProgressView
    android:id="@+id/pv2"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_centerInParent="true"
    android:visibility="visible"
    app:bgColor="@color/white"
    app:bgCornerRadius="12dp"
    app:bgWidth="70dp"
    app:edgeColor="@color/default_edge_color"
    app:innerColor="@color/white"
    app:innerRadius="17dp"
    app:percentColor="@color/gray"
    app:percentSize="11sp"
    app:ringColor="@color/default_ring_color"
    app:ringWidth="2dp" />

Simulated download of pictures

btnStart.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 101; i++) {
                    try {
                        lofterProgressView.setPercent(i);
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
});

Final custom rogressBar (Lofter ProgressView) completion


Final custom ProgressBar

Attached project address

Thank you for reading.

In the next article, I'll combine Glide with the Image ProgressView in this article to make an in-progress image preview control.

Posted by bullbreed on Sat, 08 Jun 2019 12:51:21 -0700