Android进阶之路 - DialogFragment有没有了解的必要?

发布于:2024-07-04 ⋅ 阅读:(16) ⋅ 点赞:(0)

几个月前写到了弹框业务,以前经常用Dialog、ButtomDialog 、popupWindow 组件,为了契合项目结构参考了原有的 DialogFragment 组件,特此予以记录

我一般在项目中写弹框组件的话,主要用到 alertDialogpopupWindow 组件,关于 DialogFragment 组件将在该篇简单学一下

弹框

Tip:嗯… 可以了解,但是如果你已经掌握了其他弹框技术,在无特定需求下可以先不学,毕竟这么多年下来这款组件的普及率、使用率好像并不太高,而我也在逐渐替换掉项目中 DialogFragment 的使用场景…

基础了解

起初其实我不太理解为何要用DialogFragment?它相比常用弹框组件的优势在哪里?

通过DialogFragment源码可以确定其继承自Fragment故拥有其特性,同时实现了Dialog接口监听弹框的一个取消状态、关闭状态

在这里插入图片描述

查看内部方法并不多,除了 Dilaog 的一些show、dismiss方法外,我觉得最能引起能注意的应该就是生命周期的特性了,所以这应该算是这款组件的一个优势,可以动态监听与Activity的绑定状态,以及自身的一个生命周期状态

在这里插入图片描述

单从以上源码来看,目前为止至少具备一些基础优势

  • 生命周期清晰,扩展了适用场景
  • 支持弹框布局自定义化
  • 支持Dialog相关设置
  • 与Activity生命周期绑定,会随着Activity消失而消失(未复测)

函数分析

当我们创建 DialogFragment 时,因未声明抽象方法,所以我们根据需求,可自行选择重写几个关键方法,如

  • onCreate:生命周期第一步,一般根据业务可获取创建DialogFragment时的入参,用于当前组件显示等
  • onCreateView: 用于设置弹框布局、事件处理
  • onCreateDialog:可在此处调用其Dialog特性
  • onResume:弹窗展示,可在此处获取当前显示的Dialog,便于设置一些特定属性,常用于设置组件展示范围、形式
  • onDismiss: 监听弹窗消失,根据业务需求自行加入逻辑
  • onActivityCreated:便于监听Activity的状态,支持Activity关联创建后,及时展示DialogFragment(方法已过时,未用过)

onCreate

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        //获取外部传入的数据
        String ourContent = bundle.getString("keyContent");
    }

onCreateView

绑定弹框要显示的布局,同时可以设置一些组件显示、事件等

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.our_layout, container, false);
        TextView tvContent = rootView.findViewById(R.id.tv_content);
        TextView tvClose = rootView.findViewById(R.id.tv_close);

        tvClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissAllowingStateLoss();
            }
        });
        tvContent.setOnClickListener(ourClick);
        return rootView;
    }

有的人蛮喜欢抽方法,其实本质相同,这里就是将点击事件抽到了外部(建议初方法内部复杂、繁琐、调用频繁外,并不推荐抽方法)

    View.OnClickListener ourClick = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dismissAllowingStateLoss();
        }
    };

onCreateDialog

关于Dialog属性设置,可以在此处进行设置,例如触摸、点击视图以外区域不会关闭弹框等

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        // dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setCanceledOnTouchOutside(false);
        return dialog;
    }

onResume

显示时设置弹框大小、位置、背景等

    @Override
    public void onResume() {
        super.onResume();
        Dialog dialog = getDialog();
        if (dialog != null && dialog.getWindow() != null) {
            dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes();
            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.gravity = Gravity.CENTER;
            dialog.getWindow().setAttributes(layoutParams);
        }
    }

onDismiss

通常为了灵活性,我们大多会监听弹框消失做一些逻辑处理,不过也不排除一些固有行为直接在组件内部实现

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
// 可自行做一些数据记录之类的  SPUtils.AppSP().put("key", "value");
    }

关于 DialogFragment 常用函数,我们已经都说完了,那么简单说一下它的调用方式,通常我们主要有俩种常见方式

  • 创建 DialogFragment 实例后,调用类似show()函数显示弹框

在这里插入图片描述

  • DialogFragment 内部提供静态方法

有的说遇到了 Fragment already added(重复添加) 的问题,可以参考下方DialogFragment 内部提供的静态方法

  /**
   * 静态方法,支持便捷调用,同时传入所需参数
   */
  public static OurFirstDialogFragment display(final FragmentManager fragmentManager, String contentUrl) {
      OurFirstDialogFragment fragment = (OurFirstDialogFragment) fragmentManager.findFragmentByTag(OurFirstDialogFragment.class.getCanonicalName());
      if (fragment == null) {
          fragment = new OurFirstDialogFragment();
          Bundle bundle = new Bundle();
          bundle.putString(KEY_CONTENT, contentUrl);
          fragment.setArguments(bundle);
      }
      if (!fragment.isAdded()) {
          fragment.show(fragmentManager, OurFirstDialogFragment.class.getCanonicalName());
      }
      return fragment;
  }

关于 DialogFragment 显示,通常有showshowNow函数,主要区别于此

  • show显示稍慢于showNow,这导致调用show了后,立刻修改dialog中的view(例如textView修改字符内容)会崩溃,而showNow不会(showNow容错率更高
  • 待检验:(废弃)展示弹窗后fragment对象会添加到activity,showNow会在弹窗dismiss消失后移除fragment,show不会移除
    (以前同一个对象非连续地调用两次show会崩溃,现在不会了,可能是google更新了,使show也在弹窗消失后移除了)
  • 待检验:不可连续地调用show或者showNow;这个“连续”是指在弹窗还没有消失的时候再次调用,原因在上方说了,展示弹窗后fragment对象会添加到activity,而同一个fragment只能添加一次,所以连续调用可能会崩溃

实战检验

调用方式

涉及到了Fragment,所以一般会用到fragmentManager ,在Activity、Fragment都有现成API

 //当前我用的是静态函数,可以直接通过类型+函数调用
 //关于调用函数,如果为了兼容多场景,可以重载其静态方法
 OurFirstDialogFragment .display(fragmentManager) //不传值
 OurFirstDialogFragment .display(fragmentManager,"数据") //传值

 //通过创建实例的方式,显示弹框,因项目未采用此方式,仅做示例
 var ourFirstDialogFragment = OurFirstDialogFragment()
 fragmentManager?.let { ourFirstDialogFragment.show(it, "tag一般独一无二,防止重复") }

OurFirstDialogFragment(自定义DialogFragment )

package xx;

import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

public class OurFirstDialogFragment extends DialogFragment {
    public static final String KEY_CONTENT = "KEY_CONTENT";
    private String ourContent;
    private TextView tvContent, tvClose;

  /**
   * 静态方法,支持便捷调用,同时传入所需参数
   */
  public static OurFirstDialogFragment display(final FragmentManager fragmentManager, String content) {
      OurFirstDialogFragment fragment = (OurFirstDialogFragment) fragmentManager.findFragmentByTag(OurFirstDialogFragment.class.getCanonicalName());
      if (fragment == null) {
          fragment = new OurFirstDialogFragment();
          Bundle bundle = new Bundle();
          bundle.putString(KEY_CONTENT, content);
          fragment.setArguments(bundle);
      }
      if (!fragment.isAdded()) {
          fragment.show(fragmentManager, OurFirstDialogFragment.class.getCanonicalName());
      }
      return fragment;
  }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle bundle = getArguments();
        //获取外部传入的数据
        ourContent = bundle.getString(KEY_CONTENT);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.our_layout, container, false);
        tvContent = rootView.findViewById(R.id.tv_content);
        tvClose = rootView.findViewById(R.id.tv_close);

        tvClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismissAllowingStateLoss();
            }
        });
        tvContent.setOnClickListener(ourClick);
        return rootView;
    }

    View.OnClickListener ourClick = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dismissAllowingStateLoss();
        }
    };

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog = super.onCreateDialog(savedInstanceState);
        // dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        dialog.setCanceledOnTouchOutside(false);
        return dialog;
    }

    @Override
    public void onResume() {
        super.onResume();
        Dialog dialog = getDialog();
        if (dialog != null && dialog.getWindow() != null) {
            dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
            WindowManager.LayoutParams layoutParams = dialog.getWindow().getAttributes();
            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
            layoutParams.gravity = Gravity.CENTER;
            dialog.getWindow().setAttributes(layoutParams);
        }
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        //SPUtils.AppSP().put("key", "value");
    }

}

嗯,当你看到这儿的话,不知道你是否遇到了下面这个问题,当我们在Dialog内部操作时,我们希望外部可以实时监听,这时候就用到了接口回调

    public interface DialogCallback {
        void onButtonClicked(String buttonText);
    }

    private DialogCallback callback;

   public static OurFirstDialogFragment display(final FragmentManager fragmentManager, String content) {
      OurFirstDialogFragment fragment = (OurFirstDialogFragment) fragmentManager.findFragmentByTag(OurFirstDialogFragment.class.getCanonicalName());
      if (fragment == null) {
          fragment = new OurFirstDialogFragment();
          Bundle bundle = new Bundle();
          bundle.putString(KEY_CONTENT, content);
          fragment.setArguments(bundle);
      }
      //在静态方法中绑定监听回调,如果你采用的是实例调用,需要重写构造参数,然后在绑定监听回调
      fragment.callback = callback;
      if (!fragment.isAdded()) {
          fragment.show(fragmentManager, OurFirstDialogFragment.class.getCanonicalName());
      }
      return fragment;
  }

   tvView.setOnClickListener(v -> {
     dismissAllowingStateLoss();
     callback.onButtonClicked("动态回传要监听的内容即可");
  });