自定义View进阶--手绘地图(一)

一:最近学习了自定义view,刚好就接到了相关的需求,于是就上手做了,下面描述一下需求

       需求:简单的来说就是做一个地图,不同的是,为了追求美观,于是地图是一张由UI出的图片,poi点

       为运营采集点,实现地图的缩放,移动,poi打点,以及其他的东西,由于涉及到的东西较多,因此本次就说这些

        内容,包括分析、实现、踩坑等内容

-------------------分割线------------------------------

    本篇适合有一些自定义View基础的朋友食用

二:效果



这些就是实现的效果了,因为舍不得钱开会员,没找到合适的网站,因此就分成了三个gif供看观浏览,接下来进入分析阶段

三:分析

    1.首先考虑大方向的实现方式,比较简单的是自定义viewGroup,稍复杂的是自定义view

    2.需求中包含几个内容,第一个为图片的初始化放置,第二为图片的缩放与位移,第三为图片的边界回弹,第四位poi点的位          置确定,第五为poi的点击事件

大致需求就是这些,当然还有一些双击放大,层级变换等等,不过做完这些也就一通白通了


四:实现

    本篇主要讲自定义ViewGroup的实现方式,自定义View的在下一篇进行

1.自定义一个类,集成ViewGroup,继承构造方法

public class MapLayout extends RelativeLayout {

然后集成构造方法

public MapLayout(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    mContext = context;
    init();
}

这里说一下双参和单参构造函数的区别(一般只关注这两种),单参为你的自定义view被实例化时调用,而双参中调用了一些自定义属性,也就是说只要在xml中使用了自定义view都需要调用双参,相信机智的你明白了

2.接着进行:

实例化画笔,上下文,以及将要使用到的东西。这里先将成员变量放出来,后面大家可对照查看,每个一个成员变量都打上了注释

private Context mContext;
//画笔
Paint mPaint;
//控件宽度
private int mViewWidth;
//控件高度
private int mViewHeight;
//控制画板的矩阵
private Matrix mMapMatrix;
//地图初始化需要位移
private float mInitTranslateX;
private float mInitTranslateY;
//地图Bitmap
private Bitmap mMapBitmap;
//此处手指情况只考虑单指移动和双指缩放
//上次手指停留位置(单手指)
private float mLastSinglePointX;
private float mLastSinglePointY;
//用于双指缩放
private float mLastDistancce;
//最小缩放倍数
private float mMinScale = 0.8f;
//最大缩放倍数
private float mMaxScale = 4.0f;

//上次手机离开时缩放倍数
private float mLastScale;

//是否能够移动的标志
private boolean mCouldMove = true;
//矩阵对应的值
float[] mNowMatrixvalues;

//x位移最大值
private float mMaxTranslateX;
//Y位移最大值
private float mMaxTranslateY;

/**
 * 边界回弹状态  边界起头:1   例:11
 *
 * @param context
 */
private int mNowBoundStates = 0;
//只向上恢复
private static final int BOUND_ONLY_TOP = 11;
//只向左恢复
private static final int BOUND_ONLY_LEFT = 12;
//同时向左和上恢复
private static final int BOUND_TOPANDLEFT = 13;
//只向右恢复
private static final int BOUND_ONLY_RIGHT = 14;
//同时向右上恢复
private static final int BOUND_RIGHTANDTOP = 15;
//只向下恢复
private static final int BOUND_ONLY_BOTTOM = 16;
//同时向右下恢复
private static final int BOUND_RIGHTANDBOTTOM = 17;
//同时向左下恢复
private static final int BOUND_LEFTANDBOTTOM = 18;
//属性动画起始和结束值
private static final int REBOUND_ANIMATION_START_VALUE = 0;
private static final int REBOUND_ANIMATION_END_VALUE = 100;
private static final int REBOUND_ANIMATION_TIME = 200;

//poi实体集合
List<MapPoiEntity> mMapPoiEntityList;

3.开始的话,不用过多的关注成员变量,也算是看代码的一种技巧,需要知道什么回来查找就好了

接着开始测量自定义View的宽高,因为我知道自己的使用情况,因此就定义了一种情况来测量宽高(match_parent,或者固定宽高时)

/**
 * 测量控件宽高
 *
 * @param widthMeasureSpec
 * @param heightMeasureSpec
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
        mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
    }
    if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
        mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
    }
    mMaxTranslateX = mViewWidth / 6;
    mMaxTranslateY = mViewHeight / 8;
    setMeasuredDimension(mViewWidth, mViewHeight);
}

中间有成员变量,请对照之前查看,后面就不在复述了

5.xml中使用

<com.example.a12280.maptestproject.MapView
    android:layout_width="match_parent"
    android:clipChildren="false"
    android:id="@+id/map"
    android:background="@color/transparent"
    android:layout_centerInParent="true"
    android:layout_height="match_parent"
  />

activity中申明并初始化

mMap.post(new Runnable() {
    @Override
    public void run() {
        Glide.with(MainActivity.this).load(R.drawable.map).asBitmap().into(new SimpleTarget<Bitmap>() {
            @Override
            public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
                mMap.initImageWH(resource);
                mMap.setMapPoiEntityList(mMapPoiEntityList);
            }
        });
    }
});

可以看到此处调用了控件的post方法,标识控件绘制结束,也就是可以获取正确宽高的时刻,并且此处使用Glide加载图片获取到了bitmap,同时实现网络加载,三级缓存,图片压缩,还是很方便的,至于拿到bitmap之后做的事情,会在后面将到

6.初始化图片

首先将图片放置到屏幕上面,一般来说,图片的比例不会和屏幕的比例完全吻合,因此需要对图片进行合适的缩放,此处采用的方式是保护一边,即不管怎样至少有一边完全贴合屏幕的一边,另一边进行居中显示

/**
 * 初始化图片的宽高
 */
public void initImageWH(Bitmap mapImg) {
    float imgHeight = mapImg.getHeight();
    float imgWidth = mapImg.getWidth();
    float changeWidth = 0.0f;
    float changeHeight = 0.0f;
    float scaleWidth = mViewWidth / imgWidth;
    float scaleHeight = mViewHeight / imgHeight;
    //对图片宽高进行缩放
    if (scaleHeight > scaleWidth) {
        changeHeight = mViewHeight;
        changeWidth = mViewHeight * imgWidth / imgHeight;
        mInitTranslateY = 0;
        mInitTranslateX = -Math.abs((changeWidth - mViewWidth) / 2);
    } else {
        changeWidth = mViewWidth;
        changeHeight = mViewWidth * imgHeight / imgWidth;
        mInitTranslateY = -Math.abs((changeHeight - mViewHeight) / 2);
        mInitTranslateX = 0;
    }
    Matrix matrix = new Matrix();
    matrix.postScale(changeWidth / imgWidth, changeHeight / imgHeight);
    mMapBitmap = Bitmap.createBitmap(mapImg, 0, 0, (int) imgWidth, (int) imgHeight, matrix, true);
    if (mapImg!=null&&mMapBitmap!=null&&!mapImg.equals(mMapBitmap)&&!mapImg.isRecycled()){
        mapImg=null;
    }
    //初次加载时,将Matrix移动到正确位置
    mMapMatrix.postTranslate(mInitTranslateX,mInitTranslateY);
    refreshUI();
}

此处将我们自定义view的初始化放在其post方法中使用的用处就来了,因为对图片缩放需要拿到控件的宽高,而这种异步的事情不可控,因此就等待其宽高确定再进行初始化(不要问我为啥知道。。。),然后就是初始化矩阵mMapMatrix

另外,此处说明一下,地图的缩放与移动都将采用Matrix作为中间实现对象,不明白Matrix还是先理解一下再向下看吧

然后解释一下矩阵的各个值位置


--------------分割线--------------------------

经过上面的步骤也就正确的将图片放置在了屏幕上了,接下来对其进行移动和缩放

7.移动和缩放

首先,我并没有采用手势的方案(不熟悉手势使用方法,暂时没有去看,或许会简单,或许不会),而是直接采用监听onTouch的方式,要监听ouTouch,首先得将其返回值变为true,事件分发都是通过返回值来确定行为的

/**
 * 用户触控事件
 *
 * @param event
 * @return
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
    mMapMatrix.getValues(mNowMatrixvalues);
    //缩放
    scaleCanvas(event);
    //位移
    translateCanvas(event);
    return true;
}

贴一个方法为获取bitmap对应的矩阵

/**
 * 获取当前bitmap矩阵的RectF,以获取宽高与margin
 *
 * @return
 */
private RectF getMatrixRectF() {
    RectF rectF = new RectF();
    if (mMapBitmap != null) {
        rectF.set(0, 0, mMapBitmap.getWidth(), mMapBitmap.getHeight());
        mMapMatrix.mapRect(rectF);
    }
    return rectF;
}

首先看位移事件:

/**
 * 用户手指的位移操作
 *
 * @param event
 */
public void translateCanvas(MotionEvent event) {
    if (event.getPointerCount() == 1) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //获取到上一次手指位置
                mLastSinglePointX = event.getX();
                mLastSinglePointY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                if (mCouldMove) {
                    float translateX = event.getX() - mLastSinglePointX;
                    float translateY = event.getY() - mLastSinglePointY;
                    RectF matrixRectF = getMatrixRectF();
                    //边界控制
                    //left不能大于mMaxTranslateX,right值不能小于mViewwidth-mMaxTranslateX
                    if ((matrixRectF.left >= mMaxTranslateX && translateX > 0) || ((matrixRectF.right <= mViewWidth - mMaxTranslateX) && translateX < 0)) {
                        translateX = 0;
                    }
                    //top不能大于mMaxTranslateY,bottom值不能小于mViewHeight-mMaxTranslateY
                    if ((matrixRectF.top >= mMaxTranslateY && translateY > 0) || ((matrixRectF.bottom <= mViewHeight - mMaxTranslateY) && translateY < 0)) {
                        translateY = 0;
                    }
                    //对本次移动造成的超过范围做调整
                    if (translateX > 0 && ((matrixRectF.left + translateX) > mMaxTranslateX)) {
                        translateX = mMaxTranslateX - matrixRectF.left;
                    }
                    if (translateX < 0 && ((matrixRectF.right + translateX) < mViewWidth - mMaxTranslateX)) {
                        translateX = -(mMaxTranslateX - (mViewWidth - matrixRectF.right));
                    }
                    if (translateY > 0 && ((matrixRectF.top + translateY) > mMaxTranslateY)) {
                        translateY = mMaxTranslateY - matrixRectF.top;
                    }
                    if (translateY < 0 && ((matrixRectF.bottom + translateY) < mViewHeight - mMaxTranslateY)) {
                        translateY = -(mMaxTranslateY - (mViewHeight - matrixRectF.bottom));
                    }
                    mMapMatrix.postTranslate(translateX, translateY);
                    mLastSinglePointX = event.getX();
                    mLastSinglePointY = event.getY();
                    refreshUI();
                }
                break;
            case MotionEvent.ACTION_UP:
                mLastSinglePointX = 0;
                mLastSinglePointY = 0;
                mLastDistancce = 0;
                mCouldMove = true;
                controlBound();
                break;
        }
    }
}

此处用了一个布尔值mCouldMove,用于消除双指与单指交互时的错误性,感兴趣的可以试试不加这个布尔值的效果,总的来说就是将两次位移的差值体现在矩阵中,然后绘制上去,并且需要注意,手指抬起时置空上一次按下的x、y值,controlBound为边界控制,等会再说

接下来是双指缩放:

/**
 * 用户双指缩放操作
 *
 * @param event
 */
public void scaleCanvas(MotionEvent event) {
    if (event.getPointerCount() == 2) {
        mCouldMove = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                float lastlengthOFY = Math.abs(event.getY(1) - event.getY(0));
                float lastlengthOFX = Math.abs(event.getX(1) - event.getX(0));
                mLastDistancce = (float) Math.sqrt(lastlengthOFX * lastlengthOFX + lastlengthOFY * lastlengthOFY);
                break;
            case MotionEvent.ACTION_MOVE:
                float lengthOFY = Math.abs(event.getY(1) - event.getY(0));
                float lengthOFX = Math.abs(event.getX(1) - event.getX(0));
                float distance = (float) Math.sqrt(lengthOFX * lengthOFX + lengthOFY * lengthOFY);

                float scale = distance / mLastDistancce;
                if (mLastDistancce != 0) {
                    //缩放大小控制
                    float nowScale = mNowMatrixvalues[Matrix.MSCALE_X];
                    if ((nowScale > mMaxScale && scale > 1.0f) || (nowScale < mMinScale && scale < 1.0f)) {
                        return;
                    }
                    mMapMatrix.postScale(scale, scale,
                            event.getX(0) + (event.getX(1) - event.getX(0)) / 2,
                            event.getY(0) + (event.getY(1) - event.getY(0)) / 2);
                }
                mLastDistancce = distance;
                refreshUI();
                break;
            case MotionEvent.ACTION_POINTER_UP:
                mLastDistancce = 0;
                break;
            case MotionEvent.ACTION_POINTER_2_UP:
                mLastDistancce = 0;
                break;
            default:
                break;
        }
    }
}

同样的,双指缩放拿到缩放倍数,然后体现到矩阵中去,缩放比例为第一次的x,y值获取到的三角边长与第二次的比例,需要注意的是,双指缩放,单指抬起时的时间监听(。。蒙蔽了一段时间,没法办打印事件值才找到,感兴趣的可以试验一下,两根手指,第一根和第二根的触发事件不同),缩放中心为本次move的两指中心,并且加上缩放倍数的控制,同样的需要置空之前的值

至此也就完成了缩放与移动操作,用矩阵实现还是很简单的,需要注意的是,矩阵的操作,前一次对后一次有影响,也就是对矩阵进行操作,是将要操作多少,而不是操作到多少,举个栗子:将图片进行缩放1.4倍,如果第一次缩放了1.2倍,则第二次需要缩放1.4/1.2倍,位移也是一样的

8.边界回弹

首先做边界回弹的话,需要区分一下状态,之前的成员变量表应该已经显示出来了,并且下方代码的注释中解释的很明白:

/**
 * 用于控制用户的手指抬起时,对留边的情况进行控制
 */
private void controlBound() {
    RectF matrixRectF = getMatrixRectF();
    float nowScale = mNowMatrixvalues[Matrix.MSCALE_X];
    if (mNowMatrixvalues[Matrix.MSCALE_X] < 1 && (mNowMatrixvalues[Matrix.MSCALE_X] + 0.001f) > 1) {
        //消除缩放误差
        nowScale = 1;
    }
    if (nowScale < 1) {
        //缩小的情况,缩小以四个顶角为基准,并且向位移多的地方恢复
        scaleBoundAnimation(matrixRectF);
    } else {
        //情况判断,放大或者一倍的情况
        if (matrixRectF.top > 0) {
            //头在边界下
            if (matrixRectF.left > 0) {
                //向左上恢复的情况
                mNowBoundStates = BOUND_TOPANDLEFT;
            } else if (matrixRectF.right < mViewWidth) {
                //向右上恢复的情况
                mNowBoundStates = BOUND_RIGHTANDTOP;
            } else {
                //只向上恢复的情况
                mNowBoundStates = BOUND_ONLY_TOP;
            }
        } else if (matrixRectF.top < 0 && matrixRectF.bottom > mViewHeight) {
            //头在边界上,底在边界下
            if (matrixRectF.left > 0) {
                //只向左恢复的情况
                mNowBoundStates = BOUND_ONLY_LEFT;
            } else if (matrixRectF.right < mViewWidth) {
                //只向右恢复的情况
                mNowBoundStates = BOUND_ONLY_RIGHT;
            }
        } else if (matrixRectF.top < 0 && matrixRectF.bottom < mViewHeight) {
            //底在边界上
            if (matrixRectF.left > 0) {
                //向左下恢复的情况
                mNowBoundStates = BOUND_LEFTANDBOTTOM;
            } else if (matrixRectF.right < mViewWidth) {
                //向右下恢复的情况
                mNowBoundStates = BOUND_RIGHTANDBOTTOM;
            } else {
                //只向下恢复的情况
                mNowBoundStates = BOUND_ONLY_BOTTOM;
            }
        }
        translateBoundAnimation(matrixRectF);
    }
}

这边是先将状态进行赋值,用于后面的计算,回弹区分两种,缩小时的回弹与放大时的回弹,先说放大时,放大的时候只需要进行位移回弹即可

/**
 * 边界回弹动画(位移动画)
 *
 * @param matrixRectF
 */
public void translateBoundAnimation(final RectF matrixRectF) {
    //应该位移的x值与y值
    float boundTranslateX = 0.0f;
    float boundTranslateY = 0.0f;
    //右边恢复需要的位移值
    float translateRight = mViewWidth - matrixRectF.right;
    //下边恢复需要的位移值
    float translateBottom = mViewHeight - matrixRectF.bottom;
    //放大或者原图状态,直接进行位移操作
    switch (mNowBoundStates) {
        case BOUND_ONLY_TOP:
            boundTranslateY = -matrixRectF.top;
            break;
        case BOUND_ONLY_LEFT:
            boundTranslateX = -matrixRectF.left;
            break;
        case BOUND_ONLY_RIGHT:
            boundTranslateX = translateRight;
            break;
        case BOUND_ONLY_BOTTOM:
            boundTranslateY = translateBottom;
            break;
        case BOUND_TOPANDLEFT:
            boundTranslateY = -matrixRectF.top;
            boundTranslateX = -matrixRectF.left;
            break;
        case BOUND_RIGHTANDTOP:
            boundTranslateY = -matrixRectF.top;
            boundTranslateX = translateRight;
            break;
        case BOUND_RIGHTANDBOTTOM:
            boundTranslateX = translateRight;
            boundTranslateY = translateBottom;
            break;
        case BOUND_LEFTANDBOTTOM:
            boundTranslateX = -matrixRectF.left;
            boundTranslateY = translateBottom;
            break;
        default:
            break;

    }
    //位移属性动画
    ValueAnimator valueAnimator = ValueAnimator.ofInt(REBOUND_ANIMATION_START_VALUE, REBOUND_ANIMATION_END_VALUE);
    valueAnimator.setDuration(REBOUND_ANIMATION_TIME);
    valueAnimator.setInterpolator(new LinearInterpolator());
    final float finalBoundTranslateX = boundTranslateX;
    final float finalBoundTranslateY = boundTranslateY;
    //上一次valueAnimation移动的位置[x,y]
    final float[] lastTranslate = new float[2];
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            Integer numerator = (Integer) valueAnimator.getAnimatedValue();
            float total = REBOUND_ANIMATION_END_VALUE - REBOUND_ANIMATION_START_VALUE;
            float shouldTranslateX = finalBoundTranslateX * ((float) numerator / total) - lastTranslate[0];
            lastTranslate[0] = finalBoundTranslateX * ((float) numerator / total);
            float shouldTranslateY = finalBoundTranslateY * ((float) numerator / total) - lastTranslate[1];
            lastTranslate[1] = finalBoundTranslateY * ((float) numerator / total);
            mMapMatrix.postTranslate(shouldTranslateX, shouldTranslateY);
            refreshUI();
        }
    });
    valueAnimator.start();
    mNowBoundStates = 0;
}

此处就是计算现在的x、y值与应该所处得x、y值的差值,然后运用ValueAnimation进行动画回弹,ValueAnimation也就是将目标值与初始值,按照给的时间点,分配若干进度,手机越好的,按理来说就会分的越多,效果越好,至于里面的算法,也就是这一次应该移动的值为本次目标值减去上一次移动值,同样的后面缩放也类似,不过是加减法变成乘除法

然后考虑缩放的情况:

/**
 * 边界回弹动画(缩放动画)
 */
public void scaleBoundAnimation(final RectF martrixRectF) {
    final float startScale = mNowMatrixvalues[Matrix.MSCALE_X];
    //位移属性动画
    //上一次的缩放倍数
    final float[] lastScale = {0.0f};
    ValueAnimator valueAnimator = ValueAnimator.ofInt(REBOUND_ANIMATION_START_VALUE, REBOUND_ANIMATION_END_VALUE);
    valueAnimator.setDuration(REBOUND_ANIMATION_TIME);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animator) {

        }

        @Override
        public void onAnimationEnd(Animator animator) {
            //重新获取一下值
            mMapMatrix.getValues(mNowMatrixvalues);
            //缩放结束再查看边界进行位移缩放
            controlBound();
        }

        @Override
        public void onAnimationCancel(Animator animator) {

        }

        @Override
        public void onAnimationRepeat(Animator animator) {

        }
    });
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            Integer numerator = (Integer) valueAnimator.getAnimatedValue();
            float total = REBOUND_ANIMATION_END_VALUE - REBOUND_ANIMATION_START_VALUE;
            if (lastScale[0] != 0) {
                float shouldScale = (((float) numerator / total) * (1 - startScale) + startScale) / lastScale[0];
                mMapMatrix.postScale(shouldScale, shouldScale, martrixRectF.left + martrixRectF.width() / 2, martrixRectF.top + martrixRectF.height() / 2);
                refreshUI();
            }
            lastScale[0] = ((float) numerator / total) * (1 - startScale) + startScale;
        }
    });
    valueAnimator.start();
}

之所以把两种情况分开就是为了方便,因为计划的是x、y轴都缩放同样的倍数保证图片宽高比不变,但是缩放的中心不可控,因此回弹放大如果不进行位移,必然会留边,因此此处操作是先进行缩放操作,然后再进行位移操作,这样的话目的就打成了,当然只是这样写比较方便,你也可以尝试一变缩放,一遍进行位移控制。此处是加上了动画监听,缩放结束时,进行位移回弹

-----------------------分割线----------------------------------------

至此,图片的操作也就完成了,回顾一下,实现了图片初始位置放置,缩放,平移,边界回弹

先放上绘制的方法,当然特别简单

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mMapBitmap == null)
        return;
    //绘制地图
    canvas.drawBitmap(mMapBitmap, mMapMatrix, mPaint);
}

并且,使用bitmap时,一定要注意资源管理,不然oom会经常临幸你的

//回收同步生命周期
public void onDestroy() {
    if (mMapBitmap != null) {
        mMapBitmap = null;
    }
}
@Override
protected void onDestroy() {
    super.onDestroy();
    mMap.onDestroy();
}

9.绘制点

点的思路为点相对图片的位置百分比,转化为地图百分比,然后由地图百分比转化为屏幕像素值,并且在地图进行操作时,对点的位置进行更新

首先定义实体,然后提供假数据,并且自定义view对外提供方法

private String name;
//poi点的状态
private int status;
//百分比位置
private float precentageTop;
private float precentageLeft;
private int i;
这是我定义的实体,很直观,然后进行假数据操作

mMapPoiEntityList=new ArrayList<>();
for (int i = 0; i < 10; i++) {
    MapPoiEntity mapPoiEntity=new MapPoiEntity();
    mapPoiEntity.setName("天安门:"+i);
    mapPoiEntity.setPrecentageLeft(i*5);
    mapPoiEntity.setPrecentageTop(i*5);
    mapPoiEntity.setI(i);
    mMapPoiEntityList.add(mapPoiEntity);
}
mMap.post(new Runnable() {
    @Override
    public void run() {
        Glide.with(MainActivity.this).load(R.drawable.map).asBitmap().into(new SimpleTarget<Bitmap>() {
            @Override
            public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
                mMap.initImageWH(resource);
                mMap.setMapPoiEntityList(mMapPoiEntityList);
            }
        });
    }
});

相信大家还记得,这个post里面的方法,第二个也就是添加点的方法

/**
 * 设置poi内容
 */
public void setMapPoiEntityList(List<MapPoiEntity> mapPoiEntityList) {
    mMapPoiEntityList = mapPoiEntityList;
    if (mMapPoiEntityList != null) {
        for (int i = 0; i < mMapPoiEntityList.size(); i++) {
            View view = LayoutInflater.from(mContext).inflate(R.layout.poi_point_layout_default, null);
            final int finalI = i;
            view.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(mContext, "被点击了" + mMapPoiEntityList.get(finalI).getI(), Toast.LENGTH_SHORT).show();
                }
            });
            addView(view);
        }
    }
    requestLayout();
}

很直观的直接addView就可以将xml绘制的View添加到我们的自定义View上面,调用requestLayout意味着进行从新测量布局,我们趁此进行子控件的布局分配

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    super.onLayout(changed, l, t, r, b);
    if (mMapPoiEntityList == null || mMapPoiEntityList.size() < 1) {
        return;
    }
    RectF matrixRectF = getMatrixRectF();
    mMapMatrix.getValues(mNowMatrixvalues);
    for (int i = 0; i < getChildCount(); i++) {
        MapPoiEntity mapPoiEntity = mMapPoiEntityList.get(i);
        //三角尖的位置
        int left = 0;
        int top = 0;
        left = (int) (matrixRectF.width() * (mapPoiEntity.getPrecentageLeft() / 100.0f) + matrixRectF.left);
        top = (int) (matrixRectF.height() * (mapPoiEntity.getPrecentageTop() / 100.0f) + matrixRectF.top);
        //三角尖位置记得转化为控件的左上右下
        getChildAt(i).layout(left - getChildAt(i).getMeasuredWidth() / 2,
                top - getChildAt(i).getMeasuredHeight(), left + getChildAt(i).getMeasuredWidth() / 2, top);
    }
}

这里需要注意的也就是矩阵需要实时获取,才会有正确的值,还有子空间的位置中心点,应该是之前效果图的三角尖位置,不然效果就是poi点看起来会移动

还记得之前重复出现的refreshUI么,其实也就是。。。这个

/**
 * 刷新视图
 */
private void refreshUI() {
    invalidate();
    requestLayout();
}

这样的话,也就完全的实现了需求了,并且点击事件实现起来毫不费力,但是,个人觉得完全的自定义View更加流畅,自定义View实现方法将在自定义View--手绘地图(二)中讲解

以下为踩坑与注意事项总结:

 1.成员变量命名请规范,不然你自己都会懵逼,小写m+大写单词,这样的命名方式爽爆

 2.先搞清楚生命周期以及必然方法的先后顺序,避免后面补坑(post方法里面的泪)

 3.双指事件,单指事件两者事件切换的管理与双指抬起的监听

 4.请先想好实现方式与逻辑再写代码(尤其是算法方面),不然很容易被已写的代码影响思路,用笔在纸上画一画是很有必要的事情(如果脑子不能清楚的想明白的话)

 5.多来写注释挺好的,方便自己,也方便以后的人(说不定呢。避免被骂)

 6.onDraw里面如果不是非常必要,不要new对象,就算new了,也请控制

另外附上源码:点击打开链接

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值