package com.application.zhangshi_app_android.widget;
|
|
import android.animation.ObjectAnimator;
|
import android.content.Context;
|
import android.graphics.Canvas;
|
import android.util.AttributeSet;
|
import android.view.GestureDetector;
|
import android.view.LayoutInflater;
|
import android.view.MotionEvent;
|
import android.view.ScaleGestureDetector;
|
import android.view.View;
|
import android.widget.OverScroller;
|
import android.widget.RelativeLayout;
|
|
import androidx.annotation.Nullable;
|
|
import com.application.zhangshi_app_android.R;
|
|
/**
|
* @author Ljj
|
* @date 2023.04.08. 20:03
|
* @desc
|
*/
|
public class TreeView extends RelativeLayout {
|
|
private Context mContext;
|
// 最小缩放比例
|
float smallScale = 0f;
|
// 正常缩放比例
|
float normalScale = 0f;
|
// 最大缩放比例
|
float bigScale = 0f;
|
|
// 当前比例
|
float currentScale = 0f;
|
|
// 缩放倍数
|
private static final float ZOOM_SCALE = 2f;
|
private final View treeView;
|
private final GestureDetector mGestureDetector;
|
// 双指操作
|
private final ScaleGestureDetector mScaleGestureDetector;
|
// 惯性滑动
|
private final OverScroller mOverScroller;
|
private final FlingRunner mFlingRunner;
|
// 是否放大 [默认是缩小,双击改变状态]
|
boolean isZoomIn = false;
|
// 放大后手指移动位置
|
private final OffSet moveOffset = new OffSet();
|
private boolean isDoubleClickZoomIn = false;
|
|
public TreeView(Context context) {
|
this(context, null);
|
}
|
|
public TreeView(Context context, @Nullable AttributeSet attrs) {
|
this(context, attrs, 0);
|
}
|
|
public TreeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
this(context, attrs, defStyleAttr, 0);
|
}
|
|
public TreeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
super(context, attrs, defStyleAttr, defStyleRes);
|
setWillNotDraw(false);
|
mContext = context;
|
treeView = LayoutInflater.from(context).inflate(R.layout.layout_tree_view,this,false);
|
LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
layoutParams.alignWithParent = true;
|
layoutParams.addRule(CENTER_IN_PARENT);
|
addView(treeView,layoutParams);
|
mGestureDetector = new GestureDetector(context, new MyGestureListener());
|
mScaleGestureDetector = new ScaleGestureDetector(context, new MyScaleGestureListener());
|
mOverScroller = new OverScroller(context);
|
mFlingRunner = new FlingRunner();
|
}
|
|
@Override
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
super.onSizeChanged(w, h, oldw, oldh);
|
|
// view比例
|
float viewScale = (float) getWidth() / (float) getHeight();
|
|
// 图片比例
|
float bitScale = (float) treeView.getMeasuredWidth() / (float) treeView.getMeasuredHeight();
|
|
// // 如果图片比例大于view比例
|
// if (bitScale > viewScale) {
|
// // 横向图片
|
// smallScale = (float) getWidth() / (float) treeView.getMeasuredWidth();
|
// bigScale = (float) getHeight() / (float) treeView.getMeasuredHeight() * ZOOM_SCALE;
|
// } else {
|
// // 纵向图片
|
// smallScale = (float) getHeight() / (float) treeView.getMeasuredHeight();
|
// bigScale = (float) getWidth() / (float) treeView.getMeasuredWidth() * ZOOM_SCALE;
|
// }
|
|
smallScale = (float) getWidth() / (float) treeView.getMeasuredWidth();
|
smallScale = (float) getHeight() / (float) treeView.getMeasuredHeight();
|
bigScale = (float) getHeight() / (float) treeView.getMeasuredHeight() * ZOOM_SCALE;
|
|
// 当前缩放比例 = 缩放前的比例
|
currentScale = smallScale;
|
}
|
|
@Override
|
protected void onDraw(Canvas canvas) {
|
super.onDraw(canvas);
|
canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
|
// 移动画布
|
if (isDoubleClickZoomIn){
|
if (currentScale <= bigScale) {//双击改变状态时偏移量随着缩放比例变化
|
float a = (currentScale - smallScale) / (bigScale - smallScale);
|
moveOffset.setPhyX(moveOffset.getPhyX() * a);
|
moveOffset.setPhyY(moveOffset.getPhyY() * a);
|
}
|
}
|
canvas.translate(moveOffset.getLogX(currentScale), moveOffset.getLogY(currentScale));
|
}
|
|
@Override
|
public boolean onTouchEvent(MotionEvent event) {
|
// 双指操作
|
boolean scaleTouchEvent = mScaleGestureDetector.onTouchEvent(event);
|
// 是否是双指操作
|
if (mScaleGestureDetector.isInProgress()) {
|
return scaleTouchEvent;
|
}
|
// 双击操作
|
return mGestureDetector.onTouchEvent(event);
|
}
|
|
private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
|
// 单击情况 : 抬起[ACTION_UP]时候触发
|
// 双击情况 : 第二次抬起[ACTION_POINTER_UP]时候触发
|
@Override
|
public boolean onSingleTapUp(MotionEvent e) {
|
return super.onSingleTapUp(e);
|
}
|
|
// 长按时触发 [300ms]
|
@Override
|
public void onLongPress(MotionEvent e) {
|
super.onLongPress(e);
|
}
|
|
// 滑动时候触发 类似 ACTION_MOVE 事件
|
@Override
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
// 如果是放大状态才能移动
|
// if (isZoomIn) {
|
moveOffset.setPhyX(moveOffset.getPhyX() - distanceX);
|
moveOffset.setPhyY(moveOffset.getPhyY() - distanceY);
|
// 修正偏移量,防止图片移动时边缘出现白色背景
|
fixOffset();
|
|
invalidate();
|
// }
|
return super.onScroll(e1, e2, distanceX, distanceY);
|
}
|
|
// 滑翔/飞翔 [惯性滑动]
|
@Override
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
// mOverScroller.fling(
|
// (int) moveOffset.getX(),
|
// (int) moveOffset.getY(),
|
// (int) velocityX,
|
// (int) velocityY,
|
// (int) (-(treeView.getMeasuredWidth() * bigScale - getWidth()) / 2),
|
// (int) ((treeView.getMeasuredHeight() * bigScale - getWidth()) / 2),
|
// (int) (-(treeView.getMeasuredHeight() * bigScale - getHeight()) / 2),
|
// (int) ((treeView.getMeasuredHeight() * bigScale - getHeight()) / 2),
|
// 300,
|
// 300
|
// );
|
// // 设置fling效果
|
// mFlingRunner.run();
|
return super.onFling(e1, e2, velocityX, velocityY);
|
}
|
|
// 延时触发 [100ms] -- 常用与水波纹等效果
|
@Override
|
public void onShowPress(MotionEvent e) {
|
super.onShowPress(e);
|
}
|
|
// 按下 这里必须返回true 因为所有事件都是由按下出发的
|
@Override
|
public boolean onDown(MotionEvent e) {
|
return true;
|
}
|
|
// 双击 -- 第二次按下时候触发 (40ms - 300ms) [小于40ms是为了防止抖动]
|
@Override
|
public boolean onDoubleTap(MotionEvent e) {
|
if (isDoubleClickZoomIn){
|
// 判断双击的位置是否在treeView上
|
float fixedWidth = treeView.getWidth()*(1-currentScale)/2;
|
float fixedHeight = treeView.getHeight()*(1-currentScale)/2;
|
if (treeView.getLeft()+fixedWidth <= e.getX() && e.getX() <= treeView.getRight()-fixedWidth && treeView.getTop()+fixedHeight <= e.getY() && e.getY() <= treeView.getBottom()-fixedHeight){
|
// 先改为放大,第一次双击是放大效果
|
isZoomIn = !isZoomIn;
|
if (isZoomIn) {
|
// 双击放大时定位到放大后双击的位置
|
// float currentX = e.getX() - (float) treeView.getWidth() / 2f;
|
// float currentY = e.getY() - (float) treeView.getHeight() / 2f;
|
// moveOffset.setX(currentX - currentX * bigScale);
|
// moveOffset.setY(currentY - currentY * bigScale);
|
// // 修正偏移量,防止图片移动时边缘出现白色背景
|
// fixOffset();
|
|
// 放大 放大到最大比例
|
//添加动画,更加流畅
|
scaleAnimation(currentScale, bigScale).start();
|
} else {
|
// 缩小 缩小为原先留白的比例
|
scaleAnimation(bigScale, smallScale).start();
|
}
|
}
|
}
|
return super.onDoubleTap(e);
|
}
|
|
// 双击 第二次的事件处理 DOWN MOVE UP 都会执行到这里
|
@Override
|
public boolean onDoubleTapEvent(MotionEvent e) {
|
return super.onDoubleTapEvent(e);
|
}
|
|
// 单击时触发 双击时不触发
|
@Override
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
return super.onSingleTapConfirmed(e);
|
}
|
}
|
// 缩放动画
|
public ObjectAnimator scaleAnimation(float start, float end) {
|
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "currentScale", start, end);
|
// 动画时间
|
animator.setDuration(500);
|
return animator;
|
}
|
|
// 属性动画的关键!! 内部通过反射调用set方法来赋值
|
public void setCurrentScale(float currentScale) {
|
this.currentScale = currentScale;
|
invalidate();
|
}
|
|
public void fixOffset() {
|
// 当前图片放大后的宽
|
float currentWidth = treeView.getMeasuredWidth() * currentScale;
|
// 当前图片放大后的高
|
float currentHeight = treeView.getMeasuredHeight() * currentScale;
|
|
System.out.println("currentScale = " + currentScale);
|
System.out.println("moveOffset.getLogX(currentScale) = " + moveOffset.getLogX(currentScale));
|
System.out.println("moveOffset.getLogY(currentScale) = " + moveOffset.getLogY(currentScale));
|
System.out.println("moveOffset.getPhyX() = " + moveOffset.getPhyX());
|
System.out.println("moveOffset.getPhyY() = " + moveOffset.getPhyY());
|
System.out.println("currentWidth = " + currentWidth);
|
System.out.println("currentHeight = " + currentHeight);
|
System.out.println(-(currentWidth - getWidth()) / 2 / bigScale);
|
System.out.println(-(currentWidth - getWidth()) / 2 / currentScale);
|
System.out.println(-(currentWidth - getWidth()) / 2 );
|
System.out.println(moveOffset.getPhyX());
|
// 右侧限制
|
moveOffset.setPhyX(Math.max(moveOffset.getPhyX(), -(currentWidth - getWidth()) / 2));
|
|
// 左侧限制 [左侧moveOffset.getX()为负数]
|
moveOffset.setPhyX(Math.min(moveOffset.getPhyX(), (currentWidth - getWidth()) / 2 ));
|
|
// 下侧限制
|
moveOffset.setPhyY(Math.max(moveOffset.getPhyY(), -(currentHeight - getHeight()) / 2 ));
|
|
// 上侧限制 [上侧moveOffset.getY()为负数]
|
moveOffset.setPhyY(Math.min(moveOffset.getPhyY(), (currentHeight - getHeight()) / 2 ));
|
}
|
|
class MyScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
|
// 在双指操作开始时候获取当前缩放值
|
private float scaleFactor = 0f;
|
|
|
// 双指操作
|
@Override
|
public boolean onScale(ScaleGestureDetector detector) {
|
// detector.getScaleFactor 缩放因子
|
currentScale = scaleFactor * detector.getScaleFactor();
|
|
// 刷新
|
invalidate();
|
return false;
|
}
|
|
// 双指操作开始
|
@Override
|
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
scaleFactor = currentScale;
|
// 注意这里要为true 表示开始双指操作
|
return true;
|
}
|
|
// 双指操作结束
|
@Override
|
public void onScaleEnd(ScaleGestureDetector detector) {
|
// 当前图片宽
|
float currentWidth = treeView.getMeasuredWidth() * currentScale;
|
// 缩放前的图片宽
|
float smallWidth = treeView.getMeasuredWidth() * smallScale;
|
// 缩放后的图片宽
|
float bigWidth = treeView.getMeasuredWidth() * bigScale;
|
|
// 如果当前图片 < 缩放前的图片
|
if (currentWidth < smallWidth) {
|
// 图片缩小
|
isZoomIn = false;
|
scaleAnimation(currentScale, smallScale).start();
|
}else if (currentWidth > bigWidth) {// 如果当前状态 > 缩放后的图片 那么就让他改变为最大的状态
|
scaleAnimation(currentScale, bigScale).start();
|
// 双击时候 图片放大
|
isZoomIn = true;
|
} else{
|
// 图片缩小
|
isZoomIn = true;
|
}
|
|
|
}
|
}
|
|
// 惯性滑动辅助
|
class FlingRunner implements Runnable {
|
|
@Override
|
public void run() {
|
// 判断当前是否是执行
|
if (mOverScroller.computeScrollOffset()) {
|
// 设置fling的值
|
moveOffset.setPhyX(mOverScroller.getCurrX());
|
moveOffset.setPhyY(mOverScroller.getCurrY());
|
|
// 继续执行FlingRunner.run
|
postOnAnimation(this);
|
// 刷新
|
invalidate();
|
}
|
}
|
}
|
|
|
private static class OffSet {
|
float phyX;
|
float phyY;
|
|
|
public OffSet() {
|
}
|
|
public float getPhyX() {
|
return phyX;
|
}
|
|
public void setPhyX(float phyX) {
|
this.phyX = phyX;
|
}
|
|
public float getPhyY() {
|
return phyY;
|
}
|
|
public void setPhyY(float phyY) {
|
this.phyY = phyY;
|
}
|
|
public float getLogX(float scale) {
|
return phyX / scale;
|
}
|
|
public float getLogY(float scale) {
|
return phyY / scale;
|
}
|
|
}
|
|
/**
|
* 是否双击放大
|
*/
|
public void setDoubleClickZoomIn(boolean doubleClickZoomIn) {
|
isDoubleClickZoomIn = doubleClickZoomIn;
|
}
|
}
|