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

前一篇说到了使用自定义ViewGroup实现手绘地图,没看过的可以移步,因为本篇会用到上一篇的部分内容

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

和前一篇一样,实现图片的操作经过同样的操作、onMeasure,初始化图片、位移、缩放、回弹,不一样的是poi点的绘制

自定义ViewGroup直接使用addView的方式并且经由onLayout实现位置确定,而自定义View是不一样的,放置poi点,只能通过调用ondraw绘制,点击事件,通过监听onTOuch,获取位置,然后根据范围来判断poi哪个被点击,接下来进入实现

首先,在放置poi之前,代码与实现步骤与(一)部分一样,所以就直接进入绘制poi部分了

描述一下我的思路,绘制poi同样的是根据相对图片的位置百分比,算出此时应该在屏幕中的什么位置,再绘制上去,而点击事件,在第一次绘制poi时,也就是一倍图的时候记录整个poi会占的位置,然后点击屏幕时,将onTouch获得的x、y值经过计算,得到此时点击的位置,如果是在初始图时,会在什么范围,从而获取点击到的poi点

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

onDraw中多了一个方法

/**
 * 地图移动到中间
 */
private void moveToCenter() {
}
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mMapBitmap == null)
        return;
    //绘制地图
    canvas.drawBitmap(mMapBitmap, mMapMatrix, mPaint);
    //绘制poi
    drawPoi(canvas);
}

由篇(一)的效果图知道,poi绘制主要分成四部分,圆角矩形、三角形、文字、poi图标

在进行绘制之前,先对poi图标进行缩放,缩放方式与地图图片缩放方式相同

/**
 * 对bitmap进行缩放
 */
public Bitmap scaleBitmap(Bitmap bitmap,int scaleX,int scaleY){
    float imgHeight = bitmap.getHeight();
    float imgWidth = bitmap.getWidth();
    Matrix matrix = new Matrix();
    matrix.postScale(scaleX / imgWidth, scaleY / imgHeight);
    return Bitmap.createBitmap(bitmap, 0, 0, (int) imgWidth, (int) imgHeight, matrix, true);
}
mPoiBitmapDefault=scaleBitmap(mPoiBitmapDefault,Utils.dip2px(mContext,16),Utils.dip2px(mContext,16));

mPoiBitmapDefault也就是缩放后的poi图标,此处贴出Utils中的方法

public class Utils {
    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }
    /**
     * 判断p是否在abcd组成的四边形内
     *
     * @param a
     * @param b
     * @param c
     * @param d
     * @param p
     * @return 如果p在四边形内返回true, 否则返回false.
     */
    public static boolean pInQuadrangle(Point a, Point b, Point c, Point d,
                                        Point p) {
        double dTriangle = triangleArea(a, b, p) + triangleArea(b, c, p)
                + triangleArea(c, d, p) + triangleArea(d, a, p);
        double dQuadrangle = triangleArea(a, b, c) + triangleArea(c, d, a);
        return dTriangle == dQuadrangle;
    }
    // 返回三个点组成三角形的面积
    private static double triangleArea(Point a, Point b, Point c) {
        double result = Math.abs((a.x * b.y + b.x * c.y + c.x * a.y - b.x * a.y
                - c.x * b.y - a.x * c.y) / 2.0D);
        return result;
    }
}

其中两个dip与px相互转换的方法作用不多说,另外一个pInQuadrangle在后面进行解释

到目前为止,准备就绪,开始绘制poi

/**
 * 画poi点,并确定位置
 */
private void drawPoi(Canvas canvas){
    if (mMapPoiEntityList!=null&&mMapPoiEntityList.size()>0){
        for (int i = 0; i < mMapPoiEntityList.size(); i++) {
            //加粗文字
            mPaint.setFakeBoldText(true);
            //获取文字宽高
            mPaint.setTextSize(Utils.dip2px(mContext,10));
            mPaint.setStrokeWidth(8);
            mPaint.getTextBounds(mMapPoiEntityList.get(i).getName(),0,mMapPoiEntityList.get(i).getName().length(),mTextRect);
            //获取地图图片对应矩阵
            RectF matrixRectF = getMatrixRectF();
            //poi点的宽高
            int poiWidth=mTextRect.width()+ Utils.dip2px(mContext,16+2+2+6);
            int poiHeight=0;
            if (mTextRect.height()>Utils.dip2px(mContext,16)){
                poiHeight=mTextRect.height()+Utils.dip2px(mContext,2+2+66);
            }else{
                poiHeight=Utils.dip2px(mContext,2+2+16+6);
            }
            int left=0;
            int top=0;
            //此处存储一下poi点的四个角,用于后面的点击事件

            //首先获取到中心点,即三角形下顶点的top、left值,此处left与top的值是相对于屏幕的
            left = (int) (matrixRectF.width() * (mMapPoiEntityList.get(i).getPrecentageLeft() / 100.0f) + matrixRectF.left);
            top = (int) (matrixRectF.height() * (mMapPoiEntityList.get(i).getPrecentageTop() / 100.0f) + matrixRectF.top);
            if (mIsFirstDraw){
                PoiBoundEntity poiBoundEntity=new PoiBoundEntity();
                poiBoundEntity.setId(mMapPoiEntityList.get(i).getI());
                //此处的范围是相对于地图的
                poiBoundEntity.setLeft((int) ((matrixRectF.width() * (mMapPoiEntityList.get(i).getPrecentageLeft() / 100.0f) )-poiWidth/2));
                poiBoundEntity.setTop((int) ((matrixRectF.height() * (mMapPoiEntityList.get(i).getPrecentageTop() / 100.0f))-poiHeight));
                poiBoundEntity.setRight((int) ((matrixRectF.width() * (mMapPoiEntityList.get(i).getPrecentageLeft() / 100.0f) )+poiWidth/2));
                poiBoundEntity.setBootom((int) (matrixRectF.height() * (mMapPoiEntityList.get(i).getPrecentageTop() / 100.0f)));
                mPoiBoundEntities.add(poiBoundEntity);
            }
            //画圆角矩形
            poiConersRectf.set(left-poiWidth/2,top-poiHeight,left+poiWidth/2,top-Utils.dip2px(mContext,6));
            mPaint.setColor(getResources().getColor(R.color.white));
            mPaint.setStyle(Paint.Style.FILL);
            canvas.drawRoundRect(poiConersRectf,Utils.dip2px(mContext,10),Utils.dip2px(mContext,10),mPaint);
            //画三角形
            trianglePath.reset();
            trianglePath.moveTo(left,top);
            trianglePath.lineTo(left-Utils.dip2px(mContext,3),top-Utils.dip2px(mContext,6));
            trianglePath.lineTo(left+Utils.dip2px(mContext,3),top-Utils.dip2px(mContext,6));
            trianglePath.close();
            canvas.drawPath(trianglePath,mPaint);
            //画poi图片
            canvas.drawBitmap(mPoiBitmapDefault,left-poiWidth/2+Utils.dip2px(mContext,2),
                    top-poiHeight+(poiHeight-Utils.dip2px(mContext,6+16))/2,mPaint);
            //画文字
            mPaint.setColor(getResources().getColor(R.color.black));
            canvas.drawText(mMapPoiEntityList.get(i).getName(),left-poiWidth/2+Utils.dip2px(mContext,2+16+2),
                    top-Utils.dip2px(mContext,8)-(poiHeight-Utils.dip2px(mContext,6)-mTextRect.height())/2,mPaint);

        }
        mIsFirstDraw=false;
    }
}

代码就是这些,注释的还是很清楚的,流程为先获取即将绘制的文字的宽高,然后根据UI给的设计图,得到poi的宽高,然后计算分别将圆角矩形,三角形,文字,图标绘制在合适的位置,并且因为poi的x、y值由相对于地图的百分比确定,因此在地图进行移动缩放操作时,poi点就自行跟着改变了。并且在第一次绘制的时候,存储下了每一个poi点的左上右下几个点的坐标值。接下来就进行点击事件的处理

case MotionEvent.ACTION_DOWN:
    //获取到上一次手指位置
    mLastSinglePointX = event.getX();
    mLastSinglePointY = event.getY();
    //寻找点击位置
    getWhichPoiClick(event.getX(),event.getY());
    break;

在down中增加了一个方法

/**
 * 获取poi点击事件
 */
private void getWhichPoiClick(float xTouch,float yTouch){
    List<PoiBoundEntity> mListPoiBoundEntity=new ArrayList<>();
    RectF matrixRectF = getMatrixRectF();
    float scaleX = (Math.abs(matrixRectF.left) + xTouch) / mNowMatrixvalues[Matrix.MSCALE_X];
    float scaleY = (Math.abs(matrixRectF.top) + yTouch) / mNowMatrixvalues[Matrix.MSCALE_X];
    Point point=new Point((int)scaleX,(int)scaleY);
     for (int i = 0; i < mPoiBoundEntities.size(); i++) {
        Point one=new Point(mPoiBoundEntities.get(i).getLeft(),mPoiBoundEntities.get(i).getTop());
        Point two=new Point(mPoiBoundEntities.get(i).getRight(),mPoiBoundEntities.get(i).getTop());
        Point three=new Point(mPoiBoundEntities.get(i).getLeft(),mPoiBoundEntities.get(i).getBootom());
        Point four=new Point(mPoiBoundEntities.get(i).getRight(),mPoiBoundEntities.get(i).getBootom());
        boolean b = Utils.pInQuadrangle(one, two, four,three,point);
        if (b){
            mListPoiBoundEntity.add(mPoiBoundEntities.get(i));
        }
    }
    /**
     *解决poi位置又接触时,点击冲突状态(因按照poi绘制顺序,后面的点总是在之前绘制的点上方,因此点击事件
     * 存在两个位置重合时,取上层,也就是后绘制上去的
     */
    if (mListPoiBoundEntity.size()>0){
        Toast.makeText(mContext,"被点击了"+mListPoiBoundEntity.get(mListPoiBoundEntity.size()-1).getId()+"",Toast.LENGTH_SHORT).show();
    }

这样也就获取到了点击poi,解释一下:

此处更正,因后续操作发现,如果一倍图poi点叠加在一起则会导致放大了以后  点击事件混乱qaq-------不知道有没有小伙伴发现,既然没有留言应该就是没人发现吧。。。解决办法如下:

首先在onDraw中绘制poi时,是控制了添加边界次数,本来是为了避免重复赋值,目前看来得不断的变化值。于是首先每次画poi时将边界集合清空

/**
 * 画poi点,并确定位置
 */
private void drawPoi(Canvas canvas) {
    mPoiBoundEntities.clear();

然后将之前的开关mIsFirstDraw去掉(没用了),然后在方法

getWhichPoiClick()

中更正如下

float scaleX = (Math.abs(matrixRectF.left) + xTouch) ;
float scaleY = (Math.abs(matrixRectF.top) + yTouch);

就是去除了倍数换算,而作为实际倍数

首先计算出当前点击的x、y值还原到一倍图时的x、y,(有错误我也不删除了。。。)然后取出每一个存储的值,进行遍历

判断点是否在四边形内的方法为之前贴出的Utils中的pInQuadrangle,原理为将点击点与每一个四边形的点两两相连,获取到的三角形面积相加若是大于四边形面积,则在范围外,否则在范围内,纸上画一画就知道了

后面还有一段注释,用于poi重合的处理,因为绘制的顺序,第二个绘制的总是在第一个之上,因此,如果点击的时候触发第二个的点击事件,当然 这是比较简单的做法,但是不够完善,更好的做法应该是取两个相交点的范围相交值的中心点,然后计算点击点到相交点的值为正还是负来判断点击事件具体位于哪边。感兴趣的朋友可以自己实践

至此本篇结束。感谢观看,不足之处请留言(-*..*-)

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

ps:因为有朋友下载了资源说卡,于是附上我后来优化后的代码,虽然多了些功能,但是大体的思路未变,感谢包容

Android手绘地图(优化版)

猜你喜欢

转载自blog.csdn.net/LZ511321/article/details/80319756