自定义闪屏页广告倒计时view

如今APP越来越多,我们每天所使用的的软件也越来越多,可是在我们不付费的情况下,App制造商如何变现,实现收入甚至是盈利呢?答案就是在我们打开软件所必须经过的地方穿插广告,当然为了顾及用户的感受,一般都会以倒计时的形式展示给用户,用户可以选择跳过.可能是因为自己的强迫症,总想着是怎么做的,自己就尝试了一下,分享给大家的同时,顺便加深自己的理解.效果如图:
这里写图片描述
这里写图片描述
1.为了满足产品和设计,先搞几个自定义属性
1)内层背景
2)数字的颜色
3)外层圆环宽度
4)文字大小
5)外层圆环颜色
6)圆的半径
这里,我的外环颜色和文字颜色相同,具体的自定义属性如下:

<declare-styleable name="AdTimePickView">
        <attr name="mSmallCircleBg" format="color"></attr>
        <attr name="mTextSize1" format="dimension"></attr>
        <attr name="mTextColor1" format="color"></attr>
        <attr name="mProgressWidth" format="dimension"></attr>
        <attr name="mRadius" format="dimension"></attr>
    </declare-styleable>

2.在自定义View的构造方法中读取自定义属性:

mProgressViewWidth = typedArray.getDimensionPixelSize(R.styleable.AdTimePickView_mProgressWidth, DEFAULT_PROGRESS_WIDTH);
        mRadius = typedArray.getDimensionPixelSize(R.styleable.AdTimePickView_mRadius1, DEFAULT_RADIUS);
        mSmallCircleBg = typedArray.getColor(R.styleable.AdTimePickView_mSmallCircleBg, Color.parseColor(DEFAULT_BG_COLOR));
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.AdTimePickView_mTextSize1, DEFAULT_TEXT_SIZE);
        mTextColor = typedArray.getColor(R.styleable.AdTimePickView_mTextColor1, Color.parseColor(DEFAULT_TEXT_COLOR));

3.重写onMeasure()方法,
根据宽高得出半径,为什么不适用自定义半径呢?因为根据宽高得出的半径才是这个View的内切圆半径,自定义半径只是为了在根据宽高无法得出半径的情况下才使用的.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = getViewSize(widthMeasureSpec, 0);
        mHeight = getViewSize(heightMeasureSpec, 1);
        mRadius = Math.min(mWidth, mHeight) / 2;
        setMeasuredDimension(mWidth, mHeight);
    }

    getViewSize方法如下:

    private int getViewSize(int viewMeasureSpec, int type) {
        int viewValue = 0;
        int viewSize = MeasureSpec.getSize(viewMeasureSpec);
        int viewMode = MeasureSpec.getMode(viewMeasureSpec);
        if (MeasureSpec.EXACTLY == viewMode) {
            viewValue = viewSize;
            if (type == 0) {
                mCirCleX = viewSize / 2;
            } else {
                mCircleY = viewSize / 2;
            }
        } else {
            if (type == 0) {
                mCirCleX = mRadius;
            } else {
                mCircleY = mRadius;
            }
            viewValue = 2 * (mRadius + mProgressViewWidth);
        }
        return viewValue;
    }

4.onDraw方法进行绘制
1)绘制内层圆

canvas.drawCircle(mCirCleX, mCircleY, (float) (mRadius - 1.5 * mProgressViewWidth), mPaint);

2)绘制文字,要计算好文字的位置,保持居中

        Rect textRect = getTextRect(String.valueOf(mAdTIme));
        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        int baseLine = (int) (mHeight / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);
        int x = mWidth / 2 - textRect.width() / 2;
        canvas.drawText(String.valueOf(mAdTIme), x, baseLine, mTextPaint);
//获取绘制内容的Rect        
private Rect getTextRect(String centerContent) {
        Rect rect = new Rect();
        mTextPaint.getTextBounds(centerContent, 0, centerContent.length(), rect);
        return rect;
    }

3)绘制外层不断刷新的圆环
原理:从360度开始每隔一段时间进行圆弧绘制,角度分别为:360,359,1,0,因此需要一个轮询器,不断的去绘制刷新.
绘制圆弧的代码:

        //保存Canvans的状态,因为绘制其他地方时,Canvas坐标系不需要变化
        canvas.save();
        //将坐标系围绕View的中心逆时针旋转90度数,为了从正上方开始绘制
        canvas.rotate(-90, mCirCleX, mCircleY);
        //计算圆弧的RectF
        RectF rectF = new RectF(mCirCleX - mRadius + mProgressViewWidth, mCirCleX - mRadius + mProgressViewWidth, mCirCleX + mRadius - mProgressViewWidth, mCirCleX + mRadius - mProgressViewWidth);
        //第四个参数表示逆时针还是顺时针绘制
        canvas.drawArc(rectF, 0, -mCurrentAngle, false, mPaint);
        //恢复坐标系
        canvas.restore();

5.刷新的轮询器

1)使用RxAndroid和lambda实现

//interval操作符:从1开始每隔一段时间发射递增的数
Observable.interval(1, TIME_DIFF, TimeUnit.MILLISECONDS)
                //map操作符将发射的数据转换成我们需要的数据
                .map(value -> {
                    return countAngel - value.intValue();
                })
                //限制发射的数据个数,让其停止,负责会一直发射下去
                .limit(361)
                //接收结果并处理
                .subscribe(action -> {
                    if (action % 72 == 0) {
                        mAdTIme = action / 72;
                    }
                    mCurrentAngle = action;
                    AdTimePickView.this.postInvalidate();
                });

2)使用线程的方式实现

new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 360; i>=0;i--){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (i % 72 == 0) {
                        mAdTIme = i / 72;
                    }
                    mCurrentAngle = i;
                    AdTimePickView.this.postInvalidate();
                }
            }
        }).start();

OK,这样我们的广告倒计时View就完成了,欢迎大家指正.

附:整个自定义View的代码

public class AdTimePickView extends View {
private Paint mPaint;
private Paint mTextPaint;
//大圆半径
private int mRadius = 200;

//内层小圆背景
private int mSmallCircleBg = Color.parseColor("#66f1679b");

//小圆外层线条宽度
private int mProgressViewWidth = 10;

private float mCurrentAngle;

private static final int TIME_DIFF = 25;

//圆心坐标
private int mCirCleX;
private int mCircleY;

//测量之后View的宽高,绘制中心文字时需要用到
private int mWidth;
private int mHeight;

//中心文字的大小与样式
private int mTextSize;
private int mTextColor;
//广告总时间
private int mAdTIme = 5;
private Context mContext;


public AdTimePickView(Context context) {
    this(context, null);
}

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

public AdTimePickView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AdTimePickView, defStyleAttr, 0);
    mProgressViewWidth = typedArray.getDimensionPixelSize(R.styleable.AdTimePickView_mProgressWidth, 10);
    mRadius = typedArray.getDimensionPixelSize(R.styleable.AdTimePickView_mRadius1, 100);
    mSmallCircleBg = typedArray.getColor(R.styleable.AdTimePickView_mSmallCircleBg, Color.parseColor("#66f1679b"));
    mTextSize = typedArray.getDimensionPixelSize(R.styleable.AdTimePickView_mTextSize1, 20);
    mTextColor = typedArray.getColor(R.styleable.AdTimePickView_mTextColor1, Color.parseColor("#333333"));
    //注意资源的回收
    typedArray.recycle();
    this.mContext = context;
    init();
}

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPaint.setAntiAlias(true);

    mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mTextPaint.setColor(mTextColor);
    mTextPaint.setTextSize(mTextSize);
    mTextPaint.setStyle(Paint.Style.FILL);
    mTextPaint.setAntiAlias(true);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mWidth = getViewSize(widthMeasureSpec, 0);
    mHeight = getViewSize(heightMeasureSpec, 1);
    //大半径
    mRadius = Math.min(mWidth, mHeight) / 2;
    setMeasuredDimension(mWidth, mHeight);
}

private int getViewSize(int viewMeasureSpec, int type) {
    int viewValue = 0;
    int viewSize = MeasureSpec.getSize(viewMeasureSpec);
    int viewMode = MeasureSpec.getMode(viewMeasureSpec);
    if (MeasureSpec.EXACTLY == viewMode) {
        viewValue = viewSize;
        if (type == 0) {
            mCirCleX = viewSize / 2;
        } else {
            mCircleY = viewSize / 2;
        }
    } else {
        if (type == 0) {
            mCirCleX = mRadius;
        } else {
            mCircleY = mRadius;
        }
        viewValue = 2 * (mRadius + mProgressViewWidth);
    }
    return viewValue;
}

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

    mPaint.setColor(mSmallCircleBg);
    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(mCirCleX, mCircleY, (float) (mRadius - 1.5 * mProgressViewWidth), mPaint);

    //设置画笔状态
    mPaint.setColor(mTextColor);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(mProgressViewWidth);
    //保存Canvans的状态
    canvas.save();
    //将坐标系围绕View的中心逆时针旋转90度数
    canvas.rotate(-90, mCirCleX, mCircleY);
    RectF rectF = new RectF(mCirCleX - mRadius + mProgressViewWidth, mCirCleX - mRadius + mProgressViewWidth, mCirCleX + mRadius - mProgressViewWidth, mCirCleX + mRadius - mProgressViewWidth);
    //第四个参数表示逆时针还是顺时针绘制
    canvas.drawArc(rectF, 0, -mCurrentAngle, false, mPaint);
    canvas.restore();

    Rect textRect = getTextRect(String.valueOf(mAdTIme));
    Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
    int baseLine = (int) (mHeight / 2 + (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent);
    int x = mWidth / 2 - textRect.width() / 2;
    canvas.drawText(String.valueOf(mAdTIme), x, baseLine, mTextPaint);
}

private Rect getTextRect(String centerContent) {
    Rect rect = new Rect();
    mTextPaint.getTextBounds(centerContent, 0, centerContent.length(), rect);
    return rect;
}

public void refresh() {
    final int countAngel = 360;
    Observable.interval(1, TIME_DIFF, TimeUnit.MILLISECONDS)
            .map(value -> {
                return countAngel - value.intValue();
            })
            .limit(361)
            .subscribe(action -> {
                if (action % 72 == 0) {
                    mAdTIme = action / 72;
                }
                mCurrentAngle = action;
                AdTimePickView.this.postInvalidate();
            });
}

}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值