自定义手势缩放的Recyclerview
最近做了一个类似腾讯动漫的漫画的阅读器,用Recyclerview作为基础的控件展示漫画。因为漫画需要支持手势缩放,但是原生Recyclerview并不支持,而且开源的缩放Recyclerview也没有找到,只能自己造一个轮子。这篇文章记录了一些思路。
效果预览图:https://github.com/PortgasAce/ZoomRecyclerView
基本原理
通过重写Recyclerview的dispatchDraw()方法,操作canvas缩放和平移实现手势缩放功能。如果对矩阵熟练的话,可以给canvas设置矩阵实现,但是我不熟练。。所以只能通过最基本的canvas平移和缩放实现。
protected void dispatchDraw(@NonNull Canvas canvas) {
canvas.save();
canvas.translate(mTranX, mTranY);
canvas.scale(mScaleFactor, mScaleFactor);
// 所有子view都会缩放和平移
super.dispatchDraw(canvas);
canvas.restore();
}
计算过程
通过上面的代码片段可知,只需要x,y方向的偏移量(mTranx,mTranY)和缩放系数(mScaleFactor)就可以实现缩放。
偏移量的计算
偏移量与另一个值相关,就是缩放中心(双击的触摸点 或者 是双指触摸的中心)。设想一下双击屏幕的的左上角和右下角,缩放系数的值相同,但是偏移量不同,双击左上角的偏移量为(0,0),而右下角的偏移量则为(-MaxTranX,-MaxTranY)。
双击屏幕上一点的示例图如下:
∵
总偏移量:
MaxTranX = X1+X2 = W1(S2-S1)
MaxTranY = Y1+Y2 = H1(S2-S1)
X方向偏移量比总偏移量 等于 缩放中心比屏幕宽度
X1/MaxTranX = X1/W1(S2-S1) = Tx/W1
Y1/MaxTranY = Y1/H1(S2-S1) = Ty/H1
∴
X1 = W1(S2-S1)*(Tx/W1) = (S2-S1)*Tx
Y1 = H1(S2-21)*(Ty/H1) = (S2-S1)*Ty
最终A2点的坐标因为坐标系的原因需要加一个负号:
A2 = (-X1,-Y1) = (-(S2-S1)*Tx,-(S2-S1)*Ty)
缩放中心
双击缩放通过GestureDetector实现,缩放中心在onDoubleTap()方法中直接通过MotionEvent的getX()和getY()获取。
双指缩放通过ScaleDetector实现,缩放中心通过ScaleGestureDetector的getFocusX()和getFocusY()获取。
缩放系数
双击缩放时,如果当前的缩放系数不等于1则缩放系数为1,如果当前缩放系数为1,则缩放系数等于最大缩放系数。
双指缩放时,缩放系数为当前缩放系数 乘 onScale回调中detector.getScaleFactor()。
代码
/**
* 默认缩放比只能为1
* 缩放动画时长暂时没有根据缩放比例改动
*/
@SuppressWarnings("UnnecessaryLocalVariable")
@SuppressLint("ClickableViewAccessibility")
public class ZoomRecyclerView extends RecyclerView {
private static final String TAG = "999";
// constant
private static final int DEFAULT_SCALE_DURATION = 300;
private static final float DEFAULT_SCALE_FACTOR = 1.f;
private static final float DEFAULT_MAX_SCALE_FACTOR = 2.0f;
private static final float DEFAULT_MIN_SCALE_FACTOR = 0.5f;
private static final String PROPERTY_SCALE = "scale";
private static final String PROPERTY_TRANX = "tranX";
private static final String PROPERTY_TRANY = "tranY";
private static final float INVALID_TOUCH_POSITION = -1;
// touch detector
ScaleGestureDetector mScaleDetector;
GestureDetectorCompat mGestureDetector;
// draw param
float mViewWidth; // 宽度
float mViewHeight; // 高度
float mTranX; // x偏移量
float mTranY; // y偏移量
float mScaleFactor; // 缩放系数