第一站小红书图片裁剪控件之二,自定义CoordinatorLayout联动效果
本篇续:
第一站小红书图片裁剪控件,深度解析大厂炫酷控件
先来看看几张效果图:
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( 控件宽度/图片宽度 , 控件高度/图片高度 )
复制代码
编写代码
裁切
裁切分为以下两步:
- 判定宽或高为基准边:
// 获取图片的宽度和高度Drawable drawable = getDrawable();if (null == drawable) {return;}int drawableWidth = drawable.getIntrinsicWidth();int drawableHeight = drawable.getIntrinsicHeight();// mIsWidthLarger true 宽度有基准边 高度裁剪 false 高度为基准边 宽度裁剪mIsWidthLarger = drawableWidth > drawableHeight;
复制代码
- 重写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>
复制代码
未触碰MCropImageView区域,RecyclerView消费滑动事件,滚动列表
在RecyclerView区域向上滑动,触碰到MCropImageView区域,RecyclerView与MCropImageView跟随手指移动,向上滑动移出屏幕;向下滑动则移入屏幕,当MCropImageView完全展示,MCropImageView停止移动,如果手指移动到RecyclerView区域,则消费滑动事件。
收起状态:
未滑动到RecyclerView顶部,RecyclerView自身消费滑动事件
滑动到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联动效果相关推荐
- 第一站小红书图片裁剪控件,深度解析大厂炫酷控件
先来看两张效果图: 哈哈,就是这样了.效果差了一些,感兴趣的小伙伴们可以运行代码感受丝滑与弹性.前段时间在竞品小红书上看到了这样的效果:图片可以跟随手指移动,双指可以(无限)放大,缩小,还可以挤压,手 ...
- 第一站仿小红书图片裁剪控件,深度解析大厂炫酷控件
先来看两张效果图: 哈哈,就是这样了.效果差了一些,感兴趣的小伙伴们可以运行代码感受丝滑与弹性.前段时间在竞品小红书上看到了这样的效果:图片可以跟随手指移动,双指可以(无限)放大,缩小,还可以挤压,手 ...
- Android 小红书图片裁剪框架,拍摄视频无法自定义拍摄时长
小红书裁剪框架:https://github.com/yangpeixing/YImagePicker 根据官方的api,传入Long类型的maxDuration,是不起效果的,应该传入int类型的, ...
- Android开发技巧——定制仿微信图片裁剪控件
拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动 ...
- 像小红书一样的图片裁剪控件联动效果
今日科技快讯 据CNBC报道,美国法官已经要求特斯拉首席执行官埃隆·马斯克(Elon Musk)在未来两周内设法与美国证券交易委员会(SEC)达成和解协议.否则,法院将决定是否判马斯克犯有藐视法庭罪. ...
- CropImageView android上的一个图片裁剪控件
CropImageView **文前:**本文非常容易让读者看的云里雾里,建议直接看效果图,觉得有用就去看源码吧. CropImageView的原型来自Cropimage_demo,是android上 ...
- 仿抖音--音乐裁剪控件,android实现IOS版本效果
补充:源码git地址 看了一下抖音的音乐裁剪(IOS),看上去很不错,所以决定实现一下.一路上写了三四个版本,遇到了很多问题,在这里分享一下.首先看下效果: 在说明怎么实现之前,我先分享下我在做的过程 ...
- html5图片裁剪控件原型【含缩放,旋转,拖动功能】---2、核心代码
推荐 这一篇文章是早年为了解决图片裁剪的探索性文章,现在已经开放出了falsh版及html5版本的图片裁剪插件,各位有时间可以看看: 浮士德html5图片裁剪器2016开源版 浮士德头像裁剪flash ...
- 图片剪裁控件——ClipImageView
这段时间在做自己的项目时,须要使用到图片剪裁功能,当时大概的思考了一些需求.想到了比較简单的实现方法.因此就抽了点时间做了这个图片剪裁控件--ClipImageView 这里先贴上ClipImageV ...
最新文章
- tomcat如何增大并发_系统的性能瓶颈,排查该从哪些方面入手,如何定位?
- python教程:15种字符串操作方法
- c++ windows获得当前工作目录文件_使用命令行修改当前工作目录
- cfree运行程序错误的原因_ARM Cortex-M 系列 MCU错误代码自动追踪库的使用分享
- plsql连接oracle未找到oci,Plsql Developer连接Oracle时出现Could not initialize oci.dll解决方案...
- windows 2008 开机启动 Docker Toolbox 并运行容器
- APP访问路径和销售归因分析
- pip安装mysql-python模块报错:cannot find -lmysqlclient_r
- 本地计算机策略无法建立管理单元,我的本地安全策略用不了!打开管理工具的本地安全策略管理单元初始化 爱问知识人...
- tensorflow object detect API 使用,并修改一部分
- 数据结构实验5-递归
- 阮兄弟的致远OAa8v5移动端增加功能和OEM实例
- html制作问卷调查,如何通过jQuery制作一个在线问卷调查
- 你这个视频背景太假了?
- 奥尼捷多功能证件包护照包证件夹男士卡包女士必备 A101 黑色【图片 价格 品牌 报价】-京东商城...
- NLP自然语言处理系列-音乐推荐系统实战 -基于矩阵分解(SVD)的推荐
- 苹果操作系统 leopard 10.5 (PC破解完全中文安装版,IBM X40安装成功)
- $inject的用法
- 卷积神经网络论文学习(Lecun1998)
- 2023年——个人每日分享汇总