基于Android 實(shí)現(xiàn)圖片平移、縮放、旋轉(zhuǎn)同時(shí)進(jìn)行
前言
之前因?yàn)轫?xiàng)目需求,其中使用到了圖片的單擊顯示取消,圖片平移縮放功能,昨天突然想再加上圖片的旋轉(zhuǎn)功能,在網(wǎng)上看了很多相關(guān)的例子,可是沒(méi)看到能同時(shí)實(shí)現(xiàn)我想要的功能的。
需求:
(1)圖片平移、縮放、旋轉(zhuǎn)等一系列操作后,圖片需要自動(dòng)居中顯示。
(2)圖片旋轉(zhuǎn)后選自動(dòng)水平顯示或者垂直顯示
(3)圖片在放大縮小的同時(shí)都能旋轉(zhuǎn)
Demo實(shí)現(xiàn)部分效果截圖

Demo主要代碼
Java
MainActivity.java
package com.practice.noyet.rotatezoomimageview;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import com.ypy.eventbus.EventBus;
import java.io.File;
import java.math.BigDecimal;
/**
* package: com.practice.noyet.rotatezoomimageview
* Created by noyet on 2015/11/11.
*/
public class MainActivity extends Activity implements View.OnTouchListener {
private ImageView mImageView;
private PointF point0 = new PointF();
private PointF pointM = new PointF();
private final int NONE = 0;
/**
* 平移
*/
private final int DRAG = 1;
/**
* 旋轉(zhuǎn)、縮放
*/
private final int ZOOM = 2;
/**
* 設(shè)定事件模式
*/
private int mode = NONE;
/**
* 圖片縮放矩陣
*/
private Matrix matrix = new Matrix();
/**
* 保存觸摸前的圖片縮放矩陣
*/
private Matrix savedMatrix = new Matrix();
/**
* 保存觸點(diǎn)移動(dòng)過(guò)程中的圖片縮放矩陣
*/
private Matrix matrix1 = new Matrix();
/**
* 屏幕高度
*/
private int displayHeight;
/**
* 屏幕寬度
*/
private int displayWidth;
/**
* 最小縮放比例
*/
protected float minScale = 1f;
/**
* 最大縮放比例
*/
protected float maxScale = 3f;
/**
* 當(dāng)前縮放比例
*/
protected float currentScale = 1f;
/**
* 多點(diǎn)觸摸2個(gè)觸摸點(diǎn)間的起始距離
*/
private float oldDist;
/**
* 多點(diǎn)觸摸時(shí)圖片的起始角度
*/
private float oldRotation = 0;
/**
* 旋轉(zhuǎn)角度
*/
protected float rotation = 0;
/**
* 圖片初始寬度
*/
private int imgWidth;
/**
* 圖片初始高度
*/
private int imgHeight;
/**
* 設(shè)置單點(diǎn)觸摸退出圖片顯示時(shí),單點(diǎn)觸摸的靈敏度(可針對(duì)不同手機(jī)單獨(dú)設(shè)置)
*/
protected final int MOVE_MAX = 2;
/**
* 單點(diǎn)觸摸時(shí)手指觸發(fā)的‘MotionEvent.ACTION_MOVE'次數(shù)
*/
private int fingerNumMove = 0;
private Bitmap bm;
/**
* 保存matrix縮放比例
*/
private float matrixScale= 1;
/*private String imagePath;*/
/**
* 顯示被存入緩存中的網(wǎng)絡(luò)圖片
*
* @param event 觀察者事件
*/
public void onEventMainThread(CustomEventBus event) {
if (event == null) {
return;
}
if (event.type == CustomEventBus.EventType.SHOW_PICTURE) {
bm = (Bitmap) event.obj;
showImage();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
}
public void initData() {
// TODO Auto-generated method stub
bm = BitmapFactory.decodeResource(getResources(), R.drawable.alipay);
DisplayMetrics dm = getResources().getDisplayMetrics();
displayWidth = dm.widthPixels;
displayHeight = dm.heightPixels;
mImageView = (ImageView) findViewById(R.id.image_view);
mImageView.setOnTouchListener(this);
showImage();
//顯示網(wǎng)絡(luò)圖片時(shí)使用
/*File file = MainApplication.getInstance().getImageCache()
.getDiskCache().get(圖片路徑);
if (!file.exists()) {
Toast.makeText(this, "圖片路徑錯(cuò)誤", Toast.LENGTH_SHORT).show();
} else {
new MyTask().execute(file);
}*/
}
@Override
public boolean onTouch(View view, MotionEvent event) {
ImageView imageView = (ImageView) view;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
savedMatrix.set(matrix);
point0.set(event.getX(), event.getY());
mode = DRAG;
System.out.println("MotionEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_POINTER_DOWN:
oldDist = spacing(event);
oldRotation = rotation(event);
savedMatrix.set(matrix);
setMidPoint(pointM, event);
mode = ZOOM;
System.out.println("MotionEvent--ACTION_POINTER_DOWN---" + oldRotation);
break;
case MotionEvent.ACTION_UP:
if (mode == DRAG & (fingerNumMove this.finish();
}
checkView();
centerAndRotate();
imageView.setImageMatrix(matrix);
System.out.println("MotionEvent--ACTION_UP");
fingerNumMove = 0;
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
System.out.println("MotionEvent--ACTION_POINTER_UP");
break;
case MotionEvent.ACTION_MOVE:
operateMove(event);
imageView.setImageMatrix(matrix1);
fingerNumMove++;
System.out.println("MotionEvent--ACTION_MOVE");
break;
}
return true;
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (bm != null & !bm.isRecycled()) {
bm.recycle(); // 回收?qǐng)D片所占的內(nèi)存
System.gc(); // 提醒系統(tǒng)及時(shí)回收
}
}
/**
* 顯示圖片
*/
private void showImage() {
imgWidth = bm.getWidth();
imgHeight = bm.getHeight();
mImageView.setImageBitmap(bm);
matrix.setScale(1, 1);
centerAndRotate();
mImageView.setImageMatrix(matrix);
}
/**
* 觸點(diǎn)移動(dòng)時(shí)的操作
*
* @param event 觸摸事件
*/
private void operateMove(MotionEvent event) {
matrix1.set(savedMatrix);
switch (mode) {
case DRAG:
matrix1.postTranslate(event.getX() - point0.x, event.getY() - point0.y);
break;
case ZOOM:
rotation = rotation(event) - oldRotation;
float newDist = spacing(event);
float scale = newDist / oldDist;
currentScale = (scale > 3.5f) ? 3.5f : scale;
System.out.println("縮放倍數(shù)---" + currentScale);
System.out.println("旋轉(zhuǎn)角度---" + rotation);
/** 縮放 */
matrix1.postScale(currentScale, currentScale, pointM.x, pointM.y);
/** 旋轉(zhuǎn) */
matrix1.postRotate(rotation, displayWidth / 2, displayHeight / 2);
break;
}
}
/**
* 兩個(gè)觸點(diǎn)的距離
*
* @param event 觸摸事件
* @return float
*/
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
/**
* 獲取旋轉(zhuǎn)角度
*/
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
/**
* 兩個(gè)觸點(diǎn)的中間坐標(biāo)
*
* @param pointM 中間坐標(biāo)
* @param event 觸摸事件
*/
private void setMidPoint(PointF pointM, MotionEvent event) {
float x = event.getX(0) + event.getY(1);
float y = event.getY(0) + event.getY(1);
pointM.set(x / 2, y / 2);
}
/**
* 檢查約束條件(縮放倍數(shù))
*/
private void checkView() {
if (currentScale > 1) {
if (currentScale * matrixScale > maxScale) {
matrix.postScale(maxScale / matrixScale, maxScale / matrixScale, pointM.x, pointM.y);
matrixScale = maxScale;
} else {
matrix.postScale(currentScale, currentScale, pointM.x, pointM.y);
matrixScale *= currentScale;
}
} else {
if (currentScale * matrixScale else {
matrix.postScale(currentScale, currentScale, pointM.x, pointM.y);
matrixScale *= currentScale;
}
}
}
/**
* 圖片居中顯示、判斷旋轉(zhuǎn)角度 小于(90 * x + 45)度圖片旋轉(zhuǎn)(90 * x)度 大于則旋轉(zhuǎn)(90 * (x+1))
*/
private void centerAndRotate() {
RectF rect = new RectF(0, 0, imgWidth, imgHeight);
matrix.mapRect(rect);
float width = rect.width();
float height = rect.height();
float dx = 0;
float dy = 0;
if (width 2 - width / 2 - rect.left;
} else if (rect.left > 0) {
dx = -rect.left;
} else if (rect.right if (height 2 - height / 2 - rect.top;
} else if (rect.top > 0) {
dy = -rect.top;
} else if (rect.bottom if (rotation != 0) {
int rotationNum = (int) (rotation / 90);
float rotationAvai = new BigDecimal(rotation % 90).setScale(1, BigDecimal.ROUND_HALF_UP).floatValue();
float realRotation = 0;
if (rotation > 0) {
realRotation = rotationAvai > 45 ? (rotationNum + 1) * 90 : rotationNum * 90;
} else if (rotation 0) {
realRotation = rotationAvai 45 ? (rotationNum - 1) * 90 : rotationNum * 90;
}
System.out.println("realRotation: " + realRotation);
matrix.postRotate(realRotation, displayWidth / 2, displayHeight / 2);
rotation = 0;
}
}
/**
* 顯示網(wǎng)絡(luò)圖片時(shí)使用
*/
private class MyTask extends AsyncTaskFile, File, Bitmap> {
Bitmap bitmap;
String path;
int scale = 1;
long size;
@Override
protected Bitmap doInBackground(File... params) {
// TODO Auto-generated method stub
try {
size = params[0].length();
path = params[0].getAbsolutePath();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
scale = calculateInSampleSize(options, displayWidth,
displayHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = scale;
bitmap = BitmapFactory.decodeFile(path, options);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
// TODO Auto-generated method stub
EventBus.getDefault().post(
new CustomEventBus(CustomEventBus.EventType.SHOW_PICTURE, result));
}
/**
* 獲取圖片縮放比例
*
* @param paramOptions Options
* @param paramInt1 寬
* @param paramInt2 高
* @return int
*/
private int calculateInSampleSize(BitmapFactory.Options paramOptions,
int paramInt1, int paramInt2) {
int i = paramOptions.outHeight;
int j = paramOptions.outWidth;
int k = 1;
if ((i > paramInt2) || (j > paramInt1)) {
int m = Math.round(i / paramInt2);
int n = Math.round(j / paramInt1);
k = m return k;
}
}
}
CustomEventBus.java
package com.practice.noyet.rotatezoomimageview;
/**
* package: com.practice.noyet.rotatezoomimageview
* Created by noyet on 2015/11/11.
*/
public class CustomEventBus {
public EventType type;
public Object obj;
public CustomEventBus(EventType type, Object obj) {
this.type = type;
this.obj = obj;
}
enum EventType {
SHOW_PICTURE
}
}
- Android自定義View實(shí)現(xiàn)豎向滑動(dòng)回彈效果
- android實(shí)現(xiàn)可上下回彈的scrollview
- Android實(shí)現(xiàn)回彈ScrollView的原理
- Android自定義實(shí)現(xiàn)可回彈的ScollView
- Android自定義ScrollView實(shí)現(xiàn)阻尼回彈
- Android旋轉(zhuǎn)、平移、縮放和透明度漸變的補(bǔ)間動(dòng)畫(huà)
- Android單點(diǎn)觸控實(shí)現(xiàn)圖片平移、縮放、旋轉(zhuǎn)功能
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果(二)
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果
- Android實(shí)現(xiàn)橡皮筋回彈和平移縮放效果
相關(guān)文章
[Alibaba-ARouter]淺談簡(jiǎn)單好用的Android頁(yè)面路由框架
這篇文章主要介紹了[Alibaba-ARouter]淺談簡(jiǎn)單好用的Android頁(yè)面路由框架,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
Android 后臺(tái)調(diào)度任務(wù)與省電詳解
本文主要介紹 Android 后臺(tái)調(diào)度任務(wù)與省電,這里整理了詳細(xì)的知識(shí)資料供大家學(xué)習(xí)參考,希望能幫助有需要的小伙伴2016-08-08
Android開(kāi)發(fā)返回鍵明暗點(diǎn)擊效果的實(shí)例代碼
這篇文章主要介紹了Android開(kāi)發(fā)返回鍵明暗點(diǎn)擊效果的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
詳解Android中OkHttp3的例子和在子線程更新UI線程的方法
本篇文章主要介紹了詳解Android中OkHttp3的例子和在子線程更新UI線程的方法 ,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05
Android中g(shù)son、jsonobject解析JSON的方法詳解
JSON即JavaScript Object Natation, 它是一種輕量級(jí)的數(shù)據(jù)交換格式, 與XML一樣, 是廣泛被采用的客戶端和服務(wù)端交互的解決方案.接下來(lái)由腳本之家小編給大家介紹Android中g(shù)son、jsonobject解析JSON的方法,感興趣的朋友一起學(xué)習(xí)吧2016-02-02
Android使用AudioRecord實(shí)現(xiàn)錄音功能
這篇文章主要為大家詳細(xì)介紹了Android使用AudioRecord實(shí)現(xiàn)錄音功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08

