Android custom View: BounceProgressBar; android custom view

Source: Internet
Author: User

Android custom View: BounceProgressBar; android custom view

Reprinted please indicate the source:

A few days ago, when I downloaded a desktop version of codoy, which has been useless for a long time, I found that the progress bar for loading songs is good (personal feeling), as shown below:

Then, taking advantage of the cold weather over the weekend, I put down a pile of operating system operations in the dormitory. (I want to copy a pile of texts in multiple classes... ah ..) I decided to stick it out, and the final effect was as follows (I always felt a little inharmonious ·):

We can see from the comparison that there are more shapes and images, so the next step is its implementation process.

For more information about the implementation of custom views, see Guo Shen's blog (View Series 4): Android LayoutInflater principle analysis. This will give you a step-by-step insight into View (1) in this article, we will describe how to use the ANDROID custom view-onMeasure and MeasureSpec source code.

Custom Attributes

The attributes of a custom View are generally used. You do not need to override existing controls. Yes. What special attributes do we need for this BounceProgressBar? The first thing to note is that the BounceProgressBar does not provide the implementation of the specific progress. Think about it: it requires the size of each image, calledSingleSrcSizeThe type is dimension.Speed, Type: integer; shape, calledShapeThe type is Enumeration type and provides implementation of these shapes. original, circle, pentagon, fig, and heart are all well-known. Finally, the required image resources are calledSrcType: reference | color, which can be the image or color value in drawable.

With the required properties, create a resource file under the values folder (the name is random, and you can see the name and description) to define these attributes, as shown below,The code may be in English, and the level may be a little scum,However, it is generally explained above:

<? Xml version = "1.0" encoding = "UTF-8"?> <Resources> <declare-styleable name = "BounceProgressBar"> <! -- The single child size --> <attr name = "singleSrcSize" format = "dimension"/> <! -- The bounce animation one-way duration --> <attr name = "speed" format = "integer"/> <! -- The child count, I still want to customize the number, but it is a bit difficult for me to implement it temporarily, so I will not add this --> <! -- <Attr name = "count" format = "integer" min = "1"/> --> <! -- The progress child shape --> <attr name = "shape" format = "enum"> <enum name = "original" value = "0"/> <enum name = "circle "value =" 1 "/> <enum name =" pentagon "value =" 2 "/> <enum name =" Fig "value =" 3 "/> <enum name = "heart" value = "4"/> </attr> <! -- The progress drawable resource --> <attr name = "src" format = "reference | color"> </attr> </declare-styleable> </resources>
Then write the BounceProgressBar class as follows:

public class BounceProgressBar extends View {//...}
Now we can use our BounceProgressBar in the layout. Note that we need to add the namespace in the second line of the code below to use our attributes, you can also put it in the attribute of the root element.

        <org.roc.bounceprogressbar.BounceProgressBar            xmlns:bpb=""            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerHorizontal="true"            android:layout_centerVertical="true"            bpb:shape="circle"            bpb:singleSrcSize="8dp"            bpb:speed="250"            bpb:src="#6495ED" />
The final thing we need to do after customizing the property is to get it in the Code. Where can we get it? Of course it is in the construction method of the BounceProgressBar class. The relevant code is as follows:
public BounceProgressBar(Context context) {this(context, null, 0);}public BounceProgressBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public BounceProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(attrs);}private void init(AttributeSet attrs) {if (null == attrs) {return;}TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BounceProgressBar);speed = a.getInt(R.styleable.BounceProgressBar_speed, 250);size = a.getDimensionPixelSize(R.styleable.BounceProgressBar_singleSrcSize, 50);shape = a.getInt(R.styleable.BounceProgressBar_shape, 0);src = a.getDrawable(R.styleable.BounceProgressBar_src);a.recycle();}
It is relatively simple to get the attribute. Remember to recycle the TypedArray. First, we get the defined TypedArray, and then get the attribute values one by one. Then someone may have said that I did not clearly define R. styleable. bounceProgressBar_xxx. In fact, this is the location where each attribute in the declare-styleable generated by Android is located in the index of the TypedArray. styleable. speed exists. How does it correspond? Click it and check the R file. R. styleable. bounceProgressBar_speed is 1, because speed is 2nd attributes (0, 1 ..), therefore, if you determine the location of the attribute, write a directly. the getInt (1,250) method is also supported. The second parameter is the default value.

Graph shape

After obtaining the property value, we can perform corresponding processing operations. Here is the acquisition of the image shape, andShape,SrcAndSizeAttributes, speed, and size will be discussed in the next section.

First, we observe that the three images have some gradient effects. Here I am simply doing transparency processing, that is, one change to transparency. The effect can be better processed, it may be optimized later. The image resource obtained from src is Drawable, whether it is ColorDrawable or BitmapDrawable. We need to first convert it to a Bitmap of size, and then use canvas to crop its shape. As for why we need to switch to Bitmap first, this is my practice. After reading the following operations, if there is a better way to talk about it, please.

/** * Drawable → Bitmap(the size is "size") */private Bitmap drawable2Bitmap(Drawable drawable) {Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, size, size);drawable.draw(canvas);return bitmap;}
After Bitmap is obtained, we can perform operations on the Shape. let's first talk about circular circle, diamond fig, pentagon, and heart, because the processing method is different. Like other ShapeImageView I see it seems like processing with svg, read their code, for example: seems a little trouble, in contrast to my processing is relatively simple.

Circle, rhombus fig, pentagon

You can use ShapeDrawable to obtain these shapes. We need the BitmapShader Renderer, which is required by the ShapeDrawable Paint brush. We need an empty Bitmap and a Canvas. As follows:

BitmapShader bitmapShader = new BitmapShader(srcBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Path path;ShapeDrawable shapeDrawable = new ShapeDrawable();shapeDrawable.getPaint().setAntiAlias(true);shapeDrawable.getPaint().setShader(bitmapShader);shapeDrawable.setBounds(0, 0, size, size);shapeDrawable.setAlpha(alpha);
Canvas is the Canvas on ShapeDrawable, and BitmapShader is the Renderer of ShapeDrawable Paint. It is used to render and process graphs (bitmap converted by drawable of src). CLAMP is used in rendering mode, this means that if the Renderer is out of the original boundary range, the inner edge of the replication range is stained.

The circle can be used directly:

shapeDrawable.setShape(new OvalShape());
This ShapeDrawable draws a circle. Of course, you must call shapeDrawable. draw (canvas); method, so bitmap will become a circular srcBitmap (parameters passed by the method). The complete code of this method is provided later.

What about diamond? We are like this:

path = new Path();path.moveTo(size / 2, 0);path.lineTo(0, size / 2);path.lineTo(size / 2, size);path.lineTo(size, size / 2);path.close();shapeDrawable.setShape(new PathShape(path, size, size));
It is a square with a side length of size. Take the midpoint of each side and connect the four points. We know that the coordinates of Android are generally the coordinates at the top-left corner of the screen. After the coordinate points are found, we connect the path to close. In this way, PathShape is a diamond. Almost all polygon can be painted like this, and the following pentagram is also the same. Note: All images are drawn in a square with a side length and size.

The principle of the pentagram is also PathShape, but it requires a lot of coordinate points. You need to calculate and debug it slowly.

path = new Path();// The Angle of the pentagramfloat radian = (float) (Math.PI * 36 / 180);float radius = size / 2;// In the middle of the radius of the pentagonfloat radius_in = (float) (radius * Math.sin(radian / 2) / Math.cos(radian));// The starting point of the polygonpath.moveTo((float) (radius * Math.cos(radian / 2)), 0);path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in * Math.sin(radian)),(float) (radius - radius * Math.sin(radian / 2)));path.lineTo((float) (radius * Math.cos(radian / 2) * 2),(float) (radius - radius * Math.sin(radian / 2)));path.lineTo((float) (radius * Math.cos(radian / 2) + radius_in * Math.cos(radian / 2)),(float) (radius + radius_in * Math.sin(radian / 2)));path.lineTo((float) (radius * Math.cos(radian / 2) + radius * Math.sin(radian)),(float) (radius + radius * Math.cos(radian)));path.lineTo((float) (radius * Math.cos(radian / 2)), (float) (radius + radius_in));path.lineTo((float) (radius * Math.cos(radian / 2) - radius * Math.sin(radian)),(float) (radius + radius * Math.cos(radian)));path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in * Math.cos(radian / 2)),(float) (radius + radius_in * Math.sin(radian / 2)));path.lineTo(0, (float) (radius - radius * Math.sin(radian / 2)));path.lineTo((float) (radius * Math.cos(radian / 2) - radius_in * Math.sin(radian)),(float) (radius - radius * Math.sin(radian / 2)));path.close();// Make these points closed polygonsshapeDrawable.setShape(new PathShape(path, size, size));
There are a lot of connections .. The drawing of the five-pointed shape is based on the specified angle of the five-pointed angle and the radius, then determine the starting point of the line, and then connect to the next point... closed at last, and accidentally did not know where to connect ..

Heart-shaped heart

To draw a heart-shaped path, you cannot connect to a straight line. At the beginning, you can use the quadTo (x1, y1, x2, y2) method of path to draw the besell curve, it is found that the shape is not full and is more like a cone (brain supplement), so I gave up this method. Then I found this article about Heart Curve, and then used his fourth method (for example), namely, using two elliptical shapes for cropping.

1. Draw an elliptical shape

</pre><pre name="code" class="java">
</Pre> <pre name = "code" class = "java"> // canvas bitmap bitmapshader, etc. The above code already has path = new Path (); paint paint = new Paint (); paint. setAntiAlias (true); paint. setShader (bitmapShader); Matrix matrix = new Matrix (); // controls the rotation of Region region = new Region (); // crop the RectF ovalRect = new RectF (size/4, 0, size-(size/4), size); path. addOval (ovalRect, Path. direction. CW );

</Pre> </p>  </p> 2. rotating image, about 45 degrees <p> <pre name = "code" class = "java"> matrix. postRotate (42, size/2, size/2); path. transform (matrix, path );

3. Select the right half of the rotated image and use cancas to draw the heart shape of the half side.

path.transform(matrix, path);region.setPath(path, new Region((int) size / 2, 0, (int) size, (int) size));canvas.drawPath(region.getBoundaryPath(), paint);

4. Repeat steps 1, 2, and 3 to change the direction angle and cropping area at the same time.

matrix.reset();path.reset();path.addOval(ovalRect, Path.Direction.CW);matrix.postRotate(-42, size / 2, size / 2);path.transform(matrix, path);region.setPath(path, new Region(0, 0, (int) size / 2, (int) size));canvas.drawPath(region.getBoundaryPath(), paint);
In this way, the heart-shaped image is cropped and the bitmap is changed to the heart-shaped image:

This is a good idea ..
The next step should be completed.

View rendering

When it comes to the view rendering process, we need the following trilogy:

  • Measurement -- onMeasure (): determines the size of the View.
  • Layout -- onLayout (): determines the position of the View in the ViewGroup.
  • Draw -- onDraw (): How to draw this View.


    The measurement of the BounceProgressBar control is relatively simple. When wrap_content is used, the height and width are 5 times and 4 times the size respectively. In other cases, it is better to specify the width and height as the measured value. Then determine the horizontal position of the three images in the control:

    @ Overrideprotected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {super. onMeasure (widthMeasureSpec, heightMeasureSpec); int sizeWidth = MeasureSpec. getSize (widthMeasureSpec); int sizeHeight = MeasureSpec. getSize (heightMeasureSpec); int modeWidth = MeasureSpec. getMode (widthMeasureSpec); int modeHeight = MeasureSpec. getMode (heightMeasureSpec); setMeasuredDimension (modeWidth = MeasureSpec. E XACTLY )? MWidth = sizeWidth: mWidth, (modeHeight = MeasureSpec. EXACTLY )? MHeight = sizeHeight: mHeight); firstDX = mWidth/4-size/2; // level position of the first image secondDX = mWidth/2-size/2 ;//... thirdDX = 3 * mWidth/4-size/2 ;//...}
    When the width of a specific value is specified, mWidth and mHeight are also set to sizeWidth and sizeHeight.


    When talking about the layout, it is clear that the image beat is controlled by the attribute animation. What is the attribute animation? In one sentence, you can change an object's attributes as an animation. If you do not know much about it, You can first look for information to see it.

    The layout determines the operation of various positions in the view. It is not used very much as a single control. Here I initialize the animation and start the operation. We can see that the BounceProgressBar is beating.

    The three attributes are encapsulated as follows:

    /** * firstBitmapTop's Property. The change of the height through canvas is * onDraw() method. */private Property<BounceProgressBar, Integer> firstBitmapTopProperty = new Property<BounceProgressBar, Integer>(Integer.class, "firstDrawableTop") {@Overridepublic Integer get(BounceProgressBar obj) {return obj.firstBitmapTop;}public void set(BounceProgressBar obj, Integer value) {obj.firstBitmapTop = value;invalidate();};};/** * secondBitmapTop's Property. The change of the height through canvas is * onDraw() method. */private Property<BounceProgressBar, Integer> secondBitmapTopProperty = new Property<BounceProgressBar, Integer>(Integer.class, "secondDrawableTop") {@Overridepublic Integer get(BounceProgressBar obj) {return obj.secondBitmapTop;}public void set(BounceProgressBar obj, Integer value) {obj.secondBitmapTop = value;invalidate();};};/** * thirdBitmapTop's Property. The change of the height through canvas is * onDraw() method. */private Property<BounceProgressBar, Integer> thirdBitmapTopProperty = new Property<BounceProgressBar, Integer>(Integer.class, "thirdDrawableTop") {@Overridepublic Integer get(BounceProgressBar obj) {return obj.thirdBitmapTop;}public void set(BounceProgressBar obj, Integer value) {obj.thirdBitmapTop = value;invalidate();};};
    The code for onLayout is as follows:

    @Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);if (bouncer == null || !bouncer.isRunning()) {ObjectAnimator firstAnimator = initDrawableAnimator(firstBitmapTopProperty, speed, size / 2,mHeight - size);ObjectAnimator secondAnimator = initDrawableAnimator(secondBitmapTopProperty, speed, size / 2,mHeight - size);secondAnimator.setStartDelay(100);ObjectAnimator thirdAnimator = initDrawableAnimator(thirdBitmapTopProperty, speed, size / 2,mHeight - size);thirdAnimator.setStartDelay(200);bouncer = new AnimatorSet();bouncer.playTogether(firstAnimator, secondAnimator, thirdAnimator);bouncer.start();}}private ObjectAnimator initDrawableAnimator(Property<BounceProgressBar, Integer> property, int duration,int startValue, int endValue) {ObjectAnimator animator = ObjectAnimator.ofInt(this, property, startValue, endValue);animator.setDuration(duration);animator.setRepeatCount(Animation.INFINITE);animator.setRepeatMode(ValueAnimator.REVERSE);animator.setInterpolator(new AccelerateInterpolator());return animator;}
    The animation value is changed from size to mHeight-size. The reason for subtracting the size is that the value range of the view on the left is greater than (mHeight, mHeight) in the canvas.


    You can draw bitmap on the canvas Based on the horizontal position of each image and the height controlled by the property animation.

    @Overrideprotected synchronized void onDraw(Canvas canvas) {/* draw three bitmap */firstBitmapMatrix.reset();firstBitmapMatrix.postTranslate(firstDX, firstBitmapTop);secondBitmapMatrix.reset();secondBitmapMatrix.setTranslate(secondDX, secondBitmapTop);thirdBitmapMatrix.reset();thirdBitmapMatrix.setTranslate(thirdDX, thirdBitmapTop);canvas.drawBitmap(firstBitmap, firstBitmapMatrix, mPaint);canvas.drawBitmap(secondBitmap, secondBitmapMatrix, mPaint);canvas.drawBitmap(thirdBitmap, thirdBitmapMatrix, mPaint);}
    The position is controlled by Matrix, because the deformation of the landing was also considered at the time, but now it is removed first.

    In general, the painting process is to control the position of each image on the canvas through property animation. when the property is changed, call the invalidate () method to notify the re-painting, it looks like a beating effect, and the change in the beating speed is done by setting an interpolation tool for the animation.

    This article is written here, the complete source code I put on my github (, welcome everyone star, fork together to improve it.

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.