Linjiajia
2023-04-24 fcdddf8b9b34f9930bec454b5fffe41c0e33ba3c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
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;
    }
}