Splash頁面三秒跳轉和動態(tài)下載背景圖

前言

市場上的應用大多的Splash頁都添加了動態(tài)背景圖的功能拭卿,不知道這種行為的專業(yè)名詞是什么墨礁,在我們公司里把這個叫做開屏圖醋奠,這些都不重要榛臼,名稱只是方便我們描述,所以下文中都統(tǒng)稱為開屏圖窜司。它的樣式相信大家都不陌生見下圖:

開屏圖

需求分析

  • 開屏圖背景和跳轉地址可在后臺配置沛善,跳轉地址可為空,客戶端請求接口判斷后臺是否配置塞祈,當沒有數(shù)據(jù)時金刁,使用默認圖片
  • 頁面默認3s倒計時,點擊跳過結束倒計時议薪,直接跳過
  • 當配置了跳轉地址時尤蛮,點擊背景跳轉至對應的web頁
  • 動態(tài)替換,當后臺配置多張圖片時斯议,在wifi環(huán)境下全部下載产捞,下次打開App時,隨機展示一張哼御,在非wifi環(huán)境下只隨機下載一張坯临,下次打開App,展示最新圖片
  • 該web跳轉符合業(yè)務流程(每個公司各有特殊恋昼,至于打開web頁的流程不在此展開描述)

詳細設計

本項目下載圖片使用的是傳統(tǒng)圖片加載庫Android-Universal-Image-Loader看靠,當然還可以選擇其他圖片庫,但實現(xiàn)思路基本不變液肌。

準備工作
“跳過”按鈕屬于自定義View范疇挟炬,需要自己實現(xiàn),代碼如下:

/**
 * Created by zs on 2017/6/8.
 *
 * 圓形進度View
 */

public class RoundProgressView extends View {

    /** 畫筆 */
    private Paint mPaint;

    /** 字體大小 */
    private float mTextSize;

    /** 圓環(huán)寬度 */
    private float mRoundWidth;

    /** 圓環(huán)顏色 */
    private int mRoundColor;

    /** 圓環(huán)進度顏色 */
    private int mRoundProgressColor;

    /** 圓環(huán)進度 */
    private int mProgress;

    /** 繪制圓弧對象 */
    private RectF mOval;

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

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

    public RoundProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    /**
     * 初始化
     */
    private void init() {
        setBackgroundResource(R.drawable.shape_gray_circle);
        mPaint = new Paint();
        mTextSize = ScreenUtils.sp2px(getResources(), 14);
        mRoundWidth = ScreenUtils.dp2px(getResources(), 2);
        mRoundColor = getResources().getColor(R.color.color_10000000);
        mRoundProgressColor = Color.WHITE;
        mOval = new RectF();
    }

    @Override
    protected void onDraw(Canvas canvas) {

        /*第一步:繪制最外層圓環(huán)*/
        int center = getWidth() / 2;
        mPaint.setAntiAlias(true);
        mPaint.setColor(mRoundColor);
        mPaint.setStrokeWidth(mRoundWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        int radius = (int) (center - mRoundWidth / 2);
        canvas.drawCircle(center, center, radius, mPaint);

        /*第二步:繪制正中間的文本*/
        mPaint.setTextSize(mTextSize);
        float textWidth = mPaint.measureText("跳過");
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(0);
        canvas.drawText("跳過", center - textWidth / 2, center + mTextSize / 3, mPaint);

        /*第三步:繪制圓弧*/
        mOval.set(center - radius, center - radius, center + radius, center + radius);
        mPaint.setColor(mRoundProgressColor);
        mPaint.setStrokeWidth(mRoundWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawArc(mOval, -90, 360 * mProgress / 100, false, mPaint);
    }

    /**
     * 設置圓環(huán)進度
     *
     * @param progress 進度
     */
    public void setProgress(int progress){
        this.mProgress = progress;
        if(progress > 100){
            this.mProgress = 100;
        }
        postInvalidate();
    }
}

當然嗦哆,上面的代碼也可以自定義屬性來增加擴展性谤祖,由于這個組件在本項目中可復用性較低,暫沒有自定義屬性吝秕。使用起來也比較簡單,采用屬性動畫來改變進度:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
    <ImageView
        android:id="@+id/iv_splash"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/splash"
        android:scaleType="fitXY"/>
    <com.xxx.xx.RoundProgressView
        android:id="@+id/round_progress"
        android:layout_width="@dimen/dimen_88"
        android:layout_height="@dimen/dimen_88"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginEnd="@dimen/dimen_30"
        android:layout_marginRight="@dimen/dimen_30"
        android:layout_marginTop="@dimen/dimen_40"
        android:visibility="gone"
        tools:visibility="visible"/>
</RelativeLayout>
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int progress = (int) animation.getAnimatedValue();
        mRoundProgress.setProgress(progress);
    }
});
mValueAnimator.setDuration(3000);
mValueAnimator.start();

下載圖片
請求接口泊脐,接口返回圖片信息,使用ImageLoader下載圖片烁峭,見以下代碼:

/**
 * Created by zs on 2017/6/9.
 *
 * 下載開屏頁圖片
 */

public class SplashImageDownload {

    /** 本地存儲的最終圖片數(shù)據(jù) */
    private SplashAdResp.DataResp.ContentResp mSplashAdContent = new SplashAdResp.DataResp.ContentResp();

    /** 圖片的數(shù)量 */
    private int mImageSize;

    /** 已加載的圖片數(shù)量 */
    private int mLoadedImageSize;

    /** 服務器端圖片集合 */
    private List<SplashAdResp.DataResp.ContentResp.ItemResp> mNetItemList;

    public SplashImageDownload(SplashAdResp.DataResp.ContentResp contentResp){
        mImageSize = 0;
        mLoadedImageSize = 0;
        this.startDownload(contentResp);
    }

    /**
     * 開始下載圖片信息
     *
     * @param contentResp 圖片信息
     */
    private void startDownload(SplashAdResp.DataResp.ContentResp contentResp) {
        if(contentResp == null){
            LogUtil.D("splash_down: 加載圖片時服務端圖片信息數(shù)據(jù)異常");
            return;
        }

        mNetItemList = contentResp.advertisementImages;
        if(mNetItemList == null){
            LogUtil.D("splash_down: 加載圖片時服務端圖片信息數(shù)據(jù)異常");
            return;
        }

        if(mNetItemList.isEmpty()){
            //清空本地存儲數(shù)據(jù)
            PreferencesUtils.clearString(getContext(),Constants.SPLASH_AD_RANDOM_SHOW);
            LogUtil.D("splash_down: 加載圖片時服務端圖片信息數(shù)據(jù)為空");
            return;
        }

        mSplashAdContent.advertisementImages = new Vector<>();
        DisplayImageOptions options = new DisplayImageOptions.Builder().cacheOnDisk(true).build();

        //非wifi環(huán)境只隨機下載一張圖片
        if(!NetWorkUtils.isWifiConnected()){
            mImageSize = 1;
            Random random = new Random();
            final int index = random.nextInt(mNetItemList.size());
            ImageLoader.getInstance().loadImage(mNetItemList.get(index).imageUrl,options,new SimpleImageLoadingListener(){
                @Override
                public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                    super.onLoadingComplete(imageUri, view, loadedImage);
                    mSplashAdContent.advertisementImages.add(mNetItemList.get(index));
                    LogUtil.D("splash_down: 非wifi加載成功 --->" + imageUri);
                    setTempImageSize();
                }

                @Override
                public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                    super.onLoadingFailed(imageUri, view, failReason);
                    LogUtil.D("splash_down: 非wifi加載失敗地址 --->" + imageUri);
                    setTempImageSize();
                }
            });
            return;
        }

        //保存圖片集合大小
        mImageSize = mNetItemList.size();

        for (int i = 0; i < mNetItemList.size(); i++) {
            final int itemIndex = i;
            ImageLoader.getInstance().loadImage(mNetItemList.get(i).imageUrl,options,new SimpleImageLoadingListener(){
                @Override
                public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                    super.onLoadingComplete(imageUri, view, loadedImage);
                    mSplashAdContent.advertisementImages.add(mNetItemList.get(itemIndex));
                    LogUtil.D("splash_down: 加載成功索引值 --->" + itemIndex);
                    setTempImageSize();
                }

                @Override
                public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                    super.onLoadingFailed(imageUri, view, failReason);
                    LogUtil.D("splash_down: 加載失敗索引值 --->" + itemIndex);
                    LogUtil.D("splash_down: 加載失敗地址 --->" + imageUri);
                    setTempImageSize();
                }
            });
        }
    }

    /**
     * 標記下載圖片數(shù)量
     */
    private void setTempImageSize(){
        mLoadedImageSize++;

        //已加載完成
        if (mLoadedImageSize == mImageSize) {
            downloadFinished();//下載完成保存圖片信息
        }

    }

    /**
     * 下載完成保存圖片信息
     */
    private void downloadFinished() {

        boolean isSuccess =
                mSplashAdContent != null && mSplashAdContent.advertisementImages != null &&
                        mSplashAdContent.advertisementImages.size() > 0;
        if (isSuccess) {
            Random random = new Random();
            int index = random.nextInt(mSplashAdContent.advertisementImages.size());
            String randomJson = new Gson().toJson(mSplashAdContent.advertisementImages.get(index));
            PreferencesUtils.putString(getContext(), Constants.SPLASH_AD_RANDOM_SHOW, randomJson);
            LogUtil.D("splash_down: 存儲顯示圖片數(shù)據(jù)成功" + randomJson);
        }
    }

整體的思路就是:

  1. 如果服務端返回的數(shù)據(jù)為空,則清空緩存中的數(shù)據(jù),下次打開App,不再展示
  2. 根據(jù)網(wǎng)絡環(huán)境的不同下載圖片约郁,wifi全部下載缩挑,非wifi環(huán)境只隨機下載一張
  3. 設置一個全局變量,統(tǒng)計下載圖片的個數(shù)鬓梅,或成功或失敗供置,當下載的圖片個數(shù)(下載成功+下載失敗) = 服務端返回的圖片個數(shù)時,證明圖片下載結束绽快,則保存本次下載成功的圖片信息
  4. 保存圖片的信息時芥丧,只隨機保存一張,下次直接展示
  5. 由于圖片加載庫本身就有緩存坊罢,所以沒有判斷服務端返回的圖片地址在本地有沒有緩存续担,采用直接加載的方式,讓框架幫忙處理該圖片是否需要下載活孩,在這里也可先判斷有無下載過該張圖片物遇,沒有則下載,有則不下載憾儒,采用兩種方式均可询兴。
  6. 在這里需要注意的是,當圖片地址為空時起趾,也會走到圖片下載成功處理中诗舰,所以在這里需要注意,本項目采用的是在顯示的時候训裆,增加判斷始衅,參見下面代碼

顯示圖片

/**
 * update by zs on 17/6/6 
 */
public class SplashActivity extends BaseFragmentActivity {

    /** 倒計時進度View */
    private RoundProgressView mRoundProgress;

    /** splash背景 */
    private ImageView mIvSplash;

    /** 屬性動畫 */
    private ValueAnimator mValueAnimator;

    /** 動畫是否真正結束 */
    private boolean mIsRealEndAnimator = true;

    /** 頁面是否真正進入后臺 */
    private boolean mIsRealIntoBackground = true;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_splash);

        mRoundProgress = (RoundProgressView) findViewById(R.id.round_progress);
        mIvSplash = (ImageView) findViewById(R.id.iv_splash);

        try {
            showSplashBackground();//展示開屏圖背景
        } catch (Exception e) {
            startPage();//發(fā)生異常跳轉對應頁面
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        startPage();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(mIsRealIntoBackground){
            endAnimation();
        }
    }

    /**
     * 展示開屏圖背景
     */
    private void showSplashBackground() {
        //獲取本地存儲圖片信息
        String json = PreferencesUtils.getString(getApplicationContext(), Constants.SPLASH_AD_RANDOM_SHOW);
        final SplashAdResp.DataResp.ContentResp.ItemResp itemResp = new Gson().fromJson(json, SplashAdResp.DataResp.ContentResp.ItemResp.class);

        //為空判斷
        if(itemResp == null || TextUtils.isEmpty(itemResp.imageUrl)){
            postDelay();//存儲廣告圖片信息為空
            LogUtil.D("splash_down: 隨機取出的圖片信息異常");
            return;
        }

        //查詢本地有無存儲該圖片
        File file = ImageLoader.getInstance().getDiskCache().get(itemResp.imageUrl);
        if(file == null || !file.exists()){
            LogUtil.D("splash_down: 本地沒有存儲該圖片");
            postDelay();
            return;
        }

        LogUtil.D("splash_down: 隨機取出的圖片信息 " + itemResp.toString());

        //加載圖片信息
        DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageForEmptyUri(R.drawable.splash)//url為空
                .showImageOnFail(R.drawable.splash).build();//加載異常
        ImageLoader.getInstance().displayImage(itemResp.imageUrl,mIvSplash, options, new SimpleImageLoadingListener(){
                    @Override
                    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
                        super.onLoadingComplete(imageUri, view, loadedImage);
                        loadImageSuccess(itemResp.url, itemResp.title);
                    }

                    @Override
                    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
                        super.onLoadingFailed(imageUri, view, failReason);
                        postDelay();//加載失敗本地splash頁延遲操作
                        LogUtil.D("splash_down: 加載開屏圖失敗");
                    }
                });
    }

    /**
     * 延遲操作
     */
    private void postDelay() {
        new Handler().postDelayed(new Runnable() {
            public void run() {
                startPage();
            }
        }, 3000);
    }

    /**
     * 延遲完成后跳轉對應頁面
     */
    private void startPage() {
        //啟動頁面回調onStop方法, 此時頁面不是按Home鍵進入后臺
        mIsRealIntoBackground = false ;

        //to do your work
    }

    /**
     * 加載廣告圖片成功處理
     *
     * @param url 挑戰(zhàn)地址
     * @param title 跳轉網(wǎng)頁title
     */
    private void loadImageSuccess(final String url, final String title){
        mValueAnimator = ValueAnimator.ofInt(1, 100);

        if(!TextUtils.isEmpty(url)){
            mIvSplash.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    endAnimation();//結束動畫
                    // 跳轉web頁面
                    finish();
                }
            });
        }

        mRoundProgress.setVisibility(View.VISIBLE);
        mRoundProgress.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                endAnimation();//結束動畫
                startPage();//點擊 跳轉按鈕 跳轉對應頁面
            }
        });

        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int progress = (int) animation.getAnimatedValue();
                mRoundProgress.setProgress(progress);
            }

        });

        mValueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                LogUtil.D("splash_down: 倒計時動畫開始");
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                LogUtil.D("splash_down: 倒計時動畫結束");
                if(mIsRealEndAnimator){
                    startPage();//動畫執(zhí)行結束跳轉對應頁面
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                LogUtil.D("splash_down: 倒計時動畫取消");
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                LogUtil.D("splash_down: 倒計時動畫重復");
            }
        });
        mValueAnimator.setDuration(3000);
        mValueAnimator.start();
    }

    /**
     * 結束動畫
     */
    private void endAnimation(){
        mIsRealEndAnimator = false;
        if (mValueAnimator != null) {
            mValueAnimator.end();
        }
    }
}

整體的思路就是:

  1. 從本地緩存中取出圖片信息,判斷數(shù)據(jù)是否合法
  2. 在圖片緩存中查找該圖片有無緩存
  3. 各種參數(shù)不合法以及加載圖片失敗和未知異常缭保,都直接執(zhí)行正常的業(yè)務流程
  4. 屬性動畫設置進度汛闸,且監(jiān)聽該動畫,動畫結束時艺骂,執(zhí)行正常業(yè)務流程
  5. 點擊跳過或背景诸老,結束動畫,執(zhí)行對應流程

注意事項

  1. 手動結束動畫的時候钳恕,如上面的endAnimation()方法别伏,也最終會走到動畫的onAnimationEnd回調中,這樣會導致打開頁面時忧额,會打開兩次頁面厘肮,所以使用mIsRealEndAnimator變量來區(qū)別是否時正常的結束動畫
  2. 在打開開屏頁的時候,這個時候立即按Home鍵睦番,應用進入后臺中类茂,這個時候過三秒應用又會重新打開耍属,是因為應用進入后臺時我們沒有結束動畫,在這里需要特殊處理下巩检,使用相同的思路厚骗,利用mIsRealIntoBackground變量來區(qū)別應用是否真正進入后臺,在onStop() startPage()onRestart()方法中有所體現(xiàn)

總結

同一個需求實現(xiàn)的方式可能有很多兢哭,但最終的目的只有一個领舰,感謝你耐心的看完了整篇文章,希望可以給你有參考的價值迟螺。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冲秽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子矩父,更是在濱河造成了極大的恐慌锉桑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浙垫,死亡現(xiàn)場離奇詭異刨仑,居然都是意外死亡,警方通過查閱死者的電腦和手機夹姥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門杉武,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辙售,你說我怎么就攤上這事轻抱。” “怎么了旦部?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵祈搜,是天一觀的道長。 經(jīng)常有香客問我士八,道長容燕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任婚度,我火速辦了婚禮蘸秘,結果婚禮上,老公的妹妹穿的比我還像新娘蝗茁。我一直安慰自己醋虏,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布哮翘。 她就那樣靜靜地躺著颈嚼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饭寺。 梳的紋絲不亂的頭發(fā)上阻课,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天叫挟,我揣著相機與錄音,去河邊找鬼柑肴。 笑死霞揉,一個胖子當著我的面吹牛旬薯,可吹牛的內(nèi)容都是我干的晰骑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绊序,長吁一口氣:“原來是場噩夢啊……” “哼硕舆!你這毒婦竟也來了?” 一聲冷哼從身側響起骤公,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抚官,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阶捆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凌节,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年洒试,在試婚紗的時候發(fā)現(xiàn)自己被綠了倍奢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡垒棋,死狀恐怖卒煞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叼架,我是刑警寧澤畔裕,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站乖订,受9級特大地震影響扮饶,放射性物質發(fā)生泄漏。R本人自食惡果不足惜乍构,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一甜无、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜡吧,春花似錦毫蚓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至君仆,卻和暖如春翩概,著一層夾襖步出監(jiān)牢的瞬間牲距,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工钥庇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留牍鞠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓评姨,卻偏偏與公主長得像难述,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吐句,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容