前言
市場上的應用大多的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);
}
}
整體的思路就是:
- 如果服務端返回的數(shù)據(jù)為空,則清空緩存中的數(shù)據(jù),下次打開App,不再展示
- 根據(jù)網(wǎng)絡環(huán)境的不同下載圖片约郁,wifi全部下載缩挑,非wifi環(huán)境只隨機下載一張
- 設置一個全局變量,統(tǒng)計下載圖片的個數(shù)鬓梅,或成功或失敗供置,當下載的圖片個數(shù)(下載成功+下載失敗) = 服務端返回的圖片個數(shù)時,證明圖片下載結束绽快,則保存本次下載成功的圖片信息
- 保存圖片的信息時芥丧,只隨機保存一張,下次直接展示
- 由于圖片加載庫本身就有緩存坊罢,所以沒有判斷服務端返回的圖片地址在本地有沒有緩存续担,采用直接加載的方式,讓框架幫忙處理該圖片是否需要下載活孩,在這里也可先判斷有無下載過該張圖片物遇,沒有則下載,有則不下載憾儒,采用兩種方式均可询兴。
- 在這里需要注意的是,當圖片地址為空時起趾,也會走到圖片下載成功處理中诗舰,所以在這里需要注意,本項目采用的是在顯示的時候训裆,增加判斷始衅,參見下面代碼
顯示圖片
/**
* 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();
}
}
}
整體的思路就是:
- 從本地緩存中取出圖片信息,判斷數(shù)據(jù)是否合法
- 在圖片緩存中查找該圖片有無緩存
- 各種參數(shù)不合法以及加載圖片失敗和未知異常缭保,都直接執(zhí)行正常的業(yè)務流程
- 屬性動畫設置進度汛闸,且監(jiān)聽該動畫,動畫結束時艺骂,執(zhí)行正常業(yè)務流程
- 點擊跳過或背景诸老,結束動畫,執(zhí)行對應流程
注意事項
- 手動結束動畫的時候钳恕,如上面的
endAnimation()
方法别伏,也最終會走到動畫的onAnimationEnd
回調中,這樣會導致打開頁面時忧额,會打開兩次頁面厘肮,所以使用mIsRealEndAnimator
變量來區(qū)別是否時正常的結束動畫 - 在打開開屏頁的時候,這個時候立即按Home鍵睦番,應用進入后臺中类茂,這個時候過三秒應用又會重新打開耍属,是因為應用進入后臺時我們沒有結束動畫,在這里需要特殊處理下巩检,使用相同的思路厚骗,利用
mIsRealIntoBackground
變量來區(qū)別應用是否真正進入后臺,在onStop()
startPage()
和onRestart()
方法中有所體現(xiàn)
總結
同一個需求實現(xiàn)的方式可能有很多兢哭,但最終的目的只有一個领舰,感謝你耐心的看完了整篇文章,希望可以給你有參考的價值迟螺。