博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 最简单的自定义视图管理之一
阅读量:6246 次
发布时间:2019-06-22

本文共 19149 字,大约阅读时间需要 63 分钟。

作者: Jooyer, 时间: 2018.08.08

,欢迎点赞,fork

其实一般的APP 都是这么几个状态,本次效果和其他开源没有什么区别,只是在方法数量和类数量上,和我之前博客一样,比较少! 这是自我吹捧,哈哈!

只需要三个类 + 几个 XML 文件即可,即拷即用

接下来我们依次讲解:

  1. StatusManager
  2. RootStatusLayout
  3. OnRetryListener
  4. 其他几个 XML 文件

首先我们看看 StatusManager:

/** * Desc: 视图管理器 * Author: Jooyer * Date: 2018-07-30 * Time: 11:46 */class StatusManager(builder: Builder) {    var mContext: Context    var mNetworkErrorVs: ViewStub? = null    var mNetworkErrorView:View? = null    var mNetWorkErrorRetryViewId: Int = 0    var mEmptyDataVs: ViewStub? = null    var mEmptyDataView:View? = null    var mEmptyDataRetryViewId: Int = 0    var mErrorVs: ViewStub? = null    var mErrorView:View? = null    var mErrorRetryViewId: Int = 0    var mLoadingLayoutResId: Int = 0    var mContentLayoutResId: Int = 0    var mRetryViewId: Int = 0    var mContentLayoutView: View? = null    var mRootFrameLayout: RootStatusLayout? = null    /**     * 显示loading     */    fun showLoading() {        mRootFrameLayout?.showLoading()    }    /**     * 显示内容     */    fun showContent() {        mRootFrameLayout?.showContent()    }    /**     * 显示空数据     */    fun showEmptyData() {        mRootFrameLayout?.showEmptyData()    }    /**     * 显示网络异常     */    fun showNetWorkError() {        mRootFrameLayout?.showNetWorkError()    }    /**     * 显示异常     */    fun showError() {        mRootFrameLayout?.showError()    }    /**     * 得到root 布局     */    fun getRootLayout(): View {        return mRootFrameLayout!!    }    class Builder(val context: Context) {        var loadingLayoutResId: Int = 0        var contentLayoutResId: Int = 0        var contentLayoutView: View? = null        var netWorkErrorVs: ViewStub? = null        var netWorkErrorRetryViewId: Int = 0        var emptyDataVs: ViewStub? = null        var emptyDataRetryViewId: Int = 0        var errorVs: ViewStub? = null        var errorRetryViewId: Int = 0        var retryViewId: Int = 0     //   var onShowHideViewListener: OnShowOrHideViewListener? = null        var onRetryListener: OnRetryListener? = null        fun loadingView(@LayoutRes loadingLayoutResId: Int): Builder {            this.loadingLayoutResId = loadingLayoutResId            return this        }        fun netWorkErrorView(@LayoutRes newWorkErrorId: Int): Builder {            netWorkErrorVs = ViewStub(context)            netWorkErrorVs!!.layoutResource = newWorkErrorId            return this        }        fun emptyDataView(@LayoutRes noDataViewId: Int): Builder {            emptyDataVs = ViewStub(context)            emptyDataVs!!.layoutResource = noDataViewId            return this        }        fun errorView(@LayoutRes errorViewId: Int): Builder {            errorVs = ViewStub(context)            errorVs!!.layoutResource = errorViewId            return this        }        fun contentView(contentLayoutView: View): Builder {            this.contentLayoutView = contentLayoutView            return this        }        fun contentViewResId(@LayoutRes contentLayoutResId: Int): Builder {            this.contentLayoutResId = contentLayoutResId            return this        }        fun netWorkErrorRetryViewId(netWorkErrorRetryViewId: Int): Builder {            this.netWorkErrorRetryViewId = netWorkErrorRetryViewId            return this        }        fun emptyDataRetryViewId(emptyDataRetryViewId: Int): Builder {            this.emptyDataRetryViewId = emptyDataRetryViewId            return this        }        fun errorRetryViewId(errorRetryViewId: Int): Builder {            this.errorRetryViewId = errorRetryViewId            return this        }        fun retryViewId(retryViewId: Int): Builder {            this.retryViewId = retryViewId            return this        }        fun onRetryListener(onRetryListener: OnRetryListener): Builder {            this.onRetryListener = onRetryListener            return this        }        fun build(): StatusManager {            return StatusManager(this)        }    }    companion object {        fun newBuilder(context: Context): Builder {            return Builder(context)        }    }    init {        mContext = builder.context        mLoadingLayoutResId = builder.loadingLayoutResId        mNetworkErrorVs = builder.netWorkErrorVs        mNetWorkErrorRetryViewId = builder.netWorkErrorRetryViewId        mEmptyDataVs = builder.emptyDataVs        mEmptyDataRetryViewId = builder.emptyDataRetryViewId        mErrorVs = builder.errorVs        mErrorRetryViewId = builder.errorRetryViewId        mContentLayoutResId = builder.contentLayoutResId        mRetryViewId = builder.retryViewId        mContentLayoutView = builder.contentLayoutView        mRootFrameLayout = RootStatusLayout(mContext)        mRootFrameLayout!!.setStatusManager(this)        mRootFrameLayout!!.setOnRetryListener(builder.onRetryListener)    }}复制代码

使用建造者模式, 初始化必要的布局信息和视图控件,同时使用 ViewStub 优化异常View

然后我们看看 RootStatusLayout :

/** * Desc: 视图管理布局控件 * Author: Jooyer * Date: 2018-07-30 * Time: 11:23 */class RootStatusLayout(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)    : ConstraintLayout(context, attrs, defStyleAttr) {    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)    constructor(context: Context?) : this(context, null, 0)    /**     * loading 加载id     */    val LAYOUT_LOADING_ID = 1    /**     * 内容id     */    val LAYOUT_CONTENT_ID = 2    /**     * 异常id     */    val LAYOUT_ERROR_ID = 3    /**     * 网络异常id     */    val LAYOUT_NETWORK_ERROR_ID = 4    /**     * 空数据id     */    val LAYOUT_EMPTY_ID = 5    /**     * 存放布局集合     */    private val mLayoutViews = SparseArray
() /** * 视图管理器 */ private var mStatusLayoutManager: StatusManager? = null /** * 不同视图的切换 */// private var onShowHideViewListener: OnShowOrHideViewListener? = null /** * 点击重试按钮回调 */ private var onRetryListener: OnRetryListener? = null fun setStatusManager(manager: StatusManager) { mStatusLayoutManager = manager addAllLayoutViewsToRoot() } private fun addAllLayoutViewsToRoot() { val params = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) if (0 != mStatusLayoutManager?.mContentLayoutResId) { addLayoutResId(mStatusLayoutManager?.mContentLayoutResId!!, LAYOUT_CONTENT_ID, params) } else if (null != mStatusLayoutManager?.mContentLayoutView) { addLayoutView(mStatusLayoutManager?.mContentLayoutView!!, LAYOUT_CONTENT_ID, params) } if (0 != mStatusLayoutManager?.mLoadingLayoutResId) { addLayoutResId(mStatusLayoutManager?.mLoadingLayoutResId!!, LAYOUT_LOADING_ID, params) } if (null != mStatusLayoutManager?.mEmptyDataVs) { addView(mStatusLayoutManager?.mEmptyDataVs, params) } if (null != mStatusLayoutManager?.mErrorVs) { addView(mStatusLayoutManager?.mErrorVs, params) } if (null != mStatusLayoutManager?.mNetworkErrorVs) { addView(mStatusLayoutManager?.mNetworkErrorVs, params) } } private fun addLayoutView(layoutView: View, layoutId: Int, param: ViewGroup.LayoutParams) { mLayoutViews.put(layoutId, layoutView) addView(layoutView, param) } private fun addLayoutResId(@LayoutRes layoutResId: Int, layoutId: Int, param: ViewGroup.LayoutParams) { val view: View = LayoutInflater.from(context) .inflate(layoutResId, null) mLayoutViews.put(layoutId, view) if (LAYOUT_LOADING_ID == layoutId) { view.visibility = View.GONE } addView(view, param) } /** * 显示loading */ fun showLoading() { if (mLayoutViews.get(LAYOUT_LOADING_ID) != null) showHideViewById(LAYOUT_LOADING_ID) } /** * 显示内容 */ fun showContent() { if (mLayoutViews.get(LAYOUT_CONTENT_ID) != null) showHideViewById(LAYOUT_CONTENT_ID) } /** * 显示空数据 */ fun showEmptyData() { if (inflateLayout(LAYOUT_EMPTY_ID)) showHideViewById(LAYOUT_EMPTY_ID) } /** * 显示网络异常 */ fun showNetWorkError() { if (inflateLayout(LAYOUT_NETWORK_ERROR_ID)) showHideViewById(LAYOUT_NETWORK_ERROR_ID) } /** * 显示异常 */ fun showError() { if (inflateLayout(LAYOUT_ERROR_ID)) showHideViewById(LAYOUT_ERROR_ID) } private fun showHideViewById(layoutId: Int) { for (i in 0 until mLayoutViews.size()) { val key = mLayoutViews.keyAt(i) val value = mLayoutViews[key] // 显示该 View if (layoutId == key) { value.visibility = View.VISIBLE } else { if (View.GONE != value.visibility) { value.visibility = View.GONE } } } } fun setOnRetryListener(listener: OnRetryListener?) { onRetryListener = listener } /** * 加载 StubView */ private fun inflateLayout(layoutId: Int): Boolean { var isShow = true when (layoutId) { LAYOUT_NETWORK_ERROR_ID -> { isShow = when { null != mStatusLayoutManager?.mNetworkErrorView -> { retryLoad(mStatusLayoutManager?.mNetworkErrorView!!, mStatusLayoutManager?.mNetWorkErrorRetryViewId!!) mLayoutViews.put(layoutId, mStatusLayoutManager?.mNetworkErrorView!!) return true } null != mStatusLayoutManager?.mNetworkErrorVs -> { val view: View = mStatusLayoutManager?.mNetworkErrorVs!!.inflate() mStatusLayoutManager?.mNetworkErrorView = view retryLoad(view, mStatusLayoutManager?.mNetWorkErrorRetryViewId!!) mLayoutViews.put(layoutId, view) true } else -> false } } LAYOUT_ERROR_ID -> { isShow = when { null != mStatusLayoutManager?.mErrorView -> { retryLoad(mStatusLayoutManager?.mErrorView!!, mStatusLayoutManager?.mErrorRetryViewId!!) mLayoutViews.put(layoutId, mStatusLayoutManager?.mErrorView!!) return true } null != mStatusLayoutManager?.mErrorVs -> { val view: View = mStatusLayoutManager?.mErrorVs!!.inflate() mStatusLayoutManager?.mErrorView = view retryLoad(view, mStatusLayoutManager?.mErrorRetryViewId!!) mLayoutViews.put(layoutId, view) true } else -> false } } LAYOUT_EMPTY_ID -> { isShow = when { null != mStatusLayoutManager?.mEmptyDataView -> { retryLoad(mStatusLayoutManager?.mEmptyDataView!!, mStatusLayoutManager?.mEmptyDataRetryViewId!!) mLayoutViews.put(layoutId, mStatusLayoutManager?.mEmptyDataView!!) return true } null != mStatusLayoutManager?.mEmptyDataVs -> { val view: View = mStatusLayoutManager?.mEmptyDataVs!!.inflate() mStatusLayoutManager?.mEmptyDataView = view retryLoad(view, mStatusLayoutManager?.mEmptyDataRetryViewId!!) mLayoutViews.put(layoutId, view) true } else -> false } } } return isShow } /** * 加载重试按钮,并绑定监听 */ private fun retryLoad(view: View, layoutResId: Int) { val retryView: View? = view.findViewById( if (0 != mStatusLayoutManager?.mRetryViewId!!) { mStatusLayoutManager?.mRetryViewId!! } else { layoutResId }) ?: return retryView?.setOnClickListener { onRetryListener?.onRetry() } }}复制代码

加载必要是视图到布局中,并根据需要显示和隐藏相关 View,逻辑也是很简单,哈哈!

接着看看最后一个类,其实就是个回调...

/** * Desc: 数据异常处理时点击回调 * Author: Jooyer * Date: 2018-07-30 * Time: 11:22 */interface OnRetryListener{    fun onRetry()}复制代码

这个也占了一个类,我喜欢这样,两个字 --> 任性

最后就是几个我就一股脑都抛出来了,准备接招!!! 你喜欢可以随意定制,咋舒服咋来.

widget_empty_page.xml -----> 没有数据时使用

复制代码

widget_error_page.xml -----> 异常(数据解析异常,服务器500等) 时使用

复制代码

widget_nonetwork_page.xml -----> 网络异常时使用

复制代码

widget_progress_bar.xml ----->加载数据时使用

复制代码

以上就是全部了,如果小伙伴发现有不能运行的,请看下面这个文件,它是在 values 内哦

dimens.xml -----> 定义了视图大小

100dp
100dp
18dp
10dp
6dp
16sp
复制代码

这次真的是全部了, 下面来介绍用法.

一般我们都需要定义基类,所以本次的视图管理器也在基类中,请看:

BaseActivity

/** * Desc: Activity 基类 * Author: Jooyer * Date: 2018-08-04 * Time: 21:22 */abstract class BaseActivity :AppCompatActivity(), OnRetryListener {    /**     * 请求网络异常等界面管理     */    var mStatusManager: StatusManager? = null    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        if (0 != getLayoutId()) {            setContentView(initStatusManager(savedInstanceState))        }    }	    /**     * Activity 布局文件      */    abstract fun getLayoutId(): Int    /**     * 初始化 View     */    abstract fun initializedViews(savedInstanceState: Bundle?, contentView: View)    private fun initStatusManager(savedInstanceState: Bundle?): View {        if (0 != getLayoutId()) {            val contentView = LayoutInflater.from(this)                    .inflate(getLayoutId(), null)            initializedViews(savedInstanceState, contentView)            return if (useStatusManager()) {                initialized(contentView)            } else {                contentView.visibility = View.VISIBLE                contentView            }        }        throw IllegalStateException("getLayoutId() 必须调用,且返回正常的布局ID")    }    private fun initialized(contentView: View): View {        mStatusManager = StatusManager.newBuilder(this)                .contentView(contentView)                .loadingView(R.layout.widget_progress_bar)                .emptyDataView(R.layout.widget_empty_page)                .netWorkErrorView(R.layout.widget_nonetwork_page)                .errorView(R.layout.widget_error_page)                .retryViewId(R.id.tv_retry_status)  // 注意以上布局中如果有重试ID,则必须一样,ID名称随意,记得这里填写正确                .onRetryListener(this)                .build()        mStatusManager?.showLoading()        return mStatusManager?.getRootLayout()!!    }    /**     *  是否使用视图布局管理器,默认不使用     */    open fun useStatusManager(): Boolean {        return false    }    /**     * 点击视图中重试按钮     */    override fun onRetry() {        Toast.makeText(this,"如果需要点击重试,则重写 onRetry() 方法",                Toast.LENGTH_SHORT).show()    }}复制代码

继续

BaseFragment

/** * Desc: Fragment 基类 * Author: Jooyer * Date: 2018-08-04 * Time: 21:23 */abstract class BaseFragment: Fragment(), OnRetryListener {    /**     * 请求网络异常等界面管理     */    var mStatusManager: StatusManager? = null    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {        return initStatusManager(inflater, container, savedInstanceState)    }    /**     * Fragment 布局文件      */    abstract fun getLayoutId(): Int    abstract fun initializedViews(savedInstanceState: Bundle?, contentView: View)    /**     * 此函数开始数据加载的操作,且仅调用一次     * 主要是加载动画,初始化展示数据的布局     */    private fun initStatusManager(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {        if (0 != getLayoutId()) {            val contentView = inflater.inflate(getLayoutId(), container, false)            initializedViews(savedInstanceState, contentView)            return if (useStatusManager()) {                initialized(contentView)            } else {                contentView.visibility = View.VISIBLE                contentView            }        }        throw IllegalStateException("getLayoutId() 必须调用,且返回正常的布局ID")    }    private fun initialized(contentView: View): View {        mStatusManager = StatusManager.newBuilder(contentView.context)                .contentView(contentView)                .loadingView(R.layout.widget_progress_bar)                .emptyDataView(R.layout.widget_empty_page)                .netWorkErrorView(R.layout.widget_nonetwork_page)                .errorView(R.layout.widget_error_page)                .retryViewId(R.id.tv_retry_status) // 注意以上布局中如果有重试ID,则必须一样,ID名称随意,记得这里填写正确                .onRetryListener(this)                .build()        mStatusManager?.showLoading()        return mStatusManager?.getRootLayout()!!    }    /**     *  是否使用视图布局管理器,默认不使用     */    open fun useStatusManager(): Boolean {        return false    }    /**     * 点击视图中重试按钮     */    override fun onRetry() {		Toast.makeText(this,"如果需要点击重试,则重写 onRetry() 方法",                Toast.LENGTH_SHORT).show()    }}复制代码

PS: onRetry() 的 Toast 仅仅演示用的,具体逻辑,请重写此方法来处理...

最后看看在 Activity 的用法吧, Fragment 类似,如果小伙伴在 Fragment 中不会使用或者有其他疑问请留言!

class MainActivity : BaseActivity() {    override fun getLayoutId(): Int {        return R.layout.activity_main    }	// 仅仅演示如何通过 findViewById 找到控件,其实 Kotlin 不用这么麻烦    override fun initializedViews(savedInstanceState: Bundle?, contentView: View) {        val tv_main_activity = contentView.findViewById
(R.id.tv_main_activity) } // 如果使用 StateManager 必须重写下面方法,且返回 true // 如果返回 true 则会显示加载的 loading override fun useStatusManager(): Boolean { return true } override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.state_menu, menu) return true } // 下面展示了显示各个状态的方式 override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.option_empty_page -> { mStatusManager?.showEmptyData() return true } R.id.option_error_page -> { mStatusManager?.showError() return true } R.id.option_loading_page -> { mStatusManager?.showLoading() return true } R.id.option_network_page -> { mStatusManager?.showNetWorkError() return true } R.id.option_content_page -> { mStatusManager?.showContent() return true } else -> return super.onOptionsItemSelected(item) } }// 这个仅仅演示加载 Framgment ,和本例没有什么关系 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportFragmentManager.beginTransaction() .add(R.id.fl_container,MainFragment()) .commit() }}复制代码

哈哈,我觉得很简单啊,小伙伴们觉得呢?虽然简单不过它可以满足基本的需求哦!还可以随意定制!喜欢记得点赞,收藏,转发哈!

膜拜的大神:

实在抱歉,记得有看到过大神有类似思路的,只是想不起了,如果大神看到,请通知我,我会在这里写上大神的博客

转载地址:http://iblia.baihongyu.com/

你可能感兴趣的文章
#iOS问题记录# UITextview富文本链接,禁止长按事件
查看>>
深度网络实现手写体识别
查看>>
Python Module_subprocess_调用 Powershell
查看>>
MVC原理图解
查看>>
c基础
查看>>
nodejs 平台的 webscoket 的实现
查看>>
JDK1.8源码(三)——java.util.HashMap
查看>>
给你1000万你可以把生活过的更好吗?
查看>>
<jsp:include page>和<%@ include file%>的区别
查看>>
flash 类和对象的关系
查看>>
保护模式 宏观理解
查看>>
Hat’s Words
查看>>
has_many :through VS has_and_belongs_to_many
查看>>
比较JSF、Spring MVC、Stripes、Struts 2、Tapestry、Wicket
查看>>
正则表达式介绍及案例分享
查看>>
【BZOJ】2125: 最短路 圆方树(静态仙人掌)
查看>>
【BZOJ】4530: [Bjoi2014]大融合
查看>>
线代之高斯消元
查看>>
java-循环的应用环境以及数组的创建
查看>>
关于java@Override错误
查看>>