属于对象的行为模式
定义:将每一个算法封装到具有共同接口的独立类中,从而使得他们可以相互替换
该模式涉及到3个角色:
1.环境角色:持有Strategy的引用
2.抽象策略角色:通常由一个接口或者抽象类实现
3.具体策略:包含了相关的算法或行为
本质上还是多态的应用, 这里参考了并仿照了https://www.jianshu.com/p/cd7a2a24ef23的效果
效果图里有两种加载方式:圆环旋转加载;水平方向加载; 如果实现这两种方式的代码都写到自定义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);
}
}
....省略
}
题外话: 实现旋转加载,需要考虑到每次旋转的角度等于相邻两个点的角度差;实现水平加载, 每次更新的点坐标为下一个点的坐标
网友评论