美文网首页
自定义View-启动页广告

自定义View-启动页广告

作者: aositeluoke | 来源:发表于2019-03-31 10:59 被阅读0次
    1、概述

      启动页广告几乎无处不在,大部分App都有它的身影,那么它的处理逻辑到底是什么样的呢?我们拭目以待。


    banner.gif
    2、实现流程
    1、启动页

      启动页几乎都会存在拉伸变形和黑白屏这两种情况,要彻底解决这两个问题并不简单,当然,在一些硬性前提下还是可以做到的,首先,启动页图片不要太复杂且非git动画,展示的内容不要太多、一两块区域即可,类似QQ音乐、新浪微博和QQ这样的启动页、只需要在xml中通过<layer-list></layer-list>、设置背景为白色、将内容切图(logo)堆叠起来即可、最后使用xml作为启动页主题的背景就可以避免以上两个大问题了。


    qq音乐启动页.png
    新浪启动页.png QQ启动页.jpg

    2、申请权限
      权限申请这里使用的是第三方开源库AndPermission,这里需要注意的是系统版本大于等于6.0以上才能申请相应的权限,否则,会出现一些异常情况。
    3、接口获取广告内容(是否展示广告、广告下载链接)
      这个步骤没什么好说的。
    4、图片下载
    Retrofit初始化

    OkHttpClient fileClient = new OkHttpClient.Builder()
                    .readTimeout(1, java.util.concurrent.TimeUnit.MINUTES)
                    .cache(cache).build();
    
     mRetrofit = mRetrofit.newBuilder()
                    .client(fileClient)
                    .baseUrl(loadImage)
                    .build();
    

      mRetrofit初始化这里,之前由于OkHttpClient添加拦截器,导致图片下载失败,去掉拦截器即可。具体原因未知
    api声明

    public interface FileApi {
        /**
         * 
         *
         * @return
         */
        @GET
        @Streaming
        Observable<ResponseBody> getSplashBanner(@Url String url);
    }
    

    启动页广告背景图文件夹
    /storage/sdcard/Android/data/com.xxx.xxxx/files/Download/banner

    mBannerDir = new File(mContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "banner");
            if (!mBannerDir.exists()) {
                mBannerDir.mkdirs();
            }
    

    加载启动页广告
      使用下载链接中的后缀作为文件名进行存储,下载前根据名字判断启动页广告是否已经存在,优先使用缓存图片。

    /**
         * API下载启动页广告
         */
        private void loadBanner(String url) {
            String[] urlArray = url.split("/");
            final String fileName = urlArray[urlArray.length - 1];
            File bannerFile = new File(FileUtils.mBannerDir, fileName);
            if (bannerFile.exists()) {
                showSplashBanner(bannerFile);
                return;
            }
    
            Http.http.createDownloadImage(FileApi.class).
                    getSplashBanner(url)
                    .subscribeOn(Schedulers.io())//请求网络 在调度者的io线程
                    .observeOn(Schedulers.io()) //指定线程保存文件
                    .observeOn(Schedulers.computation())
                    .map(new Func1<ResponseBody, Boolean>() {
                        @Override
                        public Boolean call(ResponseBody responseBody) {
                            return writeFileToSDCard(responseBody, fileName);
                        }
                    })
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Action1<Object>() {
                        @Override
                        public void call(Object o) {
                            File futureStudioIconFile = new File(FileUtils.mBannerDir, fileName);
                            showSplashBanner(futureStudioIconFile);
                        }
                    });
    
    
        }
    

    保存图片

     /**
         * @param body
         * @param fileName(含有后缀)
         * @return
         */
        private boolean writeFileToSDCard(ResponseBody body, String fileName) {
            try {
                File futureStudioIconFile = new File(FileUtils.mBannerDir, fileName);
                OutputStream outputStream = null;
                try {
                    futureStudioIconFile.deleteOnExit();
                    futureStudioIconFile.createNewFile();
                    outputStream = new FileOutputStream(futureStudioIconFile);
                    /*使用工具类对图片进行处理*/
                    mTargetBitmap = BitmapUtils.scaleImage(body.bytes(), outSize.x, outSize.y);
                    mTargetBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                    outputStream.flush();
                    return true;
                } catch (IOException e) {
                    return false;
                } finally {
                    if (outputStream != null) {
                        outputStream.close();
                    }
                }
    
            } catch (IOException e) {
                return false;
            }
        }
    

    图片缩放裁剪工具类

    public class BitmapUtils {
        private static final String TAG = "BitmapUtils";
    
        /**
         * 等比例缩放图片
         *
         * @param banner       图片字节数组
         * @param targetWidth  目标宽度
         * @param targetHeight 目标高度
         * @return
         */
        public static  Bitmap scaleImage(byte[] banner, int targetWidth, int targetHeight) {
            Bitmap originBitmap = BitmapFactory.decodeByteArray(banner, 0, banner.length);
            int w = originBitmap.getWidth();
            int h = originBitmap.getHeight();
            Log.i(TAG, "原图高宽: " + h + "*" + w);
            Log.i(TAG, "屏幕高宽: " + targetHeight + "*" + targetWidth);
            float hRatio = targetHeight / (h * 1.0f);//高度缩放hRatio才能铺满屏幕
            float wRatio = targetWidth / (w * 1.0f);//宽度缩放wRatio才能铺满屏幕
            float finalRatio = 0;
    
            /*1、获取缩放比例*/
            if (hRatio >= 1.0f && wRatio >= 1.0f) {
                /*图片小,放大才能铺满屏幕*/
                finalRatio = hRatio > wRatio ? hRatio : wRatio;
            } else if (hRatio >= 1f && wRatio < 1.0f) {
                /*高度需要放大、宽度需要缩小才能铺满屏幕,主流机型不存在该情况,铺满为主要目标,继续放大*/
                finalRatio = hRatio;
            } else if (hRatio < 1f && wRatio >= 1.0f) {
                /*高度需要缩小、宽度需要放大才能铺满屏幕,主流机型不存在该情况,铺满为主要目标,继续放大*/
                finalRatio = wRatio;
            } else {
                /*图片太大需要缩小才能铺满屏幕*/
                finalRatio = hRatio > wRatio ? hRatio : wRatio;
            }
    
            /*不需要缩放*/
            if (finalRatio == 1.0f) {
                return originBitmap;
            }
    
            Bitmap targetBitmap = null;
            /*2、缩放后的图片*/
            Bitmap waitCropBitmap = scaleBitmap(originBitmap, finalRatio);
            Log.i(TAG, "裁剪后的高宽: " + waitCropBitmap.getHeight() + "*" + waitCropBitmap.getWidth());
            /*3、裁剪图片*/
            if (waitCropBitmap.getHeight() > targetHeight) {
                Log.i(TAG, "scaleImage: 高度裁剪");
                int cropH = waitCropBitmap.getHeight() - targetHeight;
                targetBitmap = cropVertical(waitCropBitmap, cropH);
            } else {
                Log.i(TAG, "scaleImage: 宽度裁剪");
                int cropW = waitCropBitmap.getWidth() - targetWidth;
                targetBitmap = cropHorizontalBitmap(waitCropBitmap, cropW);
            }
    
    
            /*4、回收图片*/
            if (originBitmap != null) {
                originBitmap.recycle();
                originBitmap = null;
            }
    
            if (waitCropBitmap != null) {
                waitCropBitmap.recycle();
                waitCropBitmap = null;
            }
    
            return targetBitmap;
    
        }
    
    
        /**
         * 按比例缩放图片
         *
         * @param origin 原图
         * @param ratio  比例
         * @return 新的bitmap
         */
        private static Bitmap scaleBitmap(Bitmap origin, float ratio) {
            Log.i(TAG, "ratio: " + ratio);
            if (origin == null) {
                return null;
            }
            int width = origin.getWidth();
            int height = origin.getHeight();
            Matrix matrix = new Matrix();
            matrix.preScale(ratio, ratio);
            Bitmap newBM = Bitmap.createBitmap(origin, 0, 0, width, height, matrix, false);
            if (newBM.equals(origin)) {
                return newBM;
            }
            origin.recycle();
            return newBM;
        }
    
    
        /**
         * 裁剪头部区域
         *
         * @param bitmap 原图
         * @param height
         * @return 裁剪后的图像
         */
        private static Bitmap cropVertical(Bitmap bitmap, int height) {
            Log.i(TAG, "cropVertical: height/2=" + (height / 2));
            return Bitmap.createBitmap(bitmap, 0, height / 2, bitmap.getWidth(), bitmap.getHeight() - height);
        }
    
    
        /**
         * 裁剪头部区域
         *
         * @param bitmap 原图
         * @param width
         * @return 裁剪后的图像
         */
        private static Bitmap cropHorizontalBitmap(Bitmap bitmap, int width) {
            Log.i(TAG, "cropVertical: width/2=" + (width / 2));
            return Bitmap.createBitmap(bitmap, width / 2, 0, bitmap.getWidth() - width, bitmap.getHeight());
        }
    
    }
    
    3、高仿酷狗跳转控件(SkipView)
    1、需求分析

    1、圆形背景颜色自定义、半径自定义
    2、倒计时时间自定义、圆环宽度自定义、圆环颜色自定义
    3、文字大小、颜色和内容自定义

    2、属性说明
    属性名 默认值 备注
    progress_and_circle_distance 10 圆环与圆形背景的距离
    progress_and_text_distance 年龄 圆环与文字的间距
    progress_width 6 圆环的宽度
    text_size 20 文字大小
    progress_color Color.WHITE 倒计时圆环颜色
    circle_color Color.parseColor("#38342e") 圆形背景颜色
    text_color Color.WHITE 文本颜色
    text 跳过 文本内容
    3、实现流程

    attrs.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="SkipView">
            <attr name="progress_and_circle_distance" format="dimension|reference" />
            <attr name="progress_and_text_distance" format="dimension|reference" />
            <attr name="progress_width" format="dimension|reference" />
            <attr name="text_size" format="dimension|reference" />
            <attr name="progress_color" format="color|reference" />
            <attr name="circle_color" format="color|reference" />
            <attr name="text_color" format="color|reference" />
            <attr name="text" format="string|reference" />
        </declare-styleable>
    </resources>
    

    自定义控件

    
    public class SkipView extends View {
        private Paint mCirclePaint;
        private Paint mTextPaint;
        private int mCircleColor = Color.parseColor("#38342e");
        private float mTextSize = 20;
        private String mText = "跳过";
        private int mTextColor = Color.WHITE;
        private float mProgressWidth = 6;
        private int mProgressColor = Color.WHITE;
        private float mProAndCircleDistance = 10;
        private float mProAndTextDistance = 10;
    
        private Rect mTextRect = new Rect();
        private float mBaseYOffset = 0;
        private float mBaseY = 0;
    
        private Paint mProgressPaint;
        private RectF mProgressRectF = new RectF();
        private ValueAnimator mValueAnimator;
        private float mCurPro;
        private long mSecond = 3;
    
        public SkipView(Context context) {
            this(context, null);
        }
    
        public SkipView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SkipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SkipView, 0, 0);
            mCircleColor = a.getColor(R.styleable.SkipView_circle_color, mCircleColor);
            mTextSize = a.getDimension(R.styleable.SkipView_text_size, mTextSize);
            mText = a.getString(R.styleable.SkipView_text);
            if (TextUtils.isEmpty(mText)) {
                mText = "跳过";
            }
            mTextColor = a.getColor(R.styleable.SkipView_text_color, mTextColor);
            mProgressWidth = a.getDimension(R.styleable.SkipView_progress_width, mProgressWidth);
            mProgressColor = a.getColor(R.styleable.SkipView_progress_color, mProgressColor);
            mProAndCircleDistance = a.getDimension(R.styleable.SkipView_progress_and_circle_distance, mProAndCircleDistance);
            mProAndTextDistance = a.getDimension(R.styleable.SkipView_progress_and_text_distance, mProAndTextDistance);
            a.recycle();
    
    
            /*圆背景*/
            mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mCirclePaint.setColor(mCircleColor);
            /*文字相关*/
            mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mTextPaint.setColor(mTextColor);
            mTextPaint.setTextSize(mTextSize);
            mTextPaint.getTextBounds(mText, 0, mText.length(), mTextRect);
            Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
            mBaseYOffset = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
            /*圆弧*/
            mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
            mProgressPaint.setStrokeWidth(mProgressWidth);
            mProgressPaint.setColor(mProgressColor);
            mProgressPaint.setStyle(Paint.Style.STROKE);
            mValueAnimator = new ValueAnimator();
            mValueAnimator.setDuration(mSecond * 1000);
            mValueAnimator.setFloatValues(-360, 0);
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurPro = ((float) animation.getAnimatedValue());//0~360
                    invalidate();
                }
            });
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
    
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (onTimeOutListener != null) {
                        onTimeOutListener.onTimeOut();
                    }
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
    
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
    
                }
            });
    
        }
    
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = (int) ((mProAndCircleDistance + mProgressWidth + mProAndTextDistance) * 2 + mTextRect.width());
            setMeasuredDimension(width, width);
            mBaseY = getMeasuredHeight() / 2f + mBaseYOffset;
            mProgressRectF.left = mProAndCircleDistance + mProgressWidth / 2f;
            mProgressRectF.top = mProAndCircleDistance + mProgressWidth / 2f;
            mProgressRectF.right = width - mProgressRectF.left;
            mProgressRectF.bottom = width - mProgressRectF.top;
    
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            float halfOfWidth = getMeasuredWidth() / 2f;
            canvas.drawCircle(halfOfWidth, halfOfWidth, halfOfWidth, mCirclePaint);
            canvas.drawText(mText, halfOfWidth - mTextRect.width() / 2f, mBaseY, mTextPaint);
            canvas.drawArc(mProgressRectF, -90, mCurPro, false, mProgressPaint);
        }
    
        public void start(long second) {
            mValueAnimator.setDuration(second * 1000);
            mValueAnimator.start();
        }
    
        OnTimeOutListener onTimeOutListener;
    
        public void setOnTimeOutListener(OnTimeOutListener onTimeOutListener) {
            this.onTimeOutListener = onTimeOutListener;
        }
    
        public interface OnTimeOutListener {
            void onTimeOut();
        }
    
    
        private static final String TAG = "SkipView";
    
        /**
         * Activity销毁时,保证动画销毁
         */
        public void onDestroy() {
            if (mValueAnimator != null) {
                Log.i(TAG, "移除所有监听器: ");
                mValueAnimator.removeAllListeners();
                mValueAnimator.cancel();
                mValueAnimator = null;
            }
        }
    }
    

    绘制过程中,有两点技巧需要牢记。
    1、进度的值由-360~0进行变化

     mValueAnimator.setFloatValues(-360, 0);
    

    2、-90意思是在圆环的中心点的正上方开始绘制、mCurPro的意思是绘制的度数、正值顺时针绘制、负值逆时针绘制

     canvas.drawArc(mProgressRectF, -90, mCurPro, false, mProgressPaint);
    
    4、参考教程

    1、https://www.jb51.net/article/130850.htm
    2、https://blog.csdn.net/yanzhenjie1003/article/details/52503533

    相关文章

      网友评论

          本文标题:自定义View-启动页广告

          本文链接:https://www.haomeiwen.com/subject/ifcybqtx.html