Android 最簡單的自定義Dialog之一

天星技術團隊發表於2018-07-08

作者: Jooyer, 時間: 2018.07.08

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之一

相關文章