五、自定義SnackBar以及封裝
咳咳,好久不見,因為土豆某天突發奇想,覺得作為一隻猿,還是應該有自己的愛好,所以就去買了一把吉他,然後開始了長達三個多月的擾民生活,然後就沒空寫文章啦。。。扯遠了,上一次說這章原本應該是講popupWindow的,我是想用使用popupWindow選取相簿以及呼叫攝像頭來做案例的,不過安卓6.0之後,為了讓使用者的手機更加安全,所以將一些比較重要的許可權比如開啟攝像頭,需要動態獲得許可權,後來土豆覺得如何優雅的動態獲取許可權也是比較重要的,所以只能先放著。今天本章就講講snackBar吧
廢話不多說,先看圖吧,效果圖展示的是系統樣式的snackBar以及我們自己封裝的snackBar

snackBar.gif
想必大家都是知道toast的,這裡我沒有使用原生的toast,而是用的toasty。(土豆前面有講哦,感興趣的可以看看,不感興趣的也不用管,不影響功能實現)今天的主角SnackBar是谷歌取代推出來取代toast的一個元件,為了不讓toast傷心,土豆都沒有在這個案例中使用toast,暖心吧。。
那麼這時候有好事者又要問了,土豆土豆,那snackBar有什麼優點呢,我覺得toast很好用的啊。要說snackBar的優點的話,我們要先講講toast的缺陷:
第一:toast會重複顯示,比如這樣一個場景,我們點選一個控制元件,會彈出一個toast,但是如果一直點,不斷的點,會發現,toast的提示會顯示很久很久很久,這是因為系統將toast放進一個佇列中,前一個toast關閉後,後一個toast才顯示。網上也有很多解決這個問題的辦法,大多是採用單例來實現,土豆後面也會講。那麼作為競爭對手的snackBar,自然抓住了toast的痛處,不會犯這樣的錯誤啦,snackBar在重複點選時,會立即關閉上一個snackBar,顯示新的snackBar,這樣就提升了使用者體驗
第二:toast作為提示的元件,是不能與使用者進行互動的,就像一個大喇叭,只會把訊息傳遞給你,至於你有啥反應,它通通不管,而snackBar呢,就像一個知心大姐姐,不僅把訊息傳遞給你,還可以接收你的反饋,並作出反應。舉個小例子,我們使用recyclerView的時候,在沒資料的時候需要通知使用者,這次請求沒有資料,toast只會原封不動的告訴使用者沒有資料,但是使用snackBar的話,可以增加一個“重試”的按鈕,這樣一來,使用者不僅知道了這條訊息,還可以與這條訊息進行互動,好感度upupup
第三:顏值高,Em。。顏值高,存在感就高,存在感高,使用者就高興。。。
安利了這麼多,該咋用呢,相信有心急的同學已經迫不及待了,那麼久正式進入snackBar的使用吧。
第一步:snackBar是support design下的一個元件,我們自然要先匯入這個啦,在app級別的gradle中匯入它
compile 'com.android.support:design:26.1.0'
值得注意的是,26.1.0是土豆當前用的sdk,你的不一樣也是正常的
第二步:匯入成功之後,就是開始使用啦,首先講講系統的snackbar
Snackbar.make(toolbar, "這是系統的snackBar", Snackbar.LENGTH_INDEFINITE) .setAction("你瞅啥", new View.OnClickListener() { @Override public void onClick(View view) { ToastMessage.toastWarn("瞅你咋地", true); } }) .show();
看到這個結構,是不是想起了這個
Toast.makeText(UseSnackBarActivity.this,"測試的toast",Toast.LENGTH_SHORT) .show();
沒錯,幾乎一樣的結構,不得不說snackBar可謂是把取其精華,去其槽粕理解的很透徹,這樣也方便我們的學習啦,我們依次來說說這些屬性的作用:
第一個引數是一個view,以這個view作為父佈局,從而控制snackBar顯示的位置,土豆這裡是以我的toorBar控制元件作父佈局的
第二個引數自然是snackBar要顯示的內容啦
第三個屬性是snackBar顯示的時間,分別有LENGTH_SHORT,LENGTH_LONG和LENGTH_INDEFINITE,前兩個屬性和toast一致,後一個是表示一直在這頁面顯示的
第四個是setAction,這就是snackBar的互動特性啦,通過這個可以設定當用戶點選後的事件,onClick中的 ToastMessage.toastWarn("瞅你咋地", true);則是土豆自己的工具類啦,有興趣的可以看看土豆前面寫的第二章哦
可以看到,使用snackBar也是超級方便的,但是有個問題,系統預設的snackBar是黑色的樣式,這樣很容易與我們的APP樣式衝突,因為,我們需要自己來封裝一下snackBar啦
在實際專案中,我們的activity基本都是直接繼承我們的基類BaseActivity的,因此,將snackBar封裝在BaseActivity中,方便又實在。至於如何封裝BaseActivity,我上一篇有說過一點,但是對我們本章影響不大,此處不提,文章最後也會把我目前為止封裝的BaseActivity貼出來,僅供參考,下面是關於snackBar部分的程式碼
/** * 展示snackBar * * @param viewview * @param msg訊息 * @param isDismiss是否自動消失 * @param action事件名 * @param iSnackBarClickEvent 事件處理介面 */ protected void showSnackBar(@NonNull View view, @NonNull String msg, boolean isDismiss, String action, final ISnackBarClickEvent iSnackBarClickEvent) { //snackBar預設顯示時間為LENGTH_LONG int duringTime = Snackbar.LENGTH_LONG; if (isDismiss) { duringTime = Snackbar.LENGTH_LONG; } else { duringTime = Snackbar.LENGTH_INDEFINITE; } Snackbar snackbar; snackbar = Snackbar.make(view, msg, duringTime) .setAction(action, new View.OnClickListener() { @Override public void onClick(View view) { //以介面方式傳送出去,便於使用者處理自己的業務邏輯 iSnackBarClickEvent.clickEvent(); } }); //設定snackBar和toorBar顏色一致 snackbar.getView().setBackgroundColor(getResources().getColor(R.color.toolbar_color)); //設定action文字的顏色 snackbar.setActionTextColor(getResources().getColor(R.color.normal_white)); //設定snackBar圖示 這裡是獲取到snackBar的textView 然後給textView增加左邊圖示的方式來實現的 View snackBarView = snackbar.getView(); TextView textView = (TextView) snackBarView.findViewById(R.id.snackbar_text); Drawable drawable = getResources().getDrawable(R.drawable.icon_vector_notification);//圖片自己選擇 drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight()); textView.setCompoundDrawables(drawable, null, null, null); //增加文字和圖示的距離 textView.setCompoundDrawablePadding(20); //展示snackBar snackbar.show(); } /** * snackBar的action事件 */ public interface ISnackBarClickEvent { void clickEvent(); }
註釋也寫的很詳細了,我就不再每行程式碼進行解釋,但是值得注意的是,我們給snackbar開頭增加了一個圖示,採用的是給textView增加內部圖示一樣。另外,我們還以內部介面的形式將snackBar的action事件派發出去,這樣,任何一個activity都可以通過介面實現各自的業務邏輯了,接下來給出本章案例的程式碼
佈局檔案程式碼:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.administrator.potato.activity.UseSnackBarActivity"> <include layout="@layout/app_toolbar"/> <android.support.design.widget.CoordinatorLayout android:id="@+id/coordinatorLayout" android:layout_marginTop="36dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/buttonNormal" android:text="使用系統snackBar" android:layout_width="match_parent" android:layout_height="50dp" /> </android.support.design.widget.CoordinatorLayout> <android.support.design.widget.CoordinatorLayout android:id="@+id/coordinatorCustom" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/buttonCustom" android:text="使用封裝的snackbar" android:layout_width="match_parent" android:layout_height="50dp" /> </android.support.design.widget.CoordinatorLayout> </LinearLayout>
相應的activity程式碼:
package com.example.administrator.potato.activity; import android.os.Bundle; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.Snackbar; import android.support.v7.widget.Toolbar; import android.view.View; import com.example.administrator.potato.R; import com.example.administrator.potato.utils.ToastMessage; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; public class UseSnackBarActivity extends BaseActivity { @Bind(R.id.toolbar) Toolbar toolbar; @Bind(R.id.coordinatorLayout) CoordinatorLayout coordinatorLayout; @Bind(R.id.coordinatorCustom) CoordinatorLayout coordinatorCustom; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_use_snack_bar); ButterKnife.bind(this); initView(); initData(); } @Override protected void initView() { initToolBar(toolbar, "使用SnackBar並封裝", true, new View.OnClickListener() { @Override public void onClick(View view) { finish(); } }); } @Override protected void initData() { } @OnClick(R.id.buttonNormal) public void onViewClicked() { Snackbar.make(toolbar, "這是系統的snackBar", Snackbar.LENGTH_INDEFINITE) .setAction("你瞅啥", new View.OnClickListener() { @Override public void onClick(View view) { ToastMessage.toastWarn("瞅你咋地", true); } }) .show(); } @OnClick(R.id.buttonCustom) public void onButtonCustomClicked() { showSnackBar(coordinatorCustom, "這是我們自己封裝的snackBar", false, "再瞅一個試試", new ISnackBarClickEvent() { @Override public void clickEvent() { ToastMessage.toastWarn("試試就試試", true); } }); } }
最後是BaseActivity程式碼,由於我平時將所有的案例都寫在一個專案中,所以你會看到很多與snackBar無關的程式碼(才不是為了湊字數呢),不過沒關係,這些東西我以後也會講的,封裝BaseActivity的作用在於方便日常開發,所以大家平時也可以將自己覺得有用的程式碼封裝進去,避免寫重複程式碼,寫程式,如果不是為了偷懶,那將毫無意義,嘿嘿嘿
package com.example.administrator.potato.activity; import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.MenuRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.TextView; import com.example.administrator.potato.application.MyApplication; import com.example.administrator.potato.interfaces.ConfirmDialogInterface; import com.example.administrator.potato.R; import com.lzy.okgo.OkGo; public abstract class BaseActivity extends AppCompatActivity { //上下文物件 protected Context mContext; //耗時操作的進度提示 private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //獲取上下文物件 mContext = this; initStateBar(); initProgressDialog(); } @Override protected void onDestroy() { //統一關閉OK HTTP請求 OkGo.getInstance().cancelTag(this); super.onDestroy(); } //初始化ProgressDialog private void initProgressDialog() { progressDialog = new ProgressDialog(mContext); //無標題 progressDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); //設定手指點選dialog不消失 progressDialog.setCanceledOnTouchOutside(false); //設定dialog樣式 progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); } /** * 設定狀態列的顏色與toolbar一致 只有安卓5.0以上才能用 */ private void initStateBar() { Window window = getWindow(); //設定狀態列為透明 //5.0以上使用原生方法 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); //設定狀態列的顏色與toolBar的顏色一致 window.setStatusBarColor(getResources().getColor(R.color.toolbar_color)); } } /** * 初始檢視 */ protected abstract void initView(); /** * 初始資料 */ protected abstract void initData(); /** * 顯示確認框形式的dialog * * @param titledialog的標題 * @param msgdialog的訊息 * @param confirmDialogInterface 監聽dialog確認鍵以及取消鍵的點選事件 */ protected void showConfirmDialog(@Nullable String title, @Nullable String msg, @NonNull final ConfirmDialogInterface confirmDialogInterface) { final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); //載入佈局 View view = View.inflate(mContext, R.layout.dialog_confirm, null); //獲取元件例項 TextView textTitle = view.findViewById(R.id.textTitle); TextView textContent = view.findViewById(R.id.textContent); TextView textConfirm = view.findViewById(R.id.textConfirm); TextView textCancel = view.findViewById(R.id.textCancel); //設定標題 textTitle.setText(title); //設定訊息內容 textContent.setText(msg); //設定需要顯示的view builder.setView(view); //賦值給其父類以獲取dismiss方法 final AlertDialog alertDialog = builder.create(); //顯示dialog alertDialog.show(); //設定確定按鈕內容 textConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //確認鍵業務邏輯處理介面 confirmDialogInterface.onConfirmClickListener(); //業務邏輯處理完畢使dialog消失 alertDialog.dismiss(); } }); //設定取消按鈕內容 textCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //取消鍵業務邏輯處理介面 confirmDialogInterface.onCancelClickListener(); //業務邏輯處理完畢使dialog消失 alertDialog.dismiss(); } }); } protected void showProgressDialog(@NonNull String msg) { if (progressDialog != null && !progressDialog.isShowing()) { progressDialog.setMessage(msg); progressDialog.show(); } } protected void closeProgressDialog() { if (progressDialog != null && progressDialog.isShowing()) { progressDialog.dismiss(); } } /** * 初始化toolBar 完整版 左右兩邊都需要設定 * * @param toolbartoolbar * @param titletoolbar標題 * @param isShowLeft是否顯示左邊圖示 * @param navigationOnClickListener 左邊圖示監聽事件 * @param isShowRight是否顯示右邊menu * @param menuIdmenu的id * @param onMenuItemClickListenermenu的子項監聽事件 */ protected void initToolBar(android.support.v7.widget.Toolbar toolbar, @NonNull String title, Boolean isShowLeft, View.OnClickListener navigationOnClickListener, Boolean isShowRight , @MenuRes Integer menuId, android.support.v7.widget.Toolbar.OnMenuItemClickListener onMenuItemClickListener) { //使用toolbar替代actionBar //setSupportActionBar(toolbar); 這行程式碼刪除後 toolbar.inflateMenu才能生效 toolbar.setTitle(title); //設定左邊icon if (isShowLeft) { toolbar.setNavigationIcon(R.drawable.icon_vector_back); if (navigationOnClickListener != null) { toolbar.setNavigationOnClickListener(navigationOnClickListener); } } //設定右邊menu if (isShowRight) { if (onMenuItemClickListener != null) { toolbar.setOnMenuItemClickListener(onMenuItemClickListener); } if (menuId != null) { //如果要使用toolbar.inflateMenu 則不能使用setSupportActionBar toolbar.inflateMenu(menuId); } else { //不傳選單檔案時使用預設的menu toolbar.inflateMenu(R.menu.app_toolbar_menu); } } } /** * 初始toolbar 簡略版 因為大多數的活動是不需要設定右邊的 所以可以簡化操作 * * @param toolbartoolBar * @param titletoolBar的標題 * @param isShowLeft是否顯示左邊圖示 * @param navigationOnClickListener 左邊圖示的點選事件 */ protected void initToolBar(android.support.v7.widget.Toolbar toolbar, @NonNull String title, Boolean isShowLeft, View.OnClickListener navigationOnClickListener) { //使用toolbar替代actionBar setSupportActionBar(toolbar); toolbar.setTitle(title); //設定左邊icon if (isShowLeft) { toolbar.setNavigationIcon(R.drawable.icon_vector_back); if (navigationOnClickListener != null) { toolbar.setNavigationOnClickListener(navigationOnClickListener); } } } /** * 活動跳轉 * * @param clazz 要跳轉的活動 */ protected void gotoActivity(Class<?> clazz) { Intent intent = new Intent(MyApplication.getContext(), clazz); startActivity(intent); } /** * 活動跳轉 * * @param clazz目標跳轉活動 * @param bundle 引數 */ protected void gotoActivity(Class<?> clazz, Bundle bundle) { Intent intent = new Intent(MyApplication.getContext(), clazz); if (bundle != null) { intent.putExtras(bundle); } startActivity(intent); } /** * 活動跳轉 * * @param clazz目標跳轉活動 * @param bundle 引數 * @param action action */ protected void gotoActivity(Class<?> clazz, Bundle bundle, String action) { Intent intent = new Intent(MyApplication.getContext(), clazz); if (bundle != null) { intent.putExtras(bundle); } if (!TextUtils.isEmpty(action)) { intent.setAction(action); } startActivity(intent); } /** * 展示snackBar * * @param viewview * @param msg訊息 * @param isDismiss是否自動消失 * @param action事件名 * @param iSnackBarClickEvent 事件處理介面 */ protected void showSnackBar(@NonNull View view, @NonNull String msg, boolean isDismiss, String action, final ISnackBarClickEvent iSnackBarClickEvent) { //snackBar預設顯示時間為LENGTH_LONG int duringTime = Snackbar.LENGTH_LONG; if (isDismiss) { duringTime = Snackbar.LENGTH_LONG; } else { duringTime = Snackbar.LENGTH_INDEFINITE; } Snackbar snackbar; snackbar = Snackbar.make(view, msg, duringTime) .setAction(action, new View.OnClickListener() { @Override public void onClick(View view) { //以介面方式傳送出去,便於使用者處理自己的業務邏輯 iSnackBarClickEvent.clickEvent(); } }); //設定snackBar和titleBar顏色一致 snackbar.getView().setBackgroundColor(getResources().getColor(R.color.toolbar_color)); //設定action文字的顏色 snackbar.setActionTextColor(getResources().getColor(R.color.normal_white)); //設定snackBar圖示 這裡是獲取到snackBar的textView 然後給textView增加左邊圖示的方式來實現的 View snackBarView = snackbar.getView(); TextView textView = (TextView) snackBarView.findViewById(R.id.snackbar_text); Drawable drawable = getResources().getDrawable(R.drawable.icon_vector_notification);//圖片自己選擇 drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight()); textView.setCompoundDrawables(drawable, null, null, null); //增加文字和圖示的距離 textView.setCompoundDrawablePadding(20); //展示snackBar snackbar.show(); } /** * snackBar的action事件 */ public interface ISnackBarClickEvent { void clickEvent(); } }
那麼本章內容也講得差不多了,接下來進行一下總結
一、有喜新厭舊的同學就會問了,snackBar這麼方便,那麼是不是可以拋棄Toast了呢,其實不然,所謂存在即合理,雖然Toast有一些缺點,但是它依舊可以傳達一些不那麼重要的訊息,兩者配合使用,這樣可以使程式與使用者進行互動時,方式變得多樣化,畢竟天天大魚大肉,也會膩的撒
二、剛剛忘講了,snackBar還有一個特性是可以滑動刪除,相信大家在我的佈局檔案中看到了這個
<android.support.design.widget.CoordinatorLayout android:id="@+id/coordinatorLayout" android:layout_marginTop="36dp" android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/buttonNormal" android:text="使用系統snackBar" android:layout_width="match_parent" android:layout_height="50dp" /> </android.support.design.widget.CoordinatorLayout>
CoordinatorLayout的作用是什麼呢,我只能告訴你的是,snackBar+CoordinatorLayout=可以滑動刪除的snackBar,這個暫時不講
3、今天發現平時後排睡覺的同學竟然全部跑光了,估計以為土豆要飯去了
4、下一章講講程序通訊神器,EventBus吧,下次見,白白~