Android專案實戰 ProgressDialog自定義和封裝過程
先上圖
ProgressDialog對於Android開發者來說已經是老朋友了,本意是進度對話方塊,常用於更新包下載進度提示等。本文重點不在於此,主要目標是實現類似上圖中的載入中提示框,探索ProgressDialog在實戰專案中的使用和封裝,這在公司專案中是很重要的一個工具。接下來從簡單的使用開始,詳細內容可檢視ProgressDialog原始碼。
1、簡單使用
ProgressDialog pd1 = new ProgressDialog(this);
pd1.setMessage("載入中...");
...
pd1.show();
ProgressDialog pd2 = ProgressDialog.show(this, "溫馨提示", "載入中...");
上面是ProgressDialog的兩種建立方式,這個根據原始碼可以靈活使用:
public ProgressDialog(Context context) {
super(context);
...
}
public ProgressDialog(Context context, int theme) {
super(context, theme);
...
}
...
public static ProgressDialog show(Context context, CharSequence title,
CharSequence message, boolean indeterminate,
boolean cancelable, OnCancelListener cancelListener) {
ProgressDialog dialog = new ProgressDialog(context);
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setIndeterminate(indeterminate);
dialog.setCancelable(cancelable);
dialog.setOnCancelListener(cancelListener);
dialog.show();
return dialog;
}
以登入為例,點選登入時progressDialog.show()
,同時發起登入網路請求,請求結束時progressDialog.dismiss()
,這是普通情況;考慮特殊情況,如果有兩個網路請求同時發起了,就會呼叫兩次progressDialog.show()
,這時就可能出現兩個progressDialog
重疊的情況(見第一張圖),這種情景在實際專案中是不合理的,想象一下多個progressDialog
重疊的情況。。
通過分析以上情況,可以發現實際專案中可能會有多個網路請求同時進行的情景,但是必須保證progressDialog
只顯示一個,這時就必須考慮封裝一個全域性的progressDialog
了。經過一番嘗試,新的方案出爐了。
2、全域性ProgressDialog的封裝
public class NormalProgressDialog extends ProgressDialog implements DialogInterface.OnCancelListener {
private WeakReference<Context> mContext = new WeakReference<>(null);
private volatile static NormalProgressDialog sDialog;
private NormalProgressDialog(Context context) {
this(context, -1);
}
private NormalProgressDialog(Context context, int theme) {
super(context, theme);
mContext = new WeakReference<>(context);
setOnCancelListener(this);
}
@Override
public void onCancel(DialogInterface dialog) {
// 點手機返回鍵等觸發Dialog消失,應該取消正在進行的網路請求等
Context context = mContext.get();
if (context != null) {
Toast.makeText(context, "cancel", Toast.LENGTH_SHORT).show();
MyHttpClient.cancelRequests(context);
}
}
public static synchronized void showLoading(Context context) {
showLoading(context, "loading...");
}
public static synchronized void showLoading(Context context, CharSequence message) {
showLoading(context, message, true);
}
public static synchronized void showLoading(Context context, CharSequence message, boolean cancelable) {
if (sDialog != null && sDialog.isShowing()) {
sDialog.dismiss();
}
if (context == null || !(context instanceof Activity)) {
return;
}
sDialog = new NormalProgressDialog(context);
sDialog.setMessage(message);
sDialog.setCancelable(cancelable);
if (sDialog != null && !sDialog.isShowing() && !((Activity) context).isFinishing()) {
sDialog.show();
}
}
public static synchronized void stopLoading() {
if (sDialog != null && sDialog.isShowing()) {
sDialog.dismiss();
}
sDialog = null;
}
}
分析上面的程式碼,首先定義了全域性靜態的sDialog
,保證了progressDialog
的唯一;接下來最重要的就是保證progressDialog.show()
的唯一,關鍵程式碼是:
if (sDialog != null && sDialog.isShowing()) {
sDialog.dismiss();
}
sDialog = new NormalProgressDialog(context);
如果當前已經是sDialog.isShowing()
,就先dismiss()
再new NormalProgressDialog()
,確保同時只顯示一個,這樣就解決了多個progressDialog
同時顯示重疊的隱患。
上邊這個方案主要是針對系統自帶的ProgressDialog的封裝,對於普通的專案已經滿足需求了。但是多數情況下需求都會變成第三張圖那樣的風格,針對這種需求一般會考慮自定義ProgressDialog。
3、自定義ProgressDialog
public class CustomProgressDialog extends Dialog implements DialogInterface.OnCancelListener {
private WeakReference<Context> mContext = new WeakReference<>(null);
private volatile static CustomProgressDialog sDialog;
private CustomProgressDialog(Context context, CharSequence message) {
super(context, R.style.CustomProgressDialog);
mContext = new WeakReference<>(context);
@SuppressLint("InflateParams")
View view = LayoutInflater.from(context).inflate(R.layout.dialog_custom_progress, null);
TextView tvMessage = (TextView) view.findViewById(R.id.tv_message);
if (!TextUtils.isEmpty(message)) {
tvMessage.setText(message);
}
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(view, lp);
setOnCancelListener(this);
}
@Override
public void onCancel(DialogInterface dialog) {
// 點手機返回鍵等觸發Dialog消失,應該取消正在進行的網路請求等
Context context = mContext.get();
if (context != null) {
Toast.makeText(context, "cancel", Toast.LENGTH_SHORT).show();
MyHttpClient.cancelRequests(context);
}
}
public static synchronized void showLoading(Context context) {
showLoading(context, "loading...");
}
public static synchronized void showLoading(Context context, CharSequence message) {
showLoading(context, message, true);
}
public static synchronized void showLoading(Context context, CharSequence message, boolean cancelable) {
if (sDialog != null && sDialog.isShowing()) {
sDialog.dismiss();
}
if (context == null || !(context instanceof Activity)) {
return;
}
sDialog = new CustomProgressDialog(context, message);
sDialog.setCancelable(cancelable);
if (sDialog != null && !sDialog.isShowing() && !((Activity) context).isFinishing()) {
sDialog.show();
}
}
public static synchronized void stopLoading() {
if (sDialog != null && sDialog.isShowing()) {
sDialog.dismiss();
}
sDialog = null;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/custom_progress_dialog_bg"
android:gravity="center"
android:orientation="vertical"
android:paddingBottom="20dp"
android:paddingLeft="32dp"
android:paddingRight="32dp"
android:paddingTop="20dp">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateTint="@android:color/white"
android:indeterminateTintMode="src_atop"/>
<TextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textColor="@android:color/white"
tools:text="載入中..."/>
</LinearLayout>
</LinearLayout>
<style name="CustomProgressDialog" parent="@android:style/Theme.Dialog">
<!-- Dialog的windowFrame框為無 -->
<item name="android:windowFrame">@null</item>
<!-- 是否浮現在activity之上 -->
<item name="android:windowIsFloating">true</item>
<!-- 是否半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 是否顯示title -->
<item name="android:windowNoTitle">true</item>
<!-- 設定dialog的背景 -->
<item name="android:background">@android:color/transparent</item>
<item name="android:windowBackground">@android:color/transparent</item>
<!-- 用來控制灰度的值,當為1時,介面除了我們的dialog內容是高亮顯示的,dialog以外的區域是黑色的,完全看不到其他內容,系統的預設值是0.5 -->
<item name="android:backgroundDimAmount">0.2</item>
<!-- 是否模糊 -->
<item name="android:backgroundDimEnabled">true</item>
</style>
首先這裡沒有繼承ProgressDialog而是選擇了Dialog,一點是因為Dialog更靈活,這點從繼承關係可以看出:
<img src="http://upload-images.jianshu.io/upload_images/1868508-937f1def072b2849.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"/>
另一點是從Android O開始ProgressDialog被拋棄了:
<img src="http://upload-images.jianshu.io/upload_images/1868508-d46fb8f7f9936993.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"/>
所以繼承Dialog顯然更合適。
這裡自定義ProgressDialog最關鍵的是自定義佈局和樣式:
private CustomProgressDialog(Context context, CharSequence message) {
super(context, R.style.CustomProgressDialog);
mContext = new WeakReference<>(context);
@SuppressLint("InflateParams")
View view = LayoutInflater.from(context).inflate(R.layout.dialog_custom_progress, null);
TextView tvMessage = (TextView) view.findViewById(R.id.tv_message);
if (!TextUtils.isEmpty(message)) {
tvMessage.setText(message);
}
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(view, lp);
setOnCancelListener(this);
}
這幾步操作就實現了自定義風格樣式,也滿足了產品的需求。其他程式碼跟前面封裝系統自帶的ProgressDialog一樣,都容易理解。到此,自定義ProgressDialog完成了。
4、more important
以上是針對公司實際專案中常用的載入提示對話方塊的探索和封裝過程。下面延伸一點,探討一下點手機返回鍵等觸發Dialog消失,同時取消正在進行的網路請求的情況,這個需求其實是很重要的,但是好多專案都沒有處理,所以遺留了很大的隱患。比如正在顯示對話方塊時當前Activity執行了finish,請求成功時處理UI就容易造成空指標。針對這個問題,必須在Dialog非正常消失時及時取消當前正在進行的網路請求。要取消網路請求,就得先找專案中使用的網路框架對應的取消請求的方法,這裡以android-async-http為例:
android-async-http
取消網路請求的方法在AsyncHttpClient中:
public class AsyncHttpClient {
public void cancelRequests(final Context context, final boolean mayInterruptIfRunning) {
private void cancelRequests(final List<RequestHandle> requestList, final boolean mayInterruptIfRunning) {
public void cancelAllRequests(boolean mayInterruptIfRunning) {
public void cancelRequestsByTAG(Object TAG, boolean mayInterruptIfRunning) {
這幾個方法具體的使用和區別就不講了,可以看原始碼研究一下。這裡以第一個方法為例看一下封裝過程。
使用網路框架時一般都需要定義一個全域性的Client類來負責網路請求的排程,以android-async-http
,通常會用下面的方式:
public class MyHttpClient {
private static final AsyncHttpClient mClient = new AsyncHttpClient();
static {
try {
mClient.setTimeout(15000); // 預設10秒超時
if(BuildConfig.DEBUG){
mClient.setSSLSocketFactory(MySSLSocketFactory.getFixedSocketFactory()); //不驗證照
}else{
mClient.setSSLSocketFactory(SSLSocketFactory.getSocketFactory()); //驗證照
}
mClient.addHeader("app-platform", "Android");
mClient.addHeader("Content-Type", "application/json");
} catch (Exception e) {
e.printStackTrace();
}
}
public static AsyncHttpClient getHttptClient(){
return mClient;
}
/**
* 取消與當前context關聯的所有請求
* @param context 當前context
*/
public static void cancelRequests(Context context){
mClient.cancelRequests(context, true);
}
/**
* 取消所有請求
*/
public static void cancelAllRequests(){
mClient.cancelAllRequests(true);
}
}
以上面的封裝方式看,只需要在Dialog消失的時候呼叫cancelRequests(Context context)
就可以達到取消網路請求的目的。所以接下來重點看Dialog的消失事件,這個前面的程式碼已經給出了:
public class CustomProgressDialog extends Dialog implements DialogInterface.OnCancelListener {
private WeakReference<Context> mContext = new WeakReference<>(null);
...
private CustomProgressDialog(Context context, CharSequence message) {
super(context, R.style.CustomProgressDialog);
mContext = new WeakReference<>(context);
...
setOnCancelListener(this);
}
@Override
public void onCancel(DialogInterface dialog) {
// 點手機返回鍵等觸發Dialog消失,應該取消正在進行的網路請求等
Context context = mContext.get();
if (context != null) {
Toast.makeText(context, "cancel", Toast.LENGTH_SHORT).show();
MyHttpClient.cancelRequests(context);
}
}
到此,點手機返回鍵等觸發Dialog消失,同時取消正在進行的網路請求初步實現了,但是這裡只是針對cancelRequests(Context context)
的探索,每個專案使用的網路框架不一樣,取消的方法也不一樣,需要針對不同的需求對症下藥才能真正解決這個問題。
ProgressDialog封裝就到這裡了,這裡只是我個人的一些探索和想法,並沒有解決實際的需求,具體需要針對產品的需求和專案的情況設計對應的方案,沒有最好的方案,只有最合適的方案。相關程式碼都在android-blog-samples。
~ the end ~
相關文章
- iOS專案開發實戰——配置自定義動畫iOS動畫
- (轉)Android 自定義Dialog實現步驟及封裝Android封裝
- android實踐專案七自定義的SpinnerAndroid
- MySQL使用之五_自定義函式和自定義過程MySql函式
- 鴻蒙專案實戰(三):自定義彈窗開發實踐鴻蒙
- MVP 在專案中的最佳實戰(封裝篇)MVP封裝
- Android 自定義 View 實戰之 PuzzleViewAndroidView
- Android 自定義 View 實戰之 StickerViewAndroidView
- Kolin 更優雅的封裝ProgressDialog封裝
- 小程式自定義modal彈窗封裝實現封裝
- 自定義msi安裝包的執行過程
- Android自定義日曆控制元件的實現過程詳解Android控制元件
- 專案實施過程
- Android 專案編譯過程Android編譯
- iOS專案開發實戰——自定義設定導航欄和狀態列背景iOS
- Android 自定義View實戰系列 :時間軸AndroidView
- SpringBoot專案實戰(7):自定義異常處理介面Spring Boot
- 封裝 Laravel 自定義表單請求封裝Laravel
- 前端自定義類事件回撥封裝前端事件封裝
- iOS專案開發實戰——自定義控制元件背景與顏色iOS控制元件
- Android 自定義 View 實戰之角度選擇器AndroidView
- 案例展示自定義C函式的實現過程函式
- 自定義右鍵選單實現過程詳解
- iOS Swift和OC專案中自定義LogiOSSwift
- Android專案中自定義頂部標題欄Android
- GWC和GAT專案搭建過程
- 建立自定義專案模板
- Android MVP模式專案實戰AndroidMVP模式
- 自定義物件封裝資料警告解決物件封裝
- Android自定義View封裝的android頁面載入的幾種情況!AndroidView封裝
- Android Studio 1.0.2專案實戰——從一個APP的開發過程認識Android StudioAndroidAPP
- iOS專案開發實戰——自定義圓形進度提示控制元件iOS控制元件
- (9)mysql 中的儲存過程和自定義函式MySql儲存過程函式
- Android專案實戰之高仿網易雲音樂建立專案和配置Android
- Maven安裝本地的jar包和建立帶模板的自定義專案MavenJAR
- 實戰儲存過程排程過程儲存過程
- 08.Django自定義模板,自定義標籤和自定義過濾器Django過濾器
- 一段VUE程式碼:透過元件封裝全域性方法、自定義指令和註冊元件Vue元件封裝