概述:
本文主要是對drawBitmapMesh的api研究學習,以及介紹模仿mac吸入動效的實現原理。
drawBitmapMesh:
使bigmap產生形變,功能與drawVertices類似,區別是drawVertices直接對畫布產生作用。
首先需要看一下api中的參數列表:
其中關鍵參數分別是:
bitmap:需要扭曲的圖像
meshWidth:橫向的格數
meshHeight:縱向的格數
verts:網格交叉點座標數組,長度爲(meshWidth + 1) * (meshHeight + 1) * 2
vertOffset:控制verts數組中從第幾個數組元素開始纔對bitmap進行扭曲
這裏需要注意,根據api的描述在p以下的版本vertOffset和colorOffset不生效,默認爲0
通過一個demo來演示具體的使用方法:
1.首先創建一個自定義view,繪製一個bitmap在上面:
繪製的方法:
canvas.drawBitmapMesh(mBitmap,
WIDTH,
HEIGHT,
mVerts,
0, null, 0, mPaint);
其中WIDTH爲行數,HEIGHT爲列數,mVerts爲座標數組,暫時隨意傳即可,不會影響目前繪製的圖片。
2.假設將圖片分爲3行3列,則設置WIDTH,HEIGHT=3,根據行列以及圖片的寬高設置座標數組:
private void buildMesh(float mBmpW, float mBmpH) {
int index = 0;
for (int y = 0; y <= HEIGHT; ++y) {
float fy = y * mBmpH / HEIGHT;
for (int x = 0; x <= WIDTH; ++x) {
float fx = x * mBmpW / WIDTH;
setXY(mVerts, index, fx, fy);
index += 1;
}
}
}
並且,在ondraw的時候繪製好分割線,效果如圖:
//畫分割線
mPaint.setStyle(Paint.Style.FILL);
for (int i = 0; i + 1 < mVerts.length / 2; i++) {
if ((i + WIDTH + 1) * 2 + 1 <= mVerts.length) {
canvas.drawLine(
mVerts[i * 2],
mVerts[i * 2 + 1],
mVerts[(i + WIDTH + 1) * 2],
mVerts[(i + WIDTH + 1) * 2 + 1],
mPaint);
}
if (i != 0 && ((i + 1) % (WIDTH + 1) == 0)) {
continue;
}
canvas.drawLine(
mVerts[i * 2],
mVerts[i * 2 + 1],
mVerts[i * 2 + 2],
mVerts[i * 2 + 3],
mPaint);
}
3.增加觸控監聽,在手指點擊的位置畫一個小圓圈,並繪製兩條軌跡線,保存path路徑:
繪製的方法就不贅述了,比較簡單。
4.最關鍵的部分,點擊測試的時候,啓動一個進度值動畫,使得進度從0~行數變化,並計算動態的繪製路徑,更新座標數組並刷新視圖(這裏爲了讓視圖連貫,採取了10行10列):
其中最爲關鍵的是座標數組動態計算的方法:
/**
* 動態計算繪製路徑
* 1.計算兩條pathmeasure
* 2.根據動畫index 計算左右兩邊路徑各自的第一個點和最後一個點座標
* 3.分別計算網格里的每個點位置
* @param timeIndex
*/
private void buildMeshes(int timeIndex) {
mFirstPathMeasure.setPath(mFirstPath, false);
mSecondPathMeasure.setPath(mSecondPath, false);
int index = 0;
float[] pos1 = {0.0f, 0.0f};
float[] pos2 = {0.0f, 0.0f};
float firstLen = mFirstPathMeasure.getLength();
float secondLen = mSecondPathMeasure.getLength();
float len1 = firstLen / HEIGHT;
float len2 = secondLen / HEIGHT;
float firstPointDist = timeIndex * len1; //左邊第一個點長度
float secondPointDist = timeIndex * len2; //右邊第一個點長度
float height = mBmpH; //圖片高度
mFirstPathMeasure.getPosTan(firstPointDist, pos1, null);
mFirstPathMeasure.getPosTan(firstPointDist + height, pos2, null); //得到第一個點座標和最後一個點座標
float x1 = pos1[0];
float x2 = pos2[0];
float y1 = pos1[1];
float y2 = pos2[1];
float FIRST_DIST = (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
float FIRST_H = FIRST_DIST / HEIGHT;
mSecondPathMeasure.getPosTan(secondPointDist, pos1, null);
mSecondPathMeasure.getPosTan(secondPointDist + height, pos2, null);
x1 = pos1[0];
x2 = pos2[0];
y1 = pos1[1];
y2 = pos2[1];
float SECOND_DIST = (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
float SECOND_H = SECOND_DIST / HEIGHT;
for (int y = 0; y <= HEIGHT; ++y) {
//得到每一個點的位置
mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null);
mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null);
float w = pos2[0] - pos1[0];//橫軸最左邊到最右邊的距離
//左右兩邊的點的位置
float fx1 = pos1[0];
float fx2 = pos2[0];
float fy1 = pos1[1];
float fy2 = pos2[1];
//左右兩邊點 x 和 y軸方向的差值
float dy = fy2 - fy1;
float dx = fx2 - fx1;
for (int x = 0; x <= WIDTH; ++x) {
// y = x * dy / dx
float fx = x * w / WIDTH;
//tanα = dy/dx = fy/fx
float fy = fx * dy / dx;
mVerts[index * 2 + 0] = fx + fx1;
mVerts[index * 2 + 1] = fy + fy1;
index += 1;
}
}
}
核心思想還是根據行數等分計算每一個點的座標,並動態更新座標數組,刷新圖形在軌跡線上顯示的位置。
總結:
通過drawBitmapMesh形變方法,可以實現一些其他api做不到的效果,比如一些酷炫的吸附,拖拽效果等,以後會慢慢找一些比較複雜的效果來實現練習一下。
參考:
https://www.jianshu.com/p/51d8dd99d27d( Android:修圖技術之瘦臉效果的實現(drawBitmapMesh))