自定义圆形图片冷却倒计时

前言:这应该算正经的第一次写博客,之前应该一直都算是码农,虽然能够把拿来的代码修改为自己期望的效果,但是总感觉这样子下去技术得不到突破,下定决心开始记录自己的学习路程。

端游或者手游中的技能冷却倒计时大家都见过吧


今天我们就来实现一下,先来看一下最终的效果图。源码在文章末尾


一、创建新类,继承自ImageView

新建ColdDownTimer类


对应MainActivity.java如下

package com.lxp.android.colddowntimer;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import java.text.DecimalFormat;

public class MainActivity extends AppCompatActivity {
    private TextView countTimeTv;
    private ColdDownTimer coldDownTimer;

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

        countTimeTv = (TextView) findViewById(R.id.countDownTime_tv);
        coldDownTimer = (ColdDownTimer) findViewById(R.id.coldDownTimer);
        coldDownTimer.setWaitHint("充能中...");// 冷却中提示
        coldDownTimer.setCountTime(10);// 总冷却时间
//        coldDownTimer.setCurCountTime(5);// 设置当前冷却时间
        coldDownTimer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                coldDownTimer.startCountdown();
            }
        });

        coldDownTimer.setOnCountDownTimeListener(new ColdDownTimer.CountDownTimeListener() {
            @Override
            public void getCurCountDownTime(int time) {
                if (time > 0) {
                    countTimeTv.setText(Utils.transferTime(time));
                    countTimeTv.setVisibility(View.VISIBLE);
                } else {
                    countTimeTv.setVisibility(View.GONE);
                }
            }

            @Override
            public void countDownFinish(){
                countTimeTv.setVisibility(View.GONE);
            }
        });
    }


}

主要就是申明控件,设置一些方法和监听类。

二、XML文件

对应main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.lxp.android.colddowntimer.MainActivity">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true">

        <com.lxp.android.colddowntimer.ColdDownTimer
            android:id="@+id/coldDownTimer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/scan"
            app:waitHint="冷却中..." />

        <TextView
            android:id="@+id/countDownTime_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:shadowColor="@android:color/black"
            android:shadowRadius="10"
            android:textColor="@android:color/white"
            android:textSize="16sp" />
    </FrameLayout>
</RelativeLayout>

很简单的布局,就是用FrameLayout把自定义控件和显示时间包起来,这里有用到自定义布局属性,所要做的就是要在values中的attrs文件定义一下我们控件的布局属性,没有attrs文件的话,new一个就好了。相信大部分小伙伴都明白

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="ColdDownTimer">
        <attr name="waitHint" format="string" />
    </declare-styleable>

</resources>

三、关键类

ColdDownTimer.java

package com.lxp.android.colddowntimer;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.Toast;

/**
 * 冷却时间倒计时
 *
 * @author by LXP
 */
public class ColdDownTimer extends ImageView {
    private Context mContext;
    private ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
    private Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    private int COLORDRAWABLE_DIMENSION = 1;
    private RectF mDrawableRect = new RectF();
    private Matrix mShaderMatrix = new Matrix();
    private Paint mBitmapPaint = new Paint();
    private Paint mArcPaint = new Paint();
    private Bitmap mBitmap;
    private BitmapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;
    private float mDrawableRadius;
    private boolean mReady;
    private boolean mSetupPending;
    /**
     * 冷却中提示
     */
    private String waitHint;
    /**
     * 已过时间百分比
     */
    private float timePercent;
    /**
     * 当前冷却时间
     */
    private int curCountDownTime;
    /**
     * 总冷却时间(默认5秒)
     */
    private int countdownTime = 5;
    /**
     * 动画持续时间
     */
    private int animDuration;
    private boolean isCountOver = true;
    private CountDownTimeListener countDownTimeListener;
    private Handler handler = new Handler();

    public ColdDownTimer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColdDownTimer(Context context, AttributeSet attrs,
                         int defStyle) {
        super(context, attrs, defStyle);
        super.setScaleType(SCALE_TYPE);
        this.mContext = context;
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ColdDownTimer, defStyle, 0);
        waitHint = a.getString(R.styleable.ColdDownTimer_waitHint);
        a.recycle();
        mReady = true;
        if (mSetupPending) {
            setup();
            mSetupPending = false;
        }
    }

    @Override
    public ScaleType getScaleType() {
        return SCALE_TYPE;
    }


    public boolean isCountDownOver() {
        return isCountOver;
    }

    /**
     * 设置当前冷却时间
     *
     * @param time
     * @throws Exception
     */
    public void setCurCountTime(int time) {
        curCountDownTime = animDuration = time;
        if (curCountDownTime > countdownTime) {
            throw new IllegalArgumentException(Utils.getFunctionName(mContext, "当前冷却时间大于总冷却时间"));
        }
    }

    /**
     * 设置总冷却时间
     *
     * @param time
     * @throws Exception
     */
    public void setCountTime(int time) {
        countdownTime = time;
        if (curCountDownTime > countdownTime) {
            throw new IllegalArgumentException(Utils.getFunctionName(mContext, "当前冷却时间大于总冷却时间"));
        }
    }

    /**
     * 设置冷却中提示
     *
     * @param waitHint
     */
    public void setWaitHint(String waitHint) {
        this.waitHint = waitHint;
    }

    /**
     * 设置冷却监听器
     *
     * @param countDownTimeListener
     */
    public void setOnCountDownTimeListener(
            CountDownTimeListener countDownTimeListener) {
        this.countDownTimeListener = countDownTimeListener;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null) {
            return;
        }
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
                mBitmapPaint);
        if (!isCountOver) {
            canvas.drawArc(mDrawableRect, 270, -(360 * (1 - timePercent)),
                    true, mArcPaint);
        }

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        setup();
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        mBitmap = bm;
        setup();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        mBitmap = getBitmapFromDrawable(drawable);
        setup();
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
        setup();
    }

    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
        try {
            Bitmap bitmap;
            if (drawable instanceof ColorDrawable) {
                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION,
                        COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
            } else {
                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                        drawable.getIntrinsicHeight(), BITMAP_CONFIG);
            }
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }

    private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }
        if (mBitmap == null) {
            return;
        }
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
                Shader.TileMode.CLAMP);
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);
        mArcPaint.setAntiAlias(true);
        mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mArcPaint.setAlpha(122);
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();

        mDrawableRect.set(0, 0, getWidth(), getHeight());
        mDrawableRadius = Math.min(mDrawableRect.height() / 2,
                mDrawableRect.width() / 2);
        updateShaderMatrix();
        invalidate();
    }

    private void updateShaderMatrix() {
        float scale;
        mShaderMatrix.set(null);
        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width()
                * mBitmapHeight) {
            scale = mDrawableRect.height() / (float) mBitmapHeight;
        } else {
            scale = mDrawableRect.width() / (float) mBitmapWidth;
        }
        mShaderMatrix.setScale(scale, scale);
        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

    /**
     * 开始倒计时
     */
    public void startCountdown() {
        if (!isCountOver) {
            Toast.makeText(mContext, waitHint, Toast.LENGTH_SHORT).show();
        } else {
            if (curCountDownTime == 0) {
                curCountDownTime = animDuration = countdownTime;
            }
            isCountOver = false;
            ValueAnimator valueA = getValueAnimator();
            valueA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    timePercent = (Float) animation.getAnimatedValue();
                    invalidate();

                }
            });
            valueA.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    if (curCountDownTime > 0) {
                        isCountOver = false;
                    } else {
                        if (null != countDownTimeListener) {
                            countDownTimeListener.countDownFinish();
                        }
                        curCountDownTime = animDuration;
                        isCountOver = true;
                        handler.removeCallbacks(runnable);
                    }
                }
            });
            valueA.start();
            handler.post(runnable);
        }
    }

    /**
     * 获取值动画
     *
     * @return
     */
    private ValueAnimator getValueAnimator() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(
                (1 - (float) curCountDownTime / countdownTime), 1.F);
        valueAnimator.setDuration(animDuration * 1000);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);
        return valueAnimator;
    }

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if (null != countDownTimeListener) {
                countDownTimeListener.getCurCountDownTime(curCountDownTime);
            }
            curCountDownTime--;
            handler.postDelayed(this, 1000);
        }
    };

    public interface CountDownTimeListener {
        /**
         * 获取当前冷却时间
         * @param time
         */
        void getCurCountDownTime(int time);

        /**
         * 冷却计时结束
         */
        void countDownFinish();
    }
}

大家都知道,自定义控件基本都要重写onDraw方法,我们来看看onDraw中的方法

@Override
    protected void onDraw(Canvas canvas) {
        if (getDrawable() == null) {
            return;
        }
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
                mBitmapPaint);
        if (!isCountOver) {
            canvas.drawArc(mDrawableRect, 270, -(360 * (1 - timePercent)),
                    true, mArcPaint);
        }

    }

用mBitmapPaint来画圆,圆心的坐标分别是getWidth()/2和getHeight()/2,半径mDrawableRadius

判断isCountOver倒计时是否结束,来确定是否画弧线。

画弧线的画笔mArcPaint配置

mArcPaint.setAntiAlias(true);//设置抗锯齿效果
mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mArcPaint.setAlpha(122);

抗锯齿最好设置为true,不然冷却动画边缘会有锯齿效果。

注意:这里的画笔的style要设置为Paint.Style.FILL_AND_STROKE或者Paint.Style.FILL,不能设置成Paint.Style.STROKE

不明白的小伙伴可以去了解一下Paint类,这里有传送门

接下来就可以开始倒计时了

/**
     * 开始倒计时
     */
    public void startCountdown() {
        if (!isCountOver) {
            Toast.makeText(mContext, waitHint, Toast.LENGTH_SHORT).show();
        } else {
            if (curCountDownTime == 0) {
                curCountDownTime = animDuration = countdownTime;
            }
            isCountOver = false;
            ValueAnimator valueA = getValueAnimator();
            valueA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    timePercent = (Float) animation.getAnimatedValue();
                    invalidate();

                }
            });
            valueA.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    if (curCountDownTime > 0) {
                        isCountOver = false;
                    } else {
                        if (null != countDownTimeListener) {
                            countDownTimeListener.countDownFinish();
                        }
                        curCountDownTime = animDuration;
                        isCountOver = true;
                        handler.removeCallbacks(runnable);
                    }
                }
            });
            valueA.start();
            handler.post(runnable);
        }
    }

其中

if (curCountDownTime == 0) {
    curCountDownTime = animDuration = countdownTime;
}

这里是为了判断是否设置了当前冷却时间,如果没设置的话,把总冷却时间赋值给当前冷却时间。

通过getValueAnimator()获取ValueAnimator来达到倒计时的动画效果ValueAnimator基本使用

/**
     * 获取值动画
     *
     * @return
     */
    private ValueAnimator getValueAnimator() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(
                (1 - (float) curCountDownTime / countdownTime), 1.F);
        valueAnimator.setDuration(animDuration * 1000);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);
        return valueAnimator;
    }

这里动画值的范围设置为(1-(float)curCountDownTime/countdownTime,1),curCountDownTime等于countdownTime,取值范围从0到1,插值器使用new LinearInterpolator()均匀变化。

最后,还需要一个Runnable来启动倒计时

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            if (null != countDownTimeListener) {
                countDownTimeListener.getCurCountDownTime(curCountDownTime);
            }
            curCountDownTime--;
            handler.postDelayed(this, 1000);
        }
    };

最后的最后就是设置一个监听接口CountDownTimeListener把我们需要的参数放出去

public interface CountDownTimeListener {
        /**
         * 获取当前冷却时间
         * @param time
         */
        void getCurCountDownTime(int time);

        /**
         * 冷却计时结束
         */
        void countDownFinish();
    }

四、调用方法

coldDownTimer = (ColdDownTimer) findViewById(R.id.coldDownTimer);
coldDownTimer.setWaitHint("充能中...");// 冷却中提示
coldDownTimer.setCountTime(10);// 总冷却时间
// coldDownTimer.setCurCountTime(5);// 设置当前冷却时间
coldDownTimer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                coldDownTimer.startCountdown();
            }
        });

coldDownTimer.setOnCountDownTimeListener(new ColdDownTimer.CountDownTimeListener() {
            @Override
            public void getCurCountDownTime(int time) {
                if (time > 0) {
                    countTimeTv.setText(Utils.transferTime(time));
                    countTimeTv.setVisibility(View.VISIBLE);
                } else {
                    countTimeTv.setVisibility(View.GONE);
                }
            }

            @Override
            public void countDownFinish(){
                countTimeTv.setVisibility(View.GONE);
            }
        });

如果不想从设置的总冷却时间开始计时,可以配置

coldDownTimer.setCurCountTime(5);// 设置当前冷却时间

好啦,这样我们的自定义圆形图片冷却倒计时就做好了,非常简单的一个小例子。写博客没经验,感觉描述的还是比较乱,就当练手了

源码传送地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现倒计时圆形进度,你可以创建一个自定义的 View,使用 Canvas 和 Paint 来绘制圆形和进度条。以下是一个示例代码: ```java public class CountdownCircleView extends View { private int maxProgress = 100; // 总进度 private int progress = 100; // 当前进度 private int circleColor = Color.GRAY; // 圆形颜色 private int progressColor = Color.BLUE; // 进度条颜色 private Paint circlePaint; private Paint progressPaint; public CountdownCircleView(Context context) { super(context); init(); } public CountdownCircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public CountdownCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { circlePaint = new Paint(); circlePaint.setAntiAlias(true); circlePaint.setColor(circleColor); progressPaint = new Paint(); progressPaint.setAntiAlias(true); progressPaint.setColor(progressColor); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int viewWidth = getWidth(); int viewHeight = getHeight(); // 绘制圆形 int diameter = Math.min(viewWidth, viewHeight); int radius = diameter / 2; int centerX = viewWidth / 2; int centerY = viewHeight / 2; canvas.drawCircle(centerX, centerY, radius, circlePaint); // 绘制进度条 RectF rectF = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius); float sweepAngle = 360f * progress / maxProgress; canvas.drawArc(rectF, -90, sweepAngle, true, progressPaint); } public void setMaxProgress(int maxProgress) { this.maxProgress = maxProgress; } public void setProgress(int progress) { this.progress = progress; invalidate(); // 重新绘制 } } ``` 你可以将上述代码放入你的 Android 项目中,并在布局文件中使用 `CountdownCircleView`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值