我已经使用 ScaleGesture 在屏幕中实现了缩放和平移功能,但工作并不顺利。在全屏模式下,我使用TextureView.SurfaceTextureListener 实现了此功能。我需要在屏幕中放大和平移功能而不破坏当前活动,因为使用
TextureView.SurfaceTextureListener
我们需要破坏屏幕来加载新视频。有什么方法可以使用像TextureView.SurfaceTextureListener这样的矩阵来实现缩放和平移功能。使用TextureView.SurfaceTextureListener功能工作非常顺利
这是我的代码
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
// binding.txtScaleVideo.visibility = View.VISIBLE
if (event!=null){
val maxDx:Float = 0f
val maxDy:Float = 0f
when(event.action and MotionEvent.ACTION_MASK){
MotionEvent.ACTION_DOWN -> {
Log.d("ModTouchEvent:::","ACTION_DOWN ------ $mode")
if (scale > MIN_ZOOM){
mode = Mode.DRAG
startX = event.x - previousDx
startY = event.y - previousDy
}
}
MotionEvent.ACTION_MOVE -> {
Log.d("ModTouchEvent:::","ACTION_MOVE ------ $mode")
isEnable = false
if (mode == Mode.DRAG) {
dx = event.x - startX
dy = event.y - startY
}
}
MotionEvent.ACTION_POINTER_DOWN ->{
Log.d("ModTouchEvent:::","ACTION_POINTER_DOWN ------ $mode")
mode = Mode.ZOOM
}
MotionEvent.ACTION_UP ->{
Log.d("ModTouchEvent:::","ACTION_UP ------ $mode")
previousDx = dx
previousDy = dy
mode = Mode.NONE
}
else -> {}
}
scaleGestureDetector?.onTouchEvent(event)
gestureDetector?.onTouchEvent(event)
if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM){
binding.zoomLayout.requestDisallowInterceptTouchEvent(true)
val maxDx = ((child().width * scale - child().width) ).coerceAtLeast(0f)
val maxDy = ((child().height * scale - child().height) ).coerceAtLeast(0f)
dx = dx.coerceIn(-maxDx, maxDx)
dy = dy.coerceIn(-maxDy, maxDy)
Log.d("ZoomInValues:::","DX -> $dx DY -> $dy")
applyScaleAndTranslation()
}
}
return true
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scaleFactor = detector.scaleFactor
if (lastScaleFactor == 0f || (sign(scaleFactor) == sign(lastScaleFactor))){
scale *= scaleFactor
scale = scale.coerceIn(MIN_ZOOM, MAX_ZOOM)
val threshold = 1f
dx = if (abs(dx) < threshold) 0f else dx
dy = if (abs(dy) < threshold) 0f else dy
var scaleVal = AppGlobal.roundTwoPlaces(scale.toDouble(), "#.#").toString()
if (!scaleVal.contains(".")){
scaleVal = "$scaleVal.0"
}
val height=binding.layoutPlayerView.measuredHeight
val width=binding.layoutPlayerView.measuredWidth
val drawBB= scale== 1.0.toFloat()
binding.layoutPlayerView.removeAllViews()
if (drawBB && (seekToPositions.toDouble() == videoStartseekToPositions)){
val bBoxList = startBbox
bBoxList?.let { bBoxList ->
val drawView = PaintView(requireActivity(), bBoxList, height, width);
binding.layoutPlayerView.removeAllViews()
binding.layoutPlayerView.addView(drawView)
}
}
val scaleText = "${scaleVal}"+" x"
binding.txtScaleVideo.text = scaleText.toString()
lastScaleFactor = scaleFactor
}
else{
lastScaleFactor = 0f
}
applyScaleAndTranslation()
return true
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) {
}
private fun applyScaleAndTranslation(){
child().scaleX = scale
child().scaleY = scale
child().translationX = dx
child().translationY = dy
}
private fun child() : View{
return zoomLayout(0)
}
private fun zoomLayout(i: Int):View{
return binding.videoView
}
enum class Mode{
DRAG,
ZOOM,
NONE
}
private inner class GestureDetector : android.view.GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if (isEnable){
isEnable = false
// binding.videoView?.useController = false
}
else{
isEnable = true
//videoView?.useController = true
}
return super.onSingleTapConfirmed(e)
}
override fun onDoubleTap(event: MotionEvent): Boolean {
if (event.x < (sWidth/2)){
intLeft = true
intRight = false
}
else if (event.x > (sWidth/2)){
intLeft = false
intRight = true
}
return super.onDoubleTap(event)
}
}
在此代码中,平移不像使用 TextureView.SurfaceTextureListener 和自定义
ZoomableTextureView
那样平滑。为了加载新视频,每次需要加载新视频时,我都需要调用 onSurfaceTextureDestroyed,但 onSurfaceTextureDestroyed 仅在 Activity 中调用 onDestroy 时调用。
public class ZoomableTextureView extends TextureView {
private static final String SUPERSTATE_KEY = "superState";
private static final String MIN_SCALE_KEY = "minScale";
private static final String MAX_SCALE_KEY = "maxScale";
private Context context;
private float minScale = 1f;
private float maxScale = 5f;
private float saveScale = 1f;
public void setMinScale(float scale) {
if (scale < 1.0f || scale > maxScale)
throw new RuntimeException("minScale can't be lower than 1 or larger than maxScale(" + maxScale + ")");
else minScale = scale;
}
public void setMaxScale(float scale) {
if (scale < 1.0f || scale < minScale)
throw new RuntimeException("maxScale can't be lower than 1 or minScale(" + minScale + ")");
else minScale = scale;
}
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private Matrix matrix = new Matrix();
private ScaleGestureDetector mScaleDetector;
private float[] m;
private PointF last = new PointF();
private PointF start = new PointF();
private float right, bottom;
public ZoomableTextureView(Context context) {
super(context);
this.context = context;
initView(null);
}
public ZoomableTextureView(final Context context, final AttributeSet attrs) {
super(context, attrs);
this.context = context;
initView(attrs);
}
public ZoomableTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
initView(attrs);
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(SUPERSTATE_KEY, super.onSaveInstanceState());
bundle.putFloat(MIN_SCALE_KEY, minScale);
bundle.putFloat(MAX_SCALE_KEY, maxScale);
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
this.minScale = bundle.getInt(MIN_SCALE_KEY);
this.minScale = bundle.getInt(MAX_SCALE_KEY);
state = bundle.getParcelable(SUPERSTATE_KEY);
}
super.onRestoreInstanceState(state);
}
private void initView(AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.ZoomableTextureView,
0, 0);
try {
minScale = a.getFloat(R.styleable.ZoomableTextureView_minScale, minScale);
maxScale = a.getFloat(R.styleable.ZoomableTextureView_maxScale, maxScale);
} finally {
a.recycle();
}
setOnTouchListener(new ZoomOnTouchListeners());
}
public void deAttachFromWindow() {
onDetachedFromWindow();
onFinishTemporaryDetach();
}
public void clearWindow() {
onDetachedFromWindow();
onFinishTemporaryDetach();
}
private class ZoomOnTouchListeners implements View.OnTouchListener {
public ZoomOnTouchListeners() {
super();
m = new float[9];
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mScaleDetector.onTouchEvent(motionEvent);
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
PointF curr = new PointF(motionEvent.getX(), motionEvent.getY());
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
last.set(motionEvent.getX(), motionEvent.getY());
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_UP:
mode = NONE;
break;
case MotionEvent.ACTION_POINTER_DOWN:
last.set(motionEvent.getX(), motionEvent.getY());
start.set(last);
mode = ZOOM;
break;
case MotionEvent.ACTION_MOVE:
if (mode == ZOOM || (mode == DRAG && saveScale > minScale)) {
float deltaX = curr.x - last.x;// x difference
float deltaY = curr.y - last.y;// y difference
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
matrix.postTranslate(deltaX, deltaY);
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
ZoomableTextureView.this.setTransform(matrix);
ZoomableTextureView.this.invalidate();
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = detector.getScaleFactor();
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
MutableLiveData<Float> zoomScale = AppGlobal.Companion.getZoomScale();
zoomScale.setValue(saveScale); // Set the new value for zoomScale
right = getWidth() * saveScale - getWidth();
bottom = getHeight() * saveScale - getHeight();
if (0 <= getWidth() || 0 <= getHeight()) { matrix.postScale(mScaleFactor,mScaleFactor,detector.getFocusX(),detector.getFocusY());
if (mScaleFactor < 1) {
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (0 < getWidth()) {
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
} else {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
}
}
}
} else {
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
}
}
return true;
}
}
}
}
你把事情搞得太复杂了,使用下面的代码。
xml 文件
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".zz">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<com.otaliastudios.zoom.ZoomSurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom|center_horizontal"
app:alignment="center_horizontal"
app:flingEnabled="true"
app:horizontalPanEnabled="true"
app:maxZoom="8"
app:maxZoomType="zoom"
app:minZoom="1"
app:minZoomType="zoom"
app:oneFingerScrollEnabled="true"
app:overPinchable="false"
app:overScrollHorizontal="false"
app:overScrollVertical="false"
app:scrollEnabled="true"
app:transformation="centerInside"
app:transformationGravity="auto"
app:twoFingersScrollEnabled="true"
app:verticalPanEnabled="true"
app:zoomEnabled="true" />
<androidx.media3.ui.PlayerControlView
android:id="@+id/player_control_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</RelativeLayout>
java 文件
import android.os.Bundle;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.MediaItem;
import androidx.media3.common.VideoSize;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.ui.PlayerControlView;
import com.otaliastudios.zoom.ZoomSurfaceView;
import org.jetbrains.annotations.NotNull;
public class zz extends AppCompatActivity {
private ExoPlayer mediaPlayer;
private ZoomSurfaceView surfaceView;
private PlayerControlView playerControlView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.zz);
surfaceView = findViewById(R.id.surface_view);
playerControlView = findViewById(R.id.player_control_view);
mediaPlayer = new ExoPlayer.Builder(this).build();
playerControlView.setPlayer(mediaPlayer);
MediaItem mediaItem=
MediaItem.fromUri("https://firebasestorage.googleapis.com/v0/b/test2-
5bbd8.appspot.com/o/chat%2Fvideoplayback.mp4?alt=media");
mediaPlayer.setMediaItem(mediaItem);
mediaPlayer.prepare();
mediaPlayer.play();
surfaceView.addCallback(new ZoomSurfaceView.Callback() {
@Override
public void onZoomSurfaceCreated(@NotNull ZoomSurfaceView
view) {
Surface surface = view.getSurface();
mediaPlayer.setVideoSurface(surface);
}
@Override
public void onZoomSurfaceDestroyed(@NotNull ZoomSurfaceView
view) {
}
});
mediaPlayer.addListener(new ExoPlayer.Listener() {
@Override
public void onVideoSizeChanged(@NonNull VideoSize videoSize)
{
surfaceView.setContentSize(videoSize.width,
videoSize.height);
}
});
};
};
基本上你使用一个名为 com.otaliastudios.zoom.ZoomSurfaceView 的库,它的作用就像一个可以缩放的表面视图,并设置此表面播放器控制视图,一切都完成了。