android自定义view 图片下载进度CoverView

本文介绍如何在Android中创建一个自定义View,实现图片下载时的进度展示效果。通过项目实战和提供的demo,展示了从效果图、目录结构、XML布局到Java代码的完整实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、简介

最近项目中需要用到一个关于imageview的下载进度效果,于是去github去查查,还真找到一个效果挺不错的。


二、应用场景

关于下载进度方面的几乎都可以用到

三、项目实战

(1)效果图

     

(2)目录结构


(3)xml文件

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <com.baiyu.coverloading.CoverView
            android:id="@+id/cover_small"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="16dp"
            android:src="@drawable/cover" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="8dp"
            android:onClick="startSmall"
            android:text="start" />

        <com.baiyu.coverloading.CoverView
            android:id="@+id/cover_big"
            android:layout_width="130dp"
            android:layout_height="130dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="16dp"
            android:src="@drawable/cover2" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="8dp"
            android:onClick="startBig"
            android:text="start" />

        <com.baiyu.coverloading.CoverView
            android:id="@+id/cover_square"
            android:layout_width="170dp"
            android:layout_height="170dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="16dp"
            android:src="@drawable/cover_square"
            app:corner_radius="0dp" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="8dp"
            android:onClick="startSquare"
            android:text="start" />
    </LinearLayout>

</ScrollView>

(4)java文件

CoverView:

package com.baiyu.coverloading;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;

/**
 * Created by ccheng on 11/27/14.
 */
public class CoverView extends ImageView {

	public static final int DEFAULT_SHADOW_COLOR = 0xaa000000;
	public int SHADOW_COLOR = DEFAULT_SHADOW_COLOR;
	public static final int ROTATE_DURATION = 300;
	private static final int MAX_PROGRESS = 100;
	private int mHeight;
	private int mWidth;
	private Bitmap bitmap;
	private Canvas tempCanvas;
	private Paint transparentPaint;
	private float mOuterCircleRadius = -1;
	private float mInnerCircleRadius = -1;
	private float mPauseCircleRadius = -1;
	private float mPauseMaxCircleRadius = -1;
	private float mCornerRadius = -1;
	private int mArcStart;
	private ValueAnimator mRotateAnimator;
	private float mPauseIconHeight;
	private float mPauseIconWidth;
	private float mPauseIconGap;
	private boolean mPausing;
	private ValueAnimator mPauseAnimator;
	private ValueAnimator mResumeAnimator;
	private boolean mStart = false;
	private float mInitOuterCircleRadius;
	private int mProgress;
	private int mPendingProgress;
	private OnPauseResumeListener mOnPauseResumeListener;

	public CoverView(Context context) {
		super(context);
		init(context, null);
	}

	public CoverView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context, attrs);
	}

	private void init(Context context, AttributeSet attrs) {
		if (attrs != null) {
			TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CoverView);
			int color = typedArray.getColor(R.styleable.CoverView_background, DEFAULT_SHADOW_COLOR);
			mCornerRadius = typedArray.getDimension(R.styleable.CoverView_corner_radius, -1);
			typedArray.recycle();
		}

		resetValues();

		mPauseAnimator = ValueAnimator.ofFloat(0.001f, 1);
		int duration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
		mPauseAnimator.setDuration(duration);
		mPauseAnimator.addUpdateListener(mPauseUpdateListener);
		mPauseAnimator.addListener(mPauseListener);
		mPauseAnimator.setInterpolator(new DecelerateInterpolator());

		mResumeAnimator = ValueAnimator.ofFloat(1, 0.001f);
		mResumeAnimator.setDuration(duration);
		mResumeAnimator.addUpdateListener(mResumeUpdateListener);
		mResumeAnimator.addListener(mResumeListener);
		mResumeAnimator.setInterpolator(new AccelerateInterpolator());

	}

	private ValueAnimator getRotateAnimator(int prevProgress, int progress) {
		ValueAnimator rotateAnimator = ValueAnimator.ofInt(progressToDegress(prevProgress),
				progressToDegress(progress));
		rotateAnimator.setDuration(ROTATE_DURATION);
		rotateAnimator.addUpdateListener(mRotateListener);
		rotateAnimator.addListener(new Animator.AnimatorListener() {
			@Override
			public void onAnimationStart(Animator animator) {
				mStart = true;
				mPausing = false;
			}

			@Override
			public void onAnimationEnd(Animator animator) {
				handlePendingProgress();

				if (isFinished()) {
					getFinishAnimator().start();
				}
			}

			@Override
			public void onAnimationCancel(Animator animator) {

			}

			@Override
			public void onAnimationRepeat(Animator animator) {

			}
		});

		return rotateAnimator;
	}

	private void handlePendingProgress() {
		if (mPendingProgress != mProgress && mPendingProgress > mProgress) {
			setProgress(mPendingProgress);
		}
	}

	private int progressToDegress(float progress) {
		return (int) (360 * (progress / MAX_PROGRESS) - 90);
	}

	// 结束动画
	private ValueAnimator getFinishAnimator() {
		ValueAnimator valueAnimator = ValueAnimator.ofFloat(mInitOuterCircleRadius, mInitOuterCircleRadius * 2);
		valueAnimator.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
		valueAnimator.setInterpolator(new AccelerateInterpolator());
		valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
			@Override
			public void onAnimationUpdate(ValueAnimator valueAnimator) {
				mOuterCircleRadius = ((Float) valueAnimator.getAnimatedValue()).floatValue();
				invalidate();
			}
		});

		valueAnimator.addListener(new Animator.AnimatorListener() {
			@Override
			public void onAnimationStart(Animator animator) {
				mPausing = false;
			}

			@Override
			public void onAnimationEnd(Animator animator) {
				mStart = false;
			}

			@Override
			public void onAnimationCancel(Animator animator) {

			}

			@Override
			public void onAnimationRepeat(Animator animator) {

			}
		});
		return valueAnimator;
	}

	// 重置值
	public void resetValues() {
		mProgress = 0;
		mPendingProgress = 0;

		mOuterCircleRadius = -1;
		mInnerCircleRadius = -1;
		mPauseCircleRadius = -1;
		mPauseMaxCircleRadius = -1;
	}

	private void initSizes(int width, int height) {
		mPauseIconHeight = 45f / 200 * width;
		mPauseIconWidth = 10f / 200 * width;
		mPauseIconGap = 10f / 200 * width;

		mInitOuterCircleRadius = 90f / 200 * width;

		if (mInnerCircleRadius == -1) {
			mInnerCircleRadius = 80f / 200 * width;
		}

		if (mCornerRadius == -1) {
			mCornerRadius = 20f / 200 * width;
		}

		if (mPauseMaxCircleRadius == -1) {
			mPauseMaxCircleRadius = mInnerCircleRadius * 0.7f;
		}

		if (mOuterCircleRadius == -1) {
			mOuterCircleRadius = mInitOuterCircleRadius;
		}

		if (mPauseCircleRadius == -1) {
			mPauseCircleRadius = mPauseMaxCircleRadius;
		}
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		initSizes(getWidth(), getHeight());

		if (!mStart) {
			return;
		}

		Paint redPaint = new Paint();
		redPaint.setColor(Color.RED);

		Paint greenPaint = new Paint();
		greenPaint.setColor(Color.GREEN);

		bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		bitmap.eraseColor(Color.TRANSPARENT);
		tempCanvas = new Canvas(bitmap);

		transparentPaint = new Paint();
		transparentPaint.setColor(getResources().getColor(android.R.color.transparent));
		transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
		transparentPaint.setAntiAlias(true);

		mHeight = getHeight();
		mWidth = getWidth();

		tempCanvas.drawColor(SHADOW_COLOR);
		int cx = mWidth / 2;
		int cy = mHeight / 2;
		tempCanvas.drawCircle(cx, cy, mOuterCircleRadius, transparentPaint);

		Paint shadowPaint = new Paint();
		shadowPaint.setAntiAlias(true);
		shadowPaint.setColor(SHADOW_COLOR);
		RectF rectF = new RectF(cx - mInnerCircleRadius, cy - mInnerCircleRadius, cx + mInnerCircleRadius,
				cy + mInnerCircleRadius);
		tempCanvas.drawArc(rectF, mArcStart, 270 - mArcStart, true, shadowPaint);
		if (mCornerRadius != 0) {
			Path path = new Path();
			path.addRoundRect(new RectF(0, 0, getWidth(), getHeight()), mCornerRadius, mCornerRadius,
					Path.Direction.CCW);
			canvas.clipPath(path);
		}
		canvas.drawBitmap(bitmap, 0, 0, null);

		/**
		 * Draw pause icon.
		 */
		if (mPausing && mPauseCircleRadius * 2 > 1) {
			tempCanvas.drawCircle(cx, cy, mPauseCircleRadius, transparentPaint);

			Bitmap pauseBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
			Canvas pauseCanvas = new Canvas(pauseBitmap);

			pauseCanvas.drawCircle(cx, cy, mPauseCircleRadius, shadowPaint);

			Paint gp1 = new Paint(transparentPaint);
			gp1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

			// Draw pause1.
			int pcx = (int) (cx - mPauseIconGap / 2 - mPauseIconWidth / 2);
			int pcy = (int) (cy);

			RectF pause1 = new RectF();
			pause1.left = pcx - mPauseIconWidth / 2;
			pause1.right = pcx + mPauseIconWidth / 2;
			pause1.top = pcy - mPauseIconHeight / 2;
			pause1.bottom = pcy + mPauseIconHeight / 2;
			pauseCanvas.drawRect(pause1, gp1);

			// Draw pause2.
			int pcx2 = (int) (cx + mPauseIconGap / 2 + mPauseIconWidth / 2);
			int pcy2 = (int) (cy);

			RectF pause2 = new RectF();
			pause2.left = pcx2 - mPauseIconWidth / 2;
			pause2.right = pcx2 + mPauseIconWidth / 2;
			pause2.top = pcy2 - mPauseIconHeight / 2;
			pause2.bottom = pcy2 + mPauseIconHeight / 2;
			pauseCanvas.drawRect(pause2, gp1);

			canvas.drawBitmap(pauseBitmap, 0, 0, null);
		}
	}

	// 触摸事件
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_UP) {
			if (mStart) {
				if (mPausing) {
					resumeLoading();
				} else {
					pauseLoading();
				}
			}
			return true;
		}

		return true;
	}

	// 停止加载
	public void pauseLoading() {
		if (!mResumeAnimator.isRunning() && !mPauseAnimator.isRunning()) {
			mPausing = true;
			mPauseAnimator.start();
		}
	}

	// 继续加载
	public void resumeLoading() {
		if (!mPauseAnimator.isRunning() && !mResumeAnimator.isRunning()) {
			mPausing = true;
			mPauseAnimator.cancel();
			mResumeAnimator.start();
			handlePendingProgress();
		}
	}

	// 更新的动画监听
	private ValueAnimator.AnimatorUpdateListener mResumeUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
		@Override
		public void onAnimationUpdate(ValueAnimator valueAnimator) {
			mPauseCircleRadius = mPauseMaxCircleRadius * ((Float) valueAnimator.getAnimatedValue()).floatValue();
			invalidate();
		}
	};
	// 停止的动画监听
	private Animator.AnimatorListener mPauseListener = new Animator.AnimatorListener() {
		@Override
		public void onAnimationStart(Animator animation) {
			mPausing = true;
		}

		@Override
		public void onAnimationEnd(Animator animation) {

		}

		@Override
		public void onAnimationCancel(Animator animation) {

		}

		@Override
		public void onAnimationRepeat(Animator animation) {

		}
	};

	private ValueAnimator.AnimatorUpdateListener mRotateListener = new ValueAnimator.AnimatorUpdateListener() {
		@Override
		public void onAnimationUpdate(ValueAnimator animation) {
			mArcStart = ((Integer) animation.getAnimatedValue()).intValue();
			invalidate();
		}
	};
	// 动画更新的监听
	private ValueAnimator.AnimatorUpdateListener mPauseUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
		@Override
		public void onAnimationUpdate(ValueAnimator animation) {
			float v = ((Float) animation.getAnimatedValue()).floatValue();
			System.out.println("v = " + v);
			mPauseCircleRadius = mPauseMaxCircleRadius * v;
			invalidate();
		}
	};

	// 继续的动画监听
	private Animator.AnimatorListener mResumeListener = new Animator.AnimatorListener() {
		@Override
		public void onAnimationStart(Animator animator) {

		}

		@Override
		public void onAnimationEnd(Animator animator) {
			mPausing = false;
		}

		@Override
		public void onAnimationCancel(Animator animator) {

		}

		@Override
		public void onAnimationRepeat(Animator animator) {

		}
	};

	// 设置进度
	public void setProgress(int p) {
		if (p > MAX_PROGRESS) {
			p = MAX_PROGRESS;
		}

		if (p < mProgress) {
			throw new IllegalArgumentException();
		}

		if ((mRotateAnimator != null && mRotateAnimator.isRunning()) || mPausing) {
			mPendingProgress = p;
			return;
		}

		int prevProgress = mProgress;
		mProgress = p;

		if (mRotateAnimator != null) {
			mRotateAnimator.cancel();
		}

		mRotateAnimator = getRotateAnimator(prevProgress, p);
		mRotateAnimator.start();
	}

	// 获取进度
	public int getProgress() {
		return mProgress;
	}

	// 是否完成
	public boolean isFinished() {
		return getProgress() == MAX_PROGRESS;
	}

	public OnPauseResumeListener getOnPauseResumeListener() {
		return mOnPauseResumeListener;
	}

	public void setOnPauseResumeListener(OnPauseResumeListener onPauseResumeListener) {
		mOnPauseResumeListener = onPauseResumeListener;
	}

	public interface OnPauseResumeListener {
		public void onPause();

		public void onResume();
	}
}
MainActivity:

package com.baiyu.coverloading;


import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

    private CoverView[] mCoverView = new CoverView[3];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mCoverView[0] = (CoverView) findViewById(R.id.cover_small);
        mCoverView[1] = (CoverView) findViewById(R.id.cover_big);
        mCoverView[2] = (CoverView) findViewById(R.id.cover_square);
        //停止、继续下载的事件监听
        mCoverView[0].setOnPauseResumeListener(new CoverView.OnPauseResumeListener() {
            @Override
            public void onPause() {
                Toast.makeText(MainActivity.this, "paused", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onResume() {
                Toast.makeText(MainActivity.this, "resumed", Toast.LENGTH_SHORT).show();
            }
        });
    }


    public void startLoading(final View view, final CoverView coverView) {
        view.setEnabled(false);
        //模拟下载进度
        new Thread(new Runnable() {
            @Override
            public void run() {
                int p = 0;
                while (!coverView.isFinished()) {
                    p = (int) (p + Math.random() * 20);
                    final int finalP = p;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            coverView.setProgress(finalP);
                        }
                    });
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //重置CoverView
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        coverView.resetValues();
                        view.setEnabled(true);
                    }
                });
            }
        }).start();
    }

    public void startSmall(View view) {
        startLoading(view, mCoverView[0]);
    }

    public void startBig(View view) {
        startLoading(view, mCoverView[1]);
    }

    public void startSquare(View view) {
        startLoading(view, mCoverView[2]);
    }
}



四、demo链接

csdn:http://download.youkuaiyun.com/detail/baiyulinlin1/9561668

或GitHub:https://github.com/ufo22940268/CoverLoading

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值