parent
76f5afb11c
commit
0205abd68e
@ -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:id="@+id/toolbar" |
||||
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> |
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/> |
||||
|
@ -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> |
Loading…
Reference in new issue