如何显示在Android中使用Matrix旋转的ImageView的一半? [英] How to show half of the ImageView which is rotated using Matrix in Android?

查看:98
本文介绍了如何显示在Android中使用Matrix旋转的ImageView的一半?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

亲爱的朋友StackOverflower,

Dear fellow StackOverflower,

按照这个例子( code.tutsplus [dot] com / tutorials / android-sdk-creating-a-rotating-dialer --mobile-8868 ),我可以使用 Matrix 创建一个旋转的 ImageView 。逻辑如下:

Follow this example (code.tutsplus[dot]com/tutorials/android-sdk-creating-a-rotating-dialer--mobile-8868), I'm able to create a rotating ImageView using Matrix. The logic is as the following:


  • 位图加载到 ImageView

  • 使用 Matrix ImageView c $ c>触摸事件。

  • Load the Bitmap into the ImageView
  • Rotate the ImageView using a Matrix on touch event.

这将保证性能为位图每次车轮旋转时都不会绘制。但是我想只显示一半的轮子,以及 图像的内容strong> 是动态的,因此无法将图像切成两半然后按照上述步骤旋转。

This will guarantee performance as the Bitmap doesn't get drawn every time the wheel rotate. However I would like to display only half of the wheel, and the content of the image is dynamic so it won't be possible to cut the image in half and then rotate following the steps above.

我在 ClipDrawable ,但据我所知,它直接裁剪资源 Bitmap 所以为了让它工作,我会回到重绘位图方法(性能很糟糕)。

I've investigated in ClipDrawable, however from what I understand, it's directly clipping the resource Bitmap so in order to make it work, I would have to fall back to the redraw Bitmap method (which has terrible performance).

有没有办法限制 ImageView 的显示,所以只会显示一部分?

Is there a way to limit the display of the ImageView so only a portion of it will be displayed?

到目前为止我做了什么:

What I've done so far:

MyRotateWheel class:

public class MyRotateWheelView extends ImageView {
private MyMatrixHelper mMatrixHelper;
private int mWheelHeight, mWheelWidth;

public MyRotateWheelView(Context context, AttributeSet attrs){
    super(context, attrs);
    loadImage(R.drawable.my_rotate_wheel);
    initWheel();
}

public void loadImage(int id){
    mMatrixHelper = new MyMatrixHelper(getResources(), id);
}

public void initWheel(){
    setOnTouchListener(new MyOnTouchListener());
    //Since we only know after measuring the layout how much space our ImageView fills, we add an OnGlobalLayoutListener.
    //In the onGlobalLayout method, we can intercept the event that the layout has been drawn and query the size of our view.
    getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // method called more than once, but the values only need to be initialized one time
            if (mMatrixHelper.isNecessaryToScaleImage(getWidth(), getHeight())) {

                mWheelWidth = getWidth();
                mWheelHeight = getHeight();

                setImageBitmap(mMatrixHelper.getImageScaled());
                setImageMatrix(mMatrixHelper.getMatrix());
            }
        }
    });
}

/**
 * Simple implementation of an {@link View.OnTouchListener} for registering the mWheel's touch events.
 */
private class MyOnTouchListener implements View.OnTouchListener {

    private double startAngle;

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                startAngle = getAngle(event.getX(), event.getY());

                break;

            case MotionEvent.ACTION_MOVE:
                double currentAngle = getAngle(event.getX(), event.getY());
                double delta = startAngle - currentAngle;

                rotateDialer((float) delta); //rotate

                startAngle = currentAngle;
                break;

            case MotionEvent.ACTION_UP:

                break;
        }
        return true;
    }
}

/**
 * @return The angle of the unit circle with the image view's center
 */
private double getAngle(double xTouch, double yTouch) {
    double x = xTouch - (mWheelWidth / 2d);
    double y = mWheelHeight - yTouch - (mWheelHeight / 2d);

    switch (getQuadrant(x, y)) {
        case 1:
            return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
        case 2:
            return 180 - Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
        case 3:
            return 180 + (-1 * Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
        case 4:
            return 360 + Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
        default:
            return 0;
    }
}

/**
 * @return The selected quadrant.
 */
private static int getQuadrant(double x, double y) {
    if (x >= 0) {
        return y >= 0 ? 1 : 4;
    } else {
        return y >= 0 ? 2 : 3;
    }
}

/**
 * Rotate the mWheel.
 *
 * @param degrees The degrees, the mWheel should get rotated.
 */
private void rotateDialer(int i, float degrees) {
    mMatrixHelper.getMatrix().postRotate(degrees, mWheelWidth / 2, mWheelHeight / 2);
    setImageMatrix(mMatrixHelper.getMatrix());
}

}

MyMatrixHelper 类:

public class MyMatrixHelper {
private Bitmap imageOriginal, imageScaled;
private Matrix matrix;
private int mImageHeight, mImageWidth;

public MyMatrixHelper(Resources res, int id){
    // load the image only once
    if (imageOriginal == null) {
        imageOriginal = BitmapFactory.decodeResource(res, id);
    }

    // initialize the matrix only once
    if (matrix == null) {
        matrix = new Matrix();
    } else {
        // not needed, you can also post the matrix immediately to restore the old state
        matrix.reset();
    }
}

public boolean isNecessaryToScaleImage(int width, int height){
    if (mImageWidth == 0 || mImageHeight == 0) {
        mImageWidth = width;
        mImageHeight = height;

        // resize
        Matrix resize = new Matrix();
        resize.postScale((float) Math.min(mImageWidth, mImageHeight) / (float) imageOriginal.getWidth(), (float) Math.min(mImageWidth, mImageHeight) / (float) imageOriginal.getHeight());
        imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);

        // translate to the image view's center
        float translateX = mImageWidth / 2 - imageScaled.getWidth() / 2;
        float translateY = mImageHeight / 2 - imageScaled.getHeight() / 2;
        matrix.postTranslate(translateX, translateY);

        //imageOriginal.recycle();
        return true;
    }else{
        return false;
    }
}

public Bitmap getImageScaled() {
    return imageScaled;
}

public Matrix getMatrix() {
    return matrix;
}

在布局文件中:

<.MyRotateWheelView
...
android:scaleType="matrix"/>

UPADTE 1:

我已经部分解决了这个问题。我已经覆盖 onDraw 方法,使用更新的矩阵将图像绘制到 ImageView 触摸事件。这样可以保证性能,因为图像不会重新绘制,并且图像会在 ImageView 内进行适当的裁剪旋转。

I've partially solved this problem. I've overridden the onDraw method to draw the image into the ImageView using an updated Matrix on touch event. This guarantee performance as the image doesn't get redrawn and the image does rotate with proper cropping inside the ImageView.

然而,剩下的问题是图像当前围绕 ImageView 的中心旋转。为了使这个解决方案起作用,我需要找到一种方法来协调图像本身的中心。有什么建议吗?

However the remaining problem is the image is currently rotated around the center of the ImageView. In order for this solution to work I need to figure out a way to get the coordination of the center of the image itself. Any suggestion?

代码片段:

private class MyOnTouchListener implements View.OnTouchListener {

    private double startAngle;

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {

            ...

            case MotionEvent.ACTION_MOVE:
                double currentAngle = getAngle(event.getX(), event.getY());
                double delta = startAngle - currentAngle;

                updateMatrix((float)delta);
                invalidate();

                startAngle = currentAngle;
                break;

            ...
        }
        return true;
    }
}

private void updateMatrix(float delta){
    matrix.postRotate(delta, getWidth()/2, getHeight()/2); //need to find out the coordination of the center of the image
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawBitmap(originalImage, matrix, paint);
}

更新2:

我已经弄清楚如何计算图像中心的坐标。我现在假设图像比屏幕大,所以当每个案例都正确处理时,将发布完整的解决方案。

I've figured out how to calculate the coordinate of the image's center. I'm currently making an assumption that the image is bigger than the screen so full solution will be posted when every cases are properly handle.

不幸的是,我遇到了另一个问题。为了使我的解决方案正常工作,图像的底部必须与 ImageView 的底部对齐并正确居中,如图片A of:

Unfortunately, I encounter another problem. In order to make my solution works, the bottom of the image must be aligned with the bottom of the ImageView and properly centered, like in Picture A of this:

图片

然而,图像被绘制到 ImageView 中,如图片B

However, the image is drawn into the ImageView like in Picture B.

如果有人能告诉我正确居中并将图像底部与视图底部对齐的话,我将非常感激。

I would be very grateful if anyone can show me the way to properly center and align the bottom of the image with the bottom of the view.

推荐答案

亲爱的StackOverflower,

Dear beloved StackOverflower,

我已经解决了我的问题。它实际上非常简单。我错误地假设旋转的 Matrix 跟随屏幕协调,实际上它实际上遵循 View 协调。请参见下图:

I've solved my problem. It's actually very simple. I make the mistake of assuming the rotating Matrix follows the screen coordination when in fact it's actually follow the View coordination. Please see the picture below:

图片

然后解决方案非常简单:

Then the solution is very simple:

getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // method called more than once, but the values only need to be initialized one time
            if (mWheelHeight == 0 || mWheelWidth == 0) {
                mWheelHeight = getHeight();
                mWheelWidth = getWidth();

                // resize
                Matrix resize = new Matrix();
                resize.postScale((float)Math.min(mWheelWidth, mWheelHeight) / (float)imageOriginal.getWidth(), (float)Math.min(mWheelWidth, mWheelHeight) / (float)imageOriginal.getHeight());
                imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);
                setImageBitmap(imageScaled);

                // center the image on x axis and move it upward on y axis
                float translateX = mWheelWidth / 2 - imageScaled.getWidth() / 2;                    
                float translateY = -imageScaled.getHeight()/2; //modify this to your liking, I only want to show the bottom half of the image.
                matrix.postTranslate(translateX, translateY);
                setImageMatrix(matrix);
            }
        }
    });   

触摸事件仍然相同:

private class MyOnTouchListener implements View.OnTouchListener {

    private double startAngle;

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {

        ...

            case MotionEvent.ACTION_MOVE:
                double currentAngle = getAngle(event.getX(), event.getY());
                double delta = startAngle - currentAngle;

                updateMatrix((float)delta);
                invalidate();

                startAngle = currentAngle;
                break;

         ...
        }
        return true;
    }
}

然后更新枢轴坐标:

private void updateMatrix(float delta){
    matrix.postRotate(delta, getWidth()/2, 0f);
}

@Override
protected void onDraw(Canvas canvas) {
    canvas.drawBitmap(imageScaled, matrix, paint);
}

谢谢你的耐心。

改进:

我的第一个解决方案工作正常,如果你只想显示旋转轮的一半你的布局。但是,缺点是 ImageView 上的未使用空间阻止其他视图,如果你有一个复杂的布局,其他查看 ImageView 无法接收触摸事件。
使用这个改进的解决方案,您可以限制 ImageView 的大小,以便它不会阻止其他查看在下面的图片中。

My first solution work fine and all if you just want to display half of the rotating wheel your layout. However, the downside is unused space on your ImageView block other Views and if you has a complex layout, other View below this ImageView can't receive touch event. With this improved solution, you can limit the size of the ImageView so it wont block other View as in the Picture below.

图片

layout.xml

<.MyWheelView
    android:id="@+id/some_id"
    android:layout_width="match_parent"
    android:layout_height="100dp" //change to suit your need
    android:layout_centerInParent="true"
    android:src="@drawable/protractor_wheel"
    />

onGlobalLayout()方法中,修改如下所示:

In the onGlobalLayout() method, modify as the following:

 @Override
        public void onGlobalLayout() {
            // method called more than once, but the values only need to be initialized one time
            if (mWheelHeight == 0 || mWheelWidth == 0) {
                mWheelHeight = getHeight();
                mWheelWidth = getWidth();

                // resize
                Matrix resize = new Matrix();
                resize.postScale((float)Math.max(mWheelWidth, mWheelHeight) / (float)imageOriginal.getWidth(), (float)Math.max(mWheelWidth, mWheelHeight) / (float)imageOriginal.getHeight());
                imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);
                setImageBitmap(imageScaled);

                // center the image on x axis and move it upward on y axis
                float translateX = mWheelWidth / 2 - imageScaled.getWidth() / 2;
                float translateY = - 0.75f * imageScaled.getHeight(); //edit show how much of the image will get displayed (in my case I move 75% of my image upward)
                matrix.postTranslate(translateX, translateY);
                setImageMatrix(matrix);

                //calculate pivotY only once
                pivotY = 0.25f * imageScaled.getHeight() - (float)imageScaled.getHeight()/2;
            }
        }
    });

然后在 updateMatrix 中我们这样做:

private void updateMatrix(float delta){
    matrix.postRotate(delta, getWidth()/2, pivotY);
}

我希望我的解决方案对您有所帮助,感谢您的患者。

I hope my solution could be helpful to you, thanks for your patient.

这篇关于如何显示在Android中使用Matrix旋转的ImageView的一半?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆