《DialogFragment系列一之源码分析》
《DialogFragment系列二之Dialog封装》
《DialogFragment系列三之AlertDialog实现》
《DialogFragment系列四之StatusDialog(Progress、Success、Error)实现》
《DialogFragment系列五之ItemDialog(eg:BottomDialog)实现》
《DialogFragment系列六之常见问题》
前几篇通过对DialogFragment的源码分析定义了一个BaseDialog进而实现了AlertDialog、StatusDialog,在此期间遇到了几个比较经典的问题,与读者分享一下。
问题一:onViewCreated()不回调
笔者想通过onViewCreated()来设置布局控件,但是怎么也不回调,以前一直使用Fragment都会回调,现在怎么不回调了呢?带着疑问,进去看了一下源码,发现了猫腻。首先看下onViewCreated()在哪里被调用了,看下源码:
moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
.......
case Fragment.CREATED:
// This is outside the if statement below on purpose; we want this to run
// even if we do a moveToState from CREATED => *, CREATED => CREATED, and
// * => CREATED as part of the case fallthrough above.
ensureInflatedFragmentView(f);
if (newState > Fragment.CREATED) {
if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
if (f.mContainerId == View.NO_ID) {
throwException(new IllegalArgumentException(
"Cannot create fragment "
+ f
+ " for a container view with no id"));
}
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
String resName;
try {
resName = f.getResources().getResourceName(f.mContainerId);
} catch (NotFoundException e) {
resName = "unknown";
}
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ resName
+ ") for fragment " + f));
}
}
f.mContainer = container;
f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
container.addView(f.mView);
}
if (f.mHidden) {
f.mView.setVisibility(View.GONE);
}
//这里被调用
f.onViewCreated(f.mView, f.mSavedFragmentState);
dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
false);
// Only animate the view if it is visible. This is done after
// dispatchOnFragmentViewCreated in case visibility is changed
f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
&& f.mContainer != null;
} else {
f.mInnerView = null;
}
}
f.performActivityCreated(f.mSavedFragmentState);
dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
.......
}
通过查看源码发现,onViewCreate()在moveToState()中即performCreateView创建contentView之后被调用,进入onViewCreate()方法要通过一个判断语句f.mView != null,那我们看下f.mView 是哪来的,继续回溯源码:
void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
mPerformedCreateView = true;
mViewLifecycleOwner = new LifecycleOwner() {
@Override
public Lifecycle getLifecycle() {
if (mViewLifecycleRegistry == null) {
mViewLifecycleRegistry = new LifecycleRegistry(mViewLifecycleOwner);
}
return mViewLifecycleRegistry;
}
};
mViewLifecycleRegistry = null;
//这里啊,在这里被赋值
mView = onCreateView(inflater, container, savedInstanceState);
if (mView != null) {
// Initialize the LifecycleRegistry if needed
mViewLifecycleOwner.getLifecycle();
// Then inform any Observers of the new LifecycleOwner
mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
} else {
if (mViewLifecycleRegistry != null) {
throw new IllegalStateException("Called getViewLifecycleOwner() but "
+ "onCreateView() returned null");
}
mViewLifecycleOwner = null;
}
}
通过回溯源码,发现mView在performCreateView()里被唯一赋值,而且来源就是平时熟悉的onCreateView(),performCreateView()是在创建contentView使用,在moveToState()中先调用performCreateView()创建contentView,然后再调用onViewCreated()。既然定位到了onCreateView()那去看看其返回值:
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return null;
}
Fragment中默认返回null,而且Dialog中onCreateView()也使用Fragment的返回值,所以此时mView就会为null
@Nullable
@Override
public final View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//getDialog().setCancelable(setCancelable());
getDialog().setCanceledOnTouchOutside(setCancelable());
setCancelable(setCancelable());
//设置背景透明
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
return super.onCreateView(inflater, container, savedInstanceState);
}
综上,onViewCreated()不回调是因为onCreateView返回值为null,但是在Dialog中没有使用onCreateView去加载布局,是在onCreateDialog中加载的布局,所以可以在onCreateDialog()中主动回调onViewCreated()此问题就迎刃而解了。
@NonNull
@Override
public final android.app.Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
View dialogLayout = LayoutInflater.from(getContext()).inflate(setLayoutRes(), null);
builder.setView(dialogLayout);
//主动回调
onViewCreated(dialogLayout, null);
return builder.create();
}
问题二:设置getDialog().setCancelable(setCancelable())后点击返回键Dialog还是会dismiss
设置了getDialog().setCancelable(setCancelable())后点击返回键Dialog还是会dismiss,很纳闷,源码里逻辑也是拦截返回事件通过boolean mCancelable来判断是否dismiss,代码如下:
public void onBackPressed() {
if (mCancelable) {
cancel();
}
}
真是百思不得其解,而后返回到DialogFragment查看,发现了一个相似的方法,如下:
boolean mCancelable = true;
public void setCancelable(boolean cancelable) {
mCancelable = cancelable;
if (mDialog != null) mDialog.setCancelable(cancelable);
}
罪魁祸首终于找到了,原来DialogFragment默认设置了mCancelable,而默认值是true,所以导致了getDialog().setCancelable(setCancelable())不管作用,那可以直接通过DialogFragment的setCancelable()来设置,代码如下:
@Nullable
@Override
public final View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
//getDialog().setCancelable(setCancelable());
setCancelable(setCancelable());
getDialog().setCanceledOnTouchOutside(setCancelable());
//设置背景透明
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
return super.onCreateView(inflater, container, savedInstanceState);
}
protected boolean setCancelable() {
return dialogParams.isCancelable;
}
到此,此问题被解决
问题三:出现非contentView的背景
运行代码发现出现了非contentView的背景,此背景是DialogFragment的默认背景,去掉即可,代码如下:
@Nullable
@Override
public final View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
getDialog().setCancelable(setCancelable());
getDialog().setCanceledOnTouchOutside(setCancelable());
setCancelable(setCancelable());
//设置背景透明
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
return super.onCreateView(inflater, container, savedInstanceState);
}
以上就是在实现Dialog的过程遇到的三个问题以及解决办法,特此记录一下并分享给读者!
网友评论