鴻蒙開源第三方組件—SwipeCaptcha_ohos2.0滑動拼圖驗證組件
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
基于安卓平臺的滑動拼圖驗證組件SwipeCaptcha(https://github.com/mcxtzhang/SwipeCaptcha ),實現(xiàn)了鴻蒙化遷移和重構(gòu),代碼已經(jīng)開源到(https://gitee.com/isrc_ohos/swipe-captcha_ohos ),目前已經(jīng)獲得了很多人的Star和Fork ,歡迎各位下載使用并提出寶貴意見!
背景
在頁面登錄或者注冊的時候,為了確保不是機器人操作,會讓用戶手動驗證。驗證方式分為滑動拼圖驗證和滑動驗證兩種。
- 滑動拼圖驗證:有圖片作為背景,通過圖塊拼接實現(xiàn)安全驗證;
- 滑動驗證:無圖片背景,只拖動滑塊便可實現(xiàn)安全驗證;
本文的SwipeCaptcha_ohos2.0組件屬于滑動拼圖驗證,操作簡單,安全性強,可被應(yīng)用于各種網(wǎng)站的登錄、注冊、找回密碼或投票等場景中。
我們之前已經(jīng)實現(xiàn)了滑動拼圖驗證組件SwipeCaptcha_ohos,相關(guān)文章在:https://harmonyos.51cto.com/posts/3402 。本次SwipeCaptcha_ohos2.0是基于之前移植的項目進(jìn)行了相關(guān)功能的優(yōu)化,具體優(yōu)化內(nèi)容將在下文中詳細(xì)介紹。
組件效果展示
SwipeCaptcha_ohos2.0的主要功能和之前的SwipeCaptcha_ohos基本一致,組件在使用時,有兩個較為重要的元素:滑塊和原圖。二者被放置于同一水平線上,用戶拖動滑塊至原圖處使二者重合,誤差小于提前設(shè)定的驗證閾值,即可驗證成功。每次調(diào)用組件,滑塊和原圖的位置都會發(fā)生隨機變化。
SwipeCaptcha_ohos2.0相較于之前的版本,大幅提升了組件功能的完整性以及使用體驗,下面將依次從組件驗證失敗和驗證成功兩個狀態(tài),展示SwipeCaptcha_ohos2.0與之前版本的效果對比。
1.驗證失敗
通過圖1(a)和圖1(b)的對比可以看出,新版本移除了舊版本中“當(dāng)前進(jìn)度值預(yù)覽”的不必要功能以及下方的狀態(tài)欄,取而代之的功能如下:
驗證滑塊由正方形小塊升級為“拼圖塊”樣式;
待驗證背景圖塊增加了陰影遮罩效果;
驗證失敗后增加了滑塊閃爍效果以及“驗證失敗,請重新驗證!”的彈窗提醒。

(a)舊版本組件驗證失敗效果

(b)新版本組件驗證失敗效果
圖1 新舊版本驗證失敗效果對比
2.驗證成功
通過圖2(a)和圖2(b)的對比可以看出,新版本移除了舊版本中“當(dāng)前進(jìn)度值預(yù)覽”的不必要功能以及下方的狀態(tài)欄,取而代之的功能如下:
- 點擊“重新生成驗證碼”按鈕后,滑塊和原圖的位置都會發(fā)生隨機變化;
- 驗證成功后增加了反光條劃過的動畫效果以及“驗證成功!”的彈窗提醒。

(a)舊版本組件驗證成功效果

(b)新版本組件驗證成功效果
圖2 新舊版本驗證成功效果對比
除了上述直觀的功能優(yōu)化外,SwipeCaptcha_ohos2.0還實現(xiàn)了以下功能:
- 滑塊大小和容錯閾值的用戶自定義
滑塊大小自定義是指用戶可以通過代碼自定義滑塊的寬高;容錯閾值自定義是指用戶可以通過代碼自定義匹配時的容錯率,即相差多少視作匹配成功。
- 拼圖背景在指定范圍內(nèi)的自適應(yīng)填充。
原組件的圖片不能在指定組件寬高的前提下自動填充圖片,如果強行適配寬高會出現(xiàn)拼圖塊內(nèi)容錯位的情況;經(jīng)過改進(jìn)后,驗證圖片已經(jīng)能夠適配布局中規(guī)定的組件寬高。
Sample解析
通過上文相信大家已經(jīng)了解SwipeCaptcha_ohos2.0組件的使用效果,下面將具體講解SwipeCaptcha_ohos2.0組件的使用方法,共分為5個步驟:
步驟1. 導(dǎo)入SwipeCaptchaView類并聲明類對象。
步驟2. 在xml文件中添加SwipeCaptchaView控件。
步驟3. 綁定SwipeCaptchaView控件。
步驟4. 設(shè)置回調(diào)處理函數(shù)。
步驟5. 設(shè)置Button控件監(jiān)聽事件,重新生成驗證區(qū)域
(1)導(dǎo)入SwipeCaptchaView類并聲明類對象
在MainAbilitySlice.java文件中,通過import關(guān)鍵字導(dǎo)入SwipeCaptchaView類。
- //導(dǎo)入SwipeCaptchaView類
- import com.huawei.swipecaptchaview.lib.SwipeCaptchaView;
- public class MainAbilitySlice extends AbilitySlice {
- //聲明SwipeCaptchaView類對象
- SwipeCaptchaView swipeCaptchaView;
- ......
- }
(2)在xml文件中添加SwipeCaptchaView控件
在xml文件中添加SwipeCaptchaView控件,用于顯示滑動驗證的背景圖和動態(tài)效果。設(shè)置控件高和寬、滑塊的高和寬以及驗證閾值等屬性。
- <com.huawei.swipecaptchaview.lib.SwipeCaptchaView
- xmlns:captcha="http://schemas.huawei.com/res/ohos-auto" //聲明一個用于傳輸自定義參數(shù)的命名空間
- ohos:id="$+id:swipeCaptchaView" //規(guī)定控件id
- ohos:height="220vp" //控件的高
- ohos:width="330vp" //控件的寬
- captcha:captchaHeight="30vp" //拼圖滑塊高
- captcha:captchaWidth="30vp" //拼圖滑塊寬
- captcha:matchDeviation="9"/> //驗證失敗的閾值
(3)綁定SwipeCaptchaView控件
在MainAbilitySlice.java的onStart()方法中,使用findComponentById()方法將xml文件中SwipeCaptchaView控件與SwipeCaptchaView類對象綁定;再調(diào)用setImageId()方法設(shè)置組件的背景圖片。
- //根據(jù)id找到相應(yīng)的控件
- swipeCaptchaView = (SwipeCaptchaView) findComponentById(ResourceTable.Id_swipeCaptchaView);
- ...
- button = (Button) findComponentById(ResourceTable.Id_btn_change);
- //設(shè)置背景圖片
- swipeCaptchaView.setImageId(ResourceTable.Media_pic01);
(4)設(shè)置回調(diào)處理函數(shù)
設(shè)置SwipeCaptchaView組件的回調(diào)處理函數(shù),來提示用戶滑動驗證結(jié)果。以提示用戶驗證成功為例:首先重寫matchSuccess()方法,設(shè)置驗證成功后的提示信息,然后實例化一個ToastDialog提示框?qū)ο?,使用setText()方法設(shè)置顯示文字為“驗證成功!”;setAlignment()方法設(shè)置提示框的布局位置在整體布局的中央;show()方法用于顯示提示框。
設(shè)置驗證失敗的情況和驗證成功同理,只需重寫matchFailed()方法將文字信息設(shè)置為“驗證失敗!”即可。
- //每次滑動結(jié)束后會根據(jù)判定結(jié)果回調(diào)
- swipeCaptchaView.setOnCaptchaMatchCallback(new SwipeCaptchaView.OnCaptchaMatchCallback() {
- @Override
- public void matchSuccess(SwipeCaptchaView swipeCaptchaView) {
- new ToastDialog(getContext())
- .setText(" 驗證成功!")
- .setAlignment(LayoutAlignment.CENTER)
- .show();
- }
- });
(5)設(shè)置Button控件監(jiān)聽事件,重新生成驗證區(qū)域
綁定button對象和xml文件中“重新生成驗證碼”Button控件;為button設(shè)置監(jiān)聽事件,每次點擊按鈕,都會調(diào)用createCaptcha()方法隨機生成滑塊和原圖的位置。
- button = (Button) findComponentById(ResourceTable.Id_btn_change);//綁定Button
- button.setClickedListener(new Component.ClickedListener() {//設(shè)置監(jiān)聽
- @Override
- public void onClick(Component component) {
- swipeCaptchaView.createCaptcha();//隨機生成滑塊和原圖的位置
- ...
- }
- });
Library解析
本部分將要重點介紹的類是圖3中框出的2個類,分別是DrawHelperUtils、和SwipeCaptchaView。它們向開發(fā)者提供設(shè)置SwipeCaptcha_ohos2.0組件相關(guān)屬性的具體執(zhí)行方法,其中DrawHelperUtils是工具類,SwipeCaptchaView是具體實現(xiàn)滑塊滑動效果的類,本節(jié)將分別講解這兩個類的內(nèi)部邏輯實現(xiàn)。

圖3 Library目錄結(jié)構(gòu)
1、DrawHelperUtils類
Swipeptcha_ohos2.0升級實現(xiàn)的拼圖滑塊的原理是在方塊的左、右兩條豎邊中點處分別繪制一個凸半圓或凹半圓(隨機),可參考圖4。DrawHelperUtils類的drawPartCircle()方法具體用于繪制拼圖滑塊兩條豎邊上的半圓,先來解釋一下該方法涉及變量和參數(shù)的含義:
- 起點坐標(biāo):開始繪制半圓的起點坐標(biāo),在圖中由A表示,規(guī)定為方塊豎邊的前1/3處,由入?yún)魅搿?/li>
- 終點坐標(biāo):開始繪制半圓的起點坐標(biāo),在圖中由C表示,規(guī)定為方塊豎邊的后1/3處,由入?yún)魅搿?/li>
- 中點坐標(biāo):半圓直徑的中點坐標(biāo),在圖中由B表示,由起點A和終點C的X、Y坐標(biāo)計算得到。
- r1:半圓半徑 = AB長度 = AC長度/2 = 1/6方塊豎邊長度。
- gap1:由r1乘以貝塞爾曲線(cubicTo()方法)系數(shù)c得到,用于確定控制點D和F的坐標(biāo),控制點作用是控制半圓繪制的軌跡。
- flag:半圓的旋轉(zhuǎn)系數(shù),用來控制凹、凸半圓的繪制。當(dāng)為1時,A、B、C坐標(biāo)與變量相加,繪制向外的凸半圓;當(dāng)為-1時,其坐標(biāo)與變量相減,得到向內(nèi)的凹半圓。

圖4-1 凸半圓繪制原理圖

圖4-2 凹半圓繪制原理圖
以豎直繪制一個凸半圓為例,根據(jù)A、B、C點計算得到上述變量后,調(diào)用兩次貝塞爾曲線cubicTo(x1,y1,x2,y2,x3,y3)分別繪制前1/2和后1/2半圓,此方法中需要使用到兩個控制點,共有6個參數(shù),分別表示控制點1(x1,y1)、控制點2(x2,y2)和繪制終點(x3,y3)。
如圖4-1,繪制前1/2半圓時以起點A右側(cè)平行g(shù)ap1flag1距離處作為第一個控制點D、中點B右側(cè)平行r1距離的半圓頂點第二個控制點E、中點B作為繪制終點;繪制后1/2半圓同理,以E點作為第一個控制點,終點C右側(cè)平行g(shù)ap1flag1距離處作為第二個控制點F、終點C作為繪制終點。
其他繪制方向同理,若為從下向上繪制,則將flag設(shè)為-1;若繪制凹半圓,則在計算坐標(biāo)時橫坐標(biāo)反方向計算即可可參考圖4-2。
- public static void drawPartCircle(Point start, Point end, Path path, boolean outer) {
- float c = 0.551915024494f;
- Point middle = new Point(start.getPointX() + (end.getPointX() - start.getPointX()) / 2,start.getPointY() + (end.getPointY() - start.getPointY()) / 2);//根據(jù)起點坐標(biāo)A和終點坐標(biāo)C算出中點B坐標(biāo)
- //半徑
- float r1 = (float) Math.sqrt(Math.pow((middle.getPointX() - start.getPointX()), 2) + Math.pow((middle.getPointY() - start.getPointY()), 2));
- float gap1 = r1 * c;//距離gap
- if (start.getPointX() == end.getPointX()) {//繪制豎直方向
- boolean topToBottom = end.getPointY() - start.getPointY() > 0;
- int flag;//旋轉(zhuǎn)系數(shù)
- if (topToBottom) { //若從上到下繪制
- flag = 1;//旋轉(zhuǎn)系數(shù)設(shè)為1
- } else { flag = -1; }//若從下到上繪制,設(shè)為-1
- if (outer) {//若為凸半圓,相加
- path.cubicTo(start.getPointX() + gap1 * flag, start.getPointY(),middle.getPointX() + r1 * flag, middle.getPointY() - gap1 * flag,middle.getPointX() + r1 * flag, middle.getPointY());
- path.cubicTo(middle.getPointX() + r1 * flag, middle.getPointY() + gap1 * flag,end.getPointX() + gap1 * flag, end.getPointY(), end.getPointX(), end.getPointY());
- }... }//若為凹半圓,則相減
- }
2、SwipteCaptchaView類
SwipeCaptchaView是具體實現(xiàn)滑塊滑動效果的類,下文將從初始化滑動條并設(shè)置滑動條監(jiān)聽、初始化驗證區(qū)域背景、設(shè)置驗證后的動畫效果、生成滑動驗證區(qū)域四個方面具體講解實現(xiàn)邏輯。接下來將按類型講解類中各方法間的調(diào)用邏輯,可參考圖4。

圖4 各類間的函數(shù)調(diào)用關(guān)系示意圖
(1)初始化滑動條并設(shè)置滑動條監(jiān)聽
在SwipteCaptchaView類的構(gòu)造函數(shù)中,調(diào)用init()方法進(jìn)行初始化。其中,獲取xml文件中控件參數(shù)即寬、高和系統(tǒng)屏幕寬度;通過switch-case判斷來獲取滑塊的寬、高和滑動誤差值;
- mHeight = getHeight();//獲取控件高和款
- mWidth = getWidth();//獲取系統(tǒng)屏幕寬度
- if (mWidth == 0) {//mWidth=0為設(shè)置了match_parent的情況
- mWidth = DisplayManager.getInstance().getDefaultDisplay(context).get().getAttributes().width;
- }
- for (int i = 0; i < attrSet.getLength(); i++) {
- Optional<Attr> attr = attrSet.getAttr(i);
- if (attr.isPresent()) {
- switch (attr.get().getName()) {
- case "captchaHeight"://獲取滑塊高度
- mCaptchaHeight = attr.get().getDimensionValue();
- break;
- case "captchaWidth"://獲取滑塊寬度
- ...
- case "matchDeviation"://獲取滑動誤差值
- ...
- }
- }
- }
實例化Image類得到驗證區(qū)域圖片對象,并為其設(shè)置圖片縮放模式以及位圖格式等屬性;實例化Slider類得到拖動條對象,為其設(shè)置寬、高、進(jìn)度值、進(jìn)度顏色等屬性,以及監(jiān)聽事件;
- mImage = new Image(context);//表示驗證區(qū)域圖片
- ...
- mImage.setScaleMode(Image.ScaleMode.CLIP_CENTER);
- mImage.setPixelMap(ResourceTable.Media_no_resource);
- ...
- mSlider = new Slider(mLayout.getContext());//實例化Slider類表示拖動條
- mSlider.setWidth(mWidth); //設(shè)置寬、高
- mSlider.setHeight(SLIDER_HEIGHT);
- mSlider.setMarginTop(mHeight - SLIDER_HEIGHT);
- mSlider.setMinValue(0); //進(jìn)度最小、最大值、當(dāng)前進(jìn)度值、進(jìn)度顏色
- mSlider.setMaxValue(10000);
- mSlider.setProgressValue(0)
- mSlider.setProgressColor(Color.BLACK);
- setSlideListener(); //設(shè)置拖動條的監(jiān)聽事件
- ...
在拖動條監(jiān)聽事件setSlideListener()方法中,重寫onTouchEnd()方法,判斷滑動結(jié)束后滑塊位置的誤差值是否小于規(guī)定誤差值。若小于則驗證成功,取消滑塊的陰影并設(shè)置回調(diào);否則驗證失敗,直接設(shè)置回調(diào);
- @Override
- public void onTouchEnd(Slider slider) {
- if (onCaptchaMatchCallback != null) {
- if (Math.abs(mSlider.getProgress() * (mWidth - mCaptchaWidth) / 10000 - mCaptchaX) < mMatchDeviation) {//滑動結(jié)束后滑塊位置誤差值小于規(guī)定誤差值驗證成功
- mCaptchaPaint.setMaskFilter(null); //取消滑塊的陰影
- slider.setEnabled(false);
- onCaptchaMatchCallback.matchSuccess(SwipeCaptchaView.this);//設(shè)置驗證成功后的回調(diào)
- mSuccessAnim.start();//播放驗證成功動畫
- } else {//滑動誤差值大于規(guī)定誤差值驗證失敗
- slider.setProgressValue(0);
- onCaptchaMatchCallback.matchFailed(SwipeCaptchaView.this);//設(shè)置驗證失敗后的回調(diào)
- mFailAnim.start();//播放驗證失敗動畫
- }
- }
- }
(2)初始化滑動驗證區(qū)域
在通過Image類對象調(diào)用setPixelMap()方法設(shè)置完驗證圖片后,由initCaptcha()方法完成驗證區(qū)域的初始化。
實例化兩個Paint類分別得到畫筆對象和滑塊目標(biāo)區(qū)域?qū)ο螅瑸槠湓O(shè)置畫筆抗鋸齒和陰影、滑塊樣式和顏色等屬性;再分別調(diào)用 createMatchAnim()和createCaptcha()方法設(shè)置驗證后的動畫效果和生成滑動驗證區(qū)域。
- private void initCaptcha() {
- mRandom = new Random(System.nanoTime());
- //設(shè)置畫筆
- mCaptchaPaint = new Paint();//畫筆對象
- mCaptchaPaint.setAntiAlias(true); //抗鋸齒
- mCaptchaPaint.setDither(true); //使位圖進(jìn)行有利的抖動的位掩碼標(biāo)志
- mCaptchaPaint.setStyle(Paint.Style.FILL_STYLE);
- mCaptchaPaint.setMaskFilter(new MaskFilter(10, MaskFilter.Blur.SOLID));//陰影
- //滑塊目標(biāo)區(qū)域
- mMaskPaint = new Paint();//滑塊目標(biāo)區(qū)域?qū)ο?nbsp;
- mMaskPaint.setAntiAlias(true);
- mMaskPaint.setDither(true);
- mMaskPaint.setStyle(Paint.Style.FILL_STYLE); //填充樣式
- mMaskPaint.setColor(new Color(Color.argb(188, 0, 0, 0))); //填充顏色
- mMaskPaint.setMaskFilter(new MaskFilter(20, MaskFilter.Blur.INNER)); //陰影
- mCaptchaPath = new Path();
- createMatchAnim();//設(shè)置驗證后的動畫效果
- createCaptcha();//生成驗證碼區(qū)域
- }
(3)設(shè)置驗證后的動畫效果
由createMatchAnim()方法實現(xiàn),能夠設(shè)置驗證成功或失敗后的動畫效果。
- 驗證成功
通過AnimatorValue類對象設(shè)置動畫間隔時間為500毫秒;并為其設(shè)置當(dāng)值更新時的監(jiān)聽事件,重寫onUpdate()方法,設(shè)置成功動畫中拼圖的偏移量;
- //成功動畫
- int width = AttrHelper.vp2px(60, mLayout.getContext());
- mSuccessAnim = new AnimatorValue();
- mSuccessAnim.setDuration(500);//間隔時間為500毫秒
- mSuccessAnim.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
- @Override//設(shè)置監(jiān)聽
- public void onUpdate(AnimatorValue animatorValue, float v) {
- mSuccessAnimOffset = (int) (v * (mWidth + width));//拼圖偏移量
- invalidate();
- }
- });
通過Paint類和Path類對象分別調(diào)用相關(guān)函數(shù)來完成陰影效果和動畫路徑的繪制。
- mSuccessPaint = new Paint();
- mSuccessPaint.setShader(new LinearShader(//設(shè)置陰影
- new Point[]{new Point(0, 0), new Point(width * 3 / 2, mHeight)},
- new float[]{0, 0.5f},
- new Color[]{new Color(0x00FFFFFF), new Color(0x66FFFFFF)},
- Shader.TileMode.MIRROR_TILEMODE), Paint.ShaderType.LINEAR_SHADER);
- mSuccessPath = new Path();//繪制動畫路徑
- mSuccessPath.moveTo(0, 0);
- mSuccessPath.rLineTo(width, 0);
- mSuccessPath.rLineTo(width / 2, mHeight - SLIDER_HEIGHT);
- mSuccessPath.rLineTo(-width, 0);
- mSuccessPath.close();//關(guān)閉
- 驗證失敗
與設(shè)置驗證成功的前半部分流程相似,不同之處是將動畫間隔設(shè)為200毫秒、還要設(shè)置畫圈次數(shù)為2次;在值更新時的監(jiān)聽事件中,需要判斷當(dāng)更新值小于0.5f時,將isDrawMask置為false即不繪制滑塊,反之為true則繪制。
- //設(shè)置驗證失敗動畫
- mFailAnim = new AnimatorValue();//實例化驗證失敗的動畫對象
- mFailAnim.setDuration(200);//設(shè)置間隔時間為200毫秒
- mFailAnim.setLoopedCount(2);//設(shè)置畫圈次數(shù)為2次
- mFailAnim.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
- @Override
- public void onUpdate(AnimatorValue animatorValue, float v) {
- if (v < 0.5f) {
- isDrawMask = false;//不繪制滑塊
- } else { isDrawMask = true; }//繪制滑塊
- invalidate();
- }});
- }
(4)生成滑動驗證區(qū)域
由createCaptcha()方法實現(xiàn)。先調(diào)用createCaptchaPath()方法繪制拼圖塊的輪廓路徑。其中通過Random類的nextInt()方法隨機生成驗證區(qū)域坐標(biāo),使滑塊和原圖位置隨機變化;再使用工具類DrawHelperUtils的DrawPartCircle()方法繪制拼圖塊左上角、右上角、右下角和左下角的圖形。
- private void createCaptchaPath() {//繪制拼圖塊輪廓路徑path
- int gap = mCaptchaWidth / 3; //拼圖缺口的位置,設(shè)置在中間 1/3 處
- mCaptchaX = mRandom.nextInt(mWidth - (mCaptchaWidth * 3) - gap) + (mCaptchaWidth * 2); //隨機生成驗證區(qū)域左上角的坐標(biāo)
- mCaptchaY = mRandom.nextInt(mHeight - SLIDER_HEIGHT - mCaptchaHeight - gap);
- mCaptchaPath.reset();
- mCaptchaPath.lineTo(0, 0);
- //開始繪制圖形
- mCaptchaPath.moveTo(mCaptchaX, mCaptchaY); //左上角
- mCaptchaPath.lineTo(mCaptchaX + gap, mCaptchaY);
- drawPartCircle(new Point(mCaptchaX + gap, mCaptchaY),new Point(mCaptchaX + gap * 2, mCaptchaY),
- mCaptchaPath, mRandom.nextBoolean());
- ...//右上角、右下角和左下角同理
- mCaptchaPath.close();//繪制完成后及時關(guān)閉
- }
接著生成滑動驗證區(qū)域,前面介紹過,SwipeCaptcha_ohos2.0版升級實現(xiàn)了驗證區(qū)域背景圖片自適應(yīng)填充的效果。其實現(xiàn)原理是先獲取位圖;根據(jù)圖片的寬高和控件實際的寬高分別計算出水平方向和豎直方向上的縮放比例,兩者中較大的是圖片真實的縮放比例,這是由于上文介紹的Image控件將圖片縮放模式設(shè)為了CLIP_CENTER,該模式會將圖片的短邊縮放至合適的大小并對長邊進(jìn)行裁剪,因此較小的縮放比例代表被裁剪的邊,較大的即在填充進(jìn)控件時的真實縮放比例;接著繪制滑塊目標(biāo)區(qū)域的陰影,其不隨拖動條的移動而更新;最后繪制滑塊區(qū)域,根據(jù)拖動條的數(shù)值計算畫布偏移量,調(diào)用drawPath()方法繪制邊框,獲取圖片 PixelMapHolder,根據(jù)路徑裁剪并將畫布縮放至跟圖片縮放程度一致,根據(jù)比例計算出垂直方向上由于 CLIP_CENTER 裁剪掉的圖片的高度以及水平方向上被裁掉的寬度,即可繪制內(nèi)容。
- public void createCaptcha() {//生成驗證區(qū)域
- if (mImage.getPixelMap() != null) {
- createCaptchaPath();//繪制拼圖塊輪廓路徑Path
- ...}
- PixelMap mCaptchaPixelMap = mImage.getPixelMap();//getPixelMap(mLayout.getContext(),ResourceTable.Media_pic01);
- //根據(jù)圖片的原寬度和控件寬度算出縮放比例
- int originWidth = mCaptchaPixelMap.getImageInfo().size.width;
- int originHeight = mCaptchaPixelMap.getImageInfo().size.height;
- float ratioWidth = (float) mWidth / originWidth;
- float ratioHeight = (float) (mHeight - SLIDER_HEIGHT) / originHeight;
- float ratio = Math.max(ratioWidth, ratioHeight);//更大的ratio
- mImage.addDrawTask((component, canvas) -> { //滑塊目標(biāo)區(qū)域陰影的繪制
- canvas.drawPath(mCaptchaPath, mMaskPaint);
- });
- mLayout.addDrawTask((component, canvas) -> {//滑塊區(qū)域的繪制
- if (isDrawMask) {
- canvas.translate(mSlider.getProgress() * (mWidth - mCaptchaWidth) / 10000 - mCaptchaX, 0); //根據(jù)拖動條的數(shù)值計算畫布的偏移量
- canvas.drawPath(mCaptchaPath, mCaptchaPaint);//繪制邊框
- PixelMapHolder mCaptchaPixelMapHolder = new PixelMapHolder(mCaptchaPixelMap); //獲取圖片的 PixelMapHolder
- canvas.clipPath(mCaptchaPath, Canvas.ClipOp.INTERSECT);//根據(jù)路徑裁剪
- canvas.scale(ratio, ratio);//畫布縮放至跟圖片縮放程度一致
- if(ratio == ratioWidth) {
- float heightErr = (originHeight * ratio - (mHeight - SLIDER_HEIGHT)) / 2;//根據(jù)比例計算出垂直方向上由于 CLIP_CENTER 裁剪掉的圖片的高度
- canvas.drawPixelMapHolder(mCaptchaPixelMapHolder, 0, - heightErr / ratio, mCaptchaPaint);//繪制內(nèi)容
- }
- else {
- float widthErr = (originWidth * ratio - mWidth) / 2;//根據(jù)比例計算出水平方向上由于 CLIP_CENTER 裁剪掉的圖片的寬度
- canvas.drawPixelMapHolder(mCaptchaPixelMapHolder, - widthErr / ratio, 0, mCaptchaPaint);//繪制內(nèi)容
- }
- }});
- }
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)