Android 官方推荐: DialogFragment 创建对话框
源代码参考: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最初加入这里”