【Android】雙指旋轉手勢

一,概述

本文參考android.view.ScaleGestureDetector,對雙指旋轉手勢做了一層封裝,采用了向量計算法簡單實現,筆者在此分享下。

二,實例

如下,使用RotateGestureDetector即可委托,實現旋轉手勢的簡單封裝,在對應Callback獲取到旋轉值設置到View即可。

public class RectView extends FrameLayout {private static final String TAG = "RectView";private View mRotateView;private final ScaleGestureDetector scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {@Overridepublic boolean onScale(@NonNull ScaleGestureDetector detector) {Log.d(TAG, "onScale() called with: detector = [" + detector.getScaleFactor() + "]");mRotateView.setScaleX(detector.getScaleFactor());mRotateView.setScaleY(detector.getScaleFactor());return true;}@Overridepublic boolean onScaleBegin(@NonNull ScaleGestureDetector detector) {return super.onScaleBegin(detector);}@Overridepublic void onScaleEnd(@NonNull ScaleGestureDetector detector) {super.onScaleEnd(detector);}});private final RotateGestureDetector rotateGestureDetector = new RotateGestureDetector(new RotateGestureDetector.Listener() {@Overridepublic void onRotateStart(RotateGestureDetector detector) {}@Overridepublic void onRotating(RotateGestureDetector detector) {mRotateView.setRotation((float) detector.eulerAngle);}@Overridepublic void onRotateEnd(RotateGestureDetector detector) {}});public RectView(Context context) {super(context);mRotateView = new View(context);mRotateView.setBackgroundColor(Color.GRAY);this.addView(mRotateView, new FrameLayout.LayoutParams(100, 100, Gravity.CENTER));}public RectView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}//    @SuppressLint("ClickableViewAccessibility")
//    @Override
//    public boolean onTouchEvent(MotionEvent event) {
//        rotateGestureDetector.onTouchEvent(event);
//        return true;
//    }@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {rotateGestureDetector.onTouchEvent(ev);scaleGestureDetector.onTouchEvent(ev);return true;}
}

三,實現

Rotate具體實現如下,僅供參考。

import android.view.MotionEvent;import androidx.annotation.NonNull;/*** @author :zhong.jw* @date :Created in 2023/2/23 13:39* 旋轉手勢相關:采用向量法計算角度*/
public final class RotateGestureDetector {private static final double DEFAULT_LIMIT_START = 3f;@NonNullprivate final Listener listener;/*** 是否旋轉中*/public boolean isRotating = false;/*** 旋轉軸點x*/public int focusX;/*** 旋轉軸點y*/public int focusY;/*** 歐拉角,范圍[-180~180]*/public double eulerAngle = 0;/*** 弧度*/public double radian = 0;/*** 開始旋轉的初始向量x值*/public double x1 = 0;/*** 開始旋轉的初始向量y值*/public double y1 = 0;/*** 開始旋轉的初始向量斜率*/public double k1 = 0;public RotateGestureDetector(@NonNull Listener listener) {this.listener = listener;}public boolean onTouchEvent(MotionEvent event) {int action = event.getActionMasked();int pointCount = event.getPointerCount();if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && (pointCount == 2) && !isRotating) {double e1x = event.getX(0);double e2x = event.getX(1);double e1y = event.getY(0);double e2y = event.getY(1);focusX = (int) (e1x + e2x) / 2;focusY = (int) ((e1y + e2y) / 2);x1 = e2x - e1x;y1 = e2y - e1y;k1 = y1 / x1;return true;}if (action == MotionEvent.ACTION_MOVE && pointCount == 2) {double e1x = event.getX(0);double e2x = event.getX(1);double e1y = event.getY(0);double e2y = event.getY(1);double x2 = e2x - e1x;double y2 = e2y - e1y;//angle = arccos(ab/(|a||b|))radian = Math.acos((x1 * x2 + y1 * y2) / (Math.sqrt(Math.pow(x1, 2) + Math.pow(y1, 2)) * Math.sqrt(Math.pow(x2, 2) + Math.pow(y2, 2))));// y = k1*x2 > y2 來判斷是否屬于外角eulerAngle = (radian / Math.PI * 180) * (k1 * x2 > y2 ? -1 : 1);if (isRotating) {listener.onRotating(this);}if (Math.abs(eulerAngle) >= DEFAULT_LIMIT_START) {isRotating = true;listener.onRotateStart(this);}return true;}if ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) && isRotating) {isRotating = false;listener.onRotateEnd(this);}return true;}public interface Listener {/*** @param detector:旋轉信息*/void onRotateStart(RotateGestureDetector detector);/*** @param detector:旋轉信息*/void onRotating(RotateGestureDetector detector);/*** @param detector:旋轉信息*/void onRotateEnd(RotateGestureDetector detector);}}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/83856.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/83856.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/83856.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

B站的視頻怎么下載下來——Best Video下載器

B站(嗶哩嗶哩)作為國內最受歡迎的視頻平臺之一,聚集了無數優質內容:動漫番劇、游戲實況、學習課程、紀錄片、Vlog、鬼畜剪輯……總有那么些視頻讓人想反復觀看、離線觀看,甚至剪輯創作。 但你是否遇到過這樣的煩惱&am…

基于SFC的windows系統損壞修復程序

前言 在平時使用Windows操作系統時會遇到很多因為系統文件損壞而出現的錯誤 例如:系統應用無法打開 系統窗口(例如開始菜單)無法使用 電腦藍屏或者卡死 是如果想要修復很多人只能想到重裝系統。但其實Windows有一個內置的系統文件檢查器可以修復此類錯誤。 原理 SFC命令…

智紳科技 —— 智慧養老 + 數字健康,構筑銀發時代安全防護網

在老齡化率突破 21.3% 的當下,智紳科技以 "科技適老" 為核心理念,構建 "監測 - 預警 - 干預 - 照護" 的智慧養老閉環。 其自主研發的七彩喜智慧康養平臺,通過物聯網、AI 和邊緣計算技術,實現對老年人健康與安…

用函數實現模塊化程序設計(適合考研、專升本)

函數 定義:本質上是一段可以被連續調用、功能相對獨立的程序段 c語言是通過“函數”實現模塊化的。根據分類標準不同函數分為以下幾類。 用戶角度:庫函數、自定義函數 函數形式:有參函數、無參函數 作用域:外部函數、內部函數 …

OpenCV 滑動條調整圖像亮度

一、知識點 1、int createTrackbar(const String & trackbarname, const String & winname, int * value, int count, TrackbarCallback onChange 0, void * userdata 0); (1)、創建一個滑動條并將其附在指定窗口上。 (2)、參數說明: trackbarname: 創建的…

vcs仿真產生fsdb波形的兩種方式

目錄 方法一: 使用verilog自帶的系統函數 方法二: 使用UCLI command 2.1 需要了解什么是vcs的ucli,怎么使用ucli? 2.2 使用ucli dump波形的方法 使用vcs仿真產生fsdb波形有兩種方式,本文參考《vcs user guide 20…

【前端】每日一道面試題6:解釋Promise.any和Promise.allSettled的使用場景及區別。

Promise.any() 和 Promise.allSettled() 是 JavaScript 中用于處理異步操作的兩種不同策略的 Promise 組合器,它們的核心區別在于邏輯目標與結果處理方式: 1. Promise.any() 使用場景: 需要獲取 首個成功結果(類似競速成功優先&…

數據鏈路層__

文章目錄 數據鏈路層基本概念(1)鏈路管理:面向連接的服務(2)幀同步:成幀1、字符計數法2、字符填充法(帶填充的首尾界符法)3、帶填充的首位標志法4、物理層編碼違例法 (3&…

coze智能體后端接入問題:

是否一定要按照coze官方API文檔格式調用? 不一定:以下面代碼為例(給了注釋) app.route(/compare_models, methods[POST]) def compare_models():print("收到 compare_models 請求!") #begin-這一部分代碼作用:從前端接…

如何輕松、安全地管理密碼(新手指南)

很多人會為所有賬戶使用相同、易記的密碼,而且常常多年不換。雖然這樣方便記憶,但安全性非常低。 您可能聽說過一些大型網站的信息泄露事件,同樣的風險也可能存在于您的WordPress網站中。如果有不法分子獲取了訪問權限,您的網站和…

寶塔think PHP8 安裝使用FFmpeg 視頻上傳

寶塔think PHP8 安裝使用FFmpeg 一、 安裝think PHP8二、安裝 FFmpeg1,登錄到寶塔面板。2,進入“軟件商店”。3,搜索“FFmpeg”。4,選擇版本點擊安裝。5,檢查 FFmpeg 是否安裝成功6, 在 ThinkPHP 8 中使用 …

Android 輕松實現 增強版靈活的 滑動式表格視圖

表格視圖組件,支持: 1. 無標題模式:只有數據行也可以正常滑動 2. 兩種滑動模式:固定第一列 或 全部滑動 3. 全面的樣式自定義能力 4. 智能列寬計算 1. 無標題模式支持 設置無標題:調用 setHeaderData(null) 或 …

【Python進階】元類編程

目錄 🌟 前言🏗? 技術背景與價值🩹 當前技術痛點🛠? 解決方案概述👥 目標讀者說明 🧠 一、技術原理剖析📊 核心概念圖解💡 核心作用講解🔧 關鍵技術模塊說明?? 技術選…

DeepSeek模型性能優化:從推理加速到資源調度的全棧實踐

引言 在生產環境中部署DeepSeek模型時,性能優化直接關系到服務質量和運營成本。本文將深入探討從芯片級優化到分布式調度的全棧性能提升方案,涵蓋計算圖優化、內存管理、批處理策略等關鍵技術,并分享在千萬級QPS場景下的實戰經驗,幫助工程團隊突破性能瓶頸,實現成本與效能…

Ctrl+R 運行xxx.exe,發現有如下問題.

CtrlR 運行xxx.exe,發現有如下問題. (1)找不到Qt5Core.all,Qt5Cored.dll,Qt5Gui.dll,Qt5Guid.dll,Qt5Widgets.all,Qt5Widgetsd.dll? (2)之后找不到libwinpthread-1.dll 從這個目錄拷貝相應的庫到運行xx.exe目錄下 方法二:將庫路徑添加到系統PATH環境變量里: 在Path中添加路…

硅基計劃2.0 學習總結 陸 抽象類與接口

文章目錄 一、抽象類1. 定義2. 示例代碼3. 特性 二、接口初識1. 定義2. 命名與語法3. 示例代碼4. 常見特性5. 多接口實現6. 接口的繼承 三、Object類初識1. equals方法2. hascode方法 一、抽象類 1. 定義 請你假設這樣一個場景,我們定義一個人的類,這個…

Linux命令基礎(2)

su和exit命令 可以通過su命令切換到root賬戶 語法:su [-] 用戶名 -符號是可選的,表示是否在切換用戶后加載環境變量,建議帶上 參數:用戶名,表示要切換的用戶,用戶名可以省略,省略表示切換到ro…

C++算法訓練營 Day10 棧與隊列(1)

1.用棧實現隊列 LeetCode:232.用棧實現隊列 請你僅使用兩個棧實現先入先出隊列。隊列應當支持一般隊列支持的所有操作(push、pop、peek、empty): 實現 MyQueue 類: void push(int x)將元素x推到隊列的末尾 int pop(…

設計模式域——軟件設計模式全集

摘要 軟件設計模式是軟件工程領域中經過驗證的、可復用的解決方案,旨在解決常見的軟件設計問題。它們是軟件開發經驗的總結,能夠幫助開發人員在設計階段快速找到合適的解決方案,提高代碼的可維護性、可擴展性和可復用性。設計模式主要分為三…

【QT】自定義QWidget標題欄,可拖拽(拖拽時窗體變為normal大小),可最小/大化、關閉(圖文詳情)

目錄 0.背景 1.詳細實現 思路簡介 .h文件 .cpp文件 0.背景 Qt Linux;項目遇到問題,解決后特此記錄 項目需要,個性化的標題欄(是個widget),在傳統的三個按鈕(最大化、最小化、關閉&#xf…