1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.vdmdemo.client;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.graphics.drawable.Drawable;
22 import android.util.AttributeSet;
23 import android.view.MotionEvent;
24 import android.view.View;
25 
26 import androidx.annotation.StyleRes;
27 import androidx.recyclerview.widget.RecyclerView;
28 
29 import java.util.function.Consumer;
30 
31 /** Recycler view that can resize a child dynamically. */
32 public final class ClientView extends RecyclerView {
33 
34     private static final int MIN_SIZE = 100;
35     private int mMaxSize = 0;
36 
37     private boolean mIsResizing = false;
38     private Consumer<Rect> mResizeDoneCallback = null;
39     private Drawable mResizingRect = null;
40     private final Rect mResizingBounds = new Rect();
41     private float mResizeOffsetX = 0;
42     private float mResizeOffsetY = 0;
43 
ClientView(Context context)44     public ClientView(Context context) {
45         super(context);
46         init();
47     }
48 
ClientView(Context context, AttributeSet attrs)49     public ClientView(Context context, AttributeSet attrs) {
50         super(context, attrs);
51         init();
52     }
53 
ClientView(Context context, AttributeSet attrs, @StyleRes int defStyle)54     public ClientView(Context context, AttributeSet attrs, @StyleRes int defStyle) {
55         super(context, attrs, defStyle);
56         init();
57     }
58 
init()59     private void init() {
60         mResizingRect = getContext().getResources().getDrawable(R.drawable.resize_rect, null);
61     }
62 
startResizing(View viewToResize, MotionEvent origin, int maxSize, Consumer<Rect> callback)63     void startResizing(View viewToResize, MotionEvent origin, int maxSize,
64             Consumer<Rect> callback) {
65         mIsResizing = true;
66         mMaxSize = maxSize;
67         mResizeDoneCallback = callback;
68         viewToResize.getGlobalVisibleRect(mResizingBounds);
69         mResizingRect.setBounds(mResizingBounds);
70         getRootView().getOverlay().add(mResizingRect);
71         mResizeOffsetX = origin.getRawX() - mResizingBounds.right;
72         mResizeOffsetY = origin.getRawY() - mResizingBounds.top;
73     }
74 
stopResizing()75     private void stopResizing() {
76         if (!mIsResizing) {
77             return;
78         }
79         mIsResizing = false;
80         mResizeOffsetX = mResizeOffsetY = 0;
81         getRootView().getOverlay().clear();
82         if (mResizeDoneCallback != null) {
83             mResizeDoneCallback.accept(mResizingBounds);
84             mResizeDoneCallback = null;
85         }
86     }
87 
88     @Override
dispatchTouchEvent(MotionEvent ev)89     public boolean dispatchTouchEvent(MotionEvent ev) {
90         if (!mIsResizing) {
91             return super.dispatchTouchEvent(ev);
92         }
93         switch (ev.getAction()) {
94             case MotionEvent.ACTION_UP -> stopResizing();
95             case MotionEvent.ACTION_MOVE -> {
96                 mResizingBounds.right = (int) (ev.getRawX() - mResizeOffsetX);
97                 if (mResizingBounds.width() > mMaxSize) {
98                     mResizingBounds.right = mResizingBounds.left + mMaxSize;
99                 }
100                 if (mResizingBounds.width() < MIN_SIZE) {
101                     mResizingBounds.right = mResizingBounds.left + MIN_SIZE;
102                 }
103                 mResizingBounds.top = (int) (ev.getRawY() - mResizeOffsetY);
104                 if (mResizingBounds.height() > mMaxSize) {
105                     mResizingBounds.top = mResizingBounds.bottom - mMaxSize;
106                 }
107                 if (mResizingBounds.height() < MIN_SIZE) {
108                     mResizingBounds.top = mResizingBounds.bottom - MIN_SIZE;
109                 }
110                 mResizingRect.setBounds(mResizingBounds);
111             }
112         }
113         return true;
114     }
115 }
116