Android 最简单的自定义Dialog之一

栏目: Android · 发布时间: 7年前

内容简介:大家看到GIF,就知道,其自定义很是简单,为何还要说一说呢?主要是 github 上有很多大佬写的很好的库,只是功能多了,文件就多了,很多时候引入一个第三方,还得考虑方法数,库的大小等,而且有些时候,我们不需要那么多功能.

Github地址 ,欢迎点赞,fork

Android 最简单的自定义Dialog之一

大家看到GIF,就知道,其自定义很是简单,为何还要说一说呢?

主要是 github 上有很多大佬写的很好的库,只是功能多了,文件就多了,很多时候引入一个第三方,还得考虑方法数,库的大小等,而且有些时候,我们不需要那么多功能.

工作中使用频率又很高,每使用一次自定义一个,确实有些浪费精力了.本文还是在大佬的肩膀上做了些拓展,具体大佬链接,文章末尾给出.

好了,下面进入正题(PS:源码有彩蛋哦).

只需要四个类 + 几个 XML 文件即可,即拷即用

接下来我们依次讲解:

  1. JAlertDialog
  2. JAlertController
  3. JDialogViewHelper
  4. OnJAlertDialogClickListener
  5. 其他几个 XML 文件

首先我们看看 JAlertDialog:

public class JAlertDialog extends Dialog {

    private JAlertController mAlert;

    public JAlertDialog(Context context, int themeResId) {
        super(context, themeResId);
        mAlert = new JAlertController(this, getWindow());
    }


    public static class Builder {
        private final JAlertController.AlertParams mAlertParams;


        public Builder(Context context) {
            this(context, R.style.JDialogStyle);
        }

        public Builder(Context context, @StyleRes int themeRes) {
            mAlertParams = new JAlertController.AlertParams(context, themeRes);
        }

        public Builder setContentView(View view) {
            mAlertParams.mView = view;
            mAlertParams.mViewLayoutResId = 0;
            return this;
        }

        public Builder setContentView(int layoutResId) {
            mAlertParams.mView = null;
            mAlertParams.mViewLayoutResId = layoutResId;
            return this;
        }

        public Builder setCancelable(boolean cancelable) {
            mAlertParams.mCancelable = cancelable;
            return this;
        }


        public Builder setText(@IdRes int viewId, CharSequence text) {
            mAlertParams.mTextArr.put(viewId,text);
            return this;
        }


        public Builder setFromBottom() {
            mAlertParams.mGravity = Gravity.BOTTOM;
            return this;
        }

        public Builder setAnimation(@StyleRes int styleAnim) {
            mAlertParams.mAnimation = styleAnim;
            return this;
        }

        public Builder setHasAnimation(boolean hasAnimation) {
            mAlertParams.mHasAnimation = hasAnimation;
            return this;
        }

        public Builder setFullWidth() {
            mAlertParams.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
            return this;
        }

        public Builder setWidthAndHeight(int width,int height) {
            mAlertParams.mWidth = width;
            mAlertParams.mHeight = height;
            return this;
        }

        public Builder setOnClick(@IdRes int viewId) {
            mAlertParams.mClickArr.put(mAlertParams.mClickArr.size(),viewId);
            return this;
        }

        public Builder setOnJAlertDialogCLickListener(OnJAlertDialogClickListener onJAlertDialogClickListener) {
            mAlertParams.mOnJAlertDialogClickListener = onJAlertDialogClickListener;
            return this;
        }

        public Builder setOnCancelListener(OnCancelListener onCancelListener) {
            mAlertParams.mOnCancelListener = onCancelListener;
            return this;
        }

        public Builder setOnOnDismissListener(OnDismissListener onDismissListener) {
            mAlertParams.mOnDismissListener = onDismissListener;
            return this;
        }

        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
            mAlertParams.mOnKeyListener = onKeyListener;
            return this;
        }


        public JAlertDialog create() {
            final JAlertDialog dialog = new JAlertDialog(mAlertParams.mContext, mAlertParams.mThemeRes);
            mAlertParams.apply(dialog.mAlert);
            dialog.setCancelable(mAlertParams.mCancelable);
            if (mAlertParams.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(mAlertParams.mOnCancelListener);
            dialog.setOnDismissListener(mAlertParams.mOnDismissListener);
            if (mAlertParams.mOnKeyListener != null) {
                dialog.setOnKeyListener(mAlertParams.mOnKeyListener);
            }
            return dialog;
        }

        public JAlertDialog show() {
            JAlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}
复制代码

代码没有什么注释,其实很简单,就是一个建造者模式,仿系统做法,没有什么很特别的地方...

然后我们看看 JAlertController:

public class JAlertController {

    private JAlertDialog mDialog;
    private Window mWindow;

    public JAlertController(JAlertDialog dialog, Window window) {
        mDialog = dialog;
        mWindow = window;
    }

    public JAlertDialog getDialog() {
        return mDialog;
    }

    public Window getWindow() {
        return mWindow;
    }

    public static class AlertParams {

        public Context mContext;
        /**
         * Dialog 主题,有一个默认主题
         */
        public int mThemeRes;

        /**
         * 存放显示文本的控件和文本内容
         */
        public SparseArray<CharSequence> mTextArr = new SparseArray<>();
        /**
         * 存放点击事件的控件和监听
         */
        public SparseIntArray mClickArr = new SparseIntArray();

        /**
         * 点击空白是否可以取消,默认不可以
         */
        public boolean mCancelable = false;
        /**
         * Dialog 取消监听
         */
        public DialogInterface.OnCancelListener mOnCancelListener;
        /**
         * Dialog 消失监听
         */
        public DialogInterface.OnDismissListener mOnDismissListener;
        /**
         * Dialog 按键监听
         */
        public DialogInterface.OnKeyListener mOnKeyListener;
        /**
         * Dialog 布局 View
         */
        public View mView;
        /**
         * Dialog 布局 ID
         */
        public int mViewLayoutResId;
        public int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
        public int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
        public int mGravity = Gravity.CENTER;
        public int mAnimation = R.style.JDialogAnimation;
        public boolean mHasAnimation = true;
        public OnJAlertDialogClickListener mOnJAlertDialogClickListener;

        public AlertParams(Context context, @StyleRes int themeRes) {
            mContext = context;
            mThemeRes = themeRes;
        }

        /**
         * 设置参数
         */
        public void apply(JAlertController alert) {
            JDialogViewHelper viewHelper = null;
            // 1. 设置布局
            if (0 != mViewLayoutResId) {
                viewHelper = new JDialogViewHelper(mContext, mViewLayoutResId);
            }

            if (null != mView) {
                viewHelper = new JDialogViewHelper(mContext, mView);
            }

            if (null == viewHelper) {
                throw new IllegalArgumentException("请设置Dialog布局");
            }

            alert.getDialog().setContentView(viewHelper.getContentView());
            viewHelper.setOnJAlertDialogClickListener(mOnJAlertDialogClickListener);

            // 2. 设置文本,控件和文本内容一一对应的
            for (int i = 0, len = mTextArr.size(); i < len; i++) {
                viewHelper.setText(mTextArr.keyAt(i), mTextArr.valueAt(i));
            }

            // 3. 设置点击事件
            for (int i = 0, len = mClickArr.size(); i < len; i++) {
                viewHelper.setOnClick(mClickArr.keyAt(i), mClickArr.valueAt(i));
            }

            // 4. 设置dialog宽高动画等
            Window window = alert.getWindow();
            window.setGravity(mGravity);
            if (mHasAnimation) {
                window.setWindowAnimations(mAnimation);
            }
            WindowManager.LayoutParams params = window.getAttributes();
            params.width = mWidth;
            params.height = mHeight;
            window.setAttributes(params);
            alert.getDialog().setOnCancelListener(mOnCancelListener);
            alert.getDialog().setOnDismissListener(mOnDismissListener);

        }
    }
} 
复制代码

这里,有意思点的是, 注释3 ,点击事件,我通过拓展类似文本设置效果,在点击事件上做了点优化,具体看使用代码,不要心急!

接着,瞅瞅那个布局文件:JDialogViewHelper

public class JDialogViewHelper {
    private Context mContext;
    private View mContentView;
    private SparseArray<WeakReference<View>> mViews;
    public OnJAlertDialogClickListener mOnJAlertDialogClickListener;
    public JDialogViewHelper(Context context, int viewLayoutResId) {
        mViews = new SparseArray<>();
        mContentView = LayoutInflater.from(context).inflate(viewLayoutResId, null);
    }

    public JDialogViewHelper(Context context, View view) {
        mViews = new SparseArray<>();
        mContentView = view;
    }

    public void setText(@IdRes int viewId, CharSequence charSequence) {
        TextView textView = getView(viewId);
        if (null != textView) {
            textView.setText(charSequence);
        }
    }

    public void setOnClick(final int position, @IdRes int viewId) {
        View view = getView(viewId);
        if (null != view) {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (null != mOnJAlertDialogClickListener){
                        mOnJAlertDialogClickListener.onClick(v,position);
                    }
                }
            });
        }
    }


    public View getContentView() {
        return mContentView;
    }

    @SuppressWarnings("unchecked")
    public <T extends View> T getView(int viewId) {
        View view = null;
        WeakReference<View> viewWeakReference = mViews.get(viewId);
        if (null != viewWeakReference) {
            view = viewWeakReference.get();
        }
        if (null == view) {
            view = mContentView.findViewById(viewId);
            if (null != view)
                mViews.put(viewId, new WeakReference<View>(view));
        }
        return (T) view;
    }

复制代码

借鉴了类似 ListView 中 自定义的 ViewHolder ,不说什么了,一目了然啊,哈哈

倒数第二就是看哈回调了,贴个代码,来凑行数....,哈哈

public void setOnJAlertDialogClickListener(OnJAlertDialogClickListener onJAlertDialogClickListener) {
        mOnJAlertDialogClickListener = onJAlertDialogClickListener;
    }
}
复制代码

一个简单的回调,我能说什么呢...

最后就是几个我就一股脑都抛出来了,准备接招!!!

1.在 res/anim 中创建自己需要的动画,我这里贴上gif效果的动画

jdialog_enter.xml ---> 这个必须要,默认效果,当然如果你拷贝源码修改了也是可以不要的...

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromYDelta="100%"
    android:toYDelta="0" />
复制代码

jdialog_exit.xml ---> 这个必须要,默认效果,当然如果你拷贝源码修改了也是可以不要的...

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="200"
    android:fromYDelta="0"
    android:toYDelta="100%" />
复制代码

update_scale_fade_in.xml

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

    <scale
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:toXScale="1.0"
        android:toYScale="1.0"
        android:duration="400"
        android:pivotX="50%"
        android:pivotY="50%"
        />

    <alpha
           android:duration="400"
           android:fromAlpha="0.0"
           android:toAlpha="1.0" >

    </alpha>

</set>
复制代码

update_scale_fade_out.xml

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

    <scale
        android:fromXScale="1.0"
        android:fromYScale="1.0"
        android:toXScale="0.0"
        android:toYScale="0.0"
        android:duration="400"
        android:pivotX="50%"
        android:pivotY="50%"
        />

    <alpha
           android:duration="400"
           android:fromAlpha="1.0"
           android:toAlpha="0.0" >

    </alpha>

</set>
复制代码
  1. 在 res/values/style.xml 设置 Dialog 属性 和动画

    <!-- 默认使用,所以必须拷贝此 style  ,这个必须要,默认效果,当然如果你拷贝源码修改了也是可以不要的...  -->
    <style name="JDialogStyle" parent="@android:style/Theme.Dialog">
    
        <!-- 背景透明 -->
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <!-- 浮于Activity之上 -->
        <item name="android:windowIsFloating">true</item>
        <!-- 边框 -->
        <item name="android:windowFrame">@null</item>
        <!-- Dialog以外的区域模糊效果 -->
        <item name="android:backgroundDimEnabled">true</item>
        <!-- 决定背景透明度 -->
        <item name="android:backgroundDimAmount">0.3</item>
        <!-- 无标题 -->
        <item name="android:windowNoTitle">true</item>
        <!-- 半透明 -->
        <item name="android:windowIsTranslucent">true</item>
        <!-- Dialog进入及退出动画,这样所有使用此 style 都有这个动画, 可以单独指定的 -->
        <!--<item name="android:windowAnimationStyle">@style/JDialogAnimation</item>-->
    </style>
    
    <!-- 默认使用,所以必须拷贝此 style ,这个必须要,默认效果,当然如果你拷贝源码修改了也是可以不要的...-->
    <!-- 自定义动画,可以使用系统的,即把parent= 后面的直接 "android:windowAnimationStyle">   -->
    <style name="JDialogAnimation" parent="@android:style/Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/jdialog_enter</item>
        <item name="android:windowExitAnimation">@anim/jdialog_exit</item>
    </style>
    
    <!-- 升级时使用的类似效果 -->
    <style name="UpdateAnimation" parent="@android:style/Animation.Dialog">
        <item name="android:windowEnterAnimation">@anim/update_scale_fade_in</item>
        <item name="android:windowExitAnimation">@anim/update_scale_fade_out</item>
    </style>
    复制代码

    基本上就是这么多了,是不是很简单呢?

    下面看看基本用法

    class MainActivity : AppCompatActivity() {
        private lateinit var mBottomDialog: JAlertDialog;
        private lateinit var mUpdateDialog: JAlertDialog;
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            initBottomDialog()
            initUpdateDialog()
            tv_center.setOnClickListener {
                mBottomDialog.show()
            }
    
    
            tv_updpate.setOnClickListener {
                mUpdateDialog.show()
            }
    
        }
    
        /**
         *  底部弹出
         */
        private fun initBottomDialog() {
            mBottomDialog = JAlertDialog.Builder(this)
                    .setContentView(R.layout.dialog) // 设置布局,可以是 View
                    .setCancelable(false)
                    .setHasAnimation(true) //是否拥有动画,默认true,
    //                .setAnimation() // 设置动画,会覆盖默认动画
                    .setText(R.id.btn_left, "test1") // 设置空间文本,如果有多个,则调用多次即可
                    .setFromBottom() // 是否是从底部弹出,具体效果可以自己试试,感受更明显
                    .setFullWidth() // 宽度铺满屏幕
    //                .setWidthAndHeight() // 可以指定宽高(如果升级APP提示弹框等...)
                    .setOnClick(R.id.btn_left) //第一个点击的 View
                    .setOnClick(R.id.btn_right) // 第二个点击的 View
                    // 如果设置了点击 View,则需要下面这个方法回调, 注意 position 是从 0 开始的
                    .setOnJAlertDialogCLickListener(OnJAlertDialogClickListener { view, position ->
                        if (mBottomDialog.isShowing) {
                            mBottomDialog.dismiss()
                        }
                        when (position) {
                        // 这个顺序,和上面添加点击 View 是一致的
                            0 -> Toast.makeText(this@MainActivity, "点击左边按钮",  Toast.LENGTH_SHORT).show()
                            1 -> Toast.makeText(this@MainActivity, "点击右边按钮", Toast.LENGTH_SHORT).show()
                        }
                    }).create()
    
    
        }
    
    
        /**
         * 中间弹出,类似升级APP提示框
         */
        private fun initUpdateDialog() {
            val view = LayoutInflater.from(this).inflate(R.layout.dialog_update_app, null);
            // 彩蛋一枚,自定义 ArcBackgroundDrawable 解决底部 TextViev 设置背景没有圆角问题
            // 当然直接写xml也可以,只是本例中多了一个圆弧效果,看起来更 cool, 哈哈,不喜欢,你来打我啊!@!
            val tv_bottom_update = view.findViewById<TextView>(R.id.tv_bottom_update);
            tv_bottom_update.background = ArcBackgroundDrawable()
            mUpdateDialog = JAlertDialog.Builder(this)
                    .setAnimation(R.style.UpdateAnimation)
                    .setCancelable(false)
                    .setContentView(view)
                    .setOnClick(R.id.iv_close_update)
                    .setOnClick(R.id.tv_bottom_update)
                    .setOnJAlertDialogCLickListener { view, position ->
    
                        when (position) {
                            0 -> { // 关闭
                                mUpdateDialog.dismiss()
                            }
                            1 -> { // 开始下载
                                mUpdateDialog.dismiss()
                                // TODO
                            }
                        }
                    }
                    .create()
        }
    }
    复制代码

    用法需要注意的地方,我有提示哈,其实就是自定义一个布局,然后把需要设置文本和点击的,给设置了,最后时从哪里弹起给搞设置下.基本就可以了!我没有过多的提供动画效果,需要什么样的自己发挥了! 这样在使用时还是比较方便的,我们多个项目使用哦!

    哈哈,彩蛋就是源码有一个自定义的圆弧 ArcBackgroundDrawable, 大家可以去看看哈,就是升级弹窗底部的那个圆弧,可以解决比如一个圆形背景,但是底部按钮给了背景后圆弧不见了的问题!

    膜拜的大神:

    1. https://www.jianshu.com/p/87288925ee1f

以上所述就是小编给大家介绍的《Android 最简单的自定义Dialog之一》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Implementing Responsive Design

Implementing Responsive Design

Tim Kadlec / New Riders / 2012-7-31 / GBP 27.99

New devices and platforms emerge daily. Browsers iterate at a remarkable pace. Faced with this volatile landscape we can either struggle for control or we can embrace the inherent flexibility of the w......一起来看看 《Implementing Responsive Design》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具