提示控件之自定义Dialog

来源:互联网 发布:使命召唤 for mac 编辑:程序博客网 时间:2024/06/06 03:15

Dialog作为一个安卓应用中的常用提示控件,使用率非常之高,基本上每个APP中都有不同程度的运用。系统提供了一些便捷的使用方法,但原生的Dialog样式非常复古,使用时与本应用风格很难搭配,所以我们需要自定义一些常用的Dialog,封装常用的方法,以便需要时使用。

一、期望目标:

1.Dialog能自定义布局样式;
2.Dialog能自由设置弹出及隐藏效果;
3.Dialog能便捷控制其隐藏触发条件;
4.封装常用类型的Dialog。

二、大致思路:

1.自定义布局文件替换Dialog布局;
2.自定义Dialog属性样式;
3.监听实现点击外部点击、返回键点击等等;
4.封装常用对话框使用方法,如:普通对话框(提示信息)、加载对话框(无进度进度条或gif)、进度对话框(进度文本或带进度进度条)等。

三、具体实现
1.自定义CustomeDialog

package com.alone.custome.dialog;import android.app.Dialog;import android.content.Context;import android.content.DialogInterface;import android.os.Bundle;import android.view.KeyEvent;import android.view.View;/** * Created by Alone on 16/3/1. * 自定义dilog,避免了系统dialog样式简单不宜修改等问题 * 可自由设置样式,透明度,圆角等等 * * 示例样式查看style.xml * <!--自定义弹出框的样式 start--> * <!--自定义弹出框的样式 end--> */public class CustomeDialog extends Dialog {  private CustomeDialogListener mListener;  private int[] mViewIds;  private boolean mBackKeyCancel = true;//返回键是否隐藏  public CustomeDialog(Context context, int layoutId, int styleId, int[] viewIds) {    super(context, styleId);    setContentView(layoutId);    this.mViewIds = viewIds;  }  public CustomeDialog(Context context, View layout, int styleId, int[] viewIds) {    super(context, styleId);    setContentView(layout);    this.mViewIds = viewIds;  }  /**   * 自定义dialog构造器   * @param context   上下文   * @param layoutId  布局文件id   * @param styleId   自定义样式id   * @param viewIds   控件id数组   * @param listener  回调函数   */  public CustomeDialog(Context context, int layoutId, int styleId, int[] viewIds, CustomeDialogListener listener) {    this(context, layoutId, styleId, viewIds);    setListener(listener);  }  /**   * 自定义dialog构造器   * @param context  上下文   * @param layout   布局文件view   * @param styleId  自定义样式id   * @param viewIds  控件id数组   * @param listener 回调函数   */  public CustomeDialog(Context context, View layout, int styleId, int[] viewIds, CustomeDialogListener listener) {    this(context, layout, styleId, viewIds);    setListener(listener);  }  /**   * 在dialog的onCreate方法中绑定监听事件   * @param savedInstanceState   */  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    for (int viewId : mViewIds) {      (findViewById(viewId)).setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {          if (mListener != null) {            mListener.buttonOnClick(v);          }        }      });    }    this.setOnKeyListener(new OnKeyListener() {      @Override      public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {        //点击返回键        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {          if (!mBackKeyCancel) {            return true;          }        }        return false;      }    });  }  public void setListener(CustomeDialogListener mListener) {    this.mListener = mListener;  }  /**   * 按返回键是否取消   */  public void setBackKeyCancel(boolean mBackKeyMark) {    this.mBackKeyCancel = mBackKeyMark;  }  public interface CustomeDialogListener {    void buttonOnClick(View view);  }}

CustomeDialog继承Dialog,则拥有Dialog所有的特性;构造器中传入layoutId便于使用自定义布局文件,styleId为自定义样式属性,获取viewIds遍历添加onClick事件;Dialog的setOnKeyListener可以给对话框设置按钮监听,返回值false有效,true拦截,此处监听返回键即可,同时对外公布一个公共方法setBackKeyCancel()用以设置mBackKeyCancel属性值。

2.自定义属性样式

此处我们自定义弹出框的样式,此处定义两种:普通提示框(弹出时背景变暗),加载对话框(弹出时背景不变暗)。在res/values/styles.xml中节点下加上:

<!--自定义弹出框的样式 start--><style name="customeDialog" parent="android:style/Theme.Dialog">    <item name="android:windowBackground">@android:color/transparent</item><!-- 窗口透明,如此颜色决定为布局文件颜色 -->    <item name="android:windowFrame">@null</item><!--Dialog的windowFrame框为无 -->    <item name="android:windowNoTitle">true</item><!-- 无标题 -->    <item name="android:windowIsFloating">true</item><!-- 是否漂现在activity上 -->    <item name="android:windowIsTranslucent">true</item><!-- 是否半透明 -->    <!--<item name="android:windowContentOverlay">@null</item>-->    <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>    <item name="android:backgroundDimEnabled">true</item><!--背景变暗-->    <item name="android:backgroundDimAmount">0.6</item><!--模糊程度--></style><style name="loadingDialog" parent="android:style/Theme.Dialog">    <item name="android:windowBackground">@android:color/transparent</item><!-- 窗口透明,如此颜色决定为布局文件颜色 -->    <item name="android:windowFrame">@null</item><!--Dialog的windowFrame框为无 -->    <item name="android:windowNoTitle">true</item><!-- 无标题 -->    <item name="android:windowIsFloating">true</item><!-- 是否漂现在activity上 -->    <item name="android:windowIsTranslucent">true</item><!-- 是否半透明 -->    <!--<item name="android:windowContentOverlay">@null</item>-->    <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>    <item name="android:backgroundDimEnabled">true</item><!--背景变暗-->    <item name="android:backgroundDimAmount">0.0</item><!--模糊程度--></style><!--自定义弹出框的样式 end-->

3.自定义布局文件

自定义三种常用Dialog布局文件:提示对话框、进度对话框、正在加载对话框。
提示对话框layout_dialog_tips.xml如下:

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:background="@drawable/normal_dialog_style">    <TextView        android:id="@+id/id_text_title"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp"        android:layout_marginTop="20dp"        android:gravity="center"        android:text="提示"        android:textColor="@color/black"        android:textSize="16sp"        android:textStyle="bold" />    <TextView        android:id="@+id/id_text_details"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@+id/id_text_title"        android:layout_marginBottom="25dp"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp"        android:layout_marginTop="15dp"        android:gravity="center"        android:textColor="@color/black"        android:textSize="14sp" />    <ImageView        android:layout_width="match_parent"        android:layout_height="1px"        android:layout_above="@+id/id_btn_confirm"        android:src="@color/gray" />    <Button        android:id="@+id/id_btn_confirm"        android:layout_width="match_parent"        android:layout_height="50dp"        android:layout_below="@+id/id_text_details"        android:background="@drawable/selector_btn_dialog_state"        android:text="取   消"        android:textColor="@drawable/selector_text_dialog_state" /></RelativeLayout>

为了美化界面,layout_dialog_tips.xml中使用了自定义的圆角背景normal_dialog_style.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android">    <solid android:color="@color/white" />    <corners android:radius="5dp"/></shape>

为了美化按钮点击效果,layout_dialog_tips.xml中使用了自定义的背景selector_btn_dialog_state.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_pressed="true">        <shape android:shape="rectangle">            <corners                android:bottomLeftRadius="5dp"                android:bottomRightRadius="5dp" />            <solid android:color="@color/pale" />        </shape>    </item>    <item android:state_pressed="false">        <shape android:shape="rectangle">            <corners android:radius="5dp" />            <solid android:color="@color/white" />        </shape>    </item></selector>

按钮点击颜色变幻使用了自定义的切换效果selector_text_dialog_state.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_pressed="true" android:color="@color/red" />    <item android:state_pressed="false" android:color="@color/black" /></selector>

此处为了方便,我们用文本“**%”来表示进度,进度对话框与正在加载对话框共用一个布局文件layout_dialog_loading.xml代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:circleprogress="http://schemas.android.com/apk/res-auto"    android:layout_width="125dp"    android:layout_height="125dp"    android:background="@drawable/loading_dialog_style"    android:gravity="center_horizontal"    android:orientation="vertical">    <com.alone.custome.progressBar.CircleProgress        android:id="@+id/progress"        android:layout_width="100dp"        android:layout_height="100dp"        circleprogress:color1="@android:color/holo_red_light"        circleprogress:color2="@android:color/holo_green_light"        circleprogress:color3="@android:color/holo_blue_light" />    <TextView        android:id="@+id/id_text_details"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginBottom="10dp"        android:text="正在加载..."        android:textColor="@color/white"        android:textSize="11sp" /></LinearLayout>

为了美化界面,layout_dialog_loading.xml中使用了自定义的圆角背景loading_dialog_style,代码如下:

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android">    <solid android:color="@color/translucent" />    <corners android:radius="10dp"/></shape>

layout_dialog_loading.xml中使用了一个自定义进度条控件CircleProgress,来自于github,循环动画很棒,界面非常漂亮,地址:https://github.com/Fichardu/CircleProgress

颜色定义文件colors.xml:

<color name="transparent">#00000000</color><color name="translucent">#70000000</color><color name="white">#FFFFFF</color><color name="black">#000000</color><color name="red">#FF0000</color><color name="green">#00FF00</color><color name="blue">#0000FF</color><color name="gray">#BEBEBE</color><color name="pale">#EBEBEB</color>

4.创建工具类封装常用Diaolg

package com.alone.custome.dialog;import android.app.Activity;import android.content.Context;import android.content.DialogInterface;import android.os.Handler;import android.util.Log;import android.view.View;import android.widget.TextView;import com.alone.R;import com.alone.application.MyApplication;import java.util.ArrayList;import java.util.List;/** * Created by Alone on 16/8/12. * Be used for */public class DialogUtils {    private static final String TAG = DialogUtils.class.getSimpleName();    private static Handler mHandler = new Handler(MyApplication.getInstance().getMainLooper());    private static List<CustomeDialog> mList = new ArrayList<>();    public static void showTipsDialog(Context context, int resId) {        showTipsDialog(context, context.getString(resId));    }    /**     * 普通提示对话框     *     */    public static void showTipsDialog(final Context context, final String detailsMsg) {        ((Activity)context).runOnUiThread(new Runnable() {            @Override            public void run() {                final CustomeDialog dialog = new CustomeDialog(                        context,                        R.layout.layout_dialog_tips,                        R.style.customeDialog,                        new int[]{R.id.id_btn_confirm});                dialog.setListener(new CustomeDialog.CustomeDialogListener() {                    @Override                    public void buttonOnClick(View view) {                        switch (view.getId()) {                            case R.id.id_btn_confirm:                                if (dialog != null && dialog.isShowing()) {                                    dialog.cancel();                                }                                break;                        }                    }                });                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {                    @Override                    public void onCancel(DialogInterface dialog) {                        mList.remove(dialog);                        Log.d(TAG, "#对话框数量:" + mList.size());                    }                });                TextView detailsText = (TextView) dialog.findViewById(R.id.id_text_details);                detailsText.setText(detailsMsg);                dialog.setBackKeyCancel(true);                dialog.show();                mList.add(dialog);                Log.d(TAG, "#对话框数量:" + mList.size());            }        });    }    public static void showLoadingDialog(Context context, CustomeDialog oldDialog, int resId, DialogCallBack callBack) {        showLoadingDialog(context, oldDialog, context.getString(resId), callBack);    }    /**     * 正在加载对话框     * @param oldDialog 此参数为了确保new出多个对话框无法关闭的情况     */    public static void showLoadingDialog(final Context context, final CustomeDialog oldDialog, final String detailsMsg, final DialogCallBack callBack) {        ((Activity)context).runOnUiThread(new Runnable() {            @Override            public void run() {                CustomeDialog dialog = null;                if (oldDialog != null) {                    mList.remove(dialog);                    Log.d(TAG, "#对话框数量:" + mList.size());                    dialog = oldDialog;                } else {                    dialog = new CustomeDialog(                            context,                            R.layout.layout_dialog_loading,                            R.style.loadingDialog,                            new int[]{});                }                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {                    @Override                    public void onCancel(DialogInterface dialog) {                        mList.remove(dialog);                        Log.d(TAG, "#对话框数量:" + mList.size());                    }                });                TextView detailsText = (TextView) dialog.findViewById(R.id.id_text_details);                detailsText.setText(detailsMsg);                dialog.setBackKeyCancel(false);                dialog.setCanceledOnTouchOutside(false);                dialog.show();                mList.add(dialog);                Log.d(TAG, "#对话框数量:" + mList.size());                callBack.getDialog(dialog);            }        });    }    /**     * 下载进度对话框     * @param oldDialog 此参数为了确保new出多个对话框无法关闭的情况     */    public static void showDownloadingDialog(final Context context, final CustomeDialog oldDialog, final int progress, final DialogCallBack callBack) {        ((Activity)context).runOnUiThread(new Runnable() {            @Override            public void run() {                CustomeDialog dialog = null;                if (oldDialog != null) {                    mList.remove(dialog);                    Log.d(TAG, "#对话框数量:" + mList.size());                    dialog = oldDialog;                } else {                    dialog = new CustomeDialog(                            context,                            R.layout.layout_dialog_loading,                            R.style.customeDialog,                            new int[]{});                }                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {                    @Override                    public void onCancel(DialogInterface dialog) {                        mList.remove(dialog);                        Log.d(TAG, "#对话框数量:" + mList.size());                    }                });                TextView detailsText = (TextView) dialog.findViewById(R.id.id_text_details);                detailsText.setText("正在下载: " + progress + "%");                dialog.setBackKeyCancel(false);                dialog.setCanceledOnTouchOutside(false);                dialog.show();                mList.add(dialog);                Log.d(TAG, "#对话框数量:" + mList.size());                callBack.getDialog(dialog);            }        });    }    /**     * 设置下载进度对话框的进度     */    public static void setDownloadingProgress(CustomeDialog dialog, int progress) {        setDialogMsg(dialog, "正在下载: " + progress + "%");    }    public static void setDialogMsg(CustomeDialog dialog, int resId) {        setDialogMsg(dialog, MyApplication.getInstance().getString(resId));    }    /**     * 设置对话框文本     */    public static void setDialogMsg(final CustomeDialog dialog, final String detailsMsg) {        mHandler.post(new Runnable() {            @Override            public void run() {                if (dialog != null && dialog.isShowing()) {                    TextView textView = (TextView) dialog.findViewById(R.id.id_text_details);                    if (textView != null) {                        textView.setText(detailsMsg);                    }                }            }        });    }    /**     * 关闭对话框     */    public static void closeDialog(final CustomeDialog dialog) {        mHandler.post(new Runnable() {            @Override            public void run() {                if (dialog != null && dialog.isShowing()) {                    dialog.cancel();                }            }        });    }    public interface DialogCallBack {        void getDialog(CustomeDialog dialog);    }}

工具类中将所有的Dialog存储于一个List中进行管理,通过
setCanceledOnTouchOutside()设置外部触摸是否关闭;
setBackKeyCancel()设置点击返回键是否关闭;
setListener()设置点击监听;
通过Handler确保在主线程更新UI;
使用oldDialog考虑其复用情况;

5.测试

新建测试Activity,分别弹出普通提示对话框,正在加载对话框,进度对话框,核心代码:

private CustomeDialog mNormalDialog;private CustomeDialog mLoadingDialog;private CustomeDialog mDownloadDialog;       

弹出加载对话框,3秒后关闭:

DialogUtils.showLoadingDialog(DialogActivity.this, mLoadingDialog, "正在加载...", new DialogUtils.DialogCallBack() {    @Override    public void getDialog(CustomeDialog dialog) {        mLoadingDialog = dialog;    }});new Thread(new Runnable() {    @Override    public void run() {        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        DialogUtils.closeDialog(mLoadingDialog);    }}).start();

谈出提示对话框:

DialogUtils.showTipsDialog(DialogActivity.this, "网络错误");

弹出进度对话框,进度100则关闭:

DialogUtils.showDownloadingDialog(DialogActivity.this, mDownloadDialog, 50, new DialogUtils.DialogCallBack() {    @Override    public void getDialog(CustomeDialog dialog) {        mDownloadDialog = dialog;    }});new Thread(new Runnable() {    @Override    public void run() {        try {            for (int i = 50; i <= 100; i++) {                DialogUtils.setDownloadingProgress(mDownloadDialog, i);                Thread.sleep(100);                if (i == 100) {                    DialogUtils.closeDialog(mDownloadDialog);                }            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}).start();

如果遇到具体业务的对话框,手动创建一个正常对话框即可,示例:

private void showNormalDialog() {    mNormalDialog = new CustomeDialog(            this,            R.layout.layout_dialog_normal,            R.style.customeDialog,            new int[] {R.id.id_btn_cancel, R.id.id_btn_confirm});    mNormalDialog.setListener(new CustomeDialog.CustomeDialogListener() {        @Override        public void buttonOnClick(View buttonView) {            switch (buttonView.getId()) {                case R.id.id_btn_cancel:                    colseNormalDialog();                    break;                case R.id.id_btn_confirm:                    MyToast.makeText(DialogActivity.this, "点击确认", Toast.LENGTH_SHORT).show();                    break;            }        }    });    mNormalDialog.setCanceledOnTouchOutside(false);    mNormalDialog.setBackKeyCancel(true);    mNormalDialog.show();}private void colseNormalDialog() {    if (mNormalDialog != null && mNormalDialog.isShowing()) {        mNormalDialog.dismiss();    }}

演示视频如下(视频压成gif,失真的有点厉害):
这里写图片描述

结束语

自定义Dialog布局文件后,Dialog的界面显示效果变为无限可能,你可以编写任意符合你需求的对话框样式。本期就到这里,之后可能会做一些提示控件SnackBar分享。

0 0
原创粉丝点击