Android 状态切换控件 EasyStateView

作者: markRao | 来源:发表于2019-04-11 10:39 被阅读11次
    效果 GIF

    简单介绍一下这个控件,像我们在实际的开发过程中,经常性的会遇到这样的场景,比如进入一个页面先出来加载动画,然后请求数据,如果网络异常就显示网络异常的布局,数据异常、数据为空也有相应的布局,以及当我们请求成功完毕数据后,根据返回的数据值去区分不同VIP等级的用户显示不同的页面,这里我放了两张图,我的女神,迪丽热巴和俞飞鸿,就当做我们在业务开发中的 Layout ,把布局全部写在 xml,然后控制显示隐藏就有点不优雅了,基于这个问题,就有了这个控件。

    下面是自定义 View 的自定义属性:

    <declare-styleable name="EasyStateView">
    
            // 是否使用过渡动画
            <attr name="esv_use_anim" format="boolean"/>
    
            // 加载动画 View
            <attr name="esv_loadingView" format="reference" />
    
            // 数据异常,加载失败 View
            <attr name="esv_errorDataView" format="reference" />
    
            // 网络异常 View
            <attr name="esv_errorNetView" format="reference" />
    
            // 空白页面 View
            <attr name="esv_emptyView" format="reference" />
    
            // 设置当前显示的 viewState
            <attr name="esv_viewState" format="enum">
                <enum name="content" value="0" />
                <enum name="loading" value="-1" />
                <enum name="error_data" value="-2" />
                <enum name="error_net" value="-3" />
                <enum name="empty" value="-4" />
            </attr>
    
        </declare-styleable>
    

    Java代码:

    package com.rzj.view;
    
    import android.animation.Animator;
    import android.animation.AnimatorListenerAdapter;
    import android.animation.ObjectAnimator;
    import android.app.Activity;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Rect;
    import android.support.annotation.IntDef;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.WindowManager;
    import android.widget.FrameLayout;
    import com.rzj.stateview.R;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.ArrayList;
    
    public class EasyStateView extends FrameLayout {
    
        // 内容 View
        public static final int VIEW_CONTENT = 0;
        // 加载 View
        public static final int VIEW_LOADING = -1;
        // 数据异常( 数据异常指原本应该是有数据,但是服务器返回了错误的、不符合格式的数据 ) View
        public static final int VIEW_ERROR_DATA = -2;
        // 网络异常 View
        public static final int VIEW_ERROR_NET = -3;
        // 数据为空 View
        public static final int VIEW_EMPTY = -4;
        // View 的 Tag 标签值
        private static final int VIEW_TAG = -5;
        // 用来存放 View
        private ArrayList<View> mViews;
        // 是否使用过渡动画
        private boolean mUseAnim;
        // 是否处于动画中
        private boolean isAniming;
        // 当前显示的 ViewTag
        private int mCurrentState;
        private Context mContext;
        private StateViewListener mListener;
        // content View 是否被添加到队列
        private boolean isAddContent;
    
        public interface StateViewListener {
            void onStateChanged(int state);
        }
    
        public EasyStateView(Context context) {
            this(context, null);
        }
    
        public EasyStateView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public EasyStateView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            mContext = context;
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.EasyStateView);
            mCurrentState = typedArray.getInt(R.styleable.EasyStateView_esv_viewState, VIEW_CONTENT);
            Log.e("init", getChildCount() + "  " + mCurrentState);
            mViews = new ArrayList<>();
            int emptyResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_emptyView, VIEW_TAG);
            if (emptyResId != VIEW_TAG) {
                View view = LayoutInflater.from(getContext()).inflate(emptyResId, this, false);
                view.setTag(VIEW_EMPTY);
                mViews.add(view);
                addViewInLayout(view, -1, view.getLayoutParams());
            }
            int errorDataResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_errorDataView, VIEW_TAG);
            if (errorDataResId != VIEW_TAG) {
                View view = LayoutInflater.from(getContext()).inflate(errorDataResId, this, false);
                view.setTag(VIEW_ERROR_DATA);
                mViews.add(view);
                addViewInLayout(view, -1, view.getLayoutParams());
            }
            int errorNetResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_errorNetView, VIEW_TAG);
            if (errorNetResId != VIEW_TAG) {
                View view = LayoutInflater.from(getContext()).inflate(errorNetResId, this, false);
                view.setTag(VIEW_ERROR_NET);
                mViews.add(view);
                addViewInLayout(view, -1, view.getLayoutParams());
            }
            int loadingResId = typedArray.getResourceId(R.styleable.EasyStateView_esv_loadingView, VIEW_TAG);
            if (loadingResId != VIEW_TAG) {
                View view = LayoutInflater.from(getContext()).inflate(loadingResId, this, false);
                view.setTag(VIEW_LOADING);
                mViews.add(view);
                addViewInLayout(view, -1, view.getLayoutParams());
            }
            mUseAnim = typedArray.getBoolean(R.styleable.EasyStateView_esv_use_anim, true);
            typedArray.recycle();
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            for (View view : mViews) {
                if ((Integer) view.getTag() != mCurrentState) {
                    view.setVisibility(GONE);
                }
            }
        }
    
        @Override
        public void addView(View child) {
            addContentV(child);
            super.addView(child);
        }
    
        private void addContentV(View child) {
            if (isContentView(child)) {
                child.setTag(VIEW_CONTENT);
                mViews.add(child);
                isAddContent = true;
            }
        }
    
        @Override
        public void addView(View child, int index) {
            addContentV(child);
            super.addView(child, index);
        }
    
        @Override
        public void addView(View child, int index, ViewGroup.LayoutParams params) {
            addContentV(child);
            super.addView(child, index, params);
        }
    
        @Override
        public void addView(View child, ViewGroup.LayoutParams params) {
            addContentV(child);
            super.addView(child, params);
        }
    
        @Override
        public void addView(View child, int width, int height) {
            addContentV(child);
            super.addView(child, width, height);
        }
    
        @Override
        protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params) {
            addContentV(child);
            return super.addViewInLayout(child, index, params);
        }
    
        @Override
        protected boolean addViewInLayout(View child, int index, ViewGroup.LayoutParams params, boolean preventRequestLayout) {
            addContentV(child);
            return super.addViewInLayout(child, index, params, preventRequestLayout);
        }
    
        private boolean isContentView(View child) {
            if (!isAddContent && null != child
                    && null == child.getTag()) {
                return true;
            }
            return false;
        }
    
        /**
         * 切换默认状态的 View
         *
         * @param state
         */
        public void setViewState(int state) {
            if (state < VIEW_TAG) {
                throw new RuntimeException("ViewState 不在目标范围");
            }
            if (isAniming) {
                return;
            }
            showViewAnim(state);
        }
    
        public void setUseAnim(boolean useAnim) {
            this.mUseAnim = useAnim;
        }
    
        private void showViewAnim(int state) {
            View showView = getStateView(state);
            if (null == showView || state == mCurrentState) {
                return;
            }
            isAniming = true;
            View currentView = getStateView(mCurrentState);
            if (mUseAnim) {
                showAlpha(state, showView, currentView);
            } else {
                currentView.setVisibility(GONE);
                if(showView.getAlpha() == 0){
                    showView.setAlpha(1f);
                }
                showView.setVisibility(VISIBLE);
                mCurrentState = state;
                isAniming = false;
            }
        }
    
        private void showAlpha(final int state, final View showView,
                               final View currentView) {
            ObjectAnimator currentAnim = ObjectAnimator.ofFloat(currentView, "alpha", 1, 0);
            currentAnim.setDuration(250L);
            final ObjectAnimator showAnim = ObjectAnimator.ofFloat(showView, "alpha", 0, 1);
            showAnim.setDuration(250L);
            showAnim.addListener(new AnimatorListenerAdapter() {
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    if (null != mListener) {
                        mListener.onStateChanged(state);
                    }
                    isAniming = false;
                }
            });
            currentAnim.addListener(new AnimatorListenerAdapter() {
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    currentView.setVisibility(GONE);
                    showView.setVisibility(VISIBLE);
                    showAnim.start();
                    mCurrentState = state;
                }
            });
            currentAnim.start();
        }
    
        public int getCurrentState() {
            return mCurrentState;
        }
    
        public View getStateView(int state) {
            if (state < VIEW_TAG) {
                throw new RuntimeException("ViewState 不在目标范围");
            }
            for (View view : mViews) {
                if ((Integer) view.getTag() == state) {
                    return view;
                }
            }
            return null;
        }
    
        public void addUserView(int state, int layId) {
            setUserDefView(state, null, layId);
        }
    
        public void addUserView(int state, View view) {
            setUserDefView(state, view, -1);
        }
    
        private void setUserDefView(int state, View view, int layId) {
            if (state <= 0) {
                throw new RuntimeException("自定义的 ViewState TAG 必须大于 0");
            }
            if (null == view && layId != -1) {
                view = LayoutInflater.from(mContext).inflate(layId, this, false);
            }
            view.setTag(state);
            view.setVisibility(GONE);
            addViewInLayout(view, -1, view.getLayoutParams());
            mViews.add(view);
        }
    
    }
    
    

    简单说明一下,继承 FrameLayout 是因为帧布局是效率最高的布局,添加 View 到布局中用的是addViewInLayout,这里解释一下为什么不用addView,因为addView会触发 requestLayout,addViewInLayout会先添加进去,然后再统一触发布局,这个控件的用法非常简单,控件里面已经内置了很多常用的场景类型,你可以通过 addUserView()这个方法来添加你的 View,目前只有一个过渡动画,后续考虑迭代。

    项目的 Github 地址 https://github.com/MarkRaoAndroid/EasyStateView

    相关文章

      网友评论

        本文标题:Android 状态切换控件 EasyStateView

        本文链接:https://www.haomeiwen.com/subject/uzqwbqtx.html