本篇續:java
第一站小紅書圖片裁剪控件,深度解析大廠炫酷控件android
先來看看幾張效果圖: git
在前篇中已經講了相關手勢的處理,本篇重點講解留白,列表聯動效果。程序員
在上一篇中因爲篇幅緣由,圖片左下角裁剪狀態的切換並無講解,經過分析小紅書,有如下4種狀態: github
圖片寬度 = a
圖片高度 = b
複製代碼
若是 a > b 則以寬度爲基準,反之以高度有基準。以==demo==中的圖片爲例: 緩存
360*240
寬大於高的圖片,那麼以
寬度爲基準,控件高度縮放四分之三,最後裁切的效果以下:
留白,在圖片四周有白邊,保證圖片一邊鋪滿控件,另外一邊出現白邊,白邊的區域大小與圖片的實際尺寸有關。app
填滿,與充滿同爲默認狀態,鋪滿控件,顯示區域爲寬高相等的矩形區域(正方形區域)。ide
對了,這裏有一點須要說明,裁切狀態下控件一邊縮放至四分之三長度,與小紅書是有差別的,小紅書是根據圖片實際尺寸改變裁切區域,取的最小值纔是四分之三。工具
裁切,本質就是改變控件顯示區域,那麼怎麼改變控件顯示區域,你們必定會想到改變控件大小,對自定義view繪製流程熟悉的小夥伴確定會知道,在測量onMeasure方法中經過改變MeasureSpec.getSize()測量大小從而改變控件大小。但小編並不想改變控件大小,而是想改變控件的顯示區域,用官方說法,就是改變控件的佈局區域。測量 - 佈局 - 繪製,自定義view的三步驟,佈局相關方法以下:佈局
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
// onLayout layout 差別省略,這裏重寫layout方法
@Override
public 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;
複製代碼
@Override
public 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(控件寬度/圖片寬度,控件高度/圖片高度)
複製代碼
這樣就能保證圖片最小邊鋪滿控件,留白效果偏偏相反,圖片最小邊不須要鋪滿控件(兩邊留白,居中對齊),同時還須要保證非最小邊鋪滿控件,那麼圖片縮放比例應該取最小值,就像這樣:
@Override
public 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();
}
}
}
複製代碼
清除緩存:
@Override
protected 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);
}
複製代碼
圖片裁剪控件還有一些細節,這裏就不一一講解了,有什麼疑問,歡迎留言討論?接下來重點講解列表聯動效果。
最開始並無想到經過自定義view來實現相似CoordinatorLayout聯動效果,而是一頭扎進去研究CoordinatorLayout,查閱源碼,斷點分析,研究Behavior等,越走越遠,越想越複雜,本身離真理愈來愈遠。
心中一直有一個念頭,爲啥小紅書能夠實現,本身卻不行?是否是思路有問題?因而我再次用view層級分析工具,分析小紅書視圖層級:
接下來拆分效果,同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應該怎麼寫: 先是成員變量:
private int mTouchSlop = -1;
private VelocityTracker mVelocityTracker;
// 是否從新測量用於改變RecyclerView的高度
private boolean mIsAgainMeasure = true;
// 是否展開 默認爲true
private 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方法:
@Override
public 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繼承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 = 6
int 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() {
@Override
public void run() {
mListener.completeExpand();
}
}, SCROLL_DURATION);
}
}
if (!mOverScroller.isFinished()) {
mOverScroller.abortAnimation();
}
int dy = isExpand ? -getScrollY() : mScrollRange - getScrollY();
// SCROLL_DURATION = 500
mOverScroller.startScroll(0, getScrollY(), 0, dy, SCROLL_DURATION);
postInvalidate();
}
複製代碼
首先根據isExpand暴露isExpand接口方法,在展開狀態下而且慣性滾動完成時暴露completeExpand接口方法,而後根據是否展開獲取滾動值,最後調用mOverScroller.startScroll方法進行慣性滾動並重寫computeScroll方法:
@Override
public 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() {
@Override
public void onScroll(float y, float deltaY, int maxParentScrollRange) {
mCoordinatorLayout.onScroll(y, deltaY, maxParentScrollRange);
}
@Override
public void onFiling(int velocityY) {
mCoordinatorLayout.onFiling(velocityY);
}
});
mCoordinatorLayout.setOnScrollListener(new CoordinatorLinearLayout.OnScrollListener() {
@Override
public void onScroll(int scrollY) {
mRecyclerView.setCurrentParenScrollY(scrollY);
}
@Override
public void isExpand(boolean isExpand) {
mRecyclerView.setExpand(isExpand);
}
@Override
public void completeExpand() {
mRecyclerView.resetRecyclerHeight();
}
});
複製代碼
到這裏,聯動效果就差很少實現了,先來看看效果:
下面,小編給出本身的兼容方案,既然可以拿到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);
}
複製代碼