android 自定义view缩小放大拖动小人

效果图如下:

刚开始产品说要做这个效果的时候,一脸懵逼,光是围绕屏幕中心原点动态添加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进行缩放和移动会到点击事件有问题,仅仅只能看效果,并不能点击。

 

 

源代码下载

 

 

另外说一个小事,因为有时候会很迷茫,所以近期也开了一个微信公众号,暂时没几个人,在这里不聊代码技术了,只聊开心有趣或者聊你不想被人知道的事,要是你也感兴趣就上车吧,微信公众号《程序员的树洞》

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值