Creating a custom circular progress bar with a par

2019-05-11 07:44发布

问题:

I am trying to create a custom progress bar with a particular scroll animation effect that looks more or less like the one in mi fit android app, see screen shot below : If you see the gif carefully you will notice two things -

  1. A glowing motion revolving around the progress bars outer ring

  2. when scrolled down or up (collapsing effect), the effects are beautiful

I am trying to achieve these two effects here, any help is appreciated :)

EDIT : I made the progress bar working - see screen shot with updated code below :

What I am following is creating three layers, one for outer circle other for dash circle and another for filling the dashed with solid circle. Then using these three shapes in three progress bars, see code below : layout.xml :

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
android:gravity="center">

<ProgressBar
                    android:id="@+id/progressBar1"
                    style="?android:attr/progressBarStyleHorizontal"
                    android:layout_width="250dp"
                    android:layout_height="250dp"
                    android:layout_centerInParent="true"
                    android:max="100"
                    android:progress="100"
                    android:progressDrawable="@drawable/circular_layer_outer" />

                <ProgressBar
                    android:id="@+id/progressBar2"
                    style="?android:attr/progressBarStyleHorizontal"
                    android:layout_width="230dp"
                    android:layout_height="230dp"
                    android:layout_centerInParent="true"
                    android:max="100"
                    android:progress="100"
                    android:progressDrawable="@drawable/circular_layer_dash_inner" />

                <ProgressBar
                    android:id="@+id/main_progressBar"
                    style="?android:attr/progressBarStyleHorizontal"
                    android:layout_width="226dp"
                    android:layout_height="226dp"
                    android:layout_centerInParent="true"
                    android:max="100"
                    android:progress="80"
                    android:progressDrawable="@drawable/circular_layer_dash_filler"
                    android:visibility="visible" />

And the shapes for both progress : circular_layer_dash_inner.xml :

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:paddingBottom="13dp"
    android:paddingLeft="13dp"
    android:paddingRight="13dp"
    android:paddingTop="13dp">
    <item>
        <rotate
            android:fromDegrees="270"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toDegrees="270">
            <shape
                android:dither="true"
                android:innerRadiusRatio="3"
                android:shape="ring"
                android:thickness="0sp"
                android:useLevel="true">
                <solid android:color="@color/white" />
                <stroke
                    android:width="1dp"
                    android:color="@color/white"
                    android:dashGap="2dp"
                    android:dashWidth="1dp" />
            </shape>
        </rotate>
    </item>
</layer-list>

circular_layer_dash_filler.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:paddingBottom="13dp"
    android:paddingLeft="13dp"
    android:paddingRight="13dp"
    android:paddingTop="13dp">
    <item>
        <rotate
            android:fromDegrees="270"
            android:pivotX="50%"
            android:pivotY="50%"
            android:toDegrees="270">
            <shape
                android:dither="true"
                android:innerRadiusRatio="3"
                android:shape="ring"
                android:thickness="2sp"
                android:useLevel="true">
                <solid android:color="@color/white" />
            </shape>
        </rotate>
    </item>
</layer-list>

circular_layer_outer.xml :

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
    <shape
        android:innerRadiusRatio="3"
        android:shape="ring"
        android:thickness="8sp"
        android:useLevel="true">
        <solid android:color="@color/white" />
    </shape>
</item>

p.s. sorry for the long post.

回答1:

I was able to create the animation through the following way :

<android.support.design.widget.AppBarLayout
    android:id="@+id/appBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/blue"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsingToolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        app:expandedTitleGravity="center_horizontal"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

        <RelativeLayout
            android:id="@+id/layers_progress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/blue_safe"
            app:layout_collapseMode="parallax">

            <ProgressBar
                android:id="@+id/progressBar1"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="250dp"
                android:layout_height="250dp"
                android:layout_centerInParent="true"
                android:max="100"
                android:progress="100"
                android:progressDrawable="@drawable/circular_layer_outer" />

            <ProgressBar
                android:id="@+id/progressBar2"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="230dp"
                android:layout_height="230dp"
                android:layout_centerInParent="true"
                android:max="100"
                android:progress="100"
                android:progressDrawable="@drawable/circular_layer_dash_inner" />

            <ProgressBar
                android:id="@+id/main_progressBar"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="226dp"
                android:layout_height="226dp"
                android:layout_centerInParent="true"
                android:max="100"
                android:progress="80"
                android:progressDrawable="@drawable/circular_layer_dash_filler"
                android:visibility="visible" />

        </RelativeLayout>

        <TextView
            android:id="@+id/score_val"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Score"
            android:textColor="@color/white"
            android:textSize="36sp"
            android:textStyle="bold"
            android:visibility="visible"
            app:layout_collapseMode="parallax" />

    </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

Here one can see a CollapsingToolbarLayout is used inside AppBarLayout and then using the AppBarLayout and setting an OnOffsetChangedListener to listen to change in value of height of AppBarLayout, See code below :

AppBarLayout appbar = findViewById(R.id.appBar);
        appbar.addOnOffsetChangedListener(DashboardScore.this);

And then in onOffsetChanged :

@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
    if (mMaxScrollSize == 0)
        mMaxScrollSize = appBarLayout.getTotalScrollRange();

    currentScrollPercentage = (Math.abs(verticalOffset)) * 100 / mMaxScrollSize;
    Log.d("Debug info", "% == " + currentScrollPercentage + "  -- mMaxScrollSize  == " + mMaxScrollSize + " vertical offset - " + verticalOffset);
    showAnim(oldScrollPercentage, currentScrollPercentage);
    oldScrollPercentage = currentScrollPercentage;
}

Here private int mMaxScrollSize; private int currentScrollPercentage, oldScrollPercentage = 0;

And for showAnim() use and here parentProgress is your parent of ProgressBars:

void showAnim(int fromDegree, int toDegree) {
    final float centerX = parentProgress.getWidth() / 2.0f;
    final float centerY = parentProgress.getHeight() / 2.0f;
    final Rotate3dAnimation rotation = new Rotate3dAnimation(fromDegree, toDegree, centerX, centerY, 5.0f, false);
    rotation.setDuration(400);
    rotation.setFillAfter(true);
    rotation.setInterpolator(new AccelerateInterpolator());
    parentProgress.startAnimation(rotation);
    if (currentScrollPercentage > 0) {
        if (fromDegree < 90)
            parentProgress.setAlpha(1 - Math.abs((float) currentScrollPercentage / 100f));
        else
            parentProgress.setAlpha(0);

        Log.d("Debug info", "setAlpha ==  " + (1 - Math.abs((float) currentScrollPercentage / 100f)));
    }
}

And for Rotate3dAnimation use :

public class Rotate3dAnimation extends Animation {
private final float mFromDegrees;
private final float mToDegrees;
private final float mCenterX;
private final float mCenterY;
private final float mDepthZ;
private final boolean mReverse;
private Camera mCamera;

/**
 * Creates a new 3D rotation on the Y axis. The rotation is defined by its
 * start angle and its end angle. Both angles are in degrees. The rotation
 * is performed around a center point on the 2D space, definied by a pair
 * of X and Y coordinates, called centerX and centerY. When the animation
 * starts, a translation on the Z axis (depth) is performed. The length
 * of the translation can be specified, as well as whether the translation
 * should be reversed in time.
 *
 * @param fromDegrees the start angle of the 3D rotation
 * @param toDegrees   the end angle of the 3D rotation
 * @param centerX     the X center of the 3D rotation
 * @param centerY     the Y center of the 3D rotation
 * @param reverse     true if the translation should be reversed, false otherwise
 */
public Rotate3dAnimation(float fromDegrees, float toDegrees,
                         float centerX, float centerY, float depthZ, boolean reverse) {
    mFromDegrees = fromDegrees;
    mToDegrees = toDegrees;
    mCenterX = centerX;
    mCenterY = centerY;
    mDepthZ = depthZ;
    mReverse = reverse;
}

@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
    super.initialize(width, height, parentWidth, parentHeight);
    mCamera = new Camera();
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    final float fromDegrees = mFromDegrees;
    float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);

    final float centerX = mCenterX;
    final float centerY = mCenterY;
    final Camera camera = mCamera;

    final Matrix matrix = t.getMatrix();

    camera.save();
    if (mReverse) {
        camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
    } else {
        camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
    }
    //camera.rotateY(degrees);
    camera.rotateX(degrees);
    camera.getMatrix(matrix);
    camera.restore();

    matrix.preTranslate(-centerX, -centerY);
    matrix.postTranslate(centerX, centerY);
}
}

Also Check this and this for more understanding