在android圖片裁剪拼接實現(一):Matrix基本使用中說到了如何自定義控件並如何使用Matrix對其進行縮放、旋轉等處理,此次就說說怎麼把這些控制實現到觸摸上面。
java
首先,當用戶點下屏幕的時候,Linux會將觸摸包裝成Event,而後InputReader會收到來自EventBus發送過來的Event,最後InputDispatcher分發給ViewRootImpl,ViewRootImpl再傳遞給DecorView,這最終纔到達了咱們的當前界面,接下來的傳遞以下圖所示。android
圖畫的很差,水平有限,望見諒。git
那從這裏咱們就知道,咱們要寫的view,須要先從dispatchTouchEvent()裏面分發觸摸事件,而後再TouchEvent()裏面進行事件的處理。如下是dispatchTouchEvent中的處理。github
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
// 分發各個img的觸摸事件
if (mViewMode != VIEW_MODE_IDLE && findIndex >= 0) {
imgList.get(findIndex).onTouchEvent(event);
return true;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (mViewMode == VIEW_MODE_IDLE) {
findIndex = findTouchImg(event);
if (findIndex >= 0) {
return true;
}
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
// 判斷落點是否在img中
if (mViewMode == VIEW_MODE_IDLE) {
findIndex = findTouchImg(event);
if (findIndex >= 0) {
imgList.get(findIndex).onTouchEvent(event);
if (getParent() != null)
getParent().requestDisallowInterceptTouchEvent(true);
return true;
}
}
break;
}
return false;
}
複製代碼
這裏使用getActionMask()是爲了更好的處理多點觸控。使用findTouchImg()方法,判斷若是點落到圖片區域就消費此次事件,可是,後續的觸摸事件,父控件仍是有可能攔截的,此次只是消費了此次按壓觸摸事件。若是是多點觸控,就直接調用requestDisallowInterceptTouchEvent的方法,禁止父控件攔截子控件的後續事件,不過使用這個方法要記着後面釋放。判斷確實是多點觸控以後,就直接在方法頂部執行Img的方法,避免下面沒必要要的判斷。這裏findTouchImg()方法主要是根據每一個Img的DrawRect進行點的落位斷定。方法以下canvas
/** * @return -1 is not find */
private int findTouchImg(MotionEvent event) {
final float touchX = event.getX();
final float touchY = event.getY();
for (int i = 0; i < imgList.size(); i++) {
ImageData imageData = imgList.get(i);
if (imageData.drawRect.contains(touchX, touchY)) {
return i;
}
}
return -1;
}
複製代碼
這裏咱們主要實現兩種效果,縮放和旋轉。咱們把Img的touch處理封裝到了ImageData裏面,代碼以下:ide
/** * imageData的觸摸處理事件 * * @param e 觸摸事件 */
protected void onTouchEvent(MotionEvent e) {
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_POINTER_DOWN:
requestDisallowInterceptTouchEvent(true);
distanceStub = getPointDistance(e);
angleStub = getPointAngle(e);
break;
case MotionEvent.ACTION_MOVE:
// confirm multi touch
if (e.getPointerCount() > 1) {
float tempDistance = getPointDistance(e);
float tempAngle = getPointAngle(e);
float tempScale = this.getScale();
float tempRotateAngle = this.getRotateAngle();
tempScale += (tempDistance / distanceStub) - 1;
tempRotateAngle += tempAngle - angleStub;
angleStub = tempAngle;
distanceStub = tempDistance;
this.setRotateAngle(tempRotateAngle);
this.setScale(tempScale);
reDraw();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
runAngleAdsorbentAnim(findIndex);
requestDisallowInterceptTouchEvent(false);
if (getParent() != null)
getParent().requestDisallowInterceptTouchEvent(false);
distanceStub = 0;
angleStub = 0;
findIndex = -1;
break;
}
}
複製代碼
當多點觸控的時候,記錄下最早兩個觸摸點的距離和斜率角度,在隨後發生滑動的時候,計算與以前觸摸點距離和斜率角度發生的變化,再對Bitmap進行即時調整。計算距離和斜率角度的方法以下:性能
private float getPointDistance(MotionEvent e) {
if (e.getPointerCount() > 1) {
final float touchX1 = e.getX(0);
final float touchY1 = e.getY(0);
final float touchX2 = e.getX(1);
final float touchY2 = e.getY(1);
return (float) Math.abs(Math.sqrt(Math.pow(touchX2 - touchX1, 2) +
Math.pow(touchY2 - touchY1, 2)));
}
return 0;
}
private float getPointAngle(MotionEvent e) {
if (e.getPointerCount() > 1) {
final float touchX1 = e.getX(0);
final float touchY1 = e.getY(0);
final float touchX2 = e.getX(1);
final float touchY2 = e.getY(1);
return (float) (Math.atan2(touchY2 - touchY1, touchX2 - touchX1) * (180f
/ Math.PI));
}
return 0;
}
複製代碼
計算兩點距離很簡單,中學的計算公式動畫
那麼咱們求出角度和距離公式以後,只須要跟上一次記錄的數據進行比對,便可改變數據。咱們看看實現效果。this
可是到這一步尚未完,咱們還要加上吸附動畫。spa
咱們先直接看看吸附動畫的代碼:
private void runAngleAdsorbentAnim(int pos) {
// force run animation
if (pos >= imgList.size() || pos < 0)
return;
mViewMode = VIEW_MODE_RUN_ANIMATION;
final ImageData imageData = imgList.get(pos);
/* 吸附運算方式: e.g: space = 100; left point = 100; right point = 200; x = 161; calc process: 161+50 = 211 211/100 = 2 2x100=200 x = 149 calc process: 149+50 = 199 199/100 = 1 1x100 = 100 爲了保證運算方式的結果, 以int形式進行計算,運算 結果出來以後再轉換爲rate */
final int adsorbentAngle = 90;
final int orgAngle = (int) imageData.rotateAngle;
int toAngle = ((orgAngle + (adsorbentAngle / 2)) / adsorbentAngle) * adsorbentAngle;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(orgAngle, toAngle);
valueAnimator.setDuration(DEFAULT_ANIMATION_TIME);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
imageData.rotateAngle = (float) animation.getAnimatedValue();
reDraw();
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mViewMode = VIEW_MODE_IDLE;
}
});
valueAnimator.start();
}
複製代碼
吸附的運算原理在註釋中已經詳細距離說明了。這裏就再也不解釋了。傳參進來一個img的座標,使用ValueAnimator對其屬性進行改變,調用reDraw()方法便可完成一幀的動畫。
最後再來看看添加完吸附動畫以後的效果:
最後咱們到這裏基本的控制操做就完成了,還差最後一步,就是最終的圖片拼接。
android的View給咱們提供了getDrawingCache()方法來得到當前view的繪製界面,不過這個方法受不少因素影響,不能每次均可以調用成功,而且可能會發生不可預知的後續操做,開啓DrawingCache會產生性能影響。因此咱們本身建立一個Cavans,傳給onDraw()方法,讓其把當前最新的界面繪製到咱們傳給他的Cavans上面。代碼以下:
private Thread handleBitmapThread = new Thread(new Runnable() {
@Override
public void run() {
try {
outputBitmap = Bitmap.createBitmap(getMeasuredWidth(),
getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(outputBitmap);
draw(canvas);
generateBitmapHandler.sendEmptyMessage(BITMAP_GENERATE_RESULT);
} catch (Exception e) {
// 扔到主線程拋出
Message message = new Message();
message.what = BITMAP_GENERATE_ERROR;
Bundle bundle = new Bundle();
bundle.putSerializable(BITMAP_ERROR, e);
message.setData(bundle);
generateBitmapHandler.sendMessage(message);
}
}
});
複製代碼
使用本身建立的Cavans還能夠限定畫布大小,達到裁剪的目的。onDraw()方法執行完成以後,界面繪製到了咱們傳遞的Bitmap上面,就能夠把Bitmap拋出給處理方法來實現顯示或者存儲等一系列操做。
本文代碼:github.com/Kongdy/Imag…
我的github地址:github.com/Kongdy
我的掘金主頁:juejin.im/user/595a64…
csdn主頁:blog.csdn.net/u014303003