之前就一直覺得INS的視圖拖動縮放效果很酷炫,特地去網(wǎng)上搜索了下(本著不重復(fù)造輪子的想法),找到一個比較合適的。
地址是:https://github.com/okaybroda/ImageZoom
首先分析ins的視圖拖動效果,雙指放大以后,視圖View就會懸浮,跟著手指移動,進行縮放,效果是相當(dāng)?shù)目犰拧?br>
所以可以分成幾步來梳理:
1.雙指放上去以后有縮放動作,取出視圖View
2.根據(jù)雙指的操作,對視圖View進行放大縮小以及拖動
3.當(dāng)屏幕上的手指數(shù)小于2時,將視圖View放回原來的地方
基本上就是以上4個步驟,那就逐個分析。
一 取出視圖View
用過ins的同學(xué)都知道,不管是視頻還是圖片,都是可以拿出來縮放的,仔細觀察可以發(fā)現(xiàn)當(dāng)視圖View被拿出來的時候,原先的位置是一片空白的,隨后的移動縮放界面,除了把原來的位置上的View摳出來以后,并不影響其他,那么猜測應(yīng)該是新建了一個Act設(shè)置成透明的全屏背景加上原來的View進行展示或者是一個全屏的Dialog。那么如何把View摳出來呢?
ImageZoom這個項目里首先是將需要綁定的View進行setTag,回頭再根據(jù)這個tag找到對應(yīng)的View
/**
* Finds the view that has the R.id.zoomable tag and also contains the x and y coordinations
* of two pointers
*
* @param event MotionEvent that contains two pointers 帶兩個手指的手勢
* @param view View to find in 也就是我們需要縮放的View的ViewGroup
* @return zoomable View 我們需要縮放的View
*/
private View findZoomableView(MotionEvent event, View view) {
if (view instanceof ViewGroup) {
//首先獲得viewGoup,以及他的子View的個數(shù)
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
//獲得兩個手指所在坐標(biāo)點的對象pointerCoords1,pointerCoords2
MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
event.getPointerCoords(0, pointerCoords1);
MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
event.getPointerCoords(1, pointerCoords2);
//采取遞歸的方法尋找目標(biāo)view
for (int i = 0; i < childCount; i++) {
View child = viewGroup.getChildAt(i);
if (child.getTag(R.id.unzoomable) == null) {
Rect visibleRect = new Rect();
int location[] = new int[2];
child.getLocationOnScreen(location);
visibleRect.left = location[0];
visibleRect.top = location[1];
visibleRect.right = visibleRect.left + child.getWidth();
visibleRect.bottom = visibleRect.top + child.getHeight();
if (visibleRect.contains((int) pointerCoords1.x, (int) pointerCoords1.y) &&
visibleRect.contains((int) pointerCoords2.x, (int) pointerCoords2.y)) {
if (child.getTag(R.id.zoomable) != null)
return child;
else
return findZoomableView(event, child);
}
}
}
}
return null;
}
找到目標(biāo)View以后,
// 計算出view在界面上原來的坐標(biāo)
originalXY = new int[2];
view.getLocationInWindow(originalXY);
// 新建一個FrameLayout用來作為view的父布局
FrameLayout frameLayout = new FrameLayout(view.getContext());
// 這個視圖是用來當(dāng)用戶縮放的時候,控制背景的透明度的
darkView = new View(view.getContext());
darkView.setBackgroundColor(Color.BLACK);
darkView.setAlpha(0f);
// 將darkView添加到父布局當(dāng)中
frameLayout.addView(darkView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
// 創(chuàng)建一個dialog,用來展示縮放布局
dialog = new Dialog(activity,
android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
dialog.addContentView(frameLayout,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
dialog.show();
// 獲取zoomView的父布局,并且得到zoomview在布局中的index,以及zoomview的
// layoutparams
parentOfZoomableView = (ViewGroup) zoomableView.getParent();
viewIndex = parentOfZoomableView.indexOfChild(zoomableView);
this.zoomableViewLP = zoomableView.getLayoutParams();
// 將之前得到的參數(shù)設(shè)置到zoomview里面
zoomableViewFrameLP = new FrameLayout.LayoutParams(
view.getWidth(), view.getHeight());
zoomableViewFrameLP.leftMargin = originalXY[0];
zoomableViewFrameLP.topMargin = originalXY[1];
// 創(chuàng)建一個臨時的view,用來代替zoomview原來的位置
placeholderView = new View(activity);
// 把placeholdeView的背景設(shè)置成zoomview的緩存視圖
// 這樣可以避免在添加和刪除視圖時,造成閃爍
zoomableView.setDrawingCacheEnabled(true);
BitmapDrawable placeholderDrawable = new BitmapDrawable(
activity.getResources(),
Bitmap.createBitmap(zoomableView.getDrawingCache()));
if (Build.VERSION.SDK_INT >= 16) {
placeholderView.setBackground(placeholderDrawable);
} else {
placeholderView.setBackgroundDrawable(placeholderDrawable);
}
// placeholderView暫時替代zoomview
parentOfZoomableView.addView(placeholderView, zoomableViewLP);
// 在添加到Framelayout之前,要先把zoomview從父布局移除
parentOfZoomableView.removeView(zoomableView);
frameLayout.addView(zoomableView, zoomableViewFrameLP);
// 利用zoomview的post方法移除placeholderview的緩存視圖
zoomableView.post(new Runnable() {
@Override
public void run() {
if (dialog != null) {
if (Build.VERSION.SDK_INT >= 16) {
placeholderView.setBackground(null);
} else {
placeholderView.setBackgroundDrawable(null);
}
zoomableView.setDrawingCacheEnabled(false);
}
}
});
// 創(chuàng)建對象pointerCoords1,pointerCoords2,得到兩個手指的坐標(biāo)位置
MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
ev.getPointerCoords(0, pointerCoords1);
MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
ev.getPointerCoords(1, pointerCoords2);
// 計算出兩個坐標(biāo)點之間的距離,后面可以用來計算縮放大小
originalDistance = (int) getDistance(pointerCoords1.x, pointerCoords2.x,
pointerCoords1.y, pointerCoords2.y);
// 計算出兩個坐標(biāo)對應(yīng)的矩形的中心點位,后面可以根據(jù)這個點位來移動視圖
twoPointCenter = new int[]{
(int) ((pointerCoords2.x + pointerCoords1.x) / 2),
(int) ((pointerCoords2.y + pointerCoords1.y) / 2)
};
//計算出手指移動的距離,用來縮放移動視圖
pivotX = (int) ev.getRawX() - originalXY[0];
pivotY = (int) ev.getRawY() - originalXY[1];
sendZoomEventToListeners(zoomableView, true);
return true;
上面的注釋已經(jīng)寫得很詳細了,當(dāng)我們的兩個手指放在屏幕上時,第一次尋找zoomview時,簡單來說,就是摳出zommview,放到新的布局里面,然后創(chuàng)建一個view,臨時代替zoomview原來的位置.下面我們看看zoomview找到以后,具體又是怎么操作進行縮放移動的。
//首先還是先拿到兩個手指的位置,后面用來算出兩個手指的中心點位newCenter
MotionEvent.PointerCoords pointerCoords1 = new MotionEvent.PointerCoords();
ev.getPointerCoords(0, pointerCoords1);
MotionEvent.PointerCoords pointerCoords2 = new MotionEvent.PointerCoords();
ev.getPointerCoords(1, pointerCoords2);
int[] newCenter = new int[]{
(int) ((pointerCoords2.x + pointerCoords1.x) / 2),
(int) ((pointerCoords2.y + pointerCoords1.y) / 2)
};
//通過跟剛才初始距離的比較,計算出現(xiàn)在的縮放百分比
int currentDistance = (int) getDistance(pointerCoords1.x, pointerCoords2.x,
pointerCoords1.y, pointerCoords2.y);
double pctIncrease = (currentDistance - originalDistance) / originalDistance;
//設(shè)置zoomview的縮放中心點為剛才手指頭剛放上去的中心點
zoomableView.setPivotX(pivotX);
zoomableView.setPivotY(pivotY);
//設(shè)置zoomview的縮放比例
zoomableView.setScaleX((float) (1 + pctIncrease));
zoomableView.setScaleY((float) (1 + pctIncrease));
//更新zoomview的邊距
updateZoomableViewMargins(newCenter[0] - twoPointCenter[0] + originalXY[0],
newCenter[1] - twoPointCenter[1] + originalXY[1]);
//設(shè)置darkview的透明度
darkView.setAlpha((float) (pctIncrease / 8));
找到zoomview以后的工作量已經(jīng)不多了,就是通過計算出兩個手指頭最新的距離,跟原始距離進行比較,算出縮放的百分比以后,利用view的自帶方法,進行縮放,并且設(shè)置darkview的透明度。此時手指拖動摳出目標(biāo)view,并且對view進行縮放已經(jīng)分析完成了。接下來就分析手指頭釋放以后,將view設(shè)置回原來的位置,并且有一個回彈的動畫效果。
//首先肯定是zoomview不為空,并且動畫需要展示的情況,才允許接下來的操作
if (zoomableView != null && !isAnimatingDismiss) {
isAnimatingDismiss = true;
//先拿到zoomview的最終縮放大小,邊距,以及darkview的最終透明度
final float scaleYStart = zoomableView.getScaleY();
final float scaleXStart = zoomableView.getScaleX();
final int leftMarginStart = zoomableViewFrameLP.leftMargin;
final int topMarginStart = zoomableViewFrameLP.topMargin;
final float alphaStart = darkView.getAlpha();
//下面就是沒有進行縮放之前的參數(shù),包括縮放大小,邊距,darkview的透明度。
final float scaleYEnd = 1f;
final float scaleXEnd = 1f;
final int leftMarginEnd = originalXY[0];
final int topMarginEnd = originalXY[1];
final float alphaEnd = 0f;
//利用valueAnimator展示動畫,就是根據(jù)動畫的進行的百分比,進行百分比的縮放等回彈
//相信有一點基礎(chǔ)的Android開發(fā)同學(xué)都是懂得的,就不過多分析了
final ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(activity.getResources().getInteger
(android.R.integer.config_shortAnimTime));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedFraction = valueAnimator.getAnimatedFraction();
if (zoomableView != null) {
updateZoomableView(animatedFraction, scaleYStart, scaleXStart,
leftMarginStart, topMarginStart,
scaleXEnd, scaleYEnd, leftMarginEnd, topMarginEnd);
}
if (darkView != null) {
darkView.setAlpha(((alphaEnd - alphaStart) * animatedFraction) +
alphaStart);
}
}
});
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
end();
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
end();
}
//當(dāng)前view已經(jīng)縮放結(jié)束,恢復(fù)初始的一些狀態(tài),并且
void end() {
if (zoomableView != null) {
updateZoomableView(1f, scaleYStart, scaleXStart,
leftMarginStart, topMarginStart,
scaleXEnd, scaleYEnd, leftMarginEnd, topMarginEnd);
}
dismissDialogAndViews();
valueAnimator.removeAllListeners();
valueAnimator.removeAllUpdateListeners();
}
});
valueAnimator.start();
return true;
}
到這里基本就已經(jīng)分析完了,還有最后一個方法要分析dismissDialogAndViews(),隱藏dialog并且恢復(fù)zoomview在原來act上面的位置。
//回調(diào)通知縮放結(jié)束了
sendZoomEventToListeners(zoomableView, false);
if (zoomableView != null) {
//將zoomview從framelayout中摳出來,重新添加到之前的父布局當(dāng)中去
zoomableView.setVisibility(View.VISIBLE);
zoomableView.setDrawingCacheEnabled(true);
BitmapDrawable placeholderDrawable = new BitmapDrawable(
zoomableView.getResources(),
Bitmap.createBitmap(zoomableView.getDrawingCache()));
if (Build.VERSION.SDK_INT >= 16) {
placeholderView.setBackground(placeholderDrawable);
} else {
placeholderView.setBackgroundDrawable(placeholderDrawable);
}
ViewGroup parent = (ViewGroup) zoomableView.getParent();
parent.removeView(zoomableView);
//利用剛才拿到的viewIndex,準(zhǔn)確的添加回原來的位置
this.parentOfZoomableView.addView(zoomableView, viewIndex, zoomableViewLP);
this.parentOfZoomableView.removeView(placeholderView);
final View finalZoomView = zoomableView;
zoomableView.setDrawingCacheEnabled(false);
dismissDialog();
finalZoomView.invalidate();
} else {
dismissDialog();
}
isAnimatingDismiss = false;
終于分析完了,其他的一些相信都能看的懂,有疑問的地方的再留言,大家一起討論。