NekoX/TMessagesProj/src/main/java/org/telegram/ui/PinchToZoomHelper.java

725 lines
28 KiB
Java

package org.telegram.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.FrameLayout;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.ImageLocation;
import org.telegram.messenger.ImageReceiver;
import org.telegram.messenger.MediaController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.R;
import org.telegram.messenger.WebFile;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Components.AnimatedFileDrawable;
import org.telegram.ui.Components.BackupImageView;
import org.telegram.ui.Components.CubicBezierInterpolator;
import org.telegram.ui.Components.LayoutHelper;
public class PinchToZoomHelper {
private final ViewGroup parentView;
private final ViewGroup fragmentView;
private ZoomOverlayView overlayView;
private View child;
private ImageReceiver childImage;
private ImageReceiver fullImage = new ImageReceiver();
private boolean inOverlayMode;
float parentOffsetX;
float parentOffsetY;
float fragmentOffsetX;
float fragmentOffsetY;
float pinchCenterX;
float pinchCenterY;
private float imageX;
private float imageY;
private float imageHeight;
private float imageWidth;
private float fullImageHeight;
private float fullImageWidth;
private float finishProgress;
private float progressToFullView;
ValueAnimator finishTransition;
private MessageObject messageObject;
Callback callback;
ClipBoundsListener clipBoundsListener;
float pinchStartCenterX;
float pinchStartCenterY;
float pinchStartDistance;
float pinchTranslationX;
float pinchTranslationY;
boolean isInPinchToZoomTouchMode;
private int pointerId1, pointerId2;
float pinchScale;
private float enterProgress;
private float[] clipTopBottom = new float[2];
private boolean isHardwareVideo;
public PinchToZoomHelper(ViewGroup parentView, ViewGroup fragmentView) {
this.parentView = parentView;
this.fragmentView = fragmentView;
}
public void startZoom(View child, ImageReceiver image, MessageObject messageObject) {
this.child = child;
this.messageObject = messageObject;
if (overlayView == null) {
overlayView = new ZoomOverlayView(parentView.getContext());
overlayView.setFocusable(false);
overlayView.setFocusableInTouchMode(false);
overlayView.setEnabled(false);
}
if (fullImage == null) {
fullImage = new ImageReceiver();
fullImage.setCrossfadeAlpha((byte) 2);
fullImage.setCrossfadeWithOldImage(false);
fullImage.onAttachedToWindow();
}
inOverlayMode = true;
parentView.addView(overlayView);
finishProgress = 1f;
progressToFullView = 0f;
setFullImage(messageObject);
imageX = image.getImageX();
imageY = image.getImageY();
imageHeight = image.getImageHeight();
imageWidth = image.getImageWidth();
fullImageHeight = image.getBitmapHeight();
fullImageWidth = image.getBitmapWidth();
if (fullImageHeight / fullImageWidth != imageHeight / imageWidth) {
if (fullImageHeight / fullImageWidth < imageHeight / imageWidth) {
fullImageWidth = fullImageWidth / fullImageHeight * imageHeight;
fullImageHeight = imageHeight;
} else {
fullImageHeight = fullImageHeight / fullImageWidth * imageWidth;
fullImageWidth = imageWidth;
}
} else {
fullImageHeight = imageHeight;
fullImageWidth = imageWidth;
}
if (messageObject != null && messageObject.isVideo() && MediaController.getInstance().isPlayingMessage(messageObject)) {
isHardwareVideo = true;
MediaController.getInstance().setTextureView(overlayView.videoTextureView, overlayView.aspectRatioFrameLayout, overlayView.videoPlayerContainer, true);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) overlayView.videoPlayerContainer.getLayoutParams();
overlayView.videoPlayerContainer.setTag(R.id.parent_tag, image);
if (layoutParams.width != image.getImageWidth() || layoutParams.height != image.getImageHeight()) {
overlayView.aspectRatioFrameLayout.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL);
layoutParams.width = (int) image.getImageWidth();
layoutParams.height = (int) image.getImageHeight();
overlayView.videoPlayerContainer.setLayoutParams(layoutParams);
}
overlayView.videoTextureView.setScaleX(1f);
overlayView.videoTextureView.setScaleY(1f);
if (callback != null) {
overlayView.backupImageView.setImageBitmap(callback.getCurrentTextureView().getBitmap((int) fullImageWidth, (int) fullImageHeight));
overlayView.backupImageView.setSize((int) fullImageWidth, (int) fullImageHeight);
overlayView.backupImageView.getImageReceiver().setRoundRadius(image.getRoundRadius());
}
overlayView.videoPlayerContainer.setVisibility(View.VISIBLE);
} else {
isHardwareVideo = false;
this.childImage = new ImageReceiver();
this.childImage.onAttachedToWindow();
Drawable drawable = image.getDrawable();
this.childImage.setImageBitmap(drawable);
if (drawable instanceof AnimatedFileDrawable) {
((AnimatedFileDrawable) drawable).addSecondParentView(overlayView);
((AnimatedFileDrawable) drawable).setInvalidateParentViewWithSecond(true);
}
this.childImage.setImageCoords(imageX, imageY, imageWidth, imageHeight);
this.childImage.setRoundRadius(image.getRoundRadius());
this.fullImage.setRoundRadius(image.getRoundRadius());
overlayView.videoPlayerContainer.setVisibility(View.GONE);
}
if (callback != null) {
callback.onZoomStarted(messageObject);
}
enterProgress = 0f;
}
private void setFullImage(MessageObject messageObject) {
if (messageObject == null) {
return;
}
if (!messageObject.isPhoto()) {
return;
}
int[] size = new int[1];
ImageLocation imageLocation = getImageLocation(messageObject, size);
if (imageLocation != null) {
boolean cacheOnly = messageObject != null && messageObject.isWebpage();
Object parentObject;
parentObject = messageObject;
String filter = null;
fullImage.setImage(imageLocation, filter, null, null, null, size[0], null, parentObject, cacheOnly ? 1 : 0);
fullImage.setCrossfadeAlpha((byte) 2);
}
updateViewsLocation();
}
private boolean updateViewsLocation() {
float parentOffsetX = 0;
float parentOffsetY = 0;
View currentView = child;
while (currentView != parentView) {
if (currentView == null) {
return false;
}
parentOffsetX += currentView.getLeft();
parentOffsetY += currentView.getTop();
currentView = (View) currentView.getParent();
}
float fragmentOffsetX = 0;
float fragmentOffsetY = 0;
currentView = child;
while (currentView != fragmentView) {
if (currentView == null) {
return false;
}
fragmentOffsetX += currentView.getLeft();
fragmentOffsetY += currentView.getTop();
currentView = (View) currentView.getParent();
}
this.fragmentOffsetX = fragmentOffsetX;
this.fragmentOffsetY = fragmentOffsetY;
this.parentOffsetX = parentOffsetX;
this.parentOffsetY = parentOffsetY;
return true;
}
public void finishZoom() {
if (finishTransition != null || !inOverlayMode) {
return;
}
if (!updateViewsLocation()) {
clear();
}
finishTransition = ValueAnimator.ofFloat(1f, 0);
finishTransition.addUpdateListener(valueAnimator -> {
finishProgress = (float) valueAnimator.getAnimatedValue();
invalidateViews();
});
finishTransition.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (finishTransition != null) {
finishTransition = null;
clear();
}
}
});
finishTransition.setDuration(220);
finishTransition.setInterpolator(CubicBezierInterpolator.DEFAULT);
finishTransition.start();
}
public void clear() {
if (inOverlayMode) {
if (callback != null) {
callback.onZoomFinished(messageObject);
}
inOverlayMode = false;
}
if (overlayView != null && overlayView.getParent() != null) {
parentView.removeView(overlayView);
overlayView.backupImageView.getImageReceiver().clearImage();
if (childImage != null) {
Drawable drawable = this.childImage.getDrawable();
if (drawable instanceof AnimatedFileDrawable) {
((AnimatedFileDrawable) drawable).removeSecondParentView(overlayView);
}
}
}
if (child != null) {
child.invalidate();
child = null;
}
if (childImage != null) {
this.childImage.onDetachedFromWindow();
this.childImage.clearImage();
this.childImage = null;
}
if (fullImage != null) {
fullImage.onDetachedFromWindow();
fullImage.clearImage();
fullImage = null;
}
messageObject = null;
}
public boolean inOverlayMode() {
return inOverlayMode;
}
public boolean isInOverlayMode() {
return inOverlayMode;
}
public boolean isInOverlayModeFor(View child) {
return inOverlayMode && child == this.child;
}
public boolean onTouchEvent(MotionEvent ev) {
if (updateViewsLocation() && child != null) {
ev.offsetLocation(-fragmentOffsetX, -fragmentOffsetY);
return child.onTouchEvent(ev);
}
return false;
}
public Bitmap getVideoBitmap(int w, int h) {
if (overlayView == null) {
return null;
}
return overlayView.videoTextureView.getBitmap(w, h);
}
public ImageReceiver getPhotoImage() {
return childImage;
}
protected boolean zoomEnabled(View child, ImageReceiver receiver) {
Drawable drawable = receiver.getDrawable();
if (drawable instanceof AnimatedFileDrawable) {
if (((AnimatedFileDrawable)receiver.getDrawable()).isLoadingStream()) {
return false;
} else {
return true;
}
}
return receiver.hasNotThumb();
}
private class ZoomOverlayView extends FrameLayout {
private FrameLayout videoPlayerContainer;
private TextureView videoTextureView;
private AspectRatioFrameLayout aspectRatioFrameLayout;
private BackupImageView backupImageView;
private Path aspectPath = new Path();
private Paint aspectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public ZoomOverlayView(Context context) {
super(context);
if (Build.VERSION.SDK_INT >= 21) {
videoPlayerContainer = new FrameLayout(context);
videoPlayerContainer.setOutlineProvider(new ViewOutlineProvider() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void getOutline(View view, Outline outline) {
ImageReceiver imageReceiver = (ImageReceiver) view.getTag(R.id.parent_tag);
if (imageReceiver != null) {
int[] rad = imageReceiver.getRoundRadius();
int maxRad = 0;
for (int a = 0; a < 4; a++) {
maxRad = Math.max(maxRad, rad[a]);
}
outline.setRoundRect(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight(), maxRad);
} else {
outline.setOval(0, 0, AndroidUtilities.roundMessageSize, AndroidUtilities.roundMessageSize);
}
}
});
videoPlayerContainer.setClipToOutline(true);
} else {
videoPlayerContainer = new FrameLayout(context) {
RectF rect = new RectF();
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
aspectPath.reset();
ImageReceiver imageReceiver = (ImageReceiver) getTag(R.id.parent_tag);
if (imageReceiver != null) {
int[] rad = imageReceiver.getRoundRadius();
int maxRad = 0;
for (int a = 0; a < 4; a++) {
maxRad = Math.max(maxRad, rad[a]);
}
rect.set(0, 0, w, h);
aspectPath.addRoundRect(rect, AndroidUtilities.dp(4), AndroidUtilities.dp(4), Path.Direction.CW);
} else {
aspectPath.addCircle(w / 2, h / 2, w / 2, Path.Direction.CW);
}
aspectPath.toggleInverseFillType();
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility == VISIBLE) {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (getTag() == null) {
canvas.drawPath(aspectPath, aspectPaint);
}
}
};
aspectPath = new Path();
aspectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
aspectPaint.setColor(0xff000000);
aspectPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
backupImageView = new BackupImageView(context);
videoPlayerContainer.addView(backupImageView);
videoPlayerContainer.setWillNotDraw(false);
aspectRatioFrameLayout = new AspectRatioFrameLayout(context);
aspectRatioFrameLayout.setBackgroundColor(0);
videoPlayerContainer.addView(aspectRatioFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER));
videoTextureView = new TextureView(context);
videoTextureView.setOpaque(false);
aspectRatioFrameLayout.addView(videoTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
addView(videoPlayerContainer, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT));
setWillNotDraw(false);
// videoTextureView.setVisibility(GONE);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (finishTransition == null && enterProgress != 1f) {
enterProgress += 16f / 220;
if (enterProgress > 1f) {
enterProgress = 1f;
} else {
invalidateViews();
}
}
float progress = finishProgress * CubicBezierInterpolator.DEFAULT.getInterpolation(enterProgress);
float clipTop = 0;
float clipBottom = getMeasuredHeight();
if (progress != 1f && clipBoundsListener != null) {
clipBoundsListener.getClipTopBottom(clipTopBottom);
canvas.save();
clipTop = clipTopBottom[0] * (1f - progress);
clipBottom = clipTopBottom[1] * (1f - progress) + getMeasuredHeight() * progress;
canvas.clipRect(0, clipTop, getMeasuredWidth(), clipBottom);
drawImage(canvas);
super.dispatchDraw(canvas);
canvas.restore();
} else {
drawImage(canvas);
super.dispatchDraw(canvas);
}
float parentOffsetX = PinchToZoomHelper.this.parentOffsetX - getLeft();
float parentOffsetY = PinchToZoomHelper.this.parentOffsetY - getTop();
drawOverlays(canvas, (1f - progress), parentOffsetX, parentOffsetY, clipTop, clipBottom);
}
private void drawImage(Canvas canvas) {
if (!inOverlayMode || child == null || parentView == null) {
return;
}
updateViewsLocation();
float parentOffsetX = PinchToZoomHelper.this.parentOffsetX - getLeft();
float parentOffsetY = PinchToZoomHelper.this.parentOffsetY - getTop();
canvas.save();
float s = pinchScale * finishProgress + 1f * 1f - finishProgress;
canvas.scale(s, s, parentOffsetX + pinchCenterX, parentOffsetY + pinchCenterY);
canvas.translate(parentOffsetX + pinchTranslationX * finishProgress, parentOffsetY + pinchTranslationY * finishProgress);
if (fullImage != null && fullImage.hasNotThumb()) {
if (progressToFullView != 1) {
progressToFullView += 16f / 150f;
if (progressToFullView > 1) {
progressToFullView = 1f;
} else {
invalidateViews();
}
}
fullImage.setAlpha(progressToFullView);
}
float x = imageX;
float y = imageY;
if (imageHeight != fullImageHeight || imageWidth != fullImageWidth) {
float p;
if (s < 1f) {
p = 0;
} else if (s < 1.4f) {
p = (s - 1f) / 0.4f;
} else {
p = 1f;
}
float verticalPadding = (fullImageHeight - imageHeight) / 2f;
float horizontalPadding = (fullImageWidth - imageWidth) / 2f;
x = imageX - horizontalPadding * p;
y = imageY - verticalPadding * p;
if (childImage != null) {
childImage.setImageCoords(x, y, imageWidth + horizontalPadding * p * 2, imageHeight + verticalPadding * p * 2);
}
}
if (!isHardwareVideo) {
if (childImage != null) {
if (progressToFullView != 1f) {
childImage.draw(canvas);
fullImage.setImageCoords(childImage.getImageX(), childImage.getImageY(), childImage.getImageWidth(), childImage.getImageHeight());
fullImage.draw(canvas);
} else {
fullImage.setImageCoords(childImage.getImageX(), childImage.getImageY(), childImage.getImageWidth(), childImage.getImageHeight());
fullImage.draw(canvas);
}
}
} else {
videoPlayerContainer.setPivotX(pinchCenterX - imageX);
videoPlayerContainer.setPivotY(pinchCenterY - imageY);
videoPlayerContainer.setScaleY(s);
videoPlayerContainer.setScaleX(s);
videoPlayerContainer.setTranslationX(x + parentOffsetX + pinchTranslationX * s * finishProgress);
videoPlayerContainer.setTranslationY(y + parentOffsetY + pinchTranslationY* s * finishProgress);
}
canvas.restore();
}
}
protected void drawOverlays(Canvas canvas, float alpha, float parentOffsetX, float parentOffsetY, float clipTop, float clipBottom) {
}
private ImageLocation getImageLocation(MessageObject message, int[] size) {
if (message.messageOwner instanceof TLRPC.TL_messageService) {
if (message.messageOwner.action instanceof TLRPC.TL_messageActionUserUpdatedPhoto) {
return null;
} else {
TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize());
if (sizeFull != null) {
if (size != null) {
size[0] = sizeFull.size;
if (size[0] == 0) {
size[0] = -1;
}
}
return ImageLocation.getForObject(sizeFull, message.photoThumbsObject);
} else if (size != null) {
size[0] = -1;
}
}
} else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto && message.messageOwner.media.photo != null || message.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && message.messageOwner.media.webpage != null) {
if (message.isGif()) {
return ImageLocation.getForDocument(message.getDocument());
} else {
TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(message.photoThumbs, AndroidUtilities.getPhotoSize(), false, null, true);
if (sizeFull != null) {
if (size != null) {
size[0] = sizeFull.size;
if (size[0] == 0) {
size[0] = -1;
}
}
return ImageLocation.getForObject(sizeFull, message.photoThumbsObject);
} else if (size != null) {
size[0] = -1;
}
}
} else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) {
return ImageLocation.getForWebFile(WebFile.createWithWebDocument(((TLRPC.TL_messageMediaInvoice) message.messageOwner.media).photo));
} else if (message.getDocument() != null) {
TLRPC.Document document = message.getDocument();
if (MessageObject.isDocumentHasThumb(message.getDocument())) {
TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90);
if (size != null) {
size[0] = thumb.size;
if (size[0] == 0) {
size[0] = -1;
}
}
return ImageLocation.getForDocument(thumb, document);
}
}
return null;
}
public void setClipBoundsListener(ClipBoundsListener clipBoundsListener) {
this.clipBoundsListener = clipBoundsListener;
}
public interface Callback {
default TextureView getCurrentTextureView() {
return null;
}
default void onZoomStarted(MessageObject messageObject) {
}
default void onZoomFinished(MessageObject messageObject) {
}
}
public void setCallback(Callback callback) {
this.callback = callback;
}
public interface ClipBoundsListener {
void getClipTopBottom(float[] topBottom);
}
public boolean checkPinchToZoom(MotionEvent ev, View child, ImageReceiver image, MessageObject messageObject) {
if (!zoomEnabled(child, image)) {
return false;
}
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN || ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
if (!isInPinchToZoomTouchMode && ev.getPointerCount() == 2) {
pinchStartDistance = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0));
pinchStartCenterX = pinchCenterX = (ev.getX(0) + ev.getX(1)) / 2.0f;
pinchStartCenterY = pinchCenterY = (ev.getY(0) + ev.getY(1)) / 2.0f;
pinchScale = 1f;
pointerId1 = ev.getPointerId(0);
pointerId2 = ev.getPointerId(1);
isInPinchToZoomTouchMode = true;
}
} else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE && isInPinchToZoomTouchMode) {
int index1 = -1;
int index2 = -1;
for (int i = 0; i < ev.getPointerCount(); i++) {
if (pointerId1 == ev.getPointerId(i)) {
index1 = i;
}
if (pointerId2 == ev.getPointerId(i)) {
index2 = i;
}
}
if (index1 == -1 || index2 == -1) {
isInPinchToZoomTouchMode = false;
child.getParent().requestDisallowInterceptTouchEvent(false);
finishZoom();
return false;
}
pinchScale = (float) Math.hypot(ev.getX(index2) - ev.getX(index1), ev.getY(index2) - ev.getY(index1)) / pinchStartDistance;
if (pinchScale > 1.005f && !isInOverlayMode()) {
pinchStartDistance = (float) Math.hypot(ev.getX(index2) - ev.getX(index1), ev.getY(index2) - ev.getY(index1));
pinchStartCenterX = pinchCenterX = (ev.getX(index1) + ev.getX(index2)) / 2.0f;
pinchStartCenterY = pinchCenterY = (ev.getY(index1) + ev.getY(index2)) / 2.0f;
pinchScale = 1f;
pinchTranslationX = 0f;
pinchTranslationY = 0f;
child.getParent().requestDisallowInterceptTouchEvent(true);
startZoom(child, image, messageObject);
}
float newPinchCenterX = (ev.getX(index1) + ev.getX(index2)) / 2.0f;
float newPinchCenterY = (ev.getY(index1) + ev.getY(index2)) / 2.0f;
float moveDx = pinchStartCenterX - newPinchCenterX;
float moveDy = pinchStartCenterY - newPinchCenterY;
pinchTranslationX = -moveDx / pinchScale;
pinchTranslationY = -moveDy / pinchScale;
invalidateViews();
} else if ((ev.getActionMasked() == MotionEvent.ACTION_UP || (ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP && checkPointerIds(ev)) || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) && isInPinchToZoomTouchMode) {
isInPinchToZoomTouchMode = false;
child.getParent().requestDisallowInterceptTouchEvent(false);
finishZoom();
}
return isInOverlayModeFor(child);
}
private boolean checkPointerIds(MotionEvent ev) {
if (ev.getPointerCount() < 2) {
return false;
}
if (pointerId1 == ev.getPointerId(0) && pointerId2 == ev.getPointerId(1)) {
return true;
}
if (pointerId1 == ev.getPointerId(1) && pointerId2 == ev.getPointerId(0)) {
return true;
}
return false;
}
protected void invalidateViews() {
if (overlayView != null) {
overlayView.invalidate();
}
}
public View getChild() {
return child;
}
}