基于Android 實(shí)現(xiàn)圖片平移、縮放、旋轉(zhuǎn)同時(shí)進(jìn)行
前言
之前因?yàn)轫?xiàng)目需求,其中使用到了圖片的單擊顯示取消,圖片平移縮放功能,昨天突然想再加上圖片的旋轉(zhuǎn)功能,在網(wǎng)上看了很多相關(guān)的例子,可是沒看到能同時(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)過程中的圖片縮放矩陣 */ 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)觸摸的靈敏度(可針對不同手機(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(); // 回收圖片所占的內(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)畫
- Android單點(diǎn)觸控實(shí)現(xiàn)圖片平移、縮放、旋轉(zhuǎn)功能
- Android實(shí)現(xiàn)手勢滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果(二)
- Android實(shí)現(xiàn)手勢滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果
- Android實(shí)現(xiàn)橡皮筋回彈和平移縮放效果
相關(guān)文章
Android 調(diào)用設(shè)備已有的相機(jī)應(yīng)用詳情
這篇文章主要介紹了Android 調(diào)用設(shè)備已有的相機(jī)應(yīng)用,如果我們只是需要讓用戶能夠拍攝照片,則可以直接請求已有相機(jī)應(yīng)用拍攝照片并將照片返回給我們,下面我們一起來看看這些功能,需要的朋友可以參考一下2021-10-10實(shí)例講解Android中SQLiteDatabase使用方法
這篇文章主要以一個(gè)簡單的實(shí)例為大家詳細(xì)講解Android中SQLiteDatabase使用方法,感興趣的小伙伴們可以參考一下2016-05-05Android?Flutter實(shí)現(xiàn)"斑馬紋"背景的示例代碼
本文將通過實(shí)現(xiàn)一個(gè)canvas繪制斑馬紋類。使用Stack布局,將斑馬紋放在下方作為背景板,需要展示的內(nèi)容在上方。從而實(shí)現(xiàn)?“斑馬紋”背景,感興趣的可以了解一下2022-06-06一款不錯(cuò)的android6.0、7.0權(quán)限管理器推薦
下面小編就為大家分享一篇一款不錯(cuò)的android6.0、7.0權(quán)限管理器推薦,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01Android自定義ViewGroup實(shí)現(xiàn)絢麗的仿支付寶咻一咻雷達(dá)脈沖效果
這篇文章主要介紹了Android自定義ViewGroup實(shí)現(xiàn)絢麗的仿支付寶咻一咻雷達(dá)脈沖效果的相關(guān)資料,需要的朋友可以參考下2016-10-10Android 仿微信朋友圈點(diǎn)贊和評論彈出框功能
這篇文章主要介紹了Android 仿微信朋友圈點(diǎn)贊和評論彈出框功能的相關(guān)資料,非常不錯(cuò),具有參考解決價(jià)值,需要的朋友可以參考下2016-11-11Android Studio配置(Android Studio4.1為例)
這篇文章主要介紹了Android Studio配置(Android Studio4.1為例),文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10Flutter?Widget之FutureBuilder使用示例詳解
這篇文章主要為大家介紹了Flutter?Widget之FutureBuilder使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android SQLite數(shù)據(jù)庫連接實(shí)現(xiàn)登錄功能
這篇文章主要為大家詳細(xì)介紹了Android SQLite數(shù)據(jù)庫連接實(shí)現(xiàn)登錄功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10