簡(jiǎn)介
早些時(shí)候用過(guò)這個(gè)控件,由于業(yè)務(wù)需要也追蹤過(guò)部分源碼 發(fā)現(xiàn)這并不是一個(gè)DialogFragment的衍生類(lèi),而是通過(guò)DecorView進(jìn)行插入框沟,所以他是一個(gè)阻塞式的窗口也就是說(shuō)這個(gè)Dialog一打開(kāi) 其他控件就接收不到焦點(diǎn)了撮奏。
那么為什么要去寫(xiě)這么哥東西呢,首先我覺(jué)得理解這個(gè)隊(duì)員閱讀代碼有一定提升柬唯,然后可以模仿DialogPlus的方式對(duì)界面進(jìn)行操作
基礎(chǔ)
在閱讀源碼前,我們需要了解一些基礎(chǔ)只是,就是Activity的Gui構(gòu)建大致模型(不細(xì)講扛拨,展開(kāi)的話需要閱讀源碼)
如果想要查看布局層級(jí),可以隨便建一個(gè)布局文件举塔,打開(kāi)Hierachy Viewer如下圖所示
都有解釋主要看第二層的LinearLayout->FrameLayout這條分支
稍微解釋下某幾個(gè)View绑警,首先是DecorView這是哥FrameLayout的實(shí)現(xiàn)類(lèi),我們的自定義布局其實(shí)都是加到了id/content下央渣,最開(kāi)始是分標(biāo)題欄计盒,狀態(tài)欄跟自定義View實(shí)在屬于不同的分支的,所以如果我們獲取到decorView再對(duì)其進(jìn)行操作即可實(shí)現(xiàn)ui的覆蓋
DialogPlus的使用
要看源碼之前我們先來(lái)看看怎么簡(jiǎn)單使用這個(gè)控件(只介紹最簡(jiǎn)單的用法)
1.首先需要new ViewHolder
2.然后需要一個(gè)adapter(new SimpleAdapter)
3.需要一個(gè)DialogPlus將實(shí)現(xiàn)準(zhǔn)備好的參數(shù)傳入
final DialogPlus dialog = DialogPlus.newDialog(this)
.setContentHolder(new ViewHolder(R.layout.content))
.setHeader(R.layout.header)
.setFooter(R.layout.footer)
.setCancelable(true)
.setGravity(gravity)
.setAdapter(new SimpleAdapter(MainActivity.this, false))
.setOnClickListener(clickListener)
.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(DialogPlus dialog, Object item, View view, int position) {
Log.d("DialogPlus", "onItemClick() called with: " + "item = [" +
item + "], position = [" + position + "]");
}
})
.setOnDismissListener(dismissListener)
.setExpanded(expanded)//
.setContentWidth(800)
.setContentHeight(ViewGroup.LayoutParams.WRAP_CONTENT)
.setOnCancelListener(cancelListener)
.setOverlayBackgroundResource(android.R.color.transparent)//
.setContentBackgroundResource(R.drawable.corner_background)//
.setOutMostMargin(0, 100, 0, 0)
.create();
dialog.show();
具體ViewHolder芽丹,SimpleAdapter控制哪個(gè)地方等下分析源碼再展開(kāi)
源碼分析
分析了使用以后我們發(fā)現(xiàn)所有的操作均集中在Builder內(nèi) 那么我們追蹤到DialogPlus的builder看看
看了這個(gè)結(jié)構(gòu)圖后我們發(fā)現(xiàn)其實(shí)這個(gè)builder的實(shí)質(zhì)是構(gòu)建一個(gè)屬性集合北启,那么就不需要太關(guān)心了,但是我們要注意這個(gè)create()函數(shù)也就是我們生成DialogPlus實(shí)例的方法
public DialogPlus create() {
getHolder().setBackgroundResource(getContentBackgroundResource());
return new DialogPlus(this);
}
這是后又要回過(guò)頭看DialogPlus對(duì)應(yīng)的構(gòu)造函數(shù)
DialogPlus(DialogPlusBuilder builder) {
LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext());
Activity activity = (Activity) builder.getContext();
holder = builder.getHolder();
onItemClickListener = builder.getOnItemClickListener();
onClickListener = builder.getOnClickListener();
onDismissListener = builder.getOnDismissListener();
onCancelListener = builder.getOnCancelListener();
onBackPressListener = builder.getOnBackPressListener();
isCancelable = builder.isCancelable();
/**
* Avoid getting directly from the decor view because by doing that we are overlapping the black soft key on
* nexus device. I think it should be tested on different devices but in my opinion is the way to go.
* @link http://stackoverflow.com/questions/4486034/get-root-view-from-current-activity
*/
decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
rootView = (ViewGroup) layoutInflater.inflate(R.layout.base_container, decorView, false);
rootView.setLayoutParams(builder.getOutmostLayoutParams());
View outmostView = rootView.findViewById(R.id.dialogplus_outmost_container);
outmostView.setBackgroundResource(builder.getOverlayBackgroundResource());
contentContainer = (ViewGroup) rootView.findViewById(R.id.dialogplus_content_container);
contentContainer.setLayoutParams(builder.getContentParams());
outAnim = builder.getOutAnimation();
inAnim = builder.getInAnimation();
initContentView(
layoutInflater,
builder.getHeaderView(),
builder.getFooterView(),
builder.getAdapter(),
builder.getContentPadding(),
builder.getContentMargin()
);
initCancelable();
if (builder.isExpanded()) {
initExpandAnimator(activity, builder.getDefaultContentHeight(), builder.getContentParams().gravity);
}}
這個(gè)就是我們要關(guān)心的重點(diǎn)函數(shù)
那些listener不是我們關(guān)心的重點(diǎn)拔第,所以忽略不講
decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
首先我們根據(jù)activity獲取到了我們自定義view填充的父容器咕村,這里的decorView跟真正意義的decorView有區(qū)別(如上面所講應(yīng)該是DecorView負(fù)責(zé)繪制用戶(hù)定義UI的分支),holder,rootView我們先放著看看下面哪里用到
接下來(lái)我們用rootView填充了outmostView,contentContainer(沒(méi)往下看的時(shí)候我們其實(shí)就已經(jīng)可以猜測(cè)這個(gè)就是我們dialog布局要放的地方蚊俺,接下來(lái)就驗(yàn)證下看看)
來(lái)到第二個(gè)重要的函數(shù)initContentView,里面又嵌套了createView
private void initContentView(LayoutInflater inflater, View header, View footer, BaseAdapter adapter,
int[] padding, int[] margin) {
View contentView = createView(inflater, header, footer, adapter);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
params.setMargins(margin[0], margin[1], margin[2], margin[3]);
contentView.setLayoutParams(params);
getHolderView().setPadding(padding[0], padding[1], padding[2], padding[3]);
contentContainer.addView(contentView);}
private View createView(LayoutInflater inflater, View headerView, View footerView, BaseAdapter adapter) {
View view = holder.getView(inflater, rootView);
if (holder instanceof ViewHolder) {
assignClickListenerRecursively(view);
}
assignClickListenerRecursively(headerView);
holder.addHeader(headerView);
assignClickListenerRecursively(footerView);
holder.addFooter(footerView);
if (adapter != null && holder instanceof HolderAdapter) {
HolderAdapter holderAdapter = (HolderAdapter) holder;
holderAdapter.setAdapter(adapter);
holderAdapter.setOnItemClickListener(new OnHolderListener() {
@Override
public void onItemClick(Object item, View view, int position) {
if (onItemClickListener == null) {
return;
}
onItemClickListener.onItemClick(DialogPlus.this, item, view, position);
}
});
}
return view;}
這里又牽扯到ViewHolder懈涛,是不是有點(diǎn)繞,還是需要看看ViewHolder泳猬,而且我們也從這個(gè)函數(shù)里看出了ViewHolder與Adapter的關(guān)系
ViewHolder
ViewHolder其實(shí)是Holder接口的一個(gè)實(shí)現(xiàn)批钠,這里的getView是用來(lái)返回整個(gè)布局的,這樣我們才可以添加header得封,footer
BaseAdapter
這個(gè)是一個(gè)數(shù)據(jù)源埋心,適用于GridHolder,ListHolder忙上,注意這邊對(duì)布局有點(diǎn)要求不可隨意定制拷呆,因?yàn)槭孪纫褜?shí)現(xiàn)功能是指定控件跟id掛鉤的
以上總結(jié)是如果你選用了ViewHolder的話adapter是不對(duì)其產(chǎn)生影響的,因?yàn)檫^(guò)不了if (adapter != null && holder instanceof HolderAdapter)的條件(ViewHolder未實(shí)現(xiàn)HolderAdapter接口)
返回到上級(jí)函數(shù)我們只需要關(guān)注contentContainer.addView(contentView);
這句話驗(yàn)證了我們剛才的猜測(cè),因?yàn)槲覀兎祷氐腸ontentView正是我們自定義的顯示View
再回到上級(jí)函數(shù)茬斧,我們開(kāi)始執(zhí)行
private void initCancelable() {
if (!isCancelable) {
return;
}
View view = rootView.findViewById(R.id.dialogplus_outmost_container);
view.setOnTouchListener(onCancelableTouchListener);}
這個(gè)函數(shù)很簡(jiǎn)單箫柳,其實(shí)是實(shí)現(xiàn)了點(diǎn)擊內(nèi)容區(qū)域以外關(guān)閉Dialog的操作
前面我們獲取到了outAnim,inAnim屬性,這個(gè)在show跟dismiss中會(huì)用到啥供,看完了初始化我們就來(lái)看看悯恍,
show()
這里面只是改變了一個(gè)狀態(tài)主要邏輯在onAttached函數(shù)內(nèi)
private void onAttached(View view) {
decorView.addView(view);
contentContainer.startAnimation(inAnim);
contentContainer.requestFocus();
holder.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (event.getAction()) {
case KeyEvent.ACTION_UP:
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (onBackPressListener != null) {
onBackPressListener.onBackPressed(DialogPlus.this);
}
if (isCancelable) {
onBackPressed(DialogPlus.this);
}
return true;
}
break;
default:
break;
}
return false;
} });}
最后我們來(lái)看看這個(gè)動(dòng)畫(huà)在哪(Utils內(nèi))
這邊采用了系統(tǒng)自帶的當(dāng)然我們也可以自定義
另外還要注意dismiss函數(shù),他在動(dòng)畫(huà)監(jiān)聽(tīng)器里面將我們?cè)瓉?lái)加在decorView的rootView移除了
總結(jié)
源碼解析就到這里伙狐,原理其實(shí)蠻簡(jiǎn)單的涮毫,有很多地方?jīng)]有講解到,主要還是細(xì)節(jié)的把控才能成就一款出色的控件贷屎,文章的精華其實(shí)實(shí)在分析的過(guò)程把罢防,其實(shí)真正的東西也就沒(méi)幾句話,如果資深的開(kāi)發(fā)覺(jué)得不對(duì)的還有望指出唉侄,感覺(jué)浪費(fèi)了您的時(shí)間的也請(qǐng)諒解