简介
早些时候用过这个控件,由于业务需要也追踪过部分源码 发现这并不是一个DialogFragment的衍生类,而是通过DecorView进行插入,所以他是一个阻塞式的窗口也就是说这个Dialog一打开 其他控件就接收不到焦点了。
那么为什么要去写这么哥东西呢,首先我觉得理解这个队员阅读代码有一定提升,然后可以模仿DialogPlus的方式对界面进行操作
基础
在阅读源码前,我们需要了解一些基础只是,就是Activity的Gui构建大致模型(不细讲,展开的话需要阅读源码)
如果想要查看布局层级,可以随便建一个布局文件,打开Hierachy Viewer如下图所示
都有解释主要看第二层的LinearLayout->FrameLayout这条分支
稍微解释下某几个View,首先是DecorView这是哥FrameLayout的实现类,我们的自定义布局其实都是加到了id/content下,最开始是分标题栏,状态栏跟自定义View实在属于不同的分支的,所以如果我们获取到decorView再对其进行操作即可实现ui的覆盖
DialogPlus的使用
要看源码之前我们先来看看怎么简单使用这个控件(只介绍最简单的用法)
1.首先需要new ViewHolder
2.然后需要一个adapter(new SimpleAdapter)
3.需要一个DialogPlus将实现准备好的参数传入
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控制哪个地方等下分析源码再展开
源码分析
分析了使用以后我们发现所有的操作均集中在Builder内 那么我们追踪到DialogPlus的builder看看
看了这个结构图后我们发现其实这个builder的实质是构建一个属性集合,那么就不需要太关心了,但是我们要注意这个create()函数也就是我们生成DialogPlus实例的方法
public DialogPlus create() {
getHolder().setBackgroundResource(getContentBackgroundResource());
return new DialogPlus(this);
}
这是后又要回过头看DialogPlus对应的构造函数
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);
}}
这个就是我们要关心的重点函数
那些listener不是我们关心的重点,所以忽略不讲
decorView = (ViewGroup) activity.getWindow().getDecorView().findViewById(android.R.id.content);
首先我们根据activity获取到了我们自定义view填充的父容器,这里的decorView跟真正意义的decorView有区别(如上面所讲应该是DecorView负责绘制用户定义UI的分支),holder,rootView我们先放着看看下面哪里用到
接下来我们用rootView填充了outmostView,contentContainer(没往下看的时候我们其实就已经可以猜测这个就是我们dialog布局要放的地方,接下来就验证下看看)
来到第二个重要的函数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,是不是有点绕,还是需要看看ViewHolder,而且我们也从这个函数里看出了ViewHolder与Adapter的关系
ViewHolder
ViewHolder其实是Holder接口的一个实现,这里的getView是用来返回整个布局的,这样我们才可以添加header,footer
BaseAdapter
这个是一个数据源,适用于GridHolder,ListHolder,注意这边对布局有点要求不可随意定制,因为事先已实现功能是指定控件跟id挂钩的
以上总结是如果你选用了ViewHolder的话adapter是不对其产生影响的,因为过不了if (adapter != null && holder instanceof HolderAdapter)的条件(ViewHolder未实现HolderAdapter接口)
返回到上级函数我们只需要关注contentContainer.addView(contentView);
这句话验证了我们刚才的猜测,因为我们返回的contentView正是我们自定义的显示View
再回到上级函数,我们开始执行
private void initCancelable() {
if (!isCancelable) {
return;
}
View view = rootView.findViewById(R.id.dialogplus_outmost_container);
view.setOnTouchListener(onCancelableTouchListener);}
这个函数很简单,其实是实现了点击内容区域以外关闭Dialog的操作
前面我们获取到了outAnim,inAnim属性,这个在show跟dismiss中会用到,看完了初始化我们就来看看,
show()
这里面只是改变了一个状态主要逻辑在onAttached函数内
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;
} });}
最后我们来看看这个动画在哪(Utils内)
这边采用了系统自带的当然我们也可以自定义
另外还要注意dismiss函数,他在动画监听器里面将我们原来加在decorView的rootView移除了
总结
源码解析就到这里,原理其实蛮简单的,有很多地方没有讲解到,主要还是细节的把控才能成就一款出色的控件,文章的精华其实实在分析的过程把,其实真正的东西也就没几句话,如果资深的开发觉得不对的还有望指出,感觉浪费了您的时间的也请谅解