效果图如下:
刚开始产品说要做这个效果的时候,一脸懵逼,光是围绕屏幕中心原点动态添加view就让人头疼。况且还要进行缩放和拖动,小人头上还有个标志位,然后百度找缩放的例子,找了一圈之后,慢慢的发现效果图需要用到的几个技术点不过是如下
1:如何对所有的view进行缩放?
2:对view进行拖动?
3:小人+头顶的标志如何实现走动效果?并且一直围绕建筑物走动?
4:如何动态的以屏幕中心为原点添加view?
下边就来具体分析几个技术点
1:如何对所有的view进行缩放呢?
缩放事件的捕捉我们可以使用ScaleGestureDetector来获取,网上的例子大多都是对ImageView进行缩放,而我们这的需要是对所有的view进行缩放,要想看到缩放的效果就需要用到Matrix,每次在ScaleGestureDetector的回调中对matrix进行操作,然后通过matrix获取缩放的值。主要代码如下
ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.OnScaleGestureListener() {
@Override public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor(); // scaleFactor:2
// getMatrixScale:2
if (getMatrixScaleX() * scaleFactor <= 1.0f) {
scaleFactor = 1.0f / getMatrixScaleX();
} else if (getMatrixScaleX() * scaleFactor >= 2.0f) {
scaleFactor = 2.0f / getMatrixScaleX();
}
matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
invalidate();
return true;
}
@Override public boolean onScaleBegin(ScaleGestureDetector detector) {
isScale = true;
return true;
}
@Override public void onScaleEnd(ScaleGestureDetector detector) {
isScale = false;
}
});
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.concat(matrix);
}
2:对view进行拖动?
有人说对view进行拖动不是很简单么,直接改变坐标采用scrollBy/scrollTo就能搞定了,当然,使用scrollBy/scrollTo确实能达到我们想要的效果,但是我们这里给它进行了统一,也是使用matrix来变换,改变matrix里面的translate的值,然后进行invalidate(),不断的刷新达到我们的目的,主要代码如下:
@Override public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
gestureDetector.onTouchEvent(event);
scaleGestureDetector.onTouchEvent(event);
if (event.getPointerCount() > 1) {
pointer = true;
}
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downX = x;
downY = y;
pointer = false;
break;
case MotionEvent.ACTION_MOVE:
if (!isScale) {
int dx = Math.abs(x - downX);
int dy = Math.abs(y - downY);
if (dx > 10 && dy > 10 && !pointer) {
dx = x - lastX;
dy = y - lastY;
matrix.postTranslate(dx, dy);
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
break;
}
lastX = x;
lastY = y;
return true;
}
3:小人+头顶的标志如何实现走动效果?并且一直围绕建筑物走动?
小人实时走动,并且头上还要顶个标志,最初想到的是使用属性动画,让小人围绕一个建筑物一直转圈圈,但是后来想到的一个自认为更好的办法,实时更新小人的x和y的位置,通过handler来刷新,这种方式在仿照支付宝的蚂蚁森林中有用到。而小人走动的效果在我之前掷骰子小人走动的游戏里面也有用到,就是不断的在onDraw方法里面使用小人不同状态的图片。主要代码如下(具体逻辑要看CommunityPersonView这个类)
public class CommunityPersonView extends View {
private Paint mPaint;
private int currentX;
private int currentY;
private CommunityAnimation communityAnimation;
private int mAnimationState = CommunityAnimation.ANIM_LEFT;
public CommunityPersonView(Context context) {
this(context, null);
}
public CommunityPersonView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CommunityPersonView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mPaint = new Paint();
communityAnimation = new CommunityAnimation();
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
communityAnimation.mHeroAnim[mAnimationState].drawAnimation(canvas, mPaint, currentX, currentY);
}
/***
* 设置小人
*/
public void setCommunityPersonType(int type) {
communityAnimation.initAnimation(getContext(), type);
}
}
public class CommunityAnimation {
/**
* 向左移动动画
**/
public final static int ANIM_LEFT = 0;
/**
* 向右移动动画
**/
public final static int ANIM_RIGHT = 1;
/**
* 动画的总数量
**/
public final static int ANIM_COUNT = 2;
public final static int PERSON_TYPE_NPC = 11; // npc小人
public final static int PERSON_TYPE_0 = 0;
public final static int PERSON_TYPE_1 = 1;
public final static int PERSON_TYPE_2 = 2;
public final static int PERSON_TYPE_3 = 3;
public final static int PERSON_TYPE_4 = 4;
public GameAnimation mHeroAnim[] = new GameAnimation[ANIM_COUNT];
public void initAnimation(Context context, int personType) {
// 这里可以用循环来处理总之我们需要把动画的ID传进去
if (personType == PERSON_TYPE_NPC) {
mHeroAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_person_npc_left_nv_1, R.mipmap.img_person_npc_left_nv_2 }, true);
mHeroAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_person_npc_right_nv_1, R.mipmap.img_person_npc_right_nv_1 }, true);
} else if (personType == PERSON_TYPE_0) {
mHeroAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_2_left_nv_1, R.mipmap.img_person_hire_2_left_nv_2 }, true);
mHeroAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_2_right_nv_1, R.mipmap.img_person_hire_2_right_nv_2 }, true);
} else if (personType == PERSON_TYPE_1) {
mHeroAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_3_left_nv_1, R.mipmap.img_person_hire_3_left_nv_2 }, true);
mHeroAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_3_right_nv_1, R.mipmap.img_person_hire_3_right_nv_2 }, true);
} else if (personType == PERSON_TYPE_2) {
mHeroAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_4_left_nv_1, R.mipmap.img_person_hire_4_left_nv_2 }, true);
mHeroAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_4_right_nv_1, R.mipmap.img_person_hire_4_right_nv_2 }, true);
} else if (personType == PERSON_TYPE_3) {
mHeroAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_5_left_nv_1, R.mipmap.img_person_hire_5_left_nv_2 }, true);
mHeroAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_5_right_nv_1, R.mipmap.img_person_hire_5_right_nv_2 }, true);
} else if (personType == PERSON_TYPE_4) {
mHeroAnim[ANIM_LEFT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_6_left_nv_1, R.mipmap.img_person_hire_6_left_nv_2 }, true);
mHeroAnim[ANIM_RIGHT] = new GameAnimation(context, new int[] { R.mipmap.img_person_hire_6_right_nv_1, R.mipmap.img_person_hire_6_right_nv_2 }, true);
}
}
}
4:如何动态的以屏幕中心为原点添加view?
这一块涉及到逻辑问题,需要花费一些脑细胞,但是代码肯定都看的懂。对这个感兴趣的真可以仔细阅读,代码如下
public class CoordsGenerator {
public CoordsGenerator() {
super();
}
public static class Coords {
private int x, y;
public Coords(int x, int y) {
super();
this.x = x;
this.y = y;
}
public static Coords copyOf(Coords c) {
return new Coords(c.x, c.y);
}
@Override
public String toString() {
return String.format("(%s,%s)", this.x, this.y);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
private static List<Coords> generate(int d) {
Coords[][] arr = new Coords[d][d];
for (int i = 0; i < d; i++) {
for (int j = 0; j < d; j++) {
arr[j][i] = new Coords(j, i);
}
}
List<Coords> ret = new ArrayList<>();
for (int i = 1; i <= d; i += 2) {
ret.addAll(readPart(arr, i));
}
// print(ret);
/*
* calculate offset
*/
int offset = d / 2;
for (Coords c : ret) {
c.x -= offset;
c.y -= offset;
}
return ret;
}
private static List<Coords> readPart(Coords[][] arr, int i) {
if (i > arr.length) {
throw new IllegalArgumentException();
}
List<Coords> ret = new ArrayList<>();
int dim = arr.length;
int minX = (dim - i) / 2;
int maxX = minX + i - 1;
int minY = minX;
int maxY = maxX;
if (i == 1) {
addToList(ret, arr[minX][minY]);
return ret;
}
int startX = maxX;
int startY = maxY;
while (startX > minX) {
startX--;
addToList(ret, arr[startX][startY]);
}
while (startY > minY) {
startY--;
addToList(ret, arr[startX][startY]);
}
while (startX < maxX) {
startX++;
addToList(ret, arr[startX][startY]);
}
while (startY < maxY) {
startY++;
addToList(ret, arr[startX][startY]);
}
return ret;
}
private static void addToList(List<Coords> ret, Coords coords) {
ret.add(Coords.copyOf(coords));
}
private List<Coords> mList;
public void init(int count) {
mList = generate(count);
}
public Coords getCoords(int index) {
return mList.get(index);
}
public int getSize() {
return mList.size();
}
}
上边是所有问题的分析,其实整体缩放和移动是使用了canvas的concat方法,是对canvas进行的缩放和移动,其实还有其他方式可以实现这种效果,比如说建筑物全部使用drawBitmap的形式绘制上去,小人采用addView方法添加进去,源码中有这种方法的源码,最后说一句对canvas进行缩放和移动会到点击事件有问题,仅仅只能看效果,并不能点击。
另外说一个小事,因为有时候会很迷茫,所以近期也开了一个微信公众号,暂时没几个人,在这里不聊代码技术了,只聊开心有趣或者聊你不想被人知道的事,要是你也感兴趣就上车吧,微信公众号《程序员的树洞》