支持锚定的图像视图

由于项目的需要,写了这个支持锚定的图像视图,可以在一个图像的任意位置锚定,在运行过程中无论图像如何等比缩放,都能准确的输出所有锚定的坐标,并且支持锚定位置的单击事件。完整代码如下:
AnchorImageView.java 代码:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;


import com.racer.smart.pro.R;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/**
 * 图像锚定视图。
 * 可以将图像直到点锚定出来,在运行过程中图像等比缩放后可以得到锚定的坐标,支持多个锚定坐标,支持锚定的位置单击事件
 */
public class AnchorImageView extends View {
    private static final String TAG = "AnchorImageView";
    /**图像宽度*/
    private float imageWidth=0;
    /**图像高度*/
    private float imageHeight=0;
    /**图像左边位置*/
    private float imageLeft=0;
    /**图像顶部位置*/
    private float imageTop=0;
    /**记录单击后的点索引*/
    private int downPointIndex=-1;
    /**图像原本的实际宽度。-1表示图像为颜色类图像,无本质大小*/
    private int intrinsicWidth=-1;
    /**图像原本的实际高度。-1表示图像为颜色类图像,无本质大小*/
    private int intrinsicHeight=-1;
    /**着色*/
    private boolean isTint=false;
    /**中心图片*/
    private Bitmap bitmap=null;
    /**标志点相对图像百分比列表*/
    private List<RectF> flagPointScaleList=new ArrayList<>();
    /**标志点动态位置列表(根据标志点相对图像百分比列表计算)*/
    private List<PointF> pointList=null;
    /**着色器颜色*/
    private @ColorInt int tintColor=Color.BLACK;
    /**描点颜色*/
    private @ColorInt  int drawDotColor=Color.BLACK;
    /**显示标志点*/
    private boolean displayFlagPoint=false;
    /**圆点半径*/
    private int pointOleRadius=6;
    private float pointRadius=pointOleRadius;
    private Paint bitmapPaint;
    private Paint pointPaint;
    /**设置图像绘制完成就绪触发的事件侦听器*/
    private OnImageDrawReadyListener imageDrawReadyListener=null;
    /**设置标志点单击事件侦听器*/
    private OnFlagPointClickListener flagPointClickListener=null;
    /**图像绘制完成,准备就绪触发*/
    public interface OnImageDrawReadyListener{
        /**
         * 就绪
         * @param imageLeft 图像在视图中的左边坐标
         * @param imageTop 图像在视图中的顶部坐标
         * @param imageRight 图像在视图中的右边坐标
         * @param imageBottom 图像在视图中的底部坐标
         */
        void onReady(double imageLeft,double imageTop,double imageRight,double imageBottom);
    }
    /**标志点单击事件侦听器*/
    public interface OnFlagPointClickListener{
        /**
         * 单击
         * @param index 标志点索引
         * @param point 标志点坐标
         * @param radius 标志点半径
         */
        void onClick(int index,PointF point,float radius);
    }
    public AnchorImageView(Context context) {
        this(context,null);
    }
    public AnchorImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
        Log.e(TAG,"IDE模式");
    }
    public AnchorImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.AnchorImageView);
        int resId=typedArray.getResourceId(R.styleable.AnchorImageView_srcCompat,0); //获取 图片资源id
        if(resId!=0) {
            this.bitmap = BitmapFactory.decodeResource(getResources(), resId);//从图片资源中获取位图
            if (this.bitmap == null) {
                this.bitmap = VectorDrawableToBitmap(resId);  //从资源ID矢量图形转为位图
            }
        }
        isTint=typedArray.getResourceId(R.styleable.AnchorImageView_android_tint,0)!=0;
        if(isTint){
            tintColor=typedArray.getColor(R.styleable.AnchorImageView_android_tint,tintColor);
        }
        drawDotColor=typedArray.getColor(R.styleable.AnchorImageView_drawDotColor,drawDotColor);
        pointOleRadius=typedArray.getDimensionPixelSize(R.styleable.AnchorImageView_dotRadius,pointOleRadius);
        pointRadius=pointOleRadius;
        displayFlagPoint=typedArray.getBoolean(R.styleable.AnchorImageView_displayFlagPoint,displayFlagPoint);
        String jsonString=typedArray.getString(R.styleable.AnchorImageView_flagPointScaleList);
        //标记点百分比列表注册两种格式,JSON或数组
        if(jsonString!=null){
            try {
                JSONArray jsonArray=new JSONArray(jsonString.toLowerCase());
                for(int i=0;i<jsonArray.length();i++){
                    JSONObject jsonObject= jsonArray.optJSONObject(i);
                    float l=0,t=0,r=0,b=0;
                    if(jsonObject!=null){
                         l= (float) jsonObject.optDouble("left",0);
                         t= (float) jsonObject.optDouble("top",0);
                         r= (float) jsonObject.optDouble("right",0);
                         b= (float) jsonObject.optDouble("bottom",0);
                    }else{
                        JSONArray jsArray= jsonArray.optJSONArray(i);
                        if(jsArray!=null){
                            l= (float) jsArray.getInt(0);
                            t= (float) jsArray.getInt(1);
                            r= (float) jsArray.getInt(2);
                            b= (float) jsArray.getInt(3);
                        }
                    }
                    flagPointScaleList.add(new RectF(l,t,r,b));
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        initPaint();
        typedArray.recycle();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int  width = getMeasureSize(widthMeasureSpec);//得到View的宽
        int  height = getMeasureSize(heightMeasureSpec);//得到View的高
        setMeasuredDimension(width,height);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       if(bitmap!=null) {
           drawDrawable(canvas);
           this.setPointList();     //重新计算标志点动态位置列表
           if(displayFlagPoint||downPointIndex>-1) {
               drawPoint(canvas,downPointIndex);
           }
           if(imageDrawReadyListener!=null){
               imageDrawReadyListener.onReady(imageLeft,imageTop,imageLeft+imageWidth,imageTop+imageHeight);
           }
       }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x=event.getX();
        float y=event.getY();
        if(pointList!=null && pointList.size()>0) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    for(int i=0;i<pointList.size();i++ ){
                        if(isRegion(x,y,pointList.get(i),pointRadius)){
                            downPointIndex=i;    //将当前点击的标志点索引赋值给downPointIndex
                            Log.e(TAG,"按下:"+downPointIndex);
                            invalidate();
                            break;
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if(downPointIndex!=-1){
                        if(flagPointClickListener!=null){
                            flagPointClickListener.onClick(downPointIndex,pointList.get(downPointIndex),pointRadius);
                        }
                        invalidate();
                    }
                    Log.e(TAG,"释放:"+downPointIndex);
                    downPointIndex=-1;
            }
            return true;
        }else{
            return super.onTouchEvent(event);
        }
    }
    /**
     * 判断坐标是否在有效区域(异形按钮内部)
     * @param x 需要判断的点X坐标
     * @param y 需要判断的点Y坐标
     * @param center 需要进行判断的圆的圆心坐标
     * @param radius 圆的半径
     * @return
     */
    private boolean isRegion(float x,float y,PointF center,float radius){
        float centralOffset= (float) Math.sqrt(Math.pow(x-center.x,2)+Math.pow(y-center.y,2));//计算指定点到圆心偏移量
        return centralOffset<=radius/2;
    }
    /**初始化笔刷*/
    private void initPaint(){
        bitmapPaint = new Paint();
        bitmapPaint .setStyle( Paint.Style.STROKE );
        bitmapPaint.setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
        pointPaint= new Paint(Paint.ANTI_ALIAS_FLAG);
        pointPaint.setStrokeWidth(1);
        pointPaint.setColor(drawDotColor);
    }
    /**
     * 测量视图大小
     * @param measureSpec 父级施加的水平或垂直空间要求。需求编码为视图.测量。
     */
    private int getMeasureSize(int measureSpec){
        int result;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = 200;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }
    private void drawDrawable(Canvas canvas){
        /**可绘图区域宽度*/
        float mDrawWidth=getMeasuredWidth();
        /**可绘图区域高度*/
        float mDrawHeight=getMeasuredHeight();
        //Drawable drawable=getResources().getDrawable(R.drawable.ic_head_eye);
        /**图像宽度*/
        float drawWidth=bitmap.getWidth();//drawable.getIntrinsicWidth();
        /**图像高度*/
        float drawHeight=bitmap.getHeight();//drawable.getIntrinsicHeight();

        float scaleWidth= mDrawWidth/drawWidth;  //宽度缩放比例
        float scaleHeight= mDrawHeight/drawHeight; //高度缩放比例

        if(scaleWidth<=scaleHeight) {
            imageWidth=mDrawWidth;
            imageHeight=drawHeight*scaleWidth;
            pointRadius=pointOleRadius*scaleWidth;
        }else {
            imageWidth = drawWidth *scaleHeight;
            imageHeight = mDrawHeight;
            pointRadius=pointOleRadius*scaleHeight;
        }
        imageLeft= (mDrawWidth-imageWidth)/2;
        imageTop= (mDrawHeight-imageHeight) /2;
        Rect des=new Rect((int) imageLeft, (int) imageTop, (int) (imageWidth+imageLeft), (int) (imageHeight+imageTop));
        canvas.drawBitmap(bitmap,null,des,isTint?bitmapPaint:null);
    }
    private void drawPoint(Canvas canvas,int position){
        if(flagPointScaleList!=null && flagPointScaleList.size()>0) {
            if(position==-1 || position>=flagPointScaleList.size()) {
                for (int i = 0; i < flagPointScaleList.size(); i++) {
                    //RectF rect = flagPointScaleList.get(i);
                    PointF pointOffset = pointList.get(i);;//getFlagPoint(rect.left, rect.top, rect.right, rect.bottom);
                    canvas.drawCircle(pointOffset.x, pointOffset.y, pointRadius, pointPaint);
                }
            }else{
                PointF pointOffset = pointList.get(position);;//getFlagPoint(rect.left, rect.top, rect.right, rect.bottom);
                canvas.drawCircle(pointOffset.x, pointOffset.y, pointRadius, pointPaint);
            }
        }
    }

    /**
     * 根据中心偏移百分比计算出点的实际坐标
     * @param scaleLeftOffset   中心相对左边的偏移百分比
     * @param scaleTopOffset    中心相对顶部的偏移百分比
     * @param scaleRightOffset  中心相对右边的偏移百分比
     * @param scaleBottomOffset 中心相对底部的偏移百分比
     * @return 返回偏移点的实际View坐标(像素)
     */
    private PointF getFlagPoint(float scaleLeftOffset,float scaleTopOffset,float scaleRightOffset,float scaleBottomOffset ){
        PointF point=new PointF((float) getWidth()/2,(float)getHeight()/2);    //获取视图中心点坐标
        float offset_x,offset_y;
        if(scaleLeftOffset!=0) {
            offset_x = point.x + scaleLeftOffset * ((point.x - imageLeft) / 100.0f);
        }else{
            offset_x = point.x - scaleRightOffset * ((point.x - imageLeft) / 100.0f);
        }
        if(scaleTopOffset!=0){
            offset_y= point.y+ scaleTopOffset * ((point.y- imageTop) / 100.0f);
        }else{
            offset_y= point.y- scaleBottomOffset * ((point.y- imageTop) / 100.0f);
        }
        return new PointF(offset_x,offset_y);
    }
    /**
     * 从资源ID中的矢量图转位图
     * @param resId 资源id
     * @return
     */
    private Bitmap VectorDrawableToBitmap(int resId){
        Drawable drawable = ContextCompat.getDrawable(getContext(),resId);
        drawable = (DrawableCompat.wrap(drawable)).mutate();
        return DrawableToBitmap(drawable);
    }
    /**
     * Drawable转位图
     * @param drawable
     * @return
     */
    private Bitmap DrawableToBitmap(Drawable drawable){
        BitmapDrawable bd=null;
        if(drawable!=null) {
            intrinsicWidth = drawable.getIntrinsicWidth();
            intrinsicHeight = drawable.getIntrinsicHeight();
        }
        try {
            bd = (BitmapDrawable) drawable;
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(bd!=null) {
            return bd.getBitmap();
        }else{
            int width = drawable.getIntrinsicWidth();
            int height = drawable.getIntrinsicHeight();
            Bitmap bitmap = Bitmap.createBitmap(width, height,
                    drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888: Bitmap.Config.RGB_565);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0,0,width,height);
            drawable.draw(canvas);
            return bitmap;
        }
    }

    /**
     * 添加标志点距中心的偏移坐标
     * @param scaleLeftOffset   距中心左坐偏移百分比
     * @param scaleTopOffset    距中心顶部偏移百分比
     * @param scaleRightOffset  距中心右偏移百分比
     * @param scaleBottomOffset 距中心底部偏移百分比
     */
    public void addFlagPointScale(float scaleLeftOffset,float scaleTopOffset,float scaleRightOffset,float scaleBottomOffset){
        RectF scalePoint=new RectF(scaleLeftOffset,scaleTopOffset,scaleRightOffset,scaleBottomOffset);
        addFlagPointScale(scalePoint);
    }
    /**
     * 添加标志点距中心的偏移坐标
     * @param scalePoint 需要添加的偏移坐标
     */
    public void addFlagPointScale(RectF scalePoint){
        if(flagPointScaleList==null)
            flagPointScaleList=new ArrayList<>();
        flagPointScaleList.add(scalePoint);
        invalidate();
    }
    /**
     * 设置标志点距中心点偏移百分比列表
     * @param scalePoints 要设置的偏移百分比
     */
    public void setFlagPointScaleList(List<RectF> scalePoints) {
        if(scalePoints!=null) {
            this.flagPointScaleList = scalePoints;
            invalidate();
        }
    }
    /**获取标志点据中心偏移百分比列表*/
    public List<RectF> getFlagPointScaleList() {
        return flagPointScaleList;
    }

    /**
     * 获取标志点的实际坐标,帮助View在图像中的动态定位。
     * (需要在视图完全加载后调用,即在{@link OnImageDrawReadyListener}触发后调用)
     * @return
     */
    public List<PointF> getFlagPoints(){
        return pointList;
    }
    /**重新计算标志点列表*/
    private void setPointList() {
        if(flagPointScaleList!=null){
             pointList=new ArrayList<>();
            for(int i=0;i<flagPointScaleList.size();i++){
                RectF rect=flagPointScaleList.get(i);
                pointList.add(getFlagPoint(rect.left,rect.top,rect.right,rect.bottom));
            }
        }
    }
    /**
     * 设置着色器颜色
     * @param tintColor
     */
    public void setTintColor(int tintColor) {
        this.tintColor = tintColor;
        bitmapPaint.setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
        isTint=true;
        invalidate();
    }
    /**
     * 设置着色器颜色
     * @param tintColor
     */
    public void setTintColor(Color tintColor) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if(tintColor!=null) {
                setTintColor(tintColor.toArgb());
            }else{
                isTint=false;
            }
        }
    }
    /**
     * 设置着色器颜色
     * @param colorString
     */
    public void setTintColor(String colorString) {
        try {
            setTintColor(Color.parseColor(colorString));
        } catch (Exception e) {
            Log.e(TAG,"setTintColor::"+e.getMessage());
            isTint=false;
        }
    }

    /**
     * 设置着色器颜色资源id
     * @param id
     */
    public void setTintColorId(int id) {
        setTintColor(getResources().getColor(id));
    }
    /**获取着色器颜色**/
    public int getTintColor() {
        return tintColor;
    }

    /**
     * 显示标志点
     * @param displayFlagPoint 显示
     */
    public void setDisplayFlagPoint(boolean displayFlagPoint) {
        this.displayFlagPoint = displayFlagPoint;
        invalidate();
    }
    /**显示标志点*/
    public boolean isDisplayFlagPoint() {
        return displayFlagPoint;
    }
   /**设置图像绘制完成就绪事件侦听器*/
    public void setOnImageDrawReadyListener(OnImageDrawReadyListener listener) {
        this.imageDrawReadyListener = listener;
    }

    public void setOnFlagPointClickListener(OnFlagPointClickListener flagPointClickListener) {
        this.flagPointClickListener = flagPointClickListener;
    }
    /**设置描点颜色*/
   public void setDrawDotColor(int color) {
       this.drawDotColor = color;
       pointPaint.setColor(drawDotColor);
       if(displayFlagPoint)
           invalidate();
   }
    /**设置描点颜色*/
    public void setDrawDotColor(String colorString) {
        try {
            setDrawDotColor(Color.parseColor(colorString));
        } catch (Exception e) {
            Log.e(TAG,"setDrawDotColor::"+e.getMessage());
            isTint=false;
        }
    }
    /**设置描点颜色资源id*/
    public void setDrawDotColorId(int id) {
        setDrawDotColor(getResources().getColor(id));
    }
    /**描点颜色*/
    public int getDrawDotColor() {
        return drawDotColor;
    }
    public void setImageResource(int resId){
        if(resId!=0) {
            this.bitmap = BitmapFactory.decodeResource(getResources(), resId);//从图片资源中获取位图
            if (this.bitmap == null) {
                this.bitmap = VectorDrawableToBitmap(resId);  //从资源ID矢量图形转为位图
                invalidate();
            }else {
                invalidate();
            }
        }
    }
    public int getIntrinsicWidth() {
        return intrinsicWidth;
    }
    public int getIntrinsicHeight() {
        return intrinsicHeight;
    }
    public void setPointRadius(int pointRadius) {
        this.pointOleRadius = pointRadius;
        this.pointRadius=pointRadius;
        invalidate();
    }
    /**获取描点半径(有可能是经过缩放后重新计算的值)*/
    public float getPointRadius() {
        return pointRadius;
    }
}

attr.xml属性文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="AnchorImageView">
        <!--图像资源-->
        <attr name="srcCompat" format="reference"/>
        <!--距中心偏移百分比的点列表。格式:[{left:0,top:0,right:0,bottom:0},...,{left:0,top:0,right:0,bottom:0}]或[[0,0,0,0],...,[0,0,0,0]]-->
        <attr name="flagPointScaleList" format="string"/>
        <attr name="android:tint"/>
        <!--显示标记点-->
        <attr name="displayFlagPoint" format="boolean"/>
        <!--描点颜色-->
        <attr name="drawDotColor" format="color|reference"/>
        <!--描点半径(半径会在运行过程中随着图像大小变化而变化)-->
        <attr name="dotRadius" format="dimension|reference"/>
    </declare-styleable>
</resources>
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值