MVVM基础框架

480 阅读6分钟

依赖库:

// androidx
api 'androidx.appcompat:appcompat:1.1.0'
api 'androidx.core:core-ktx:1.3.0'
api 'androidx.constraintlayout:constraintlayout:1.1.3'
api "androidx.fragment:fragment-ktx:1.2.4"
api "androidx.lifecycle:lifecycle-extensions:2.2.0"
api "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
api "androidx.navigation:navigation-fragment-ktx:2.2.2"
api "androidx.navigation:navigation-ui-ktx:2.2.2"
api "androidx.viewpager2:viewpager2:1.1.0-alpha01"
api "androidx.legacy:legacy-support-v4:1.0.0"

//rx
api 'io.reactivex.rxjava2:rxjava:2.2.16'
api 'io.reactivex.rxjava2:rxandroid:2.1.1'

// webView
api 'com.github.delight-im:Android-AdvancedWebView:v3.0.0'

BaseMvvmActivity:

import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import com.blankj.utilcode.util.ToastUtils
import com.etekcity.vesyncbase.widget.dialog.GlobalLoadingDialog


abstract class BaseMvvmActivity<VB : ViewDataBinding, VM : BaseViewModel> : AppCompatActivity() {
    protected var binding: VB? = null
    protected lateinit var viewModel: VM
    private var viewModelId = 0

    private val loadingDialog: GlobalLoadingDialog by lazy {
        GlobalLoadingDialog.init(supportFragmentManager)
            .setCancelableOutside(false)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //页面接受的参数方法
        initParam()
        //私有的初始化Databinding和ViewModel方法
        initViewDataBinding()
        //初始化页面配置的方法
        initView(savedInstanceState)
        //私有的ViewModel与View的契约事件回调逻辑
        registerUIChangeLiveDataCallBack()
        //页面数据初始化方法
        initData()
        //页面事件监听的方法,一般用于ViewModel层转到View层的事件注册
        initViewObservable()
    }

    override fun onDestroy() {
        super.onDestroy()
        binding?.unbind()
    }

    /**
     * 注入绑定
     */
    private fun initViewDataBinding() { //DataBindingUtil类需要在project的build中配置 dataBinding {enabled true }, 同步后会自动关联android.databinding包
        binding = DataBindingUtil.setContentView(this, layoutId())
        binding?.lifecycleOwner = this
        viewModelId = initVariableId()
//        viewModel =
        viewModel = createViewModel(this)
        //关联ViewModel
        binding!!.setVariable(viewModelId, viewModel)
    }

    /**
     * 初始化界面传递参数
     */
    open fun initParam() {}

    /**
     * 初始化数据
     */
    open fun initData() {}

    /**
     * 初始化界面观察者的监听
     */
    open fun initViewObservable() {}

    /**
     * 初始化界面
     */
    open fun initView(savedInstanceState: Bundle?) {}

    /**
     * =====================================================================
     */
    /**
     * 初始化根布局
     *
     * @return 布局layout的id
     */
    abstract fun layoutId(): Int

    /**
     * 初始化ViewModel的id
     *
     * @return BR的id
     */
    abstract fun initVariableId(): Int


    /**
     * 创建ViewModel 需子类必须实现
     *
     * @param activity
     * @return VM
     */
    abstract fun createViewModel(activity: FragmentActivity): VM

    /**
     * 处理自定义事件,子类重写根据Message code分类处理
     */
    open fun handleEvent(msg: Message) {

    }

    //可供子类实现处理接口异常 子类实现返回true
    open fun handleCloudError(event: CloudErrorEvent): Boolean {
        return false
    }

    /**
     * 注册 UI 事件
     */
    private fun registerUIChangeLiveDataCallBack() {
        viewModel.uiEvent.showLoadingDialog.observe(this, Observer {
            showLoading()
        })
        viewModel.uiEvent.dismissLoadingDialog.observe(this, Observer {
            dismissLoading()
        })
        viewModel.uiEvent.toastEvent.observe(this, Observer {
            ToastUtils.showShort(it)
        })
        viewModel.uiEvent.msgEvent.observe(this, Observer {
            handleEvent(it)
        })
        viewModel.uiEvent.finishEvent.observe(this, Observer {
            finish()
        })

        viewModel.uiEvent.cloudErrorEvent.observe(this, Observer {
            if (!handleCloudError(it)) {
                if (!it.isProceed) {
                    ToastUtils.showShort(it.errorMessage)
                    it.isProceed = true
                }
            }
        })
    }


    /**
     * 打开等待框
     */
    private fun showLoading() {
        loadingDialog.show()
    }

    /**
     * 关闭等待框
     */
    private fun dismissLoading() {
        if(loadingDialog.dialog != null && loadingDialog.dialog!!.isShowing)
            loadingDialog.dismiss()
    }


}

BaseViewModel:

import androidx.lifecycle.*
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable


abstract class BaseViewModel : ViewModel(), LifecycleObserver {

    private var compositeDisposable: CompositeDisposable? = null

    val uiEvent: UIEvent by lazy { UIEvent() }

    /**
     * UI事件
     */
    inner class UIEvent {
        val showLoadingDialog by lazy { SingleLiveEvent<String>() }
        val dismissLoadingDialog by lazy { SingleLiveEvent<Void>() }
        val toastEvent by lazy { SingleLiveEvent<String>() }
        val finishEvent by lazy { SingleLiveEvent<Void>() }
        // 接口请求异常事件
        val cloudErrorEvent by lazy { MutableLiveData<CloudErrorEvent>() }
        // 自定义传递事件
        val msgEvent by lazy { SingleLiveEvent<Message>() }

    }

    @JvmOverloads
    fun postMessageEvent(
        code: Int = 0,
        msg: String = "",
        arg1: Int = 0,
        arg2: Int = 0,
        obj: Any? = null
    ) {
        uiEvent.msgEvent.value = Message(code, msg, arg1, arg2, obj)
    }


    fun showLoading(msg: String? = null) {
        if (msg.isNullOrEmpty()) {
            uiEvent.showLoadingDialog.call()
        } else {
            uiEvent.showLoadingDialog.value = msg
        }
    }

    fun dismissLoading() {
        uiEvent.dismissLoadingDialog.call()
    }

    fun showToast(msg: String) {
        uiEvent.toastEvent.value = msg
    }

    fun finish(){
        uiEvent.finishEvent.call()
    }


    fun handleError(throwable: Throwable) {
        uiEvent.cloudErrorEvent.postValue(CloudErrorEvent.handleError(throwable))
    }


    override fun onCleared() {
        super.onCleared()
        compositeDisposable?.clear()
    }

    private fun addSubscribe(disposable: Disposable) {
        if (compositeDisposable == null) {
            compositeDisposable = CompositeDisposable()
        }
        compositeDisposable!!.add(disposable)
    }

    fun <T> Observable<T>.addDisposable(): Observable<T> {
        return this.doOnSubscribe {
            addSubscribe(it)
        }
    }

    fun <T> Observable<T>.subscribeExt(onNext: (it: T) -> Unit): Disposable {
        return addDisposable().subscribe({
            onNext(it)
        }, {
            handleError(it)
        })
    }

    fun setMessageEvent(message: Message) {
        uiEvent.msgEvent.value = message
    }
}

SingleLiveEvent:

import android.util.Log

import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer

import java.util.concurrent.atomic.AtomicBoolean

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 *
 *
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 *
 *
 * Note that only one observer is going to be notified of changes.
 */
class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val pending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {

        if (hasActiveObservers()) {
            Log.w(
                "SingleLiveEvent",
                "Multiple observers registered but only one will be notified of changes."
            )
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer { t ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        pending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}

Message:

class Message @JvmOverloads constructor(
    val code: Int = 0,
    val msg: String = "",
    val arg1: Int = 0,
    val arg2: Int = 0,
    val obj: Any? = null
)

GlobalLoadingDialog:

import android.view.Gravity
import android.view.View
import android.view.animation.AnimationUtils
import android.widget.ImageView
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import com.etekcity.vesyncbase.R
import com.etekcity.vesyncwidget.dialog.base.BaseDialog
import com.etekcity.vesyncwidget.dialog.base.ViewHandlerListener
import com.etekcity.vesyncwidget.dialog.base.ViewHolder


class GlobalLoadingDialog : BaseDialog<GlobalLoadingDialog>() {

    @androidx.annotation.AnimRes
    private var animationResource = R.anim.rotation

    /**
     * View Handler
     * The management of the relevant state of the view is written here
     */
    override fun viewHandler(): ViewHandlerListener? {
        return object : ViewHandlerListener() {
            override fun convertView(holder: ViewHolder, dialog: BaseDialog<*>) {

                holder.getView<ImageView>(R.id.iv_loading).apply {
                    startAnimation(AnimationUtils.loadAnimation(context, animationResource))
                }
            }
        }
    }

    override fun layoutRes(): Int = R.layout.layout_loading_dialog

    override fun layoutView(): View? = null

    fun setAnimation(@androidx.annotation.AnimRes animation: Int): GlobalLoadingDialog {
        animationResource = animation
        return this
    }

    companion object {
        fun init(fragmentManager: FragmentManager): GlobalLoadingDialog {
            val dialog = GlobalLoadingDialog()
                dialog.setFragmentManager(fragmentManager)
                dialog.setDimAmount(0.0f)
                dialog.setBackgroundDrawableRes(0)
                dialog.setWidthScale(0f)
                dialog.setHeightScale(0f)
                dialog.setKeepWidthScale(false)
                dialog.setGravity(Gravity.CENTER)
                return dialog
        }
    }

}

BaseDialog:

import android.content.Context
import android.content.DialogInterface
import android.content.res.Configuration
import android.graphics.Point
import android.os.Bundle
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 com.etekcity.vesyncwidget.R
import kotlinx.android.parcel.Parcelize

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

    protected 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

    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
    }

    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 {
        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();
            }
        }
        return this as T
    }

    /*** 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 = R.drawable.def_dialog_bg,
        var animStyle: Int = 0,
        var needKeyboardViewId: Int = 0,
        var dimAmount:Float = 0.3f
    ) : UnParcelableParams(), Parcelable

}

LoginActivity:

class LoginActivity : BaseMvvmActivity<LoginActivityLoginBinding, LoginViewModel>() {
    override fun layoutId() = R.layout.login_activity_login

    override fun initVariableId() = BR.viewModel

    override fun createViewModel(activity: FragmentActivity): LoginViewModel {
        return ViewModelProvider(activity).get(LoginViewModel::class.java)
    }

    override fun initView(savedInstanceState: Bundle?) {
        super.initView(savedInstanceState)
        // 设置状态栏颜色为白色
        SystemBarHelper.immersiveStatusBar(window, 0.0.toFloat())
        SystemBarHelper.setStatusBarDarkMode(this, true)

        ClickUtils.applyGlobalDebouncing(btn_login) {
            // 逻辑判断
            viewModel.showLoading()
            AliAuthHelper.getLoginToken(object : AliAuthLoginConfig() {
                @SuppressLint("CheckResult")
                override fun onAuthSuccess(token: String) {
                    LogUtils.d("onTokenSuccess token = $token")
                    viewModel.showLoading()
                    AccountManager.quickLogin(token)
                        .doFinally {
                            viewModel.dismissLoading()
                        }
                        .subscribe({
                            // 登录成功
                        }, {
                            LogUtils.d("error it = $it")
                            viewModel.handleError(it)
                        })
                }

                override fun onUiEventClick(viewType: Int) {
                    when (viewType) {
                        TYPE_CLICK_OTHER -> {
                            LogUtils.d("TYPE_CLICK_OTHER")
                            ActivityUtils.startActivity(LoginByPhoneActivity::class.java)
                        }
                        TYPE_CLICK_WECHAT -> {
                            LogUtils.d("TYPE_CLICK_WECHAT")
                            weChatLogin()
                        }
                        TYPE_CLICK_BY_PWD -> {
                            LogUtils.d("TYPE_CLICK_BY_PWD")
                            ActivityUtils.startActivity(LoginByPwdActivity::class.java)
                        }
                    }
                }

                override fun onStartAuthUISuccess(ret: String?) {
                    viewModel.dismissLoading()
                }

                override fun onAuthFailed(ret: String?) {
                    LogUtils.d("onTokenFailed ret = $ret")
                    viewModel.dismissLoading()
                    ActivityUtils.startActivity(LoginByPhoneActivity::class.java)
                }
            })
        }

        val loginTerm = getString(R.string.login_term)
        val loginPolicy = getString(R.string.login_policy)
        tv_term_and_policy.makeLinks(
            Pair(loginTerm, View.OnClickListener {
                gotoTermOfUser(loginTerm)
            }),
            Pair(loginPolicy, View.OnClickListener {
                gotoPrivacyPolicy(loginPolicy)
            })
        )

        otherWayLoginInit()
    }

    override fun initData() {
        super.initData()
        var bundle = intent.extras
        var isShowTokenExpiredDialog =
            bundle?.getBoolean(LOGIN_KEY_IS_TOKEN_EXPIRED, false) ?: false
        if (isShowTokenExpiredDialog) {
            showTokenExpiredDialog()
        }
    }
}

LoginViewModel:

class LoginViewModel:BaseViewModel() {
}

WebViewActivity:

import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.webkit.WebSettings
import com.etekcity.vesyncbase.R
import com.etekcity.vesyncbase.utils.SystemBarHelper
import com.etekcity.vesyncbase.widget.dialog.GlobalLoadingDialog
import im.delight.android.webview.AdvancedWebView
import kotlinx.android.synthetic.main.activity_webview.*


class WebViewActivity : BaseActivity(), AdvancedWebView.Listener {

    private val loadingDialog: GlobalLoadingDialog by lazy {
        GlobalLoadingDialog.init(supportFragmentManager)
            .setCancelableOutside(false)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView( R.layout.activity_webview)
        // 设置状态栏颜色为白色
        SystemBarHelper.immersiveStatusBar(window, 0.0.toFloat())
        SystemBarHelper.setStatusBarDarkMode(this, true)
        SystemBarHelper.setHeightAndPadding(this, toolbar)
        toolbar.setNavigationOnClickListener {
            finish()
        }
        tv_title.text = intent.getStringExtra(KEY_TITLE)
        web_view.settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
        web_view.setListener(this, this)
        web_view.loadUrl(intent.getStringExtra(KEY_URL))
        loadingDialog.show()
    }

    override fun onPageFinished(url: String?) {
        loadingDialog.dismiss()
    }

    override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) {
        loadingDialog.dismiss()
    }

    override fun onDownloadRequested(
        url: String?,
        suggestedFilename: String?,
        mimeType: String?,
        contentLength: Long,
        contentDisposition: String?,
        userAgent: String?
    ) {

    }

    override fun onExternalPageRequest(url: String?) {

    }

    override fun onPageStarted(url: String?, favicon: Bitmap?) {

    }


    override fun onResume() {
        super.onResume()
        web_view.onResume()
    }

    override fun onPause() {
        web_view.onPause()
        super.onPause()
    }

    override fun onDestroy() {
        web_view.onDestroy()
        super.onDestroy()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        web_view.onActivityResult(requestCode, resultCode, data)
    }

    override fun onBackPressed() {
        if (!web_view.onBackPressed()) {
            return
        }
        super.onBackPressed()
    }

    companion object {
        const val KEY_TITLE = "web_view_title"
        const val KEY_URL = "web_view_url"
    }
}

webViewActivity布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fafafa">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/white"
        app:layout_constraintTop_toTopOf="parent"
        app:navigationIcon="@drawable/icon_return_black"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

        <TextView
            android:id="@+id/tv_title"
            style="@style/TextAppearance.Medium.ToolbarTitle"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:layout_marginEnd="?attr/actionBarSize" />
    </androidx.appcompat.widget.Toolbar>

    <im.delight.android.webview.AdvancedWebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

</androidx.constraintlayout.widget.ConstraintLayout>