create BaseActivity

pull/30/head
Invinciblelee 6 years ago
parent 76f5afb11c
commit 0205abd68e
  1. 41
      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.view.MenuItem
import androidx.annotation.LayoutRes
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
abstract fun getLayoutID(): Int
protected lateinit var dataBinding: BD
private set
protected abstract val viewModel: VM
protected abstract val layoutID: Int
override fun onCreate(savedInstanceState: Bundle?) {
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 {
item?.let {
if (it.itemId == android.R.id.home) {
@ -22,7 +31,7 @@ abstract class BaseActivity : AppCompatActivity() {
return true
}
}
return if (item == null) true else onCompatOptionsItemSelected(item)
return if (item == null) false else onCompatOptionsItemSelected(item)
}
open fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
@ -30,26 +39,18 @@ abstract class BaseActivity : AppCompatActivity() {
}
override fun setTitle(title: CharSequence?) {
supportActionBar?.let {
it.title = title
}
supportActionBar?.title = title
}
override fun setTitle(titleId: Int) {
supportActionBar?.let {
it.setTitle(titleId)
}
supportActionBar?.setTitle(titleId)
}
fun setSubTitle(subtitle: CharSequence?){
supportActionBar?.let {
it.subtitle = subtitle;
}
fun setSubTitle(subtitle: CharSequence?) {
supportActionBar?.subtitle = subtitle
}
fun setSubTitle(subtitleId: Int){
supportActionBar?.let {
it.setSubtitle(subtitleId)
}
fun setSubTitle(subtitleId: Int) {
supportActionBar?.setSubtitle(subtitleId)
}
}

@ -1,24 +1,46 @@
package io.legado.app.ui.widget
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.appbar.AppBarLayout
import io.legado.app.R
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 {
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() {
super.onAttachedToWindow()
attachToActivity()
if (attachToActivity) {
attachToActivity(context)
}
}
private fun attachToActivity(){
private fun attachToActivity(context: Context) {
val activity = getCompatActivity(context)
activity?.let {
activity.setSupportActionBar(toolbar)
@ -37,4 +59,35 @@ class TitleBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
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"?>
<com.google.android.material.appbar.AppBarLayout
<androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
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:layout_width="match_parent"
android:layout_height="wrap_content"
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="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

@ -5,4 +5,9 @@
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</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>

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

@ -14,4 +14,28 @@
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<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>

Loading…
Cancel
Save