本篇续:

第一站小红书图片裁剪控件,深度解析大厂炫酷控件

先来看看几张效果图:

emmmm,想感受高清丝滑的动画效果,有以下两种方式:

https://github.com/HpWens/MeiWidgetView 欢迎Star

https://www.pgyer.com/zKF4 APK地址

在前篇中已经讲了相关手势的处理,本篇重点讲解留白,列表联动效果。

在上一篇中由于篇幅原因,图片左下角裁剪状态的切换并没有讲解,通过分析小红书,有以下4种状态:

分别对应:裁切,填满,留白,充满。这里的裁切,填满(是楼主大大取的中文名字,不一定准确),他们分别对应图1,图2。那么4种状态怎么控制图片的显示?

  • 裁切,改变图片的显示区域,在前文中已经提到图片有任意尺寸,默认显示的区域为宽高相等的矩形区域(正方形区域),而在裁切状态下,显示的区域为宽高不相等的区域。以最小边为基准,剩余的一边缩至原来的四分之三,那么什么又是基准呢?这里以简单的公式来理解:
 图片宽度 = a 图片高度 = b
复制代码

如果 a > b 则以宽度为基准,反之以高度有基准。以==demo==中的图片为例:

图片分辨率为 360*240 宽大于高的图片,那么以宽度为基准,控件高度缩放四分之三,最后裁切的效果如下:

  • 留白,在图片四周有白边,保证图片一边铺满控件,另一边出现白边,白边的区域大小与图片的实际尺寸有关。

  • 填满,与充满同为默认状态,铺满控件,显示区域为宽高相等的矩形区域(正方形区域)。

对了,这里有一点需要说明,裁切状态下控件一边缩放至四分之三长度,与小红书是有差异的,小红书是根据图片实际尺寸改变裁切区域,取的最小值才是四分之三。

构思代码

裁切

裁切,本质就是改变控件显示区域,那么怎么改变控件显示区域,大家一定会想到改变控件大小,对自定义view绘制流程熟悉的小伙伴肯定会知道,在测量onMeasure方法中通过改变MeasureSpec.getSize()测量大小从而改变控件大小。但小编并不想改变控件大小,而是想改变控件的显示区域,用官方说法,就是改变控件的布局区域。测量 - 布局 - 绘制,自定义view的三步骤,布局相关方法如下:

    @Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);}// onLayout layout 差异省略,这里重写layout方法@Overridepublic void layout(int l, int t, int r, int b) {super.layout(l, t, r, b);}
复制代码

我们可以改变 super.layout(l, t, r, b);l, t, r, b 的值来控制控件的显示区域。注意这里 r, b 含义:

 r = l + 控件宽度 b = t + 控件高度
复制代码

填满、充满

填满、充满同默认状态。

留白

一边铺满,一边留白边,白边的区域大小跟图片尺寸有关,图片尺寸比例越接近1.0白边越小,反之越大。记得,在前篇中为了保证图片铺满控件,缩放取值如下:

 Math.max( 控件宽度/图片宽度 , 控件高度/图片高度 )
复制代码

那么只保证一边铺满,只需要取最小值就可以了:

 Math.min( 控件宽度/图片宽度 , 控件高度/图片高度 )
复制代码

编写代码

裁切

裁切分为以下两步:

  1. 判定宽或高为基准边:
   // 获取图片的宽度和高度Drawable drawable = getDrawable();if (null == drawable) {return;}int drawableWidth = drawable.getIntrinsicWidth();int drawableHeight = drawable.getIntrinsicHeight();// mIsWidthLarger  true 宽度有基准边 高度裁剪   false 高度为基准边 宽度裁剪mIsWidthLarger = drawableWidth > drawableHeight;
复制代码
  1. 重写layout方法,改变显示宽高:
    @Overridepublic void layout(int l, int t, int r, int b) {if (mIsCrop && l == 0 && t == 0) {float scaleRatio = 1.0F;float defaultRatio = 1.0F;if (mIsWidthLarger) {// 高度为原高度 3/4 居中scaleRatio = defaultRatio + defaultRatio / 4F;} else {// 宽度为原宽度 3/4 居中scaleRatio = defaultRatio - defaultRatio / 4F;}int width = r - l;int height = b - t;if (scaleRatio > defaultRatio) {int offsetY = (int) (height * (scaleRatio - defaultRatio) / 2F);// 除了2  上加下减  改变高度显示区域t += offsetY;b -= offsetY;} else if (scaleRatio < defaultRatio) {int offsetX = (int) (width * (defaultRatio - scaleRatio) / 2F);// 左加右减  改变宽度显示区域l += offsetX;r -= offsetX;}}super.layout(l, t, r, b);}
复制代码

有不明白的地方,请参考注释或留言,效果图就像这样:

留白

填满、充满为默认状态,在前篇已经讲解过了。留白,一边留白一边铺满,那么图片的缩放比例就会发生改变,还记得前篇中的缩放比例吗:

   Math.max(控件宽度/图片宽度,控件高度/图片高度)
复制代码

这样就能保证图片最小边铺满控件,留白效果恰恰相反,图片最小边不需要铺满控件(两边留白,居中对齐),同时还需要保证非最小边铺满控件,那么图片缩放比例应该取最小值,就像这样:

    @Overridepublic void onGlobalLayout() {// 省略...... // 图片缩放比mBaseScale = mIsLeaveBlank ? Math.min((float) viewWidth / drawableWidth, (float) viewHeight / drawableHeight) : Math.max((float) viewWidth / drawableWidth, (float) viewHeight / drawableHeight);}
复制代码

mIsLeaveBlank 参数控制是否留白,true 取最小值;false 取最大值。

留白改变了图片显示区域,那么==边界检测== 的越界判定条件也会发生变化,让我们一起来回忆一下,非留白越界判定条件:

    // 边界检测private void boundCheck() {// 获取图片矩阵RectF rectF = getMatrixRectF();if (rectF.left >= 0) {// 左越界}if (rectF.top >= 0) {// 上越界}if (rectF.right <= getWidth()) {// 右越界}if (rectF.bottom <= getHeight()) {// 下越界}}
复制代码

那么留白的越界判定条件又是什么呢?先来看张图,注意左右留白的红色实线:

如上图,留白的情况下,左右越界的条件就需要左加右减红线部分,那么红线的长度又为多少呢?

   红线长度 = (控件宽度 -  图片宽度) / 2
复制代码

获取到留白长度,左越界的条件就需要加上留白的长度:

        RectF rectF = getMatrixRectF();float rectWidth = rectF.right - rectF.left;float rectHeight = rectF.bottom - rectF.top;// 获取到左右留白的长度int leftLeaveBlankLength = (int) ((getWidth() - rectWidth) / 2);leftLeaveBlankLength = leftLeaveBlankLength <= 0 ? 0 : leftLeaveBlankLength;float leftBound = mIsLeaveBlank ? leftLeaveBlankLength : 0;if (rectF.left >= 0 + leftBound) {// 左越界startBoundAnimator(rectF.left, 0 + leftBound, true);}
复制代码

右越界需要减去留白的长度:

        float rightBound = mIsLeaveBlank ? getWidth() - leftLeaveBlankLength : getWidth();if (rectF.right <= rightBound) {// 右越界startBoundAnimator(rectF.left, rightBound - rectWidth, true);}
复制代码

上下越界的情况同左右越界的情况,好了,来看下效果图:

缓存,压缩,保存裁剪图片

缓存

有关LruCache的介绍,郭霖大神的 Android DiskLruCache完全解析,硬盘缓存的最佳方案 这篇文章依旧记忆犹新。使用非常简单:

    // 图片缓存private LruCache<String, Bitmap> mLruCache;//  根据实际情况 设置 maxSize 大小mLruCache = new LruCache<>(Integer.MAX_VALUE);/*** @param path 图片地址*/public synchronized void setImagePath(String path) {if (path != null && !path.equals("")) {Bitmap lruBitmap = mLruCache.get(path);if (lruBitmap == null) {// 图片压缩Bitmap bitmap = BitmapUtils.getCompressBitmap(getContext(), path);mLruCache.put(path, bitmap);lruBitmap = bitmap;}if (lruBitmap != null) {mFirstLayout = true;mMaxScale = 3.0F;// 根据实际情况改变留白裁切状态setImageBitmap(lruBitmap);onGlobalLayout();}}}复制代码

清除缓存:

    @Overrideprotected void onDetachedFromWindow() {// 清除缓存if (mLruCache != null) {mLruCache.evictAll();}}
复制代码

压缩

相信有关图片的压缩大家也是知根知底,这里就简单的贴下代码:

    public static Bitmap getCompressBitmap(Context context, String path) {BitmapFactory.Options options = new BitmapFactory.Options();// 不加载到内存中options.inJustDecodeBounds = true;BitmapFactory.decodeFile(path, options);// 判定是否是横竖图boolean verEnable = options.outWidth < options.outHeight;int screenWidth = context.getResources().getDisplayMetrics().widthPixels;int screenHeight = context.getResources().getDisplayMetrics().heightPixels;options.inSampleSize = BitmapUtils.calculateInSampleSize(options, verEnable ? screenWidth : screenHeight, verEnable ? screenHeight : screenWidth);options.inJustDecodeBounds = false;return BitmapFactory.decodeFile(path, options);}
复制代码

裁剪图片

最终我们需要得到,控件区域内的图片并转换成bitmap,我们可以借鉴以下方法:

    /*** @param leaveBlankColor 留白区域颜色* @return @return view转换成bitmap*/public Bitmap convertToBitmap(int leaveBlankColor) {int w = getWidth();int h = getHeight();Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);Canvas c = new Canvas(bmp);c.drawColor(leaveBlankColor);layout(0, 0, w, h);draw(c);return bmp;}
复制代码

在小红书中如果再次切到选中的图片,图片处于上次操作状态(记忆),说的简明点,图片的x,y轴平移以及缩放比同上次操作一样,怎么实现呢,需要保存与恢复图片位置缩放比信息。

保存:

    /*** 获取到位置信息** @return float[2] = { x坐标, y坐标 }*/public float[] getLocation() {float[] values = new float[9];mMatrix.getValues(values);return new float[]{values[Matrix.MTRANS_X], values[Matrix.MTRANS_Y]};}/*** @return 获取图片缩放比*/private float getScale() {float[] values = new float[9];mMatrix.getValues(values);return values[Matrix.MSCALE_X];}
复制代码

恢复:

    /*** 恢复位置信息** @param x     图片平移x坐标* @param y     图片平移y坐标* @param scale 图片当前缩放比*/public void restoreLocation(float x, float y, float scale) {float[] values = new float[9];mMatrix.getValues(values);values[Matrix.MSCALE_X] = scale;values[Matrix.MSCALE_Y] = scale;values[Matrix.MTRANS_X] = x;values[Matrix.MTRANS_Y] = y;mMatrix.setValues(values);setImageMatrix(mMatrix);}
复制代码

图片裁剪控件还有一些细节,这里就不一一讲解了,有什么疑问,欢迎留言讨论?接下来重点讲解列表联动效果。

列表联动

相信大家第一眼看到这个效果,一定会想到CoordinatorLayout的联动效果,没错,小编刚开始也是想通过CoordinatorLayout去实现这个效果,然后就没有然后了,不知道是不是自己的姿势不对,最终嗝屁了。这个糟老头坏得很,白白花费了我大量时间

最开始并没有想到通过自定义view来实现类似CoordinatorLayout联动效果,而是一头扎进去研究CoordinatorLayout,查阅源码,断点分析,研究Behavior等,越走越远,越想越复杂,自己离真理越来越远。

心中一直有一个念头,为啥小红书可以实现,自己却不行?是不是思路有问题?于是我再次用view层级分析工具,分析小红书视图层级:

当我看到这里,心里那个畅快,原来小红书也没有使用CoordinatorLayout,而是用的LinearLayout线性布局,如果是CoordinatorLayout这里应该显示ViewGroup,基本可以肯定小红书是通过自定义LinearLayout来实现列表联动效果。

接下来拆分效果,同CoordinatorLayout联动类似,同样有展开与收起两种状态,支持 ==“甩”== filing 效果,在展开状态下:

xml布局层级:

    <LinearLayout ><com.demo.mcropimageview.MCropImageView /><android.support.v7.widget.RecyclerView /></LinearLayout>
复制代码
  1. 未触碰MCropImageView区域,RecyclerView消费滑动事件,滚动列表

  2. 在RecyclerView区域向上滑动,触碰到MCropImageView区域,RecyclerView与MCropImageView跟随手指移动,向上滑动移出屏幕;向下滑动则移入屏幕,当MCropImageView完全展示,MCropImageView停止移动,如果手指移动到RecyclerView区域,则消费滑动事件。

收起状态:

  1. 未滑动到RecyclerView顶部,RecyclerView自身消费滑动事件

  2. 滑动到RecyclerView顶部并向下滑动,RecyclerView与MCropImageView跟随手指移动,向下滑动移入屏幕,向上滑动移出屏幕,当MCropImageView完全移出屏幕,继续向上滑动,则RecyclerView消费滑动事件

大多数情况下,当我们要做一个View跟随手指移动的效果时,都是直接setOnTouchListener或者直接重写onTouchEvent去实现的,但这种方式用在我们即将要做的这个效果上,就很不合适了,因为因为我们是要做到可以作用在任意一个View上的(这里指RecyclerView与MCropImageView),这样一来,如果目标View本来就已经重写了OnTouchEvent或者设置了OnTouchListener,就很可能会滑动冲突,而且还非常不灵活,这个时候,使用自定义ViewGroup的方式是最佳选择。上文中已经明确了使用自定义LinearLayout来实现列表的联动效果。

构思代码

联动,联动,那么第一个问题就是解决, 的问题,怎样让view动起来?emmmm,这个难不倒我,动态改变view在父控件中的位置信息,在view中提供了一系列的方法来让view动起来:

scrollBy,scrollTo,setTranslation,layout,offsetTopAndBottom,setScrollY等方法,效果图上在手指抬起的时候,view会根据当前的滑动距离惯性滑动,那么借助OverScroller类实现惯性滑动就非常容易了。

知道了怎么动,那么动的距离呢,与RecyclerView滑动有关,重写onTouchEvent获取滑动偏移量,RecyclerView的父控件根据偏移量进行移动,在手指抬起时,根据偏移量判定父控件是否展开,收起。

当手指松开,借助VelocityTracker获得滑动速率,如果速率大于指定值,则判定为 “甩”,并通过Scroller来进行惯性移动,同时改变展开,收起状态。

如手指松开后滑动速率低于指定值,则视为 “放手”,这时候根据getScrollY是否大于指定值,并通过Scroller来进行展开或收起的惯性移动。

大概过程就是这样,接下来开工写代码洛~

起名字

怎么样才能取一个接地气的名字呢?我看就叫CoordinatorLinearLayout ,同时还需要自定义RecyclerView,我们就叫它,CoordinatorRecyclerView。同时还给这两个名字卜了一挂,哈哈,大吉还不错。

编写代码

创建CoordinatorRecyclerView

好,那我们来看看CoordinatorRecyclerView应该怎么写: 先是成员变量:

    private int mTouchSlop = -1;private VelocityTracker mVelocityTracker;// 是否重新测量用于改变RecyclerView的高度private boolean mIsAgainMeasure = true;// 是否展开 默认为trueprivate boolean mIsExpand = true;// 父类最大的滚动区域 = 裁剪控件的高度private int mMaxParentScrollRange;// 父控件在y方向滚动的距离private int mCurrentParenScrollY = 0;// 最后RawY坐标private float mLastRawY = 0;private float mDeltaRawY = 0;// 是否消费touch事件 true 消费RecyclerView接受不到滚动事件private boolean mIsConsumeTouchEvent = false;// 回调接口private OnCoordinatorListener mListener;
复制代码

再到构造方法:

    public CoordinatorRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 用于处理手势filing动作mVelocityTracker = VelocityTracker.obtain();// 最大滑动范围 = 图片裁剪控件高度 (图片裁剪控件是宽高相等)mMaxParentScrollRange = context.getResources().getDisplayMetrics().widthPixels;}
复制代码

通过上文的构思,CoordinatorRecyclerView暴露滚动,“甩” 的接口方法:

    public interface OnCoordinatorListener {/*** @param y                    相对RecyclerView的距离* @param deltaY               偏移量* @param maxParentScrollRange 最大滚动距离*/void onScroll(float y, float deltaY, int maxParentScrollRange);/*** @param velocityY y方向速度*/void onFiling(int velocityY);}
复制代码

重写onTouchEvent方法:

    @Overridepublic boolean onTouchEvent(MotionEvent e) {switch (e.getAction()) {case MotionEvent.ACTION_DOWN:// 重置数据 由于篇幅原因 省略相应代码 ......break;case MotionEvent.ACTION_MOVE:// y 相对于 RecyclerView y坐标float y = e.getY();measureRecyclerHeight(y);if (mLastRawY == 0) {mLastRawY = e.getRawY();}mDeltaRawY = mLastRawY - e.getRawY();if (mIsExpand) {// 展开mListener.onScroll(y, mDeltaRawY, mMaxParentScrollRange);} else {// 收起 canScrollVertically 判定是否滑动到底部if (!mIsConsumeTouchEvent && !canScrollVertically(-1)) {mIsConsumeTouchEvent = true;}if (mIsConsumeTouchEvent && mDeltaRawY != 0) {mListener.onScroll(y, mDeltaRawY, mMaxParentScrollRange);}}// 处于非临界状态mIsConsumeTouchEvent = mCurrentParenScrollY > 0 & mCurrentParenScrollY < mMaxParentScrollRange;mVelocityTracker.addMovement(e);mLastRawY = e.getRawY();if (y < 0 || mIsConsumeTouchEvent) {return false;}break;case MotionEvent.ACTION_CANCEL:case MotionEvent.ACTION_UP:// 重置数据resetData();mLastRawY = 0;// 处理滑动速度mVelocityTracker.addMovement(e);mVelocityTracker.computeCurrentVelocity(1000);int velocityY = (int) Math.abs(mVelocityTracker.getYVelocity());mListener.onFiling(mDeltaRawY > 0 ? -velocityY : velocityY);mDeltaRawY = 0;y = e.getY();if (y < 0) {return false;}break;}return super.onTouchEvent(e);}
复制代码

可以看到,ACTION_MOVE事件中通过e.getY()来获取相对父类的y轴坐标,前后两次e.getRawY()值获取偏移量,在展开状态下,暴露接口onScroll方法,在收起状态下,根据是否滑动到底部且偏移量不为0,暴露接口onScroll方法;在ACTION_UP事件中获取手指抬起的速度与方向暴露onFiling接口方法。注意,onTouchEvent方法的返回值,如果返回false,RecyclerView向下传递消费事件(不能滑动)。

有一个细节大家是否注意到了,RecyclerView的高度在父类展开,收起过程中并不一样,如下图,在非完全展开的状态下,高度为绿色+粉丝区域;在完全展开状态下,高度为绿色区域。

相关代码如下:

    /*** @param y 手指相对RecyclerView的y轴坐标*          y <= 0 表示手指已经滑出RecyclerView顶部*/private void measureRecyclerHeight(float y) {if (y <= 0 && mIsAgainMeasure) {if (getHeight() < mMaxParentScrollRange && mIsExpand) {mIsAgainMeasure = false;getLayoutParams().height = getHeight() + mMaxParentScrollRange;requestLayout();}}}// 重置高度public void resetRecyclerHeight() {if (getHeight() > mMaxParentScrollRange && mIsExpand && mIsAgainMeasure) {getLayoutParams().height = getHeight() - mMaxParentScrollRange;requestLayout();}}
复制代码

接下来看看父类CoordinatorLinearLayout怎么写。

创建CoordinatorLinearLayout

在上文中已经提及到CoordinatorLinearLayout继承LinearLayout,功能相对简单,根据CoordinatorRecyclerView暴露的接口方法进行惯性滑动,同样先是成员变量:

    // 是否展开private boolean mIsExpand;private OverScroller mOverScroller;// 快速抛的最小速度private int mMinFlingVelocity;// 滚动最大距离 = 图片裁剪控件的高度private int mScrollRange;// 滚动监听接口private OnScrollListener mListener;// 最大展开因子private static final int MAX_EXPAND_FACTOR = 6;// 滚动时长private static final int SCROLL_DURATION = 500;
复制代码

构造方法,相关变量的初始化:

    public CoordinatorLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mOverScroller = new OverScroller(context);mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();// 设置默认值 =  图片裁剪控件的宽度mScrollRange = context.getResources().getDisplayMetrics().widthPixels;}
复制代码

onScroll方法:

    /*** @param y                    相对RecyclerView的距离* @param deltaY               偏移量* @param maxParentScrollRange 最大滚动距离*/public void onScroll(float y, float deltaY, int maxParentScrollRange) {int scrollY = getScrollY();int currentScrollY = (int) (scrollY + deltaY);if (mScrollRange != maxParentScrollRange) {mScrollRange = maxParentScrollRange;}// 越界检测if (currentScrollY > maxParentScrollRange) {currentScrollY = maxParentScrollRange;} else if (currentScrollY < 0) {currentScrollY = 0;}// 处于展开状态if (y <= 0) {setScrollY(currentScrollY);} else if (y > 0 && scrollY != 0) { // 处于收起状态setScrollY(currentScrollY);}if (mListener != null) {mListener.onScroll(getScrollY());}}
复制代码

先获取到y轴方向滑动值,然后滑动值的最大最小判定,最后根据展开,收起状态设置滑动值并同时暴露滑动值。

onFiling方法:

    /*** @param velocityY y方向速度*/public void onFiling(int velocityY) {int scrollY = getScrollY();// 判定非临界状态if (scrollY != 0 && scrollY != mScrollRange) {// y轴速度是否大于最小抛速度if (Math.abs(velocityY) > mMinFlingVelocity) {if (velocityY > mScrollRange || velocityY < -mScrollRange) {startScroll(velocityY > mScrollRange);} else {collapseOrExpand(scrollY);}} else {collapseOrExpand(scrollY);}}}
复制代码

在手指抬起时,先获取y轴方向滑动值,在展开与收起的过程当中,根据RecyclerView返回的y方向速度与 ==“甩”== 的最小值比较。如果小于最小值,则根据滑动值进行惯性滑动;反之,大于最小值,并在(mScrollRange , -mScrollRange)区间之外,分别展开与收起,在区间之类同样根据滑动值进行惯性滑动。

   /*** 展开或收起** @param scrollY*/private void collapseOrExpand(int scrollY) {// MAX_EXPAND_FACTOR = 6int maxExpandY = mScrollRange / MAX_EXPAND_FACTOR;if (isExpanding()) {startScroll(scrollY < maxExpandY);} else {startScroll(scrollY < (mScrollRange - maxExpandY));}}
复制代码

在展开与收起状态下,根据滑动值scrollY是否大于指定值来控制展开与收起。

    /*** 开始滚动** @param isExpand 是否展开*/private void startScroll(boolean isExpand) {mIsExpand = isExpand;if (mListener != null) {mListener.isExpand(isExpand);if (mIsExpand) {// 必须保证滚动完成 再触发回调postDelayed(new Runnable() {@Overridepublic void run() {mListener.completeExpand();}}, SCROLL_DURATION);}}if (!mOverScroller.isFinished()) {mOverScroller.abortAnimation();}int dy = isExpand ? -getScrollY() : mScrollRange - getScrollY();// SCROLL_DURATION = 500mOverScroller.startScroll(0, getScrollY(), 0, dy, SCROLL_DURATION);postInvalidate();}
复制代码

首先根据isExpand暴露isExpand接口方法,在展开状态下并且惯性滚动完成时暴露completeExpand接口方法,然后根据是否展开获取滚动值,最后调用mOverScroller.startScroll方法进行惯性滚动并重写computeScroll方法:

    @Overridepublic void computeScroll() {// super.computeScroll();if (mOverScroller.computeScrollOffset()) {setScrollY(mOverScroller.getCurrY());postInvalidate();}}
复制代码

相关接口方法如下:

    public interface OnScrollListener {void onScroll(int scrollY);/*** @param isExpand 是否展开*/void isExpand(boolean isExpand);// 完全展开void completeExpand();}
复制代码

CoordinatorRecyclerView与CoordinatorLinearLayout接口实现如下:

        // 实现回调接口mRecyclerView.setOnCoordinatorListener(new CoordinatorRecyclerView.OnCoordinatorListener() {@Overridepublic void onScroll(float y, float deltaY, int maxParentScrollRange) {mCoordinatorLayout.onScroll(y, deltaY, maxParentScrollRange);}@Overridepublic void onFiling(int velocityY) {mCoordinatorLayout.onFiling(velocityY);}});mCoordinatorLayout.setOnScrollListener(new CoordinatorLinearLayout.OnScrollListener() {@Overridepublic void onScroll(int scrollY) {mRecyclerView.setCurrentParenScrollY(scrollY);}@Overridepublic void isExpand(boolean isExpand) {mRecyclerView.setExpand(isExpand);}@Overridepublic void completeExpand() {mRecyclerView.resetRecyclerHeight();}});
复制代码

到这里,联动效果就差不多实现了,先来看看效果:

在感受丝滑的过程中,发现了一个很奇怪的问题。如下图: 问题:点击RecyclerView的子view,点击事件失效。猜测,滑出了RecyclerView区域,事件ACTION_CANCEL执行,导致再次点击RecyclerView区域,事件标志Flag重置的原因造成 。同时新草app也同样存在改问题,小红书却不存在。具体原因小编会具体跟进事件传递机制,查找相关资料给出大家一个合理的解释。

下面,小编给出自己的兼容方案,既然能够拿到RecyclerView触摸点的坐标,那么可以通过坐标判定在哪个RecyclerView的子view中,然后调用performClick方法,模拟点击事件:

    /*** @param recyclerView* @param touchX* @param touchY*/public void handlerRecyclerInvalidClick(RecyclerView recyclerView, int touchX, int touchY) {if (recyclerView != null && recyclerView.getChildCount() > 0) {for (int i = 0; i < recyclerView.getChildCount(); i++) {View childView = recyclerView.getChildAt(i);if (childView != null) {if (childView != null && isTouchView(touchX, touchY, childView)) {childView.performClick();return;}}}}}// 触摸点是否view区域内private boolean isTouchView(int touchX, int touchY, View view) {Rect rect = new Rect();view.getGlobalVisibleRect(rect);return rect.contains(touchX, touchY);}
复制代码

好了,本篇文章到此结束,明天是妇女节,祝程序员嫂子节日快乐!

有错误的地方请指出,多谢~

Github地址:https://github.com/HpWens/MeiWidgetView 欢迎 star

扫一扫 关注我的公众号 新号希望大家能够多多支持我~

第一站小红书图片裁剪控件之二,自定义CoordinatorLayout联动效果相关推荐

  1. 第一站小红书图片裁剪控件,深度解析大厂炫酷控件

    先来看两张效果图: 哈哈,就是这样了.效果差了一些,感兴趣的小伙伴们可以运行代码感受丝滑与弹性.前段时间在竞品小红书上看到了这样的效果:图片可以跟随手指移动,双指可以(无限)放大,缩小,还可以挤压,手 ...

  2. 第一站仿小红书图片裁剪控件,深度解析大厂炫酷控件

    先来看两张效果图: 哈哈,就是这样了.效果差了一些,感兴趣的小伙伴们可以运行代码感受丝滑与弹性.前段时间在竞品小红书上看到了这样的效果:图片可以跟随手指移动,双指可以(无限)放大,缩小,还可以挤压,手 ...

  3. Android 小红书图片裁剪框架,拍摄视频无法自定义拍摄时长

    小红书裁剪框架:https://github.com/yangpeixing/YImagePicker 根据官方的api,传入Long类型的maxDuration,是不起效果的,应该传入int类型的, ...

  4. Android开发技巧——定制仿微信图片裁剪控件

    拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动 ...

  5. 像小红书一样的图片裁剪控件联动效果

    今日科技快讯 据CNBC报道,美国法官已经要求特斯拉首席执行官埃隆·马斯克(Elon Musk)在未来两周内设法与美国证券交易委员会(SEC)达成和解协议.否则,法院将决定是否判马斯克犯有藐视法庭罪. ...

  6. CropImageView android上的一个图片裁剪控件

    CropImageView **文前:**本文非常容易让读者看的云里雾里,建议直接看效果图,觉得有用就去看源码吧. CropImageView的原型来自Cropimage_demo,是android上 ...

  7. 仿抖音--音乐裁剪控件,android实现IOS版本效果

    补充:源码git地址 看了一下抖音的音乐裁剪(IOS),看上去很不错,所以决定实现一下.一路上写了三四个版本,遇到了很多问题,在这里分享一下.首先看下效果: 在说明怎么实现之前,我先分享下我在做的过程 ...

  8. html5图片裁剪控件原型【含缩放,旋转,拖动功能】---2、核心代码

    推荐 这一篇文章是早年为了解决图片裁剪的探索性文章,现在已经开放出了falsh版及html5版本的图片裁剪插件,各位有时间可以看看: 浮士德html5图片裁剪器2016开源版 浮士德头像裁剪flash ...

  9. 图片剪裁控件——ClipImageView

    这段时间在做自己的项目时,须要使用到图片剪裁功能,当时大概的思考了一些需求.想到了比較简单的实现方法.因此就抽了点时间做了这个图片剪裁控件--ClipImageView 这里先贴上ClipImageV ...

最新文章

  1. tomcat如何增大并发_系统的性能瓶颈,排查该从哪些方面入手,如何定位?
  2. python教程:15种字符串操作方法
  3. c++ windows获得当前工作目录文件_使用命令行修改当前工作目录
  4. cfree运行程序错误的原因_ARM Cortex-M 系列 MCU错误代码自动追踪库的使用分享
  5. plsql连接oracle未找到oci,Plsql Developer连接Oracle时出现Could not initialize oci.dll解决方案...
  6. windows 2008 开机启动 Docker Toolbox 并运行容器
  7. APP访问路径和销售归因分析
  8. pip安装mysql-python模块报错:cannot find -lmysqlclient_r
  9. 本地计算机策略无法建立管理单元,我的本地安全策略用不了!打开管理工具的本地安全策略管理单元初始化 爱问知识人...
  10. tensorflow object detect API 使用,并修改一部分
  11. 数据结构实验5-递归
  12. 阮兄弟的致远OAa8v5移动端增加功能和OEM实例
  13. html制作问卷调查,如何通过jQuery制作一个在线问卷调查
  14. 你这个视频背景太假了?
  15. 奥尼捷多功能证件包护照包证件夹男士卡包女士必备 A101 黑色【图片 价格 品牌 报价】-京东商城...
  16. NLP自然语言处理系列-音乐推荐系统实战 -基于矩阵分解(SVD)的推荐
  17. 苹果操作系统 leopard 10.5 (PC破解完全中文安装版,IBM X40安装成功)
  18. $inject的用法
  19. 卷积神经网络论文学习(Lecun1998)
  20. 2023年——个人每日分享汇总

热门文章

  1. android 测量文字长度,【Android】TextView文字长度测量和各种Padding解析
  2. 人才测评,你想得到和想不到的五大趋
  3. mac 电脑CPU温度怎么看?怎么可以监控Mac CPU温度,为什么我的 MacBook Air 这么热?
  4. 服务端渲染与客户端渲染详解(vue)
  5. windows中禁止U盘写入
  6. 经典多线程设计模式(重要)
  7. 假如被公司辞退,你知道该做什么吗
  8. 在职人士,围绕工作开展学习更实在
  9. 引导行业视频方向的全球领导者Brightcove推出大胆的新品牌
  10. BNCT知识宝典(中文2022版)