源代码参考:360云盘中---自己的学习资料---Android总结过的项目---DialogFragmentDemo.rar
一、概述
DialogFragment 在 android 3.0 时被引入。是一种特殊的 Fragment,用于在 Activity 的内容之上展示一个模态的对话框。典型的用于:展示警告框,输入框,确认框等等。
在 DialogFragment 产生之前,我们创建对话框:一般采用 AlertDialog 和 Dialog。注:官方不推荐直接使用 Dialog 创建对话框。
--------------------------------------------------------------------------------------------
二、好处与用法
使用 DialogFragment 来管理对话框,当旋转屏幕和按下后退键时可以更好的管理其声明周期,他和 Fragment 有着基本一致的声明周期。且 DialogFragment 也允许开发者把Dialog 作为内嵌的组件进行重用,类似 Fragment(可以在大屏幕和小屏幕显示出不同的效果)。上面会通过例子展示这些好处。
使用 DialogFragment 至少需要实现 onCreateView 或者 onCreateDialog 方法。onCreateView 即使用定义的 xml 布局文件展示 Dialog。onCreateDialog 即利用AlertDialog 或者 Dialog 创建出 Dialog。
--------------------------------------------------------------------------------------------
三、重写 onCreateView 创建 Dialog
1)布局文件,我们创建一个设置名称的布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/tvLabelYourName"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:gravity="center_vertical"
android:text="Your name:" />
<EditText
android:id="@+id/etYourName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/tvLabelYourName"
android:imeOptions="actionDone"
android:inputType="text" />
<Button
android:id="@+id/btnEditName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/etYourName"
android:text="ok" />
</RelativeLayout>
2)继承 DialogFragment,重写 onCreateView 方法
/**
* 编辑名字 DialogFragment
*/
public class EditNameDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View tView=inflater.inflate(R.layout.dialog_fragment_edit_name, null);
return tView;
}
}
3)测试运行:
Main方法中调用:
/**
* 编辑名字主界面
*/
public class EditNameActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EditNameDialogFragment tEdFragment=new EditNameDialogFragment();
tEdFragment.show(getFragmentManager(), "EditNameDialog");
}
}
效果图:
可以看到,对话框成功创建并显示出来,不过默认对话框有个讨厌的标题,我们怎么去掉呢:可以在 onCreateView 中调用 getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);即可去掉。即:
/**
* 编辑名字 DialogFragment
*/
public class EditNameDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); //去标题
View tView = inflater.inflate(R.layout.dialog_fragment_edit_name, null);
return tView;
}
}
效果图:
很完美的去掉了讨厌的标题。
--------------------------------------------------------------------------------------------
四、重写 onCreateDialog 创建 Dialog
在 onCreateDialog 中一般可以使用 AlertDialog 或者 Dialog 创建对话框,不过既然 Google 不推荐直接使用 Dialog,我们就使用 AlertDialog 来创建一个登录的对话框。
1)布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<ImageView
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="#FFFFBB33"
android:contentDescription="@string/app_name"
android:scaleType="center"
android:src="@drawable/title" />
<EditText
android:id="@+id/etUserName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="16dp"
android:hint="input username"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/etPassWord"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="4dp"
android:fontFamily="sans-serif"
android:hint="input password"
android:inputType="textPassword" />
</LinearLayout>
2)继承 DialogFragment 重写 onCreateDialog 方法
/**
* 登录 DialogFragment
*/
public class LoginDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder tBuilder = new AlertDialog.Builder(getActivity());
// Get the layout inflater
LayoutInflater tLInflater = getActivity().getLayoutInflater();
View tView = tLInflater.inflate(R.layout.dialog_fragment_login, null);
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
tBuilder.setView(tView)
.setPositiveButton("Sign in",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
}
}).setNegativeButton("Cancel", null);
return tBuilder.create();
}
}
3)调用:
private void showLoginDialog() {
LoginDialogFragment tLDFragment = new LoginDialogFragment();
tLDFragment.show(getFragmentManager(), "loginDialog");
}
4)效果图:
可以看到通过重写 onCreateDialog 同样可以实现创建对话框,效果还是很 nice 的。
--------------------------------------------------------------------------------------------
五、传递数据给 Activity
从 Dialog 传递数据给 Activity,可以使用“fragment interface pattern”的方式,下面通过一个改造上面的登录框来展示这种模式。(这里我自己说明一下,其实就是监听模式,只不过在 Fragment 中可以直接使用 getActivity() 方法调用接口中的方法,不过还不如果直接使用原监听模式,要进行判断的。)
if (getActivity() instanceof LoginInputListener) {
((LoginInputListener) getActivity())
.onLoginInputComplete(mUserName
.getText().toString(),
mPassword.getText()
.toString());
}
效果是一样的,不过哪个好,自己考虑吧。(好吧这和下边代码一样呢,别乱想了,原监听模式在上边会实例接口的。)
改动比较小,直接贴代码了:
/**
* 登录 DialogFragment
*/
public class LoginDialogFragment extends DialogFragment {
private EditText mUserName;
private EditText mPassword;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder tBuilder = new AlertDialog.Builder(getActivity());
// Get the layout inflater
LayoutInflater tLInflater = getActivity().getLayoutInflater();
View tView = tLInflater.inflate(R.layout.dialog_fragment_login, null);
mUserName = (EditText) tView.findViewById(R.id.etUserName);
mPassword = (EditText) tView.findViewById(R.id.etPassWord);
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
tBuilder.setView(tView)
.setPositiveButton("Sign in",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
LoginInputListener tLIListener = (LoginInputListener) getActivity();
tLIListener.onLoginInputComplete(mUserName
.getText().toString(), mPassword
.getText().toString());
}
}).setNegativeButton("Cancel", null);
return tBuilder.create();
}
/**
* 登录监听,用来与 Activity 传递数据
*/
public interface LoginInputListener {
void onLoginInputComplete(String username, String password);
}
}
拿到 username 和 password 的引用,在点击登录的时候,把 Activity 强转为我们自定义的接口:LoginInputListener,然后将用户输入的数据返回。
MainActivity 中需要实现我们的接口 LoginInputListener,实现我们的方法,就可以实现当用户点击登陆时,获得我们的帐号密码了:
/**
* 登录 Dialog 主页面
*/
public class LoginDialogActivity extends Activity implements LoginInputListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
showLoginDialog();
}
private void showLoginDialog() {
LoginDialogFragment tLDFragment = new LoginDialogFragment();
tLDFragment.show(getFragmentManager(), "loginDialog");
}
@Override
public void onLoginInputComplete(String username, String password) {
StringBuffer tSBuffer = new StringBuffer();
tSBuffer.append("账号:");
tSBuffer.append(username);
tSBuffer.append(",密码:");
tSBuffer.append(password);
Toast.makeText(this, tSBuffer.toString(), Toast.LENGTH_SHORT).show();
}
}
这种方式就可以满足屏幕旋转依然保存 Dialog 的状态,并且不会报错,但是我把 showLoginDialog() 写在了 onCreate() 方法中了,所以每次旋转都会创建一个新的 Dilog ,下面贴出正确代码。
/**
* 登录 Dialog 主页面
*/
public class LoginDialogActivity extends Activity implements LoginInputListener, OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_dialog);
Button tBtnLoginDialog=(Button) this.findViewById(R.id.btnLoginDialog);
tBtnLoginDialog.setOnClickListener(this);
}
public void showLoginDialog() {
LoginDialogFragment tLDFragment = new LoginDialogFragment();
tLDFragment.show(getFragmentManager(), "loginDialog");
}
@Override
public void onLoginInputComplete(String username, String password) {
StringBuffer tSBuffer = new StringBuffer();
tSBuffer.append("账号:");
tSBuffer.append(username);
tSBuffer.append(",密码:");
tSBuffer.append(password);
Toast.makeText(this, tSBuffer.toString(), Toast.LENGTH_SHORT).show();
}
@Override
public void onClick(View v) {
showLoginDialog();
}
}
其实加一个 Button 按钮,做个中转就好了,因为不按下 Button 按钮,是不会创建 Dialog 的(‘...’)
--------------------------------------------------------------------------------------------
六、DialogFragment 做屏幕适配
我们希望,一个对话框在大屏幕上以对话框的形式展示,而小屏幕上则直接嵌入当前的 Actvity 中。这种效果的对话框,只能通过重写 onCreateView 实现。下面我们利用上面的 EditNameDialogFragment 来显示。
EditNameDialogFragment 我们已经编写好了,直接在 MainActivity 中写调用
/**
* 加载适配版 DialogFragment
*/
private void initEditNameAdapterDialog() {
FragmentManager tFManager = getFragmentManager();
EditNameDialogFragment tEDFragment = new EditNameDialogFragment();
boolean tBolLargeLayout = getResources()
.getBoolean(R.bool.large_layout);
Log.e("TAG", tBolLargeLayout + "");
if (tBolLargeLayout) {
// The device is using a large layout, so show the fragment as a
// dialog
tEDFragment.show(tFManager, "dialog");
} else {
// The device is smaller, so show the fragment fullscreen
FragmentTransaction tFTransaction = tFManager.beginTransaction();
// For a little polish, specify a transition animation
tFTransaction
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// To make it fullscreen, use the 'content' root view as the
// container
// for the fragment, which is always the root view for the activity
tFTransaction.replace(R.id.rlEditName, tEDFragment).commit();
}
}
可以看到,我们通过读取 R.bool.large_layout,然后根据得到的布尔值,如果是大屏幕则直接以对话框显示,如果是小屏幕则嵌入我们的 Activity 布局中
这个 R.bool.large_layout 是我们定义的资源文件:
1.在默认的 values 下新建一个 bools.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="large_layout">false</bool>
</resources>
2.然后在 res 下新建一个 values-large,在 values-large 下再新建一个 bools.xml
<resources>
<bool name="large_layout">true</bool>
</resources>
注意在,EditNameDialogFragment 中必须加一个判断,否则 getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); 报空异常,好像是小屏幕时嵌入到屏幕里了,没有标题的概念,所以为空。
/**
* 编辑名字 DialogFragment
*/
public class EditNameDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (getResources().getBoolean(R.bool.large_layout)) { // 判断是否为大屏幕
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); // 去标题
}
View tView = inflater.inflate(R.layout.dialog_fragment_edit_name, null);
return tView;
}
}
最后测试:
左边为模拟器,右边为我的手机。
--------------------------------------------------------------------------------------------
七、屏幕旋转
当用户输入帐号密码时,忽然旋转了一下屏幕,帐号密码不见了……是不是会抓狂
传统的 new AlertDialog 在屏幕旋转时,第一不会保存用户输入的值,第二还会报异常,因为 Activity 销毁前不允许对话框未关闭。而通过 DialogFragment 实现的对话框则可以完全不必考虑旋转的问题。
我们直接把上面登录使用 AlertDialog 创建的登录框,拷贝到 MainActivity 中直接调用:
正确的屏幕旋转代码在上面已经有了,现在贴正常的 AlertDialog 并附上错误信息。
/**
* 传统 AlertDialog 实验,屏幕旋转
*/
public class TraditionDialogActivity extends Activity implements
OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_dialog);
Button tBtnLoginDialog = (Button) this
.findViewById(R.id.btnLoginDialog);
tBtnLoginDialog.setOnClickListener(this);
}
@Override
public void onClick(View v) {
showLoginDialogWithoutFragment();
}
public void showLoginDialogWithoutFragment() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// Get the layout inflater
LayoutInflater inflater = this.getLayoutInflater();
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(inflater.inflate(R.layout.dialog_fragment_login, null))
// Add action buttons
.setPositiveButton("Sign in",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// sign in the user ...
}
}).setNegativeButton("Cancel", null).show();
}
}
Activity com.xjl.dialogfragmentdemo.tradition.TraditionDialogActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40f7bbf8 that was originally added here
活动窗口COM com.xjl.dialogfragmentdemo.tradition.traditiondialogactivity泄漏。Android内部政策实施。。。。phonewindow decorview美元40f7bbf8最初加入这里”