自定义Dialog

292 阅读3分钟

package com.etekcity.vesyncwidget.dialog.base

import android.content.Context import android.content.DialogInterface import android.content.res.Configuration import android.graphics.Point import android.os.Bundle import android.os.Handler import android.os.Parcelable import android.view.* import android.view.inputmethod.InputMethodManager import android.widget.EditText import androidx.annotation.DrawableRes import androidx.annotation.FloatRange import androidx.annotation.LayoutRes import androidx.annotation.StyleRes import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.LifecycleOwner import kotlinx.android.parcel.Parcelize

@Suppress("UNCHECKED_CAST") abstract class BaseDialog<T : BaseDialog> : DialogFragment() {

protected lateinit var baseParams: BaseDialogParams

protected var viewHandlerListener: ViewHandlerListener?

private var onDialogDismissListener: OnDialogDismissListener? = null

protected lateinit var mContext: Context

protected var dialogConfig: DialogConfig? = null

protected var level = DialogLevel.NORMAL

protected var cleanAllDialog: Boolean = false

private val MIN_SHOW_TIME = 500L
private val MIN_DELAY = 500L

private var mStartTime: Long = -1
private var mPostedHide = false
private var mPostedShow = false
private var mDismissed = false

private val mHandler: Handler = Handler()

private val mDelayedHide = Runnable {
    mPostedHide = false
    mStartTime = -1
    dismiss()
}

private val mDelayedShow = Runnable {
    mPostedShow = false
    if (!mDismissed) {
        mStartTime = System.currentTimeMillis()
        initDialogConfig()
        if(dialogConfig != null)
            DialogManager.instance.show(dialogConfig!!)
        else {
            try {
                //在每个add事务前增加一个remove事务,防止连续的add
                baseParams.fragmentManager?.beginTransaction()?.remove(this)?.commit();
                super.show(baseParams.fragmentManager!!, baseParams.tag)
            } catch (e:Exception ) {
                //同一实例使用不同的tag会异常,这里捕获一下
                e.printStackTrace()
            }
        }
    }
}

init {
    baseParams = BaseDialogParams().apply {
        layoutRes = layoutRes()
        view = layoutView()
    }
    viewHandlerListener = this.viewHandler()
}

@LayoutRes
protected abstract fun layoutRes(): Int

protected abstract fun layoutView(): View?

protected abstract fun viewHandler(): ViewHandlerListener?

open fun initView(view: View) {}

override fun onAttach(context: Context) {
    super.onAttach(context)
    mContext = context
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    //Restore UI status
    savedInstanceState?.let {
        baseParams = it.getParcelable(KEY_PARAMS) ?: BaseDialogParams()
        viewHandlerListener = savedInstanceState.getParcelable(KEY_VIEW_HANDLER)
        onDialogDismissListener = savedInstanceState.getParcelable(KEY_DISMISS_LISTENER)
    }
}


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    super.onCreateView(inflater, container, savedInstanceState)
    //Clear the title of Android4.4
    dialog?.requestWindowFeature(Window.FEATURE_NO_TITLE)
    return when {
        baseParams.layoutRes > 0 -> inflater.inflate(baseParams.layoutRes, container)
        baseParams.view != null -> baseParams.view!!
        else ->
            throw IllegalArgumentException("请先设置LayoutRes或View!")
    }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewHandlerListener?.convertView(ViewHolder.create(view), this)
    initView(view)

    //Set open Keyboard
    if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && baseParams.needKeyboardViewId != 0) {
        val editText = view.findViewById<EditText>(baseParams.needKeyboardViewId)

        editText.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
                    ?: return
                editText.isFocusable = true
                editText.isFocusableInTouchMode = true
                editText.requestFocus()
                if (imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)) {
                    editText.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
            }
        })
    }
}

//save UI state
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.apply {
        putParcelable(KEY_PARAMS, baseParams)
        putParcelable(KEY_VIEW_HANDLER, viewHandlerListener)
        putParcelable(KEY_DISMISS_LISTENER, onDialogDismissListener)
    }
}

override fun onStart() {
    super.onStart()

    //Get screen size
    val point = Point()
    val windowManager = activity?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
    windowManager?.defaultDisplay?.getSize(point)

    //Set window
    dialog?.window?.let {
        val params = it.attributes
        params.dimAmount = baseParams.dimAmount
        params.gravity = baseParams.gravity
        it.attributes
        //Set dialog width
        when {
            baseParams.widthScale > 0f -> {
                if ((this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && baseParams.keepWidthScale)
                    || this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
                    //横屏并且保持比例 或者 竖屏
                    params.width = (point.x * baseParams.widthScale).toInt()
                }
            }
            baseParams.widthDp > 0f -> params.width = dp2px(mContext, baseParams.widthDp)
        }

        //Set dialog height
        when {
            baseParams.heightScale > 0f -> {
                if ((this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE && baseParams.keepHeightScale)
                    || this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
                    //横屏并且保持比例 或者 竖屏
                    params.height = (point.y * baseParams.heightScale).toInt()
                }
            }
            baseParams.heightDp > 0f -> params.height = dp2px(mContext, baseParams.heightDp)
        }
        //Set Window verticalMargin
        params.verticalMargin = baseParams.verticalMargin

        it.attributes = params
        if (baseParams.backgroundDrawableRes == 0) {
            it.setBackgroundDrawable(null)
        } else {
            it.setBackgroundDrawableResource(baseParams.backgroundDrawableRes)
        }
        it.setWindowAnimations(baseParams.animStyle)
    }

    //Set touch cancelable
    if (!baseParams.cancelable) {
        isCancelable = baseParams.cancelable
    } else {
        dialog?.setCanceledOnTouchOutside(baseParams.cancelableOutside)
    }
}

fun setDialogLevel(level: DialogLevel) {
    this.level = level
}

fun setCleanAll(cleanAll: Boolean) {
    this.cleanAllDialog = cleanAll
}

override fun onCancel(dialog: DialogInterface) {
    if(dialogConfig != null)
        DialogManager.instance.remove(dialogConfig!!)
    else
        super.onCancel(dialog)
}

override fun onDismiss(dialog: DialogInterface) {
    if (baseParams.needKeyboardViewId != 0) {
        val editText = view?.findViewById<EditText>(baseParams.needKeyboardViewId)
        val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
            ?: return
        imm.hideSoftInputFromWindow(editText?.windowToken, 0)
    }
    if(dialogConfig != null)
        DialogManager.instance.remove(dialogConfig!!)
    else
        super.onDismiss(dialog)
    onDialogDismissListener?.onDismiss(dialog)
}


protected fun setFragmentManager(fragmentManager: FragmentManager) {
    baseParams.fragmentManager = fragmentManager
}

protected fun setLifecycleOwner(lifecycleOwner: LifecycleOwner?) {
    baseParams.lifecycleOwner = lifecycleOwner
}

private fun initDialogConfig () {
    if(baseParams.lifecycleOwner == null)
        return

    if(dialogConfig == null) {
        dialogConfig = DialogConfig.builder(this, baseParams.fragmentManager!! , baseParams.lifecycleOwner!!, baseParams.tag)
        dialogConfig!!.dialogLevel = level
        dialogConfig!!.cleanAllDialog = cleanAllDialog
    }
}

/*** Set Params  (start) [External call]***/
fun setTag(tag: String): T {
    baseParams.tag = tag
    return this as T
}

fun setDismissListener(onDialogDismissListener: OnDialogDismissListener): T {
    this.onDialogDismissListener = onDialogDismissListener
    return this as T
}

fun setGravity(gravity: Int): T {
    baseParams.gravity = gravity
    return this as T
}

/**
 * Dialog occupies the proportion of the screen
 * {setWidthScale()} priority is higher than {setWidthDp()}
 * @param scale Float
 * @return T
 */
fun setWidthScale(@FloatRange(from = 0.0, to = 1.0) scale: Float): T {
    baseParams.widthScale = scale
    return this as T
}

fun setWidthDp(dp: Float): T {
    baseParams.widthDp = dp
    return this as T
}

fun setHeightScale(@FloatRange(from = 0.0, to = 1.0) scale: Float): T {
    baseParams.heightScale = scale
    return this as T
}

fun setHeightDp(dp: Float): T {
    baseParams.heightDp = dp
    return this as T
}

/**
 * Whether to maintain the {setWidthScale()} when the screen is rotated
 * If not set {setWidthScale()}, This item does not take effect
 * @param isKeep Boolean [Default false]
 * @return T
 */
fun setKeepWidthScale(isKeep: Boolean): T {
    baseParams.keepWidthScale = isKeep
    return this as T
}

/**
 * Whether to maintain the {setHeightScale()} when the screen is rotated
 * If not set {setHeightScale()}, This item does not take effect
 * @param isKeep Boolean [Default false]
 * @return T
 */
fun setKeepHeightScale(isKeep: Boolean): T {
    baseParams.keepHeightScale = isKeep
    return this as T
}

fun setVerticalMargin(@FloatRange(from = 0.0, to = 0.1) verticalMargin: Float): T {
    baseParams.verticalMargin = verticalMargin
    return this as T
}


fun setCancelableAll(cancelable: Boolean): T {
    baseParams.cancelable = cancelable
    return this as T
}


fun setCancelableOutside(cancelableOutside: Boolean): T {
    baseParams.cancelableOutside = cancelableOutside
    return this as T
}

fun setBackgroundDrawableRes(@DrawableRes resId: Int): T {
    baseParams.backgroundDrawableRes = resId
    return this as T
}

fun setAnimStyle(@StyleRes animStyleRes: Int): T {
    baseParams.animStyle = animStyleRes
    return this as T
}

/**
 * 设置dialog外部背景的透明度
 */
fun setDimAmount(@FloatRange(from = 0.0, to = 1.0) dimAmount: Float): T {
    baseParams.dimAmount = dimAmount
    return this as T
}

/**
 * auto open keyboard, (only EditText)
 * @param id Int EditTextView ID
 * @return T
 */
fun setNeedKeyboardEditTextId(id: Int): T {
    baseParams.needKeyboardViewId = id
    return this as T
}

open fun show(): T {
    mStartTime = -1
    mDismissed = false
    mHandler.removeCallbacks(mDelayedHide)
    mPostedHide = false
    if (!mPostedShow) {
        mHandler.postDelayed(mDelayedShow, MIN_DELAY)
        mPostedShow = true
    }
    return this as T
}

open fun dismissDialog() {
    mDismissed = true
    mHandler.removeCallbacks(mDelayedShow)
    mPostedShow = false
    val diff = System.currentTimeMillis() - mStartTime
    if (diff >= MIN_SHOW_TIME || mStartTime == -1L) {
        dismiss()
    } else {
        if (!mPostedHide) {
            mHandler.postDelayed(mDelayedHide, MIN_SHOW_TIME - diff)
            mPostedHide = true
        }
    }
}

/*** Set Params  (end)***/

companion object {
    private const val KEY_PARAMS = "key_params"
    private const val KEY_VIEW_HANDLER = "view_handler"
    private const val KEY_DISMISS_LISTENER = "dismiss_listener"

    private fun dp2px(context: Context, dipValue: Float): Int {
        val scale = context.resources.displayMetrics.density
        return (dipValue * scale + 0.5f).toInt()
    }

}

abstract class UnParcelableParams(var fragmentManager: FragmentManager? =null,
                                  var lifecycleOwner: LifecycleOwner? = null,
                                  var view: View? = null)

@Parcelize
class BaseDialogParams(
    @LayoutRes var layoutRes: Int = 0,
    var widthScale: Float = 0f,
    var widthDp: Float = 0f,

    var heightScale: Float = 0f,
    var heightDp: Float = 0f,
    var keepWidthScale: Boolean = false,
    var keepHeightScale: Boolean = false,
    var verticalMargin: Float = 0f,

    var gravity: Int = Gravity.CENTER,
    var tag: String = "LDialog",
    var cancelable: Boolean = true,
    var cancelableOutside: Boolean = true,
    var backgroundDrawableRes: Int = 0,
    var animStyle: Int = 0,
    var needKeyboardViewId: Int = 0,
    var dimAmount:Float = 0.4f
) : UnParcelableParams(), Parcelable

}