TMaize Blog

Android中的各种弹窗实现

在 Android 中实现弹出层方式很多,下面介绍几种常用的

1.通过FrameLayout模拟弹出层

优点:实现简单,布局也比较好写

缺点:位置在上下左右比较好,其他复杂的布局要写大量的点击事件来阻止点击向下传递

比如实现下面的效果:

p1

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/but01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Click"/>

    </RelativeLayout>

    <!--弹出层一定要放在最后面才会显示在最上层,同时默认隐藏-->
    <LinearLayout
        android:id="@+id/popouter"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="#AAA"
            android:alpha="0.5"
            android:id="@+id/popinner1"
            android:layout_weight="5">

        </LinearLayout>

        <LinearLayout
            android:id="@+id/popinner2"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:background="#FFF"
            android:orientation="vertical"
            android:layout_weight="1">

            <Button
                android:text="弹出的按钮"
                android:id="@+id/but02"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </LinearLayout>

    </LinearLayout>

</FrameLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    popouter = findViewById(R.id.popouter);
    popinner1 = findViewById(R.id.popinner1);
    popinner2 = findViewById(R.id.popinner2);
    but01 = (Button) findViewById(R.id.but01);
    but02 = (Button) findViewById(R.id.but02);
    but01.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            popouter.setVisibility(View.VISIBLE);
        }
    });

    //这是底部弹窗的外部区域,点击取消显示,同时起到阻止冒泡的作用
    popinner1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            popouter.setVisibility(View.GONE);
        }
    });

    //防止冒泡
    popinner2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Do nothing..
        }
    });

    but02.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Toast.makeText(MainActivity.this, "Hello World", Toast.LENGTH_SHORT).show();
        }
    });

}

2.各种Dialog

这里以Dialog为例子,系统中其他的Dialog类似

注意:官方不推荐直接使用Dialog创建对话框而是推荐使用DialogFragment

Dialog dialog = new Dialog(mContext);
dialog.setTitle("选择xxx");

Window window = dialog.getWindow();
window.setGravity(Gravity.BOTTOM|Gravity.CENTER);

//好奇怪这样可以取消外边距
//dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.WHITE));

dialog.setContentView(LayoutInflater.from(mContext).inflate(R.layout.pop,null));
dialog.show();

3.DialogFragment

使用DialogFragment来管理对话框,当旋转屏幕和按下后退键时可以更好的管理其声明周期,它和Fragment有着基本一致的声明周期。 且DialogFragment也允许开发者把Dialog作为内嵌的组件进行重用,类似Fragment(可以在大屏幕和小屏幕显示出不同的效果)

public class MyDialog extends DialogFragment {
    
    private Context context;
    
    //获得context
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }

    //初始化
    @Override
    public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {

        Window window = getDialog().getWindow();
        //不显示标题
        window.requestFeature(Window.FEATURE_NO_TITLE);
        
        View view = inflater.inflate(R.layout.pop, container);

        return view;
    }
}

创建Dialog并使用

MyDialog dialog = new MyDialog();
dialog.show(getFragmentManager(), "mydialog");

4.PopupWindow

PopupWindow是一个以弹窗方式呈现的控件,可以用来显示任意视图(View),而且会浮动在当前活动(activity)的顶部”。因此我们可以通过PopupWindow实现各种各样的弹窗效果,进行信息的展示或者是UI交互,由于PopupWindow自定义布局比较方便,而且在显示位置比较自由不受限制,因此受到众多开发者的青睐。

AlertDialog在位置显示上是固定的,而PopupWindow则相对比较随意,能够在主屏幕上的任意位置显示。

效果如下

p2

//contentView为要显示的view,width和height为宽和高,值为像素值,也可以是MATCHT_PARENT和WRAP_CONTENT
public PopupWindow(View contentView, int width, int height, boolean focusable)
public PopupWindow (Context context)
public PopupWindow(View contentView, int width, int height)
public PopupWindow(View contentView)
public void showAtLocation(View parent, int gravity, int x, int y)
public void showAsDropDown(View anchor)
public void showAsDropDown(View anchor, int xoff, int yoff)

通过showAtLocation设置,parent可以为Activity中的任意一个View(最终的效果一样),会通过这个View找到其父Window,也就是Activity的Window。

gravity,默认为Gravity.NO_GRAVITY,等效于Gravity.LEFT Gravity.TOP

x, y,边距。这里的x,y表示距离Window边缘的距离,方向由Gravity决定。例如:设置了Gravity.TOP,则y表示与Window上边缘的距离;而如果设置了Gravity.BOTTOM,则y表示与下边缘的距离。

如果弹窗位置超出了Window的范围,会自动处理使其处于Window中。对于屏幕(左上角0,0),窗位置超出了Window的范围,会自动处理使其处于Window中,显示范围是顶部和底部

注意使用的时候,如果设置Gravity.TOP,则要计算状态栏的高度

通过showAsDropDown(相对于组件)来设置,弹窗的位置是相对于anchor。

如果指定了xoff和yoff,则会在原有位置向右偏移xoff,向下偏移yoff。

如果指定gravity为Gravity.RIGHT,则弹窗和控件右对齐;否则左对齐。注意,计算右对齐时使用了- PopupWindow的宽度,如果指定的宽度不是固定值,则计算会失效(可以从源码中看出来)。

如果弹窗位置超出了Window的范围,会自动处理使其处于Window中。

如果anchor可以滚动,则滚动过程中,PopupWindow可以自动更新位置,跟随anchor控件。

View popView = LayoutInflater.from(this).inflate(R.layout.pop,null);
final PopupWindow popWindow = new PopupWindow(popView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);

popWindow.setTouchInterceptor(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return false;
    }
});

// 如果不设置PopupWindow的背景,有些版本就会出现一个问题:无论是点击外部区域还是Back键都无法dismiss弹框
popWindow.setBackgroundDrawable(new ColorDrawable());
popWindow.showAtLocation(findViewById(R.id.outer), Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0);

5.Toast

这是最常见的一种弹窗,用法也比较简单

Toast toast = Toast.makeText(mContext, "Hello", Toast.LENGTH_SHORT);
//设置位置
toast.setGravity(Gravity.TOP|Gravity.RIGHT,0,0);

View view =LayoutInflater.from(mContext).inflate(R.layout.pop, null);

//在原始布局上添加布局
LinearLayout toastView = (LinearLayout) toast.getView();
//这个很重要,不然宽度很小
view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
toastView.addView(view,0);

//设置布局,会取消默认的Toast样式
//toast.setView(view);

toast.show();

6.Activity并配合Dialog主题的方式实现弹窗

建立一个样式继承Dialog主题,在Activity中指定样式

为主Activity设置这个主题后会在桌面上弹出一个弹窗,且空白部分可以看到桌面上的东西,效果不错

<style name="BaseDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar">
    <item name="android:windowBackground">@android:color/transparent</item>
    <!--<item name="android:windowCloseOnTouchOutside">false</item>-->
    <item name="android:windowAnimationStyle">@android:style/Animation</item>
    <item name="android:windowIsTranslucent">false</item><!--半透明-->
    <item name="android:backgroundDimEnabled">false</item><!--模糊-->
</style>

<style name="AppTheme" parent="BaseDialog">
</style>