create BaseActivity

pull/30/head
Invinciblelee 6 years ago
parent 76f5afb11c
commit 0205abd68e
  1. 37
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  2. 65
      app/src/main/java/io/legado/app/ui/widget/TitleBar.kt
  3. 221
      app/src/main/java/io/legado/app/ui/widget/dynamiclayout/DynamicFrameLayout.kt
  4. 29
      app/src/main/java/io/legado/app/ui/widget/dynamiclayout/ViewSwitcher.kt
  5. 20
      app/src/main/res/layout/view_dynamic.xml
  6. 27
      app/src/main/res/layout/view_error.xml
  7. 19
      app/src/main/res/layout/view_loading.xml
  8. 10
      app/src/main/res/layout/view_titlebar.xml
  9. 40
      app/src/main/res/values/attrs.xml
  10. 2
      app/src/main/res/values/colors.xml
  11. 5
      app/src/main/res/values/dimens.xml
  12. 4
      app/src/main/res/values/strings.xml
  13. 24
      app/src/main/res/values/styles.xml

@ -2,19 +2,28 @@ package io.legado.app.base
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.ViewModel
abstract class BaseActivity : AppCompatActivity() { abstract class BaseActivity<BD : ViewDataBinding, VM : ViewModel> : AppCompatActivity() {
@LayoutRes protected lateinit var dataBinding: BD
abstract fun getLayoutID(): Int private set
protected abstract val viewModel: VM
protected abstract val layoutID: Int
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(getLayoutID()) dataBinding = DataBindingUtil.setContentView(this, layoutID)
onViewModelCreated(viewModel, savedInstanceState)
} }
abstract fun onViewModelCreated(viewModel: VM, savedInstanceState: Bundle?)
override fun onOptionsItemSelected(item: MenuItem?): Boolean { override fun onOptionsItemSelected(item: MenuItem?): Boolean {
item?.let { item?.let {
if (it.itemId == android.R.id.home) { if (it.itemId == android.R.id.home) {
@ -22,7 +31,7 @@ abstract class BaseActivity : AppCompatActivity() {
return true return true
} }
} }
return if (item == null) true else onCompatOptionsItemSelected(item) return if (item == null) false else onCompatOptionsItemSelected(item)
} }
open fun onCompatOptionsItemSelected(item: MenuItem): Boolean { open fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
@ -30,26 +39,18 @@ abstract class BaseActivity : AppCompatActivity() {
} }
override fun setTitle(title: CharSequence?) { override fun setTitle(title: CharSequence?) {
supportActionBar?.let { supportActionBar?.title = title
it.title = title
}
} }
override fun setTitle(titleId: Int) { override fun setTitle(titleId: Int) {
supportActionBar?.let { supportActionBar?.setTitle(titleId)
it.setTitle(titleId)
}
} }
fun setSubTitle(subtitle: CharSequence?) { fun setSubTitle(subtitle: CharSequence?) {
supportActionBar?.let { supportActionBar?.subtitle = subtitle
it.subtitle = subtitle;
}
} }
fun setSubTitle(subtitleId: Int) { fun setSubTitle(subtitleId: Int) {
supportActionBar?.let { supportActionBar?.setSubtitle(subtitleId)
it.setSubtitle(subtitleId)
}
} }
} }

@ -1,24 +1,46 @@
package io.legado.app.ui.widget package io.legado.app.ui.widget
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.appbar.AppBarLayout
import io.legado.app.R import io.legado.app.R
import kotlinx.android.synthetic.main.view_titlebar.view.* import kotlinx.android.synthetic.main.view_titlebar.view.*
class TitleBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, attrs) {
init { init {
inflate(context, R.layout.view_titlebar, this) inflate(context, R.layout.view_titlebar, this)
val a = context.obtainStyledAttributes(
attrs, R.styleable.TitleBar,
0, 0
)
val navigationIcon = a.getDrawable(R.styleable.TitleBar_navigationIcon)
val navigationContentDescription = a.getText(R.styleable.TitleBar_navigationContentDescription)
val navigationIconTint = a.getColorStateList(R.styleable.TitleBar_navigationIconTint)
val navigationIconTintMode = a.getInt(R.styleable.TitleBar_navigationIconTintMode, 9)
val showNavigationIcon = a.getBoolean(R.styleable.TitleBar_showNavigationIcon, true)
val attachToActivity = a.getBoolean(R.styleable.TitleBar_attachToActivity, true)
a.recycle()
if (showNavigationIcon) {
toolbar.apply {
this.navigationIcon = navigationIcon
this.navigationContentDescription = navigationContentDescription
wrapDrawableTint(this.navigationIcon, navigationIconTint, navigationIconTintMode)
}
} }
override fun onAttachedToWindow() { if (attachToActivity) {
super.onAttachedToWindow() attachToActivity(context)
attachToActivity() }
} }
private fun attachToActivity(){ private fun attachToActivity(context: Context) {
val activity = getCompatActivity(context) val activity = getCompatActivity(context)
activity?.let { activity?.let {
activity.setSupportActionBar(toolbar) activity.setSupportActionBar(toolbar)
@ -37,4 +59,35 @@ class TitleBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
else -> null else -> null
} }
} }
private fun wrapDrawableTint(drawable: Drawable?, tintList: ColorStateList?, tintMode: Int) {
if (drawable == null || tintList == null) return
val wrappedDrawable = DrawableCompat.wrap(drawable.mutate())
DrawableCompat.setTintList(wrappedDrawable, tintList)
DrawableCompat.setTintMode(wrappedDrawable, intToMode(tintMode))
}
private fun intToMode(`val`: Int): PorterDuff.Mode {
when (`val`) {
0 -> return PorterDuff.Mode.CLEAR
1 -> return PorterDuff.Mode.SRC
2 -> return PorterDuff.Mode.DST
3 -> return PorterDuff.Mode.SRC_OVER
4 -> return PorterDuff.Mode.DST_OVER
5 -> return PorterDuff.Mode.SRC_IN
6 -> return PorterDuff.Mode.DST_IN
7 -> return PorterDuff.Mode.SRC_OUT
8 -> return PorterDuff.Mode.DST_OUT
9 -> return PorterDuff.Mode.SRC_ATOP
10 -> return PorterDuff.Mode.DST_ATOP
11 -> return PorterDuff.Mode.XOR
16 -> return PorterDuff.Mode.DARKEN
17 -> return PorterDuff.Mode.LIGHTEN
13 -> return PorterDuff.Mode.MULTIPLY
14 -> return PorterDuff.Mode.SCREEN
12 -> return PorterDuff.Mode.ADD
15 -> return PorterDuff.Mode.OVERLAY
else -> return PorterDuff.Mode.CLEAR
}
}
} }

@ -0,0 +1,221 @@
package io.legado.app.ui.widget.dynamiclayout
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ProgressBar
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.R
import kotlinx.android.synthetic.main.view_dynamic.view.*
class DynamicFrameLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs), ViewSwitcher {
private var errorView: View? = null
private var errorImage: AppCompatImageView? = null
private var errorTextView: AppCompatTextView? = null
private var actionBtn: AppCompatButton? = null
private var progressView: View? = null
private var progressBar: ProgressBar? = null
private var contentView: View? = null
private var errorIcon: Drawable? = null
private var emptyIcon: Drawable? = null
private var errorActionDescription: CharSequence? = null
private var emptyActionDescription: CharSequence? = null
private var emptyDescription: CharSequence? = null
private var errorAction: Action? = null
private var emptyAction: Action? = null
private var changeListener: OnVisibilityChangeListener? = null
init {
View.inflate(context, R.layout.view_dynamic, this)
val a = context.obtainStyledAttributes(attrs, R.styleable.DynamicFrameLayout)
errorIcon = a.getDrawable(R.styleable.DynamicFrameLayout_errorSrc)
emptyIcon = a.getDrawable(R.styleable.DynamicFrameLayout_emptySrc)
emptyActionDescription = a.getText(R.styleable.DynamicFrameLayout_emptyActionDescription)
emptyDescription = a.getText(R.styleable.DynamicFrameLayout_emptyDescription)
errorActionDescription = a.getText(R.styleable.DynamicFrameLayout_errorActionDescription)
if (errorActionDescription == null) {
errorActionDescription = context.getString(R.string.dynamic_click_retry)
}
a.recycle()
}
override fun onFinishInflate() {
super.onFinishInflate()
if (childCount > 2) {
contentView = getChildAt(2)
}
}
override fun addView(child: View) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child)
}
override fun addView(child: View, index: Int) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child, index)
}
override fun addView(child: View, params: ViewGroup.LayoutParams) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child, params)
}
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child, index, params)
}
override fun showErrorView(message: CharSequence) {
ensureErrorView()
setViewVisible(errorView, true)
setViewVisible(contentView, false)
setViewVisible(progressView, false)
errorTextView?.text = message
errorImage?.setImageDrawable(errorIcon)
actionBtn?.let {
it.tag = ACTION_WHEN_ERROR
it.visibility = View.VISIBLE
if (errorActionDescription != null) {
it.text = errorActionDescription
}
}
dispatchVisibilityChanged(ViewSwitcher.SHOW_ERROR_VIEW)
}
override fun showErrorView(messageId: Int) {
showErrorView(resources.getText(messageId))
}
override fun showEmptyView() {
ensureErrorView()
setViewVisible(errorView, true)
setViewVisible(contentView, false)
setViewVisible(progressView, false)
errorTextView?.text = emptyDescription
errorImage?.setImageDrawable(emptyIcon)
actionBtn?.let {
it.tag = ACTION_WHEN_EMPTY
if (errorActionDescription != null) {
it.visibility = View.VISIBLE
it.text = errorActionDescription
} else {
it.visibility = View.INVISIBLE
}
}
dispatchVisibilityChanged(ViewSwitcher.SHOW_EMPTY_VIEW)
}
override fun showProgressView() {
ensureProgressView()
setViewVisible(errorView, false)
setViewVisible(contentView, false)
setViewVisible(progressView, true)
dispatchVisibilityChanged(ViewSwitcher.SHOW_PROGRESS_VIEW)
}
override fun showContentView() {
setViewVisible(errorView, false)
setViewVisible(contentView, true)
setViewVisible(progressView, false)
dispatchVisibilityChanged(ViewSwitcher.SHOW_CONTENT_VIEW)
}
fun setOnVisibilityChangeListener(listener: OnVisibilityChangeListener) {
changeListener = listener
}
fun setErrorAction(action: Action) {
errorAction = action
}
fun setEmptyAction(action: Action) {
emptyAction = action
}
private fun setViewVisible(view: View?, visible: Boolean) {
view?.let {
it.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
}
private fun ensureErrorView() {
if (errorView == null) {
errorView = errorViewStub.inflate()
errorImage = errorView?.findViewById(R.id.iv_error_image)
errorTextView = errorView?.findViewById(R.id.tv_error_message)
actionBtn = errorView?.findViewById(R.id.btn_error_retry)
actionBtn?.setOnClickListener {
when (it.tag) {
ACTION_WHEN_EMPTY -> emptyAction?.onAction(this@DynamicFrameLayout)
ACTION_WHEN_ERROR -> errorAction?.onAction(this@DynamicFrameLayout)
}
}
}
}
private fun ensureProgressView() {
if (progressView == null) {
progressView = progressViewStub.inflate()
progressBar = progressView?.findViewById(R.id.loading_progress)
}
}
private fun dispatchVisibilityChanged(@ViewSwitcher.Visibility visibility: Int) {
changeListener?.onVisibilityChanged(visibility)
}
interface Action {
fun onAction(switcher: ViewSwitcher)
}
interface OnVisibilityChangeListener {
fun onVisibilityChanged(@ViewSwitcher.Visibility visibility: Int)
}
companion object {
private const val ACTION_WHEN_ERROR = "ACTION_WHEN_ERROR"
private const val ACTION_WHEN_EMPTY = "ACTION_WHEN_EMPTY"
}
}

@ -0,0 +1,29 @@
package io.legado.app.ui.widget.dynamiclayout
import androidx.annotation.IntDef
import androidx.annotation.StringRes
interface ViewSwitcher {
companion object{
const val SHOW_CONTENT_VIEW = 0
const val SHOW_ERROR_VIEW = 1
const val SHOW_EMPTY_VIEW = 2
const val SHOW_PROGRESS_VIEW = 3
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(SHOW_CONTENT_VIEW, SHOW_ERROR_VIEW, SHOW_EMPTY_VIEW, SHOW_PROGRESS_VIEW)
annotation class Visibility
fun showErrorView(message: CharSequence)
fun showErrorView(@StringRes messageId: Int)
fun showEmptyView()
fun showProgressView()
fun showContentView()
}

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/errorViewStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout="@layout/view_error" />
<ViewStub
android:id="@+id/progressViewStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout="@layout/view_loading" />
</merge>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_error_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_error_message"
style="@style/Style.Text.Primary.Normal"
android:paddingTop="16dp"
android:paddingBottom="16dp" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorAccent"
android:minWidth="96dp"
android:text="@string/dynamic_click_retry" />
</LinearLayout>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/loading_progress"
android:layout_width="48dp"
android:layout_height="48dp"
style="@style/Widget.AppCompat.ProgressBar"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_loading_message"
style="@style/Style.Text.Second.Normal.Wrap"
android:paddingTop="16dp"
android:text="@string/dynamic_loading"/>
</LinearLayout>

@ -1,16 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout <androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:popupTheme="@style/AppTheme.PopupOverlay"/> app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.AppBarLayout>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TitleBar">
<attr name="attachToActivity" format="boolean" />
<attr name="showNavigationIcon" format="boolean" />
<attr name="navigationIcon" format="reference" />
<attr name="navigationContentDescription" format="reference|string" />
<attr name="navigationIconTint" format="color|reference" />
<attr name="navigationIconTintMode" format="enum">
<enum name="clear" value="0" />
<enum name="src" value="1" />
<enum name="dst" value="2" />
<enum name="src_over" value="3" />
<enum name="dst_over" value="4" />
<enum name="src_in" value="5" />
<enum name="dst_in" value="6" />
<enum name="src_out" value="7" />
<enum name="dst_out" value="8" />
<enum name="src_atop" value="9" />
<enum name="dst_atop" value="10" />
<enum name="xor" value="11" />
<enum name="darken" value="16" />
<enum name="lighten" value="17" />
<enum name="multiply" value="13" />
<enum name="screen" value="14" />
<enum name="add" value="12" />
<enum name="overlay" value="15" />
</attr>
</declare-styleable>
<declare-styleable name="DynamicFrameLayout">
<attr name="errorSrc" format="reference" />
<attr name="emptySrc" format="reference" />
<attr name="errorActionDescription" format="string|reference" />
<attr name="emptyActionDescription" format="string|reference" />
<attr name="emptyDescription" format="string|reference" />
</declare-styleable>
</resources>

@ -3,4 +3,6 @@
<color name="colorPrimary">#008577</color> <color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color> <color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color> <color name="colorAccent">#D81B60</color>
</resources> </resources>

@ -5,4 +5,9 @@
<dimen name="nav_header_vertical_spacing">8dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen> <dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen>
<dimen name="font_size_normal">14sp</dimen>
<dimen name="font_size_middle">16sp</dimen>
<dimen name="font_size_large">18sp</dimen>
</resources> </resources>

@ -13,4 +13,8 @@
<string name="menu_tools">Tools</string> <string name="menu_tools">Tools</string>
<string name="menu_share">Share</string> <string name="menu_share">Share</string>
<string name="menu_send">Send</string> <string name="menu_send">Send</string>
<string name="dynamic_click_retry">点击重试</string>
<string name="dynamic_loading">正在加载</string>
</resources> </resources>

@ -14,4 +14,28 @@
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
<style name="Style.Text.Primary.Normal" parent="android:Widget">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/font_size_normal</item>
<item name="android:textColor">#000</item>
<item name="android:includeFontPadding">false</item>
</style>
<style name="Style.Text.Primary.Normal.Wrap">
<item name="android:layout_width">wrap_content</item>
</style>
<style name="Style.Text.Second.Normal" parent="android:Widget">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/font_size_normal</item>
<item name="android:textColor">#676767</item>
<item name="android:includeFontPadding">false</item>
</style>
<style name="Style.Text.Second.Normal.Wrap">
<item name="android:layout_width">wrap_content</item>
</style>
</resources> </resources>

Loading…
Cancel
Save