前言
- 第一次在簡書發(fā)表文章是目,突然就有點(diǎn)小緊張,寫不好标捺,會不會有人打我啊懊纳。
- 該片文章所講的Dialog適用于那種漂亮可愛妹紙?jiān)O(shè)計(jì)的多種彈框,各種彈亡容,各種動畫嗤疯,各種方位、姿勢和變化闺兢。
- 寫該文章的初衷在于剛好我們公司就有這么一對可愛的設(shè)計(jì)師茂缚,這樣多樣式的彈框,如果一個(gè)個(gè)單獨(dú)寫會造成代碼臃腫和可讀性十分差屋谭,所以做了一下二次封裝脚囊。
- 本文章其實(shí)早在兩個(gè)月前就打算寫了,由于項(xiàng)目時(shí)間十分緊張(通宵加班加得我懷疑猿生)桐磁,所以拖到了現(xiàn)在悔耘,終于開始寫了(我在調(diào)休,嘿嘿嘿...)。
本文會用到Builder設(shè)計(jì)模式:Android開發(fā)---Builder 模式必知必會
- 給大家獻(xiàn)上效果圖我擂,彈框自行yy出來的衬以,可能有點(diǎn)丑,不過這不是重點(diǎn)校摩,因?yàn)槌鰣D是設(shè)計(jì)妹紙的事情看峻。
以上是部分效果展示,下面我們將進(jìn)入正題:
目錄
1.繼承Dialog衙吩,高仿一個(gè)AlertDialog
1.1 開始按照v7包的AlertDialog擼一個(gè)輪子
- 閱讀并理解AlertDialog源碼
上一個(gè)鏈接讓大家看看如何簡單自定義:Android AlertDialog/AlertDialog.builder 以及 自定義AlertDialog方法
- 在package android.support.v7.app下面我們找到這個(gè)類AlertDialog.class 我精簡了一些 剩下的主要內(nèi)容如下:
private final AlertController mAlert;
static final int LAYOUT_HINT_NONE = 0;
static final int LAYOUT_HINT_SIDE = 1;
protected AlertDialog(@NonNull Context context) {
this(context, 0);
}
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
protected AlertDialog(@NonNull Context context, boolean cancelable,
@Nullable OnCancelListener cancelListener) {
this(context, 0);
setCancelable(cancelable);
setOnCancelListener(cancelListener);
}
private static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) {
if (resid >= 0x01000000) { // start of real resource IDs.
return resid;
} else {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
return outValue.resourceId;
}
}
public void setView(View view) {
mAlert.setView(view);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
public static class Builder {
private final AlertController.AlertParams P;
private final int mTheme;
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
@NonNull
public Context getContext() {
return P.mContext;
}
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
return this;
}
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
return this;
}
public Builder setOnDismissListener(OnDismissListener onDismissListener) {
P.mOnDismissListener = onDismissListener;
return this;
}
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
return this;
}
public Builder setView(int layoutResId) {
P.mView = null;
P.mViewLayoutResId = layoutResId;
P.mViewSpacingSpecified = false;
return this;
}
public Builder setView(View view) {
P.mView = view;
P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = false;
return this;
}
@Deprecated
public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
int viewSpacingRight, int viewSpacingBottom) {
P.mView = view;
P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = true;
P.mViewSpacingLeft = viewSpacingLeft;
P.mViewSpacingTop = viewSpacingTop;
P.mViewSpacingRight = viewSpacingRight;
P.mViewSpacingBottom = viewSpacingBottom;
return this;
}
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
從源碼可以看出互妓,類中有個(gè)AlertController類型的屬性,我們進(jìn)入這個(gè)類文件可以看到定義了很多屬性,比如Window View 上下左右邊距等等车猬,這個(gè)類的作用我們將會在2.1中提到霉猛。
- 開始自定義PowerfulDialog
我們新建一個(gè)PowerfulDialog仿照這個(gè)類,去繼承Dialog珠闰,這里有人拿著刀就要說了惜浅,人家是繼承的AppCompatDialog(這里我解釋一下AppCompatDialog ,點(diǎn)進(jìn)去看源碼伏嗜,發(fā)現(xiàn)它還是繼承的Dialog坛悉,是為了簡便的實(shí)現(xiàn)Material Design風(fēng)格的彈框),所以很多人使用AlertDialog承绸,基本都會是長這樣:
當(dāng)然了裸影,原生的AlertDialog可以通過Biulder中的setView方法去設(shè)置自定義的View但是,這樣寫達(dá)不到我們理想中的代碼量盡可能少的要求军熏,好了轩猩,現(xiàn)在我們直接繼承Dialog,不熟悉Builder的建議可以直接copy一份源碼荡澎,直接在源碼上面修改均践。
我們會在PowerFulDialog中寫一個(gè)Builder的靜態(tài)內(nèi)部類,這個(gè)靜態(tài)類主要是用來通知控制器來設(shè)置這個(gè)彈框的事件摩幔、動畫彤委、屬性及view的操作,所以我們一開始要擴(kuò)展Dialog的功能或衡,就可以在這里面配置編寫相應(yīng)的方法焦影,在Builder內(nèi)部的方法一般是可以鏈?zhǔn)讲僮鞯模_(dá)到靈活配置的作用封断。
在Builder靜態(tài)內(nèi)部類外部的方法除了構(gòu)造函數(shù)以外斯辰,其他的方法應(yīng)該是可以直接通過自定義的Dialog獲取到的屬性或事件等,這些操作大多是單個(gè)操作澄港,主要是獲取某些屬性或者事件椒涯,比如獲取Dialog中View的某個(gè)控件等。
好了 回梧,PowerFulDialog這個(gè)類的組成大概就是這個(gè)樣子废岂。下面我們來說說直接和彈框打交道的控制類PowerfulController.class
2.編寫一個(gè)控制類(統(tǒng)一配置事件、參數(shù)和彈窗屬性)
2.1 為什么要編寫一個(gè)控制類
增強(qiáng)代碼可讀性狱意,邏輯清晰湖苞,該類主要是設(shè)置彈框?qū)傩院屯ㄟ^輔助類設(shè)置彈框內(nèi)容。而PowerFulDialog類主要是負(fù)責(zé)去操作Dialog的狀態(tài)(create show dismiss...)
方便后期需求擴(kuò)展详囤,增加屬性時(shí)候從一定程度上減少代碼間的耦合度
2.2 如何編寫這個(gè)控制類
首先我們可以參考一下源碼财骨,在這個(gè)類里面我們會寫一些必要的參數(shù)镐作,被傳入的當(dāng)前需要顯示的Dialog、當(dāng)前Dialog需展示的Window隆箩、還有一個(gè)就是我們?yōu)榱藢iT處理View而新建的一個(gè)ViewHelper輔助類该贾,并且會寫一些設(shè)置view相關(guān)的方法給AlertParams內(nèi)部類調(diào)用。
其次捌臊,看AlertController源碼我們也可以發(fā)現(xiàn)杨蛋,我們會在PowerfulController這個(gè)類里面建立一個(gè)靜態(tài)內(nèi)部類AlertParams,作用主要是用來存放需要配置的參數(shù)屬性(顯示的view理澎、上下文逞力、文字、顏色糠爬、大小寇荧、位置等),一個(gè)Dialog能夠顯示的基本屬性條件基本上都必須寫在這個(gè)內(nèi)部類里面执隧。
在內(nèi)部類方法apply中揩抡,我們會傳當(dāng)前Dialog的Controller 對象,然后去設(shè)置這個(gè)傳進(jìn)來的Controller對象镀琉,并獲取到當(dāng)前處理view的輔助類捅膘,并完成view的處理。
那么滚粟,什么時(shí)候應(yīng)該調(diào)用這個(gè)apply方法呢,上面說過了刃泌,apply方法是去給這個(gè)Dialog的Controller(控制器)下達(dá)配置Dialog(包括設(shè)置view凡壤、大小、字體耙替、位置等)的指令亚侠,所以,這個(gè)方法應(yīng)該是我們在Dialog執(zhí)行create方法的時(shí)候調(diào)用俗扇。鏈?zhǔn)脚渲猛瓿珊笳{(diào)用create方法硝烂,我們就可以得到我們配置后建造出來的Dialog了。
有的人就有疑問了铜幽,鏈?zhǔn)奖磉_(dá)后有的直接用show也可以配置啊滞谢。如果真的有這樣的人,我勸你還是多看看源碼除抛,再出來裝逼狮杨,其實(shí)它也還是走了這一步的,如下圖到忽。
3.編寫一個(gè)View輔助類(用于實(shí)際直接操作view)
2.1 為什么要編寫一個(gè)輔助類
增強(qiáng)代碼可讀性橄教,邏輯清晰,該類主要是獲取到指定view并直接設(shè)置View的屬性,而PowerfulController主要是設(shè)置整個(gè)window的狀態(tài)护蝶、動畫华烟、位置等,還有操控輔助類設(shè)置view
方便后期需求擴(kuò)展持灰,增加屬性時(shí)候從一定程度上減少代碼間的耦合度
2.2 如何編寫一個(gè)輔助類
- 既然該類的主要作用是用于設(shè)置view的屬性盔夜,那么必不可少的參數(shù)有Context、View(或者布局ID),所以構(gòu)造函數(shù)需要傳遞這兩個(gè)參數(shù)搅方,這個(gè)類內(nèi)部應(yīng)該提供設(shè)置view的方法比吭。DialogViewHelper.class代碼如下:
private View mDialogView=null;
//防止泄露
private SparseArray<WeakReference<View>> mViews;
public DialogViewHelper(Context mContext, int mViewLayoutResId) {
this();
mDialogView= LayoutInflater.from(mContext).inflate(mViewLayoutResId,null);
}
public DialogViewHelper() {
mViews=new SparseArray<>();
}
/**
* 設(shè)置布局
* @param mView
* */
public void setDialogView(View mView) {
this.mDialogView=mView;
}
/**
* 設(shè)置文本
* @param viewId
* @param text
*/
public void setText(int viewId, CharSequence text) {
TextView tv=getView(viewId);
if(tv!=null){
tv.setText(text==null?"":text);
}
}
/**
* 設(shè)置文本顏色
* @param viewId
* @param colorRes
*/
public void setTextColor(Context context, int viewId, int colorRes) {
TextView tv=getView(viewId);
if(tv!=null){
tv.setTextColor(context.getResources().getColor(colorRes==0? R.color.text_gray_normal:colorRes));
}
}
/**
* 設(shè)置圖片
* @param viewId
* @param imgRes
*/
public void setImage(int viewId, int imgRes) {
ImageView iv=getView(viewId);
if(iv!=null){
iv.setImageResource(imgRes==0? R.mipmap.default_head:imgRes);
}
}
/**
* 設(shè)置view的顯示和隱藏
* @param viewId
* @param visibilityMode
*/
public void setVisiable(int viewId, int visibilityMode) {
View v=getView(viewId);
if(v!=null){
v.setVisibility(visibilityMode);
}
}
/**
* 優(yōu)化findViewById
* @param viewId
* @param <T>
* @return
*/
public <T extends View> T getView(int viewId) {
View v=null;
WeakReference<View> viewRefrence=mViews.get(viewId);
if(viewRefrence!=null){
v=viewRefrence.get();
}
if(v==null){
v=mDialogView.findViewById(viewId);
if(v!=null){
mViews.put(viewId,new WeakReference<View>(v));
}
}
return (T)v;
}
/**
* 設(shè)置點(diǎn)擊事件
* @param viewId
* @param onClickListener
*/
public void setOnclickListener(int viewId, View.OnClickListener onClickListener) {
View v=getView(viewId);
if(v!=null){
v.setOnClickListener(onClickListener);
}
}
public View getDialogView() {
return mDialogView;
}
總結(jié)
1.做一個(gè)“懶”的程序員
- 這里的懶,不是指整天喜歡裝逼姨涡,不干事兒衩藤,不敲代碼,不在實(shí)踐中求真知的那個(gè)懶涛漂,指的是赏表,會用簡短高效的代碼,去完成項(xiàng)目中的需求匈仗。
- “用最少的代碼實(shí)現(xiàn)最牛逼的效果”相信這是每個(gè)“懶”程序員的終極目標(biāo)瓢剿,所以,我們平時(shí)在項(xiàng)目實(shí)際開發(fā)中悠轩,多去從中思考间狂,有沒有更加優(yōu)秀的實(shí)現(xiàn)方式,多嘗試火架,多積累鉴象。
2.時(shí)刻想著這段代碼是否還可以被優(yōu)化
- 在這個(gè)demo編寫過程中,大家可以看到何鸡,有很多地方用到了SparseArray和弱引用纺弊。
- 其中我們主要是用SparseArray這個(gè)集合去存儲已經(jīng)從Dialog中取到的view id和對應(yīng)的值,剛好利用view id為int類型骡男,使用SparseArray更加節(jié)約內(nèi)存淆游,從而提高性能,當(dāng)某個(gè)擁有id的view通過findviewbyid被查詢到后都會放入集合隔盛,下次使用時(shí)不會再經(jīng)過findviewbyid而是直接從SparseArray去取出犹菱,如若沒有查詢到該id則又通過findviewbyid查詢,這樣就提高了性能骚亿,減少了內(nèi)存開支已亥。
- 使用弱引用主要是為了系統(tǒng)內(nèi)存不足時(shí)候造成內(nèi)存泄漏的問題。
老司機(jī)帶你理解SparseArray:Android內(nèi)存優(yōu)化(使用SparseArray和ArrayMap代替HashMap)
老司機(jī)帶你理解為什么要使用弱引用:Java 理論與實(shí)踐
用弱引用堵住內(nèi)存泄漏)
3.要善于去學(xué)習(xí)源碼
- 講真来屠,本文demo其實(shí)就是一個(gè)源碼擴(kuò)展和優(yōu)化版本虑椎,其實(shí)其中的思維有百分之七八十的相似震鹉,無論是設(shè)計(jì)模式還是實(shí)現(xiàn)思路。所以捆姜,多看源碼传趾,你會學(xué)到很多東西的。
在線查看Android源碼地址:在線查看android源碼
4.最后奉上我的demo
- 最近上傳到的GitHub泥技,未來會長期更新和完善浆兰,如果有什么好的建議或者問題,歡迎提issue珊豹。
- 在簡書的第一篇文章簸呈,如果寫得有不好的地方或有誤的地方,還希望指正店茶,在下感激不盡蜕便。
- 最后,歡迎大家star and fork贩幻。
一個(gè)萬能的Dialog demo地址:PowerfulDialog轿腺,為程序猿而生