如何显示在Android中使用Matrix旋转的ImageView的一半? [英] How to show half of the ImageView which is rotated using Matrix in Android?
问题描述
亲爱的朋友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 theImageView
- Rotate the
ImageView
using aMatrix
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屋!