美文网首页
设计模式之策略模式

设计模式之策略模式

作者: wislie_zhu | 来源:发表于2020-03-22 00:02 被阅读0次

    属于对象的行为模式
    定义:将每一个算法封装到具有共同接口的独立类中,从而使得他们可以相互替换
    该模式涉及到3个角色:
    1.环境角色:持有Strategy的引用
    2.抽象策略角色:通常由一个接口或者抽象类实现
    3.具体策略:包含了相关的算法或行为
    本质上还是多态的应用, 这里参考了并仿照了https://www.jianshu.com/p/cd7a2a24ef23的效果

    loading.gif
    效果图里有两种加载方式:圆环旋转加载;水平方向加载; 如果实现这两种方式的代码都写到自定义View中, 代码的可读性不够好,修改或者增加功能时还要考虑每一种情况,因此这里我使用策略模式来分别实现这两种加载方式.

    一.先实现抽象策略类:

    public abstract class LoadingStrategy {
    
        /**
         * 自定义view所需要的宽高
         */
        protected int requiredWidth, requiredHeight;
    
        /**
         * 生成dots点集合
         */
        public abstract List<VivoLoadingView.Dot> generateDots(int startColor, int endColor);
    
        /**
         * 计算位移
         */
        public abstract void calculateShift(List<VivoLoadingView.Dot> dotList);
    
        /**
         * 画布位移
         */
        public void shiftCanvas(Canvas canvas){
    
        };
    
        public int getRequiredWidth() {
            return requiredWidth;
        }
    
        public int getRequiredHeight() {
            return requiredHeight;
        }
    }
    

    二. 具体策略
    旋转加载

    public class RotateStrategy extends LoadingStrategy {
        /*** 圆的半径 */
        private float circleRadius;
        private float dotRadius;
        private int dotCount;
        private boolean isScaled;
        /*** 平均角度 */
        private int averageAngle;
        /*** 旋转的角度 */
        private float rotateAngle;
    
        private static final int TOTAL_ANGLE = 360;
    
        public RotateStrategy(float circleRadius, float dotRadius, int dotCount, boolean isScaled) {
            this.circleRadius = circleRadius;
            this.dotRadius = dotRadius;
            this.dotCount = dotCount;
            this.isScaled = isScaled;
            averageAngle = TOTAL_ANGLE / dotCount;
            requiredWidth = requiredHeight = (int) ((circleRadius + dotRadius) * 2);
        }
    
        @Override
        public List<VivoLoadingView.Dot> generateDots(int startColor, int endColor) {
            ArgbEvaluator argbEvaluator = new ArgbEvaluator();
            //点集合
            List<VivoLoadingView.Dot> dotList = new ArrayList<>();
            for (int i = 0; i < dotCount; i++) {
                //当前的角度
                int curAngle = i * averageAngle;
                //当前的弧度
                double curRad = DegreeUtil.toRadians(curAngle);
                //计算当前的坐标
                float x = (float) DegreeUtil.getCosSideLength(circleRadius, curRad);
                float y = (float) DegreeUtil.getSinSideLength(circleRadius, curRad);
                //计算当前的颜色
                float fraction = curAngle * 1.0f / TOTAL_ANGLE;
                //当前颜色
                int color = (int) argbEvaluator.evaluate(fraction, endColor, startColor);
    
                float radius = dotRadius;
                if (isScaled) {
                    radius = fraction * dotRadius;
                }
                VivoLoadingView.Dot dot = new VivoLoadingView.Dot(x, y, radius, color);
                dotList.add(dot);
            }
    
            return dotList;
        }
    
        @Override
        public void calculateShift(List<VivoLoadingView.Dot> dotList) {
            if (rotateAngle >= TOTAL_ANGLE) {
                rotateAngle = rotateAngle - TOTAL_ANGLE;
            } else {
                rotateAngle += averageAngle;
            }
        }
    
        @Override
        public void shiftCanvas(Canvas canvas) {
            canvas.translate(canvas.getWidth() / 2, canvas.getHeight() / 2);
            canvas.rotate(rotateAngle);
        }
    }
    

    水平加载

    public class TranslateStrategy extends LoadingStrategy {
    
        private int dotCount;
        private float interval;
        private float dotRadius;
        /*** x轴方向的偏移量 */
        private float translateX;
    
        public TranslateStrategy(int dotCount, float interval, float dotRadius) {
            this.dotCount = dotCount;
            this.interval = interval;
            this.dotRadius = dotRadius;
            requiredWidth = (int) (dotRadius * 2 * dotCount + interval * (dotCount - 1));
            requiredHeight = (int) (dotRadius * 2);
            translateX = interval + dotRadius;
        }
    
        @Override
        public List<VivoLoadingView.Dot> generateDots(int startColor, int endColor) {
            ArgbEvaluator argbEvaluator = new ArgbEvaluator();
            //点集合
            List<VivoLoadingView.Dot> dotList = new ArrayList<>();
            for (int i = 0; i < dotCount; i++) {
                float x = dotRadius * (i + 1) + i * (interval + dotRadius);
                float y = dotRadius;
                //计算当前的颜色
                float fraction = i * 1.0f / dotCount;
                //当前颜色
                int color = (int) argbEvaluator.evaluate(fraction, endColor, startColor);
                VivoLoadingView.Dot dot = new VivoLoadingView.Dot(x, y, dotRadius, color);
                dotList.add(dot);
            }
            return dotList;
        }
    
        @Override
        public void calculateShift(List<VivoLoadingView.Dot> dotList) {
            for (int i = 0; i < dotList.size(); i++) {
                VivoLoadingView.Dot dot = dotList.get(i);
                //设置每个点的x坐标
                dot.x = dot.x + translateX;
                if (dot.x >= requiredWidth) {
                    dot.x = dotRadius;
                }
            }
        }
    }
    

    三.使用策略的环境

    public class VivoLoadingView extends View {
    
        private String TAG = "VivoLoadingView";
        ....
        /*** 是否旋转 */
        private boolean isRotated;
        /*** 加载策略 */
        private LoadingStrategy mStrategy;
        public VivoLoadingView(Context context) {
            this(context, null);
        }
    
        public VivoLoadingView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
    
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.VivoLoadingView);
            //旋转
            isRotated = ta.getBoolean(R.styleable.VivoLoadingView_isRotated, false);
            ....省略
            ta.recycle();
            ....省略
            //创建具体的策略
            if (isRotated) {
                mStrategy = new RotateStrategy(circleRadius, dotRadius, dotCount, isScaled);
            } else {
                mStrategy = new TranslateStrategy(dotCount, interval, dotRadius);
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(getSize(widthMeasureSpec, mStrategy.getRequiredWidth()),
                    getSize(heightMeasureSpec, mStrategy.getRequiredHeight()));
        }
        ....省略
        private Runnable mRunnable = new Runnable() {
            @Override
            public void run() {
                //计算变换后的位置
                mStrategy.calculateShift(mDotList);
                invalidate();
                mHandler.postDelayed(this, refreshDuration);
            }
        };
    
        /*** 开始加载 */
        public void startLoading() {
            if (mDotList.size() <= 0) {
                //生成点集合
                mDotList = mStrategy.generateDots(startColor, endColor);
            }
            mHandler.postDelayed(mRunnable, refreshDuration);
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            startLoading();
        }
        ....
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //变换画布
            mStrategy.shiftCanvas(canvas);
            for (int i = 0; i < mDotList.size(); i++) {
                Dot dot = mDotList.get(i);
                mPaint.setColor(dot.color);
                canvas.drawCircle(dot.x, dot.y, dot.radius, mPaint);
            }
        }
        ....省略
    }
    

    题外话: 实现旋转加载,需要考虑到每次旋转的角度等于相邻两个点的角度差;实现水平加载, 每次更新的点坐标为下一个点的坐标

    代码链接:https://github.com/A18767101271/HWOptimizeView/tree/master/app/src/main/java/com/wislie/customview/vivo

    相关文章

      网友评论

          本文标题:设计模式之策略模式

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