Android重写ImageView实现图片镜像效果

前两天朋友问我一个问题,如何实现从手机系统相册加载一张图片,实现拖拽、缩放功能并且能以屏幕中间为分界线镜像显示,当时我的第一反应是继承ImageView,重写onDraw方法,下面就按照这个思路逐步实现下需求。

从系统相册选择图片

打开系统相册采用隐式Intent,因为调用者需要接受返回的结果,所以使用startActivityForResult启动Activity,打开系统相册核心代码如下

public final static int ALBUM_PIC_CODE = 1;
...
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, ALBUM_PIC_CODE);

通过打印日志的方式看一下从系统相册中选择图片以后返回的内容是什么:
这里写图片描述
可以断定这是个Uri,那么好,我们直接通过Uri获取图片即可:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == ALBUM_PIC_CODE && resultCode == RESULT_OK){
        Log.i("onActivityResult", data.getData().toString());
        try {
            Uri imageUri = data.getData();
            Bitmap bitmap= BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
            iv.setImageBitmap(bitmap);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

这样我们就通过Uri以流的方式载入了图片

重写ImageView

载入图片以后,我们需要在显示以前对图片进行处理以达到所需的显示效果,我了方便我就直接对系统控件ImageView进行重写来达到想要的功能。

拖拽功能

要实现拖拽功能,首先会想到重写onTouchEvent方法,需要在手指落下时获取坐标点的信息,并在手指移动时根据移动的位置实时更新图片位置.

public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = event.getX();
            lastY = event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            float currentX = event.getX();
            float currentY = event.getY();
            offsetX += currentX - lastX;//计算移动距离
            offsetY += currentY - lastY;
            lastX = currentX;//替换上一次位置
            lastY = currentY;
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
    }
    super.onTouchEvent(event);
    return true;
}

重写onDraw方法对图片位置进行实时更新

protected void onDraw(Canvas canvas) {
    if (bitmap != null) {
        bitmapWidth = bitmap.getWidth();
        bitmapHeight = bitmap.getHeight();
        int scaleHeight = (int) (viewWidth / (float) bitmapWidth * bitmapHeight);
        bitmapLeft = (int) offsetX;
        bitmapRight = (int) (viewWidth + offsetX);
        bitmapTop = (int) offsetY;
        bitmapBottom = (int) (scaleHeight + offsetY);
        Paint paint = new Paint();
        canvas.drawBitmap(bitmap, new Rect(0, 0, bitmapWidth, bitmapHeight),
                new Rect(bitmapLeft, bitmapTop, bitmapRight, bitmapBottom), paint);
        invalidate();
    } else {
        try {
            bitmap = ((BitmapDrawable) getDrawable()).getBitmap();
        } catch (Exception e) {
            e.printStackTrace();
            bitmap = null;
        }
    }
}

上面代码用到的bitmap从setImageBitmap中获取即可

public void setImageBitmap(Bitmap bm) {
    super.setImageBitmap(bm);
    bitmap = bm;
}

控件的尺寸在onMeasure中获取

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    viewWidth = getMeasuredWidth();
    viewHeight = getMeasuredHeight();
}

效果如下:

缩放功能

  • 缩放需要检测两个手指的状态,为了检测两个手指都放到屏幕上,需要用到MotionEvent.ACTION_POINTER_DOWN事件,与ACTION_DOWN的区别是有多个手指按下时才会触发这个事件。
  • 除此之外在移动过程中还需要判断是两个手指接触还是一个手指接触需要用到event.getPointerCount()方法获取触点数。

因此修改过后的onTouchEvent方法如下:

public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            lastX = event.getX();
            lastY = event.getY();
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            firstDistance = getDistance(event);
            break;
        case MotionEvent.ACTION_MOVE:
            if (event.getPointerCount() == 2) {//两点触摸
                float dis = getDistance(event);
                float varScale = (float) Math.pow(dis*1.0 / firstDistance, 1.0 / 4);
                scale *= varScale;
                if (scale > 10) {
                    scale = 10f;
                }
                if (scale < 0.1) {
                    scale = 0.1f;
                }
            } else if (event.getPointerCount() == 1) {//单点触摸
                float currentX = event.getX();
                float currentY = event.getY();
                offsetX += currentX - lastX;
                offsetY += currentY - lastY;
                lastX = currentX;//替换上一次位置
                lastY = currentY;
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
    }
    super.onTouchEvent(event);
    return true;
}

其中getDistance()函数用来测量两点间的距离

private float getDistance(MotionEvent event) {
    float disX = event.getX(0) - event.getX(1);//x轴的距离
    float disY = event.getY(0) - event.getY(1);//y轴的距离
    return (float) Math.sqrt(disX * disX + disY * disY);
}

然后在onDraw中计算上、下、左、右的公式进行相应的修改即可

bitmapLeft = (int) (offsetX + (viewWidth - viewWidth * scale) / 2);
bitmapRight = (int) (viewWidth * scale) + bitmapLeft;
bitmapTop = (int) (offsetY + (scaleHeight - scaleHeight * scale) / 2);
bitmapBottom = (int) (scaleHeight * scale) + bitmapTop;

缩放的实现效果图就不在这里展示了,最后会附上总体效果图

镜像显示

  • 镜像显示可以利用Matrix的postScale(float sx, float sy)方法,两个参数分别表示x、y轴的缩放大小,如果设置成负数则会出现镜像效果。
  • 因为镜像是以屏幕竖直方向中间位置为分界线所以,原图像的右边界和镜像图像的左边界也就固定了,而且需要注意的是手指落点在屏幕的左边和在屏幕的右边拖动,图片的移动方向应该是相反的,所以需要在onTouchEvent中进行处理。
  • -

首先需要获取下分界线的位置

//该函数获取的屏幕宽度,除以2即为分界线位置
public static int getScreenWidth(Context context)
{
    WindowManager wm = (WindowManager) context
            .getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics outMetrics = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(outMetrics);
    return outMetrics.widthPixels;
}

在onTouchEvent中对拖拽进行处理

public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        ...
        case MotionEvent.ACTION_MOVE:
            if (event.getPointerCount() == 2) {//两点触摸
                ...
            } else if (event.getPointerCount() == 1) {//单点触摸
                float currentX = event.getX();
                float currentY = event.getY();
                if (event.getRawX() < halfScreenWidth) {
                    offsetX += currentX - lastX;
                } else {
                    offsetX += lastX - currentX;
                }
                offsetY += currentY - lastY;
                lastX = currentX;//替换上一次位置
                lastY = currentY;
            }
            break;
        ...
    }
    super.onTouchEvent(event);
    return true;
}

然后在onDraw中绘制原图和镜像图片

protected void onDraw(Canvas canvas) {
    if (bitmap != null) {
        //图片尺寸
        bitmapWidth = bitmap.getWidth();
        bitmapHeight = bitmap.getHeight();
        //图片和View的比例关系
        float s = (float) bitmapWidth / viewWidth;
        //按照原图比例,为适应View宽度而计算显示图片高度
        int scaleHeight = (int) (viewWidth / (float) bitmapWidth * bitmapHeight);
        //原图左边位置=屏幕中间位置-缩放后的图片宽度+x轴偏移量
        bitmapLeft = (int) (halfScreenWidth - viewWidth * scale + (offsetX > 0 ? offsetX * scale : 0));
        //镜像图右边位置=屏幕中间位置+缩放后的图片宽度-x轴偏移量
        bitmapRight = (int) (halfScreenWidth + viewWidth * scale - (offsetX > 0 ? offsetX * scale : 0));
        //图片上边位置=Y轴偏移量+缩放变化的一半
        bitmapTop = (int) (offsetY + (scaleHeight - scaleHeight * scale) / 2);
        //图片下边位置=缩放后的图片高度+上边位置
        bitmapBottom = (int) (scaleHeight * scale) + bitmapTop;
        Paint paint = new Paint();
        canvas.drawBitmap(bitmap, new Rect(0, 0, (int) (bitmapWidth - offsetX * s), bitmapHeight),
                new Rect(bitmapLeft, bitmapTop, halfScreenWidth, bitmapBottom), paint);
        Matrix m = new Matrix();
        m.postScale(-1, 1);//水平方向镜像
        Bitmap b = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, m, true);
        canvas.drawBitmap(b, new Rect((int) (offsetX * s), 0, b.getWidth(), b.getHeight()),
                new Rect(halfScreenWidth, bitmapTop, bitmapRight, bitmapBottom), paint);

        invalidate();
    } else {
        try {
            bitmap = ((BitmapDrawable) getDrawable()).getBitmap();
        } catch (Exception e) {
            e.printStackTrace();
            bitmap = null;
        }
    }
}

最终实现效果如下:

待解决问题

  • 打开大图时会出现OOM问题有待解决

github源码地址

你可能感兴趣的:(Android)