热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

从仿QQ消息提示框来谈弹出式对话框

《代码里的世界》—UI篇用文字札记描绘自己android学习之路转载请保留出处byQiaohttp:blog.csdn.netqiaoideaartic

《代码里的世界》UI篇

用文字札记描绘自己 android学习之路

转载请保留出处 by Qiao
http://blog.csdn.net/qiaoidea/article/details/45896477

【导航】
- 自定义弹出式对话框的简单用法 列举各种常见的对话框实现方案


1.概述

  android原生控件向来以丑著称(新推出的Material Design当另说),因此几乎所有的应用都会特殊定制自己的UI样式。而其中弹出式提示框的定制尤为常见,本篇我们将从模仿QQ退出提示框来看一下常见的几种自定义提示框的实现方式。
  这里使用的几种弹出框实现方法概括为以下几种:

  1. 自定义Dialog
  2. 自定义PopupWindow
  3. 自定义Layout View
  4. Activity的Dialog样式
  5. FragmentDialog

      先看下最终的效果图
    弹出式对话框


2.实践

  前面提到几种实现方式均可以达到同样的演示效果,但其中又是各有不同。这里先逐一列举各种具体实现,最后加以综述总结和归纳吧。
  在此之前呢,先看一下这里实现的对话框共用布局layout/confirm_dialog.xml 。


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_marginRight="5dp" android:background="@drawable/confirm_dialog_bg" android:orientation="vertical">

    <LinearLayout  android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@android:color/transparent" android:orientation="vertical" >

        <TextView  android:id="@+id/title_name" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:paddingBottom="10dp" android:paddingTop="15dp" android:text="Message Title" android:textColor="@android:color/black" android:textSize="20sp" android:visibility="visible" />
    LinearLayout>

    <LinearLayout  android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/transparent" android:orientation="vertical" >

        <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:layout_marginRight="15dp" android:layout_marginTop="10dp" android:textColor="@android:color/black" android:text="this is message content" android:textSize="16dip"/>

        <View  android:layout_width="match_parent" android:layout_height="1px" android:layout_marginTop="15dip" android:background="#c5c5c5" />

        <LinearLayout  android:layout_width="fill_parent" android:layout_height="50dip" android:background="@android:color/transparent" android:gravity="center_horizontal" android:orientation="horizontal" >

            
            <Button  android:id="@+id/btn_cancel" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" android:text="Cancel" android:textStyle="bold" android:textColor="#0072c6" android:background="@drawable/confirm_dialog_cancel_selector" android:textSize="15sp" />

            

            <View android:layout_width="1px" android:layout_height="match_parent" android:layout_gravity="center_horizontal" android:background="#c5c5c5"/>

            <Button  android:id="@+id/btn_ok" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_weight="1" android:text="OK" android:textStyle="bold" android:textColor="#0072c6" android:background="@drawable/confirm_dialog_ok_selector" android:textSize="15sp" />
        LinearLayout>
    LinearLayout>

LinearLayout>

仅仅通过布局预览就可以看到效果了:
  图片预览
  下边我们分别通过上述几种方式来使用这个布局展示消息提示框。
  

2.1 Dialog 

  这个是最基本也最常见的非阻塞式对话框。具体形式可分为七种,详细参见网上各种文章,随便引用一篇7种形式的Android Dialog使用举例。
  (注:官方在fragmentDialog推出后就不在推荐直接使用Dialog来创建对话框,这是后话)
  我们这里自定义的提示框ConfirmDialog继承自Dialog,使用confirm_dialog.xml 初始化布局,绑定相应事件。

public class ConfirmDialog extends Dialog {
    private Context context;
    private TextView titleTv,contentTv;
    private View okBtn,cancelBtn;
    private OnDialogClickListener dialogClickListener;

    public ConfirmDialog(Context context) {
        super(context);
        this.cOntext= context;
        initalize();
    }

    //初始化View
    private void initalize() {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.confirm_dialog, null);
        setContentView(view);
        initWindow();

        titleTv = (TextView) findViewById(R.id.title_name);
        cOntentTv= (TextView) findViewById(R.id.text_view);
        okBtn = findViewById(R.id.btn_ok);
        cancelBtn = findViewById(R.id.btn_cancel);
        okBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dismiss();
                if(dialogClickListener != null){
                    dialogClickListener.onOKClick();
                }
            }
        });
        cancelBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dismiss();
                if(dialogClickListener != null){
                    dialogClickListener.onCancelClick();
                }
            }
        });
    }

    /** *添加黑色半透明背景 */
    private void initWindow() {
        Window dialogWindow = getWindow();
        dialogWindow.setBackgroundDrawable(new ColorDrawable(0));//设置window背景
        dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);//设置输入法显示模式
        WindowManager.LayoutParams lp = dialogWindow.getAttributes();
        DisplayMetrics d = context.getResources().getDisplayMetrics();//获取屏幕尺寸
        lp.width = (int) (d.widthPixels * 0.8); //宽度为屏幕80% 
        lp.gravity = Gravity.CENTER;     //中央居中
        dialogWindow.setAttributes(lp);
    }

    public void setOnDialogClickListener(OnDialogClickListener clickListener){
        dialogClickListener = clickListener;
    }

    /** *添加按钮点击事件 */
    public interface OnDialogClickListener{
        void onOKClick();
        void onCancelClick();
    }
}

2.2 PopupWindow 

  PopupWindow是阻塞式对话框,只有在退出操作时候程序才会继续运行。另外PopupWindow可以根据自由确定自身位置。按照位置有无偏移分,可以分为偏移和无偏移两种;按照参照物的不同,可以分为相对于某个控件(Anchor锚)和相对于父控件。具体如下

showAsDropDown(View anchor):相对某个控件的位置(正左下方),无偏移
showAsDropDown(View anchor, int xoff, int yoff):相对某个控件的位置(正下方),有偏移
showAtLocation(View parent, int gravity, int x, int y):相对于父控件的位置(例如正中央Gravity.CENTER,下方Gravity.BOTTOM等),可以设置偏移或无偏移

这里只是达到同样的显示效果,仅示范下showAtBottom的使用:

public class ConfirmPopWindow extends PopupWindow{
    private Context context;
    private TextView titleTv,contentTv;
    private View okBtn,cancelBtn;
    private OnDialogClickListener dialogClickListener;

    public ConfirmPopWindow(Context context) {
        super(context);
        this.cOntext= context;
        initalize();
    }

    private void initalize() {
        LayoutInflater inflater = LayoutInflater.from(context);
        View view = inflater.inflate(R.layout.confirm_dialog, null);
        setContentView(view);
        initWindow();

        titleTv = (TextView) view.findViewById(R.id.title_name);
        cOntentTv= (TextView)  view.findViewById(R.id.text_view);
        okBtn =  view.findViewById(R.id.btn_ok);
        cancelBtn =  view.findViewById(R.id.btn_cancel);
        okBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dismiss();
                if(dialogClickListener != null){
                    dialogClickListener.onOKClick();
                }
            }
        });
        cancelBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                dismiss();
                if(dialogClickListener != null){
                    dialogClickListener.onCancelClick();
                }
            }
        });
    }

    private void initWindow() {
        this.setBackgroundDrawable(new ColorDrawable(0)); 
        DisplayMetrics d = context.getResources().getDisplayMetrics();
        this.setWidth((int) (d.widthPixels * 0.8));  
        this.setHeight(LayoutParams.WRAP_CONTENT);  
        this.setFocusable(true);  
        this.setOutsideTouchable(true);  
        this.update();  
    }

    public void showAtBottom(View view){
        showAsDropDown(view, Math.abs((view.getWidth() - getWidth())/2), 20);
    }

    public void setOnDialogClickListener(OnDialogClickListener clickListener){
        dialogClickListener = clickListener;
    }

    public interface OnDialogClickListener{
        void onOKClick();
        void onCancelClick();
    }
}

2.3 自定义Layout 

  前边两种是系统封装好的View ,同样的,我们也可以自定义layout布局来实现的弹出式对话框效果。既然是自定义,有必要细致讲述一下,
  ConfirmLayout继承自FrameLayout,通过获取窗口管理器WindowManager 将我们的自定义view添加到窗口最前端并显示出来,达到预期效果。
    

1.初始化View

  先初始化半透明黑色背景和对应的confirm_layout,然后给窗体添加按键返回事件

    protected void initialize() {
        initBackground();//初始化黑色背景
        initContentView();//初始化confirm_layout 对应的View

        windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        setOnKeyListener(new OnKeyListener() { //添加按键返回事件
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (KeyEvent.KEYCODE_BACK == keyCode && KeyEvent.ACTION_DOWN == event.getAction()) {
                    hide();//隐藏当前view
                    return true;
                }
                return false;
            }

        setFocusable(true); //可获得焦点
        setFocusableInTouchMode(true); //可触碰获得焦点
    }

2.显示自定义VIew : show()

  调用显示的时候保证在主线程中,如果当前View没有被添加至窗口中,则添加;然后使用动画渐变效果显示背景,最后动画完成时显示当前对话框View.

public void show() {
        ((Activity) getContext()).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (getParent() == null) { //没有添加则添加至窗体
                    //获取窗体的布局属性,设置左上角对齐,填充父容器
                    WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
                    wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION;
                    wlp.format = PixelFormat.TRANSPARENT;
                    wlp.gravity = Gravity.LEFT | Gravity.TOP;
                    wlp.width = LayoutParams.MATCH_PARENT;
                    wlp.height = LayoutParams.MATCH_PARENT;
                    windowManager.addView(ConfirmLayout.this, wlp);
                }
                showBackGround();//显示背景动画和自定义View
            }
        });
    }

    /** *显示背景动画 */
    protected void showBackGround() {
        if (isShowing)
            return;
        isShowing = true;
        background.clearAnimation();
        background.setVisibility(View.VISIBLE);
        AlphaAnimation an = new AlphaAnimation(0, 1);
        an.setDuration(durationMillis);
        background.startAnimation(an);
    }

3.隐藏自定义VIew : hide()

  隐藏对话框的方法跟show()恰恰相反,首先调用隐藏动画,动画结束从窗体中移除View

    public void hide() {
        ((Activity) getContext()).runOnUiThread(new Runnable() {
            @Override
            public void run() {
                hideBackGround();//隐藏背景
                if (getParent() != null)
                    windowManager.removeView(ConfirmLayout.this);//移除view
            }
        });
    }

    /** *隐藏背景背景动画 */
    protected void hideBackGround() {
        if (!isShowing)
            return;
        isShowing = false;
        background.clearAnimation();
        AlphaAnimation an = new AlphaAnimation(1, 0);
        an.setDuration(durationMillis);
        an.setAnimationListener(new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                background.setVisibility(View.GONE);
            }
        });
        background.startAnimation(an);
    }

  其他部分同上,不再一一贴出,详细可查看示例源码。
  
  

2.4 Activity的Dialog样式 

  通过使用主题Theme来实现Activity作为一个dialog来显示的效果。我们首先在 AndroidManifest.xml 中配置该activity,使得

android:theme=”@android:style/Theme.Dialog”

同样我们可以自定义继承于Theme.Dialog的style样式增加自定义属性,比如:

<resources>
    <style name="DialogStyle" parent="@android:style/Theme.Dialog"> <item name="android:windowBackground">@android:color/transparent "android:windowFrame">@null "android:windowNoTitle">true "android:windowIsFloating">true "android:windowIsTranslucent">true "android:windowFullscreen">true "android:backgroundDimEnabled">true style>
resources>
然后使用 > android:theme=”@style/DialogStyle” 达到上述效果。具体实现跟dialog类似:
public class ConfirmActivity extends Activity{
    private TextView titleTv,contentTv;
    private View okBtn,cancelBtn;
    private OnDialogClickListener dialogClickListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.confirm_dialog);
        initViews();
        initListeners();
    }

    private void initViews() {
        initWindow();
        titleTv = (TextView) findViewById(R.id.title_name);
        cOntentTv= (TextView) findViewById(R.id.text_view);
        okBtn = findViewById(R.id.btn_ok);
        cancelBtn = findViewById(R.id.btn_cancel);
        okBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                finish();
                if(dialogClickListener != null){
                    dialogClickListener.onOKClick();
                }
            }
        });
    }

    private void initWindow() {
       getWindow().setBackgroundDrawable(new ColorDrawable(0));
       getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN |
               WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
    }

    private void initListeners() {
        cancelBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                finish();
                if(dialogClickListener != null){
                    dialogClickListener.onCancelClick();
                }
            }
        });
    }

    public void setOnDialogClickListener(OnDialogClickListener clickListener){
        dialogClickListener = clickListener;
    }


    public interface OnDialogClickListener{
        void onOKClick();
        void onCancelClick();
    }
}

2.5 DialogFragment 

  DialogFragment在android 3.0时被引入并被加以推广。
  我们在使用DialogFragment时,至少需要实现onCreateView或者onCreateDIalog方法。这里在onCreateDIalog中直接返回前面写好的ConfirmDialog来实现这个Fragment。

public class ConfirmFragment extends DialogFragment{

    @Override
    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new ConfirmDialog(getActivity());
    }

public void setOnDialogClickListener(OnDialogClickListener clickListener){
        ((ConfirmDialog)getDialog()).setOnDialogClickListener(clickListener);
    }
}

  当然并不推荐偷懒直接返回前面定义好的ConfirmDialog。我们实际上使用fragment的onCreateView也更合理和简介,他可以产生同样的Dialog效果,同时可以作为内嵌fragment引用。从使用总结来看,FragmentDialog 相较于Dialog有两点好处:

  • 在手机配置变化,导致Activity需要重新创建时,例如旋屏,DialogFragment对话框将会由FragmentManager自动重建,然而Dialog实现的对话框则不会重新生成;

  • DialogFragment还拥有fragment的优点,即可以在一个Activity内部实现回退(因为FragmentManager会管理一个回退栈 ,另外,他可以直接作为一个普通Fragment嵌套在其他布局里边;


3.小结

  从实现效果来看我们确实有很多选择,当然我们用的最多的必然要数Dialog(FragmentDialog)和PopupWindow了。但在一般情况下,选择Dialog和PopupWindow是由我们的具体使用场景来定。比如有些提示消息,比较适合Dialog,而弹出一些具体选项,需要等待选择结果等情况,更倾向于使用PopupWindow了。

  • FragmentDialog的几种使用场景

           

  • PopupWindow的几种使用场景

           


4.补充

  其实还有种长按弹出菜单,这种除了可以通过上述方法弹出菜单选项外,还可以通过系统提供的 View.setOnCreateContextMenu()方法来实现。比如:

itemView.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
                @Override
                public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
                    menu.add("删除").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                        @Override
                        public boolean onMenuItemClick(MenuItem item) {
                            //执行删除操作
                            return true;
                        }
                    });
                }
            });

长按弹出,基本效果为:
onCreateContextMenu
  有兴趣的不妨试一下。

  后边的话我们先从源码角度来看一下这里讲的几种实现方案的具体原理,最后通过一些简易封装来做一个类似IOS上的ActionSheet控件的效果。
  演示效果大概为:
  
  ActionSheet
  详情请继续关注接下来的博文。


最后附上本篇所讲内容的源码:
示例源码demo下载地址(已重新更新)



推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了django中视图函数的使用方法,包括如何接收Web请求并返回Web响应,以及如何处理GET请求和POST请求。同时还介绍了urls.py和views.py文件的配置方式。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • ①页面初始化----------收到客户端的请求,产生相应页面的Page对象,通过Page_Init事件进行page对象及其控件的初始化.②加载视图状态-------ViewSta ... [详细]
  • centos php部署到nginx 404_NodeJS项目部署到阿里云ECS服务器全程详解
    本文转载自:http:www.kovli.com20170919ecs-deploy作者:Kovli本文详细介绍如何部署NodeJS项目到阿里云ECS上, ... [详细]
  • 开发笔记:UEditor调用上传图片上传文件等模块
    1、引入ue相关文件,写好初始代码为了更好的封装整一个单独的插件,这里我们要做到示例化ue后隐藏网页中的编辑窗口,并移除焦点。 ... [详细]
  • 这两天用到了ListView,写下遇到的一些问题。首先是ListView本身与子控件的焦点问题,比如我这里子控件用到了Button,在需要ListView中的根布局属性上加上下面的这一个属性:and ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • JS实现一键分享功能
    本文介绍了如何使用JS实现一键分享功能,并提供了2019独角兽企业招聘Python工程师的标准。同时,给出了分享到QQ空间、新浪微博和人人网的链接。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • android 触屏处理流程,android触摸事件处理流程 ? FOOKWOOD「建议收藏」
    android触屏处理流程,android触摸事件处理流程?FOOKWOOD「建议收藏」最近在工作中,经常需要处理触摸事件,但是有时候会出现一些奇怪的bug,比如有时候会检测不到A ... [详细]
  • struts2重点——ValueStack和OGNL
    一、值栈(ValueStack)1.实现类:OGNLValueStack2.对象栈:CompoundRoot( ... [详细]
  • 本篇文章笔者在上海吃饭的时候突然想到的这段时间就有想写几篇关于返回系统的笔记,所以回家到之后就奋笔疾书的写出来发布了事先在网上找了很多方法,发现有 ... [详细]
author-avatar
T糖糖Tsweet_629
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有