13

I'm trying to create a progress bar where the bar itself animates in a vertical spin as it progresses horizontally. I'm successfully using my progress drawable as the drawable via:

<ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        style="@android:style/Widget.ProgressBar.Horizontal"
        android:progressDrawable="@drawable/custom_progress"
        android:layout_marginRight="5dp" />

Here is my drawable:

enter image description here

But I want it to have a subtle roll effect as its progressing. So it would look like the vertical lines are moving backwards sorta. You follow? Any help is much appreciated. Thanks.

EDIT: I tried created an animation-list as my progress drawable but I'm still not able to see the animation. Can an animation-list be inside of a clip for the progress item?

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background" android:drawable="@drawable/gutter"></item>

<item android:id="@android:id/progress">

<clip>
    <animation-list android:oneshot="false">
        <item android:drawable="@drawable/progress_bar_animate" android:duration="100" />
        <item android:drawable="@drawable/progress_bar_animate2" android:duration="100" />
        <item android:drawable="@drawable/progress_bar_animate3" android:duration="100" />
        <item android:drawable="@drawable/progress_bar_animate4" android:duration="100" />
        <item android:drawable="@drawable/progress_bar_animate5" android:duration="100" />
        <item android:drawable="@drawable/progress_bar_animate6" android:duration="100" />
        <item android:drawable="@drawable/progress_bar_animate7" android:duration="100" />
    </animation-list>
</clip>

</item>
</layer-list>
askilondz
  • 3,264
  • 2
  • 28
  • 40

9 Answers9

10

Yahoo! Finally ! Works ! (BUT !!!! can cause memory leak with big images. It's fixed in the post below)

This code takes the tile picture (tile1), repeats it(TileMode.REPEAT), makes shifted animation (10 fragments), adds this in Animation set.

enter image description here

private void initAnimation() {
//      R.drawable.tile1 is PNG
        Bitmap b = BitmapFactory.decodeResource(getResources(),R.drawable.tile1);
        AnimationDrawable shiftedAnimation = getAnimation(b);

//      R.id.img_3 is ImageView in my application
        View v = findViewById(R.id.img_3);
        v.setBackground(shiftedAnimation);
        shiftedAnimation.start();
}

private Bitmap getShiftedBitmap(Bitmap bitmap, int shiftX) {
    Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
    Canvas newBitmapCanvas = new Canvas(newBitmap);

    Rect srcRect1 = new Rect(shiftX, 0, bitmap.getWidth(), bitmap.getHeight());
    Rect destRect1 = new Rect(srcRect1);
    destRect1.offset(-shiftX, 0);
    newBitmapCanvas.drawBitmap(bitmap, srcRect1, destRect1, null);

    Rect srcRect2 = new Rect(0, 0, shiftX, bitmap.getHeight());
    Rect destRect2 = new Rect(srcRect2);
    destRect2.offset(bitmap.getWidth() - shiftX, 0);
    newBitmapCanvas.drawBitmap(bitmap, srcRect2, destRect2, null);

    return newBitmap;
}

private List<Bitmap> getShiftedBitmaps(Bitmap bitmap) {
    List<Bitmap> shiftedBitmaps = new ArrayList<Bitmap>();
    int fragments = 10;
    int shiftLength = bitmap.getWidth() / fragments;

    for(int i = 0 ; i < fragments; ++i){
        shiftedBitmaps.add( getShiftedBitmap(bitmap,shiftLength * i));
    }

    return shiftedBitmaps;
}

private AnimationDrawable getAnimation(Bitmap bitmap) {
    AnimationDrawable animation = new AnimationDrawable();
    animation.setOneShot(false);

    List<Bitmap> shiftedBitmaps = getShiftedBitmaps(bitmap);
    int duration = 50;

    for(Bitmap image: shiftedBitmaps){
        BitmapDrawable navigationBackground = new BitmapDrawable(getResources(), image);
        navigationBackground.setTileModeX(TileMode.REPEAT);

        animation.addFrame(navigationBackground, duration);
    }
    return animation;
}
StepanM
  • 4,222
  • 1
  • 21
  • 25
2

I don't think this can be done the way I thought it could. The reason being that I can't reference the animated-list, which at that point is a ClipDrawable, and cast it to an AnimateDrawable which is necessary so I can programmatically start the animation. It's also necessary to contain it in the clip element as that's the ProgressBar's way of masking an image so it only displays part of the image as its progressing.

Aside from faking my own progress bar using ImageViews and animating those I don't see another way around it for this specific ProgressBar.

askilondz
  • 3,264
  • 2
  • 28
  • 40
1

I found a solution. Create an xml say, progress_loading.xml as follows and assign it to the progress drawable of the progressbar :

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
  <item android:id="@android:id/progress">
    <clip>
      <shape>
        <solid android:color="#33FFFFFF" />
      </shape>
    </clip>
  </item>
</layer-list>

Then in code:

AnimationDrawable anim = (AnimationDrawable) getResources()
                .getDrawable(R.drawable.loading);
        ClipDrawable clipDrawable = new ClipDrawable(anim, Gravity.LEFT,
                ClipDrawable.HORIZONTAL);
        LayerDrawable layerDrawable = (LayerDrawable) progressBar
                .getProgressDrawable();
        layerDrawable.setDrawableByLayerId(android.R.id.progress,
                clipDrawable);
        anim.start();

Where the xml loading.xml has the actual animation-list. This is worked for me.

trev9065
  • 3,371
  • 4
  • 30
  • 45
abhishek89m
  • 261
  • 3
  • 6
1

My previous method had problem with memory consume. I had improved it to use TranslateAnimation. BUT it has little LAG (pay attention on HACK comment in ProgressBgView.initAnimation method)

Layout xml :

    <com.example.tweenanimation.ProgressBgView
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:id="@+id/progress_db_view"
        />

Backend :

    final ProgressBgView prog = (ProgressBgView) findViewById(R.id.progress_db_view);
        prog.setBackgroundAsTile(R.drawable.bg_tile_1);
        prog.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                prog.startAnimation();
            }
        });

And the class :

public class ProgressBgView extends FrameLayout {
    private ImageView mProgressImage;
    private TranslateAnimation mProgressAnimation;

    public ProgressBgView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mProgressImage = new ImageView(getContext());
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
        mProgressImage.setLayoutParams(layoutParams);
        addView(mProgressImage);
    }

    public void setBackgroundAsTile(int tileImageResId) {
        Bitmap tileBitmap = BitmapFactory.decodeResource(getResources(), tileImageResId);
        BitmapDrawable tileRepeatedBitmap = new BitmapDrawable(getResources(), tileBitmap);
        tileRepeatedBitmap.setTileModeX(TileMode.REPEAT);

        initAnimation(tileBitmap.getWidth());

        mProgressImage.setBackgroundDrawable(tileRepeatedBitmap);
    }

    private void initAnimation(int tileImageWidth) {
        FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mProgressImage.getLayoutParams();
        layoutParams.setMargins(-tileImageWidth, 0, 0, 0);

        // *HACK* tileImageWidth-3 is used because of *lags*(slow pause) in the moment
        // of animation END-RESTART.
        mProgressAnimation = new TranslateAnimation(0, tileImageWidth - 3, 0, 0);
        mProgressAnimation.setInterpolator(new LinearInterpolator());
        mProgressAnimation.setDuration(1000);
        mProgressAnimation.setRepeatCount(Animation.INFINITE);
    }

    public void startAnimation() {
        mProgressImage.startAnimation(mProgressAnimation);
    }
}
StepanM
  • 4,222
  • 1
  • 21
  • 25
  • 2
    I found that this solution had problem on Android 2.3 (last tile was jumping), the solution from Apr 3 at 13:24 was also working on Gingerbread. – zilinx Aug 12 '13 at 12:28
1

This what I get. Custom horizontal progress bar

And this is way:

<ProgressBar
    android:layout_width="match_parent"
    android:layout_height="15dp"
    android:indeterminate="true"
    style="@style/IndeterminateProgressBar" />

After that in styles.xml:

<style name="IndeterminateProgressBar" parent="@android:style/Widget.ProgressBar.Horizontal">
    <item name="android:indeterminateDrawable">@anim/progress_bar_indeterminate</item>
</style>

In file res/anim/progress_bar_indeterminate.xml:

<?xml version="1.0" encoding="utf-8"?>
<animation-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/progress_bar_horizontal_1" android:duration="200" />
    <item android:drawable="@drawable/progress_bar_horizontal_2" android:duration="200" />
    <item android:drawable="@drawable/progress_bar_horizontal_3" android:duration="200" />
    <item android:drawable="@drawable/progress_bar_horizontal_4" android:duration="200" />
</animation-list>

And here 4 images that need to put in folder res/drawable:

progress_bar_horizontal_1

progress_bar_horizontal_2

progress_bar_horizontal_3

progress_bar_horizontal_4

P.S. If you need reverse direction of the progress bar, just flip the numbers in name of images in progress_bar_indeterminate.xml.

Good day! =)

Taras Okunev
  • 410
  • 6
  • 9
  • You might want the items in the animation-list like this: , so the images will repeat instead of stretch. – HedeH Nov 07 '16 at 07:36
1

For those who want to make progress bar determinate, I post my improvement to user1269737's answer.

I've changed intAnimation method like this:

private void initAnimation(int tileImageResId) {
    Bitmap b = BitmapFactory.decodeResource(getResources(), tileImageResId);
    if (b != null) {
        shiftedAnimation = getAnimation(b);

        setBackgroundColor(ContextCompat.getColor(getContext(), #14000000));
        mProgressImage.setBackground(shiftedAnimation);
    }
}

And added setProgress method with animated progress change:

 public void setProgress(double progress){
    if (progress > 100){
        progress = 100;
    }
    else if (progress < 0){
        progress = 0;
    }
    int parentWidth = getWidth();
    final int newWidth =  (int)(parentWidth*(progress/100.0));

    widthAnimator = ValueAnimator.ofInt(mProgressImage.getLayoutParams().width, newWidth);
    widthAnimator.setDuration(200);
    widthAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mProgressImage.getLayoutParams().width = (Integer) animation.getAnimatedValue();
            mProgressImage.requestLayout();
        }
    });
    widthAnimator.start();
}

And some changes in constructor:

public LProgressBar(Context context, AttributeSet attrs) {
    super(context, attrs);

    mProgressImage = new ImageView(getContext());
    FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, FrameLayout.LayoutParams.MATCH_PARENT);
    mProgressImage.setLayoutParams(layoutParams);
    addView(mProgressImage);

    initAnimation(R.drawable.pb_background);
}
anro
  • 1,300
  • 16
  • 30
0

I found one solution, kind of similar to above explanation but not originated from it. Works great!

Say below is our progress_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape>
            <solid
                android:color="@color/black_20"/>
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <animation-list
                android:oneshot="false">
                <item
                    android:drawable="@drawable/stripe3"
                    android:duration="100"/>
                <item
                    android:drawable="@drawable/stripe2"
                    android:duration="100"/>
                <item
                    android:drawable="@drawable/stripe1"
                    android:duration="100"/>
            </animation-list>
        </clip>
    </item>
</layer-list>

then in Java code, do the following:

mDownloadProgressBar.setProgressDrawable(getResources().getDrawable(R.drawable.download_progress, null));
LayerDrawable drawable = (LayerDrawable)mDownloadProgressBar.getProgressDrawable();
ClipDrawable clipDrawable = (ClipDrawable) drawable.getDrawable(1);
AnimationDrawable animationDrawable =(AnimationDrawable)clipDrawable.getDrawable();
animationDrawable.start();
prateek
  • 440
  • 4
  • 12
  • The limitation with above solution is that clipDrawable.getDrawable() only works for API 23+ so I was forced to implement this new solution as posted below. I had to then manually create a LayerDrawable for my ProgressBar. – prateek Aug 10 '17 at 11:07
0

download_progress_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/stripe3"
        android:duration="100"/>
    <item
        android:drawable="@drawable/stripe2"
        android:duration="100"/>
    <item
        android:drawable="@drawable/stripe1"
        android:duration="100"/>
</animation-list>

Java code:

mDownloadProgressBar.setProgressDrawable(createProgressDrawable(context));

private Drawable createProgressDrawable(Context context) {

    ShapeDrawable shape = new ShapeDrawable();
    shape.getPaint().setStyle(Paint.Style.FILL);
    shape.getPaint()
            .setColor(ContextCompat.getColor(context, R.color.black_20));

    AnimationDrawable animationDrawable = (AnimationDrawable) getDrawable(R.drawable.download_progress_anim, context);
    ClipDrawable clipDrawable = new ClipDrawable(animationDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL);
    animationDrawable.start();

    LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{shape, clipDrawable});
    layerDrawable.setId(0, android.R.id.background);
    layerDrawable.setId(1, android.R.id.progress);
    return layerDrawable;
}

private Drawable getDrawable(@DrawableRes int drawable, Context context) {
        if (Build.VERSION.SDK_INT < 21) {
            return context.getResources().getDrawable(drawable);
        } else {
            return context.getResources().getDrawable(drawable, null);
        }
    }

Hope it helps. You can easily modify it to support your own implementation.

prateek
  • 440
  • 4
  • 12
0

IF and only IF you want to have a custom colored-height progress bar :

I believe there is a View to do that from google (LinearProgressIndicator). you can custom colors and height. I explained it in this post :

https://stackoverflow.com/a/68505259/2719243

Vahab Ghadiri
  • 2,016
  • 20
  • 26