縮放手勢 ScaleGestureDetector 源碼解析,這一篇就夠了

其實在我們日常的編程中,對于縮放手勢的使用并不是很經常,這一手勢主要是用在圖片瀏覽方面,比如下方例子。但是(敲重點),作為 Android 入門的基礎來說,學習 ScaleGestureDetector 的使用,算是不得不過的一道坎,好在 ScaleGestureDetector 使用起來非常簡單,就是源碼分析上得花些功夫。

本文首先將簡單的介紹下 ScaleGestureDetector 的使用,在重點給大家分析下源碼(由于源碼方面是我自己的理解,可能有偏差,希望各位大佬能在評論區指出,萬分感謝~)

16b996688722ec94?w=1280&h=904&f=jpeg&s=235091


ScaleGestureDetector 使用

ScaleGestureDetector 包括一個監聽器,以及它所有方法的空實現:

名稱用途
ScaleGestureDetector縮放手勢的監聽器
SimpleOnScaleGestureListener該監聽器的空實現,在其中重寫方法

ScaleGestureDetector 方法

名稱用途
onScaleBegin當 >= 2 個手指碰觸屏幕時調用,若返回 false 則忽略改事件調用
onScale滑動(縮放)過程中調用,若成功處理,則用戶返回 true,監聽器繼續記錄下一個縮放等動作,若為 false 表明數據未處理,則監聽器繼續積累
onScaleEnd全部手指離開屏幕,結束監聽

通常情況下,手勢監聽會結合自定義 View 來講,這里我給出一個最簡單的使用,具體的使用實例,以后再結合自定義 View 講講。

    private void iniScaleGestureListener(){mListener = new ScaleGestureDetector.SimpleOnScaleGestureListener(){@Overridepublic boolean onScaleBegin(ScaleGestureDetector detector) {return super.onScaleBegin(detector);}@Overridepublic boolean onScale(ScaleGestureDetector detector) {MyLog.d("X:" + detector.getFocusX());MyLog.d("Y:" + detector.getFocusY());MyLog.d("scale:" + detector.getScaleFactor());return super.onScale(detector);}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {super.onScaleEnd(detector);}};detector = new ScaleGestureDetector(getContext(), mListener);}@Overridepublic boolean onTouchEvent(MotionEvent event) {detector.onTouchEvent(event);return true;}

ScaleGestureDetector 的使用

ScaleGestureDetector 在具體項目的使用有點復雜,我打算過段時間結合自定義 View 寫一篇用來總結,所以這篇我們就先了解下 ScaleGestureDetector 的基本使用。


ScaleGestureDetector 源碼分析

好了,現在我們進入本章重點,ScaleGestureDetector 源碼分析,敲黑板敲黑板。首先,我們打開 ScaleGestureDetector 的源碼可以看到,幾乎所有的代碼都集中在了 onTouchEvent 這個方法上,所以在這里,我就主要給大家介紹這個方法的實現。

第一部分:前期準備

        if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}mCurrTime = event.getEventTime();final int action = event.getActionMasked();// Forward the event to check for double tap gestureif (mQuickScaleEnabled) {mGestureDetector.onTouchEvent(event);}final int count = event.getPointerCount();final boolean isStylusButtonDown =(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

mInputEventConsistencyVerifier

  • 輸入事件一致性驗證器 @有道
  • 根據名字以及前面的定義
  • 我們可以猜測這個對象應該是手勢監聽 Event 是否注冊(連接到硬件)
  • 所以,如果他為空,那么我們在這里調用 onTouchEvent 進行注冊
        if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}

mCurrTime

  • 獲得事件發生時的時間
        mCurrTime = event.getEventTime();

action

  • 獲得事件類型
        final int action = event.getActionMasked();

mQuickScaleEnabled

  • Forward the event to check for double tap gesture
  • @有道 轉發事件以檢查雙擊手勢
  • 首先是 mQuickScaleEnabled 這個對象
  • 翻譯過來是: @有道 啟用快速擴展
  • 作用大概就是調用雙擊監聽事件,比如雙擊最大化
        if (mQuickScaleEnabled) {mGestureDetector.onTouchEvent(event);}

count

  • 獲得屏幕上手指的數目
        final int count = event.getPointerCount();

isStylusButtonDown

這個主要是由于判斷手寫筆是否按下
由于我們很少處理手寫筆,所以這里不做過多說明

        final boolean isStylusButtonDown =(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

## 第二部分:處理與手勢變化

用戶的縮放手勢不總是一定的,就是說對于用戶而言,隨時可能有手指碰觸或離開屏幕,這就使得縮放中心的(焦點)隨時可能發生變化,這部分主要是用來處理這一變化,并做出響應。

        final boolean anchoredScaleCancelled =mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;final boolean streamComplete = action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;// 如果發生了上面這種小動作,或者說有一手指離開了屏幕,進行調用if (action == MotionEvent.ACTION_DOWN || streamComplete) {// Reset any scale in progress with the listener.// If it's an ACTION_DOWN we're beginning a new event stream.// This means the app probably didn't give us all the events. Shame on it.if (mInProgress) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;} else if (inAnchoredScaleMode() && streamComplete) {mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}if (streamComplete) {return true;}}

### anchoredScaleCancelled

  • @Google 錨定規模取消
  • 我的理解是:用于判斷滑動事件是否被取消
        final boolean anchoredScaleCancelled =mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;

streamComplete

  • @Google Translate: 流完成
  • 我的理解是,這個布爾變量用于標記
  • 當前動作是否完成
  • 我這里說的動作有兩種
  • 這里指的是:在大動作如三指觸屏放大過程中,又一個手指離開了屏幕這種
  • 在大動作三指觸屏中發生的一個小動作,離開一指
        final boolean streamComplete = action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;

action == MotionEvent.ACTION_DOWN || streamComplete

  • 如果發生了上面這種小動作,或者說有一手指離開了屏幕,就進行調用
if (action == MotionEvent.ACTION_DOWN || streamComplete) {...}

if (mInProgress)

  • @google Translate:重置偵聽器正在進行的任何縮放。
  • 如果是ACTION_DOWN,我們開始一個新的事件流。
  • 這意味著應用程序可能沒有給我們所有的事件。很遺憾。
  • 首先判斷該進程(從第一個手指碰上屏幕,到最后一個手指離開屏幕為止)是否結束
  • 如果仍在運行中,這調用回調方法:onScaleEnd 使其結束
            if (mInProgress) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}

else if (inAnchoredScaleMode() && streamComplete)

  • 如果當前進程已經結束
  • 判斷 mAnchoredScaleMode 是否為 ANCHORED_SCALE_MODE_STYLUS 狀態
  • 同時判斷操作流 streamComplete 是否完成
  • 都符合的情況下結束這一手勢變化
            else if (inAnchoredScaleMode() && streamComplete) {mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}

if (streamComplete)

  • 結束本次 onTouchEvent 方法的調用,等待下一次調用發生
            if (streamComplete) {return true;}

總結: 可以看到,當觸發 down 或者觸發 up,cancel 時,如果之前處于縮放計算的狀態,會將其狀態重置, 并調用 onScaleEnd 方法。


進入錨定比例模式

  • 當判斷用戶動作,如果為雙擊這類點擊事件,進入該模式
  • 與正常縮放區分。這個模式功能一般是:雙擊最大化和最小化
        if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()&& !streamComplete && isStylusButtonDown) {// Start of a button scale gesturemAnchoredScaleStartX = event.getX();mAnchoredScaleStartY = event.getY();mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;mInitialSpan = 0;}

mAnchoredScaleStartX & mAnchoredScaleStartY

  • 后文中將用于重新計算焦點
            mAnchoredScaleStartX = event.getX();mAnchoredScaleStartY = event.getY();

mAnchoredScaleMode

  • 賦值之后,再次調用 inAnchoredScaleMode() 方法,返回值變為 true
            mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;

計算縮放中心

        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_POINTER_UP ||action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;final int skipIndex = pointerUp ? event.getActionIndex() : -1;// Determine focal pointfloat sumX = 0, sumY = 0;final int div = pointerUp ? count - 1 : count;final float focusX;final float focusY;if (inAnchoredScaleMode()) {// In anchored scale mode, the focal pt is always where the double tap// or button down gesture startedfocusX = mAnchoredScaleStartX;focusY = mAnchoredScaleStartY;if (event.getY() < focusY) {mEventBeforeOrAboveStartingGestureEvent = true;} else {mEventBeforeOrAboveStartingGestureEvent = false;}} else {for (int i = 0; i < count; i++) {if (skipIndex == i) continue;sumX += event.getX(i);sumY += event.getY(i);}focusX = sumX / div;focusY = sumY / div;}

configChanged

  • 布爾類型量,標志著一個操作的完成或者結束(手指離開,手指按下)
        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_POINTER_UP ||action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;

pointerUp

  • 布爾類型量,用于判斷當前動作,是否為手指離開(抬起動作)
        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;

skipIndex

  • 標記量,在是手指離開的情況下,標記離開手指
  • 在后面計算新的焦點代碼中,跳過該手指的標記點坐標,進行計算
        final int skipIndex = pointerUp ? event.getActionIndex() : -1;

初始化計算所需臨時變量

        // Determine focal pointfloat sumX = 0, sumY = 0;// 如果是抬起手指,則當前手指數減1,否則不變final int div = pointerUp ? count - 1 : count;final float focusX;final float focusY;

判斷是否為錨定比例模式

  • 是的話直接將點擊時記下的點,作為焦點
  • 不是的話,把所有點累加求和,除以總個數,計算平均值
        if (inAnchoredScaleMode()) {// In anchored scale mode, the focal pt is always where the double tap// or button down gesture started// 在錨定比例模式中,焦點pt始終是雙擊的位置,或按下手勢開始focusX = mAnchoredScaleStartX;focusY = mAnchoredScaleStartY;if (event.getY() < focusY) {mEventBeforeOrAboveStartingGestureEvent = true;} else {mEventBeforeOrAboveStartingGestureEvent = false;}} else {for (int i = 0; i < count; i++) {if (skipIndex == i) continue;sumX += event.getX(i);sumY += event.getY(i);}focusX = sumX / div;focusY = sumY / div;}

算縮放比例

  • 計算縮放比例也很簡單,就是計算各個手指到焦點的平均距離,在用戶手指移動后用新的平均距離除以舊的平均距離,并以此計算得出縮放比例。
        // Determine average deviation from focal point @Google translate float devSumX = 0, devSumY = 0;for (int i = 0; i < count; i++) {if (skipIndex == i) continue;// Convert the resulting diameter into a radius.devSumX += Math.abs(event.getX(i) - focusX);devSumY += Math.abs(event.getY(i) - focusY);}final float devX = devSumX / div;final float devY = devSumY / div;// Span is the average distance between touch points through the focal point;// i.e. the diameter of the circle with a radius of the average deviation from// the focal point.final float spanX = devX * 2;final float spanY = devY * 2;final float span;if (inAnchoredScaleMode()) {span = spanY;} else {span = (float) Math.hypot(spanX, spanY);}

計算平均偏差

  • 確定焦點的平均偏差
        float devSumX = 0, devSumY = 0;for (int i = 0; i < count; i++) {if (skipIndex == i) continue;// Convert the resulting diameter into a radius.devSumX += Math.abs(event.getX(i) - focusX);devSumY += Math.abs(event.getY(i) - focusY);}final float devX = devSumX / div;final float devY = devSumY / div;

計算縮放比例

  • 跨度是通過焦點的觸摸點之間的平均距離;
  • 即圓的直徑,其半徑為平均偏差
  • 這里的 Math.hypot(spanX, spanY) 方法,相當于 sqrt(xx + yy)
        final float spanX = devX * 2;final float spanY = devY * 2;final float span;if (inAnchoredScaleMode()) {span = spanY;} else {span = (float) Math.hypot(spanX, spanY);}

結束縮放事件

  • @Google Translate:根據需要調度開始/結束事件。
  • 如果配置發生更改,請通過開始通知應用重置其當前狀態
  • 一個新的比例事件流。
  • 這里就不做太多描述,主要就是:
  • 判斷是不是所有手指都離開了屏幕
  • 如果是,那么索命這個縮放進程結束了
  • 則保存當前縮放的數據
  • 調用 onScaleEnd 方法,結束當前操作
        final boolean wasInProgress = mInProgress;mFocusX = focusX;mFocusY = focusY;if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = span;}if (configChanged) {mPrevSpanX = mCurrSpanX = spanX;mPrevSpanY = mCurrSpanY = spanY;mInitialSpan = mPrevSpan = mCurrSpan = span;}

觸發 onScaleBegin 開始縮放

  • 當手指移動的距離超過一定數值(數值大小由系統定義)后,會觸發 onScaleBegin 方法
  • 如果用戶在 onScaleBegin 方法里面返回了 true,則接受事件后,就會重置縮放相關數值,并且開始積累縮放因子。
        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;if (!mInProgress && span >= minSpan &&(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {mPrevSpanX = mCurrSpanX = spanX;mPrevSpanY = mCurrSpanY = spanY;mPrevSpan = mCurrSpan = span;mPrevTime = mCurrTime;mInProgress = mListener.onScaleBegin(this);}

通知用戶進行縮放處理

  • @ Google Translate: 處理動作;焦點和跨度/比例因子正在發生變化。
  • 這塊代碼的功能主要就是通知用戶(編程者)
  • 根據這些數據進行縮放
        if (action == MotionEvent.ACTION_MOVE) {mCurrSpanX = spanX;mCurrSpanY = spanY;mCurrSpan = span;boolean updatePrev = true;if (mInProgress) {updatePrev = mListener.onScale(this);}if (updatePrev) {mPrevSpanX = mCurrSpanX;mPrevSpanY = mCurrSpanY;mPrevSpan = mCurrSpan;mPrevTime = mCurrTime;}}

updatePrev

  • 這個用于接收用戶的返回值
  • 只要我們放回 true ,系統就會保存當前數據
  • 重新獲取并計算新的數據和比例
  • 系統默認返回 false 然后進行下一次事件的計算
            if (mInProgress) {updatePrev = mListener.onScale(this);}if (updatePrev) {mPrevSpanX = mCurrSpanX;mPrevSpanY = mCurrSpanY;mPrevSpan = mCurrSpan;mPrevTime = mCurrTime;}

結語

我要講的所有內容,到這里就完全結束了

由于源碼是按照我自己的理解來講的,所以難免會有一些出入

希望大家能在評論區中幫我指出,謝謝~ ?

轉載于:https://www.cnblogs.com/yuanhao-1999/p/11102806.html

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

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

相關文章

postgres的數據庫備份和恢復

備份和恢復 一條命令就可以解決很簡單: 這是備份的命令&#xff1a; pg_dump -h 127/0.0.1 -U postgres databasename > databasename.bak 指令解釋&#xff1a; pg_dump 是備份數據庫指令&#xff0c;164.82.233.54是數據庫的ip地址&#xff08;必須保證數據庫允許外部訪…

java 類的執行順序_Java中類的執行順序

講解在代碼中&#xff1a;package 類執行順序;/*** java類執行順序** 1、如果父類有靜態成員賦值或者靜態初始化塊&#xff0c;執行靜態成員賦值和靜態初始化塊* 2、如果類有靜態成員賦值或者靜態初始化塊&#xff0c;執行靜態成員賦值和靜態初始化塊* 3、將類的成員賦予初值(原…

ZooKeeper相關資料集錦

1、ZooKeeper相關概念總結 https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/ZooKeeper.md 2、ZooKeeper在Windows下的安裝和配置 https://blog.csdn.net/morning99/article/details/40426133 3、Curator框架應用 http://ifeve.com/zookeepe…

JQuery.Ajax()的data參數傳遞方式

最近&#xff0c;新學c# mvc&#xff0c;通過ajax post方式傳遞數據到controller。剛開始傳遞參數&#xff0c;controller中總是為null。現記錄一下&#xff0c;可能不全&#xff0c;純粹記個學習日記。 重點在于參數的方式&#xff0c;代碼為例子 1、這里 dataType: "js…

java如何實現封裝_java如何實現封裝

Java中類的封裝是如何實現的封裝是將對象的信息隱藏在對象內部&#xff0c;禁止外部程序直接訪問對象內部的屬性和方法。 java封裝類通過三個步驟實現&#xff1a; (1)修改屬性的可見性&#xff0c;限制訪問。 (2)設置屬性的讀取方法。 (3)在讀取屬性的方法中&#xff0c;添加對…

用了30天整理的一些GO語言學習資料,2019請你加油

因為極其優秀的并發性能&#xff0c;Google的親兒子Go語言站上了風潮之巔。出現在21世紀的GO語言&#xff0c;雖然不能如愿對C取而代之&#xff0c;但是其近C的執行性能和近解析型語言的開發效率以及近乎于完美的編譯速度&#xff0c;已經風靡全球。特別是在云項目中&#xff0…

Kubernetes網絡設計原則

在配置集群網絡插件或者實踐K8S 應用/服務部署請時刻想到這些原則&#xff1a; 1.每個Pod都擁有一個獨立IP地址&#xff0c;Pod內所有容器共享一個網絡命名空間2.集群內所有Pod都在一個直接連通的扁平網絡中&#xff0c;可通過IP直接訪問 所有容器之間無需NAT就可以直接互相訪問…

php token 驗證,PHP如何實現Token驗證

PHP如何實現Token驗證首先將Token進行解析&#xff1b;然后根據解析出來的信息部分驗證是否過期&#xff0c;如果未過期再將解析出的信息部分進行加密&#xff1b;最后將加密出來的數據和解析出來簽名進行比對&#xff0c;如果相同則驗證成功。示例代碼&#xff1a;<?php f…

關于Linux fontconfig 字體庫的坑

01、安裝字體軟件yum -y install fontconfig然后把字體拷過去就行了 cd /usr/share/fonts fc-list 這是查看02、拷貝字體到指定目錄 cp simsun.ttc /usr/share/fonts/然后把字體拷過去就行了 cd /usr/share/fonts 03、驗證字體安裝情況 fc-list //"宋體"中文字體…

滿江紅.互聯網

小小寰球&#xff0c;有多少信息瞬抵。互聯網&#xff0c;幾多濤生&#xff0c;幾多云逸。螞蟻緣槐近大國&#xff0c;菜鳥搭枝成鳳翼。正臺風綠葉下臨安&#xff0c;何足懼?多少事&#xff0c;從來急&#xff1b;天地轉&#xff0c;光陰隙。一百年太久&#xff0c;只爭朝夕。…

Python startswith()函數 與 endswith函數

函數&#xff1a;startswith() 作用&#xff1a;判斷字符串是否以指定字符或子字符串開頭一、函數說明語法&#xff1a;string.startswith(str, beg0,endlen(string)) 或string[beg:end].startswith(str)參數說明&#xff1a;string&#xff1a; 被檢測的字符串str&#xff1a;…

GitLab 在多分支中的一個push

情景&#xff1a;a.本地庫新建的分支&#xff0c;而Git服務器沒有這個分支服務器分支master本地新建分支&#xff1a;rdar-MS&#xff0c;并git checkout rdar-MS上masterrdar-testrdar-MS更改rdar-MS分支上的文件&#xff0c;git add .git commit -m " "后&#xf…

php post 獲取xml,php 獲取post的xml數據并解析示例

這篇文章主要為大家詳細介紹了php 獲取post的xml數據并解析示例&#xff0c;具有一定的參考價值&#xff0c;可以用來參考一下。對php獲取post過來的xml數據并解析感興趣的小伙伴&#xff0c;下面一起跟隨512筆記的小編兩巴掌來看看吧&#xff01;如何獲取請求的xml數據,對方通…

值得一用的Windows軟件

該清單僅本人使用后所作推薦&#xff0c;可能會比較主觀&#xff0c;所以僅供參考哈。可能某些軟件鏈接會失效&#xff0c;可以自行百度搜索下載即可。 殺軟 火絨安全&#xff1a;國內殺毒軟件的一股清流&#xff0c;界面簡潔&#xff0c;無推廣。現在已經開啟了 5.0 公測&…

《JavaScript模式》讀書筆記一:基本技巧

《JavaScript模式》的讀書筆記&#xff0c;個人向&#xff01;更新進度隨我的閱讀進度 基本技巧 盡量少用全局變量 防止變量污染注意JS變量提升問題盡量使用單一var模式&#xff0c;只使用一個var在函數頂部進行變量聲明function fun () {var a 1,b2,sum ab,函數體//} for循環…

Python字符串處理全攻略(四):常用內置方法輕松掌握

文章目錄 引言Python字符串常用內置方法切片功能介紹語法示例注意事項 str.isalpha()功能介紹語法示例注意事項 str.isdigit()功能介紹語法示例注意事項總結 str.isalnum()功能介紹語法示例注意事項總結 str.isupper()功能介紹語法示例注意事項 islower()功能介紹語法示例注意事…

php空間限制磁盤限額,ORA-01536:超出表空間XXXX的空間限額

問題描述&#xff1a;在FMIS2600用戶下進行某個DDL或DML操作時&#xff0c;提示&#xff1a;ORA-01536&#xff1a;超出表空間FMIS2600 的空間限額 或者 ORA-01950: 對表空間/*******************ORA-01536&#xff1a;超出表空間XXXX的空間限額*******************//*********…

01爬蟲基本原理及Requests庫下載

一、爬蟲基本原理 1.什么是爬蟲 ? 爬蟲就是爬取數據 2.什么是互聯網&#xff1f; ? 就是由一堆網絡設備&#xff0c;把一臺臺的電腦互聯在一起 3.互聯網建立的目的 ? 數據的傳遞和數據共享 4.什么是數據&#xff1f; ? 例如&#xff1a; ? 電商平臺的商業信息&#xff08;…

php 怎么實現收藏功能,php收藏功能如何實現

php收藏功能如何實現php收藏功能的實現方法&#xff1a;首先創建好數據庫表 &#xff1b;然后創建前臺代碼&#xff0c;實現登錄界面&#xff1b;接著通過html實現收藏樣式&#xff1b;最后使用php進行后臺處理即可。推薦&#xff1a;《PHP視頻教程》這是數據庫表話不多說上代碼…

quartus FIR仿真筆記

第一章&#xff1a; 最近百度了一些fir濾波器的資料&#xff0c;都沒有自己想要的。容我吐槽一大段文字> 在舊版的quartus中&#xff0c;比如13.0&#xff0c;有兩個fir濾波器的選項&#xff0c;如下所示&#xff1a; 網上很多都是講不帶II的那個&#xff0c;而在新版的quar…