上次試著搞了搞點(diǎn)擊回到頂部的效果,不過最后也沒搞出個(gè)所以然來,這次是照片選擇和上傳頁的效果,找到了一個(gè)別人的項(xiàng)目所以分享一下。
先放Ins上的效果(強(qiáng)行調(diào)分辨率弄的圖有點(diǎn)糊):

布局:直觀看過去就是外層的Toolbar和ViewPager我們先不管,再里面LinearLayout里裝著ImageView + RecyclerView

總結(jié)下來,簡單來說實(shí)際上就是如果手只在RecyclerView的范圍內(nèi)劃動(dòng)就正?;掌斜?,劃到上面的照片的話就把照片推上去
其他一些別的效果回頭再說。
關(guān)于這個(gè)效果我找到了一個(gè)實(shí)現(xiàn)用的Demo,感謝大佬作者
Github: InstagramPhotoPicker by Skykai521
原版代碼各位自己點(diǎn)進(jìn)去看就是了,我改了改來實(shí)現(xiàn)點(diǎn)別的,以及debug
我就不全貼了,貼一部分核心邏輯和能改的東西
關(guān)于里面的邏輯全寫在注釋里了,應(yīng)該已經(jīng)寫得很詳細(xì)了
使用方法和注意事項(xiàng)在最下面
如果哪寫錯(cuò)了歡迎和我說……
/**
* Created by sky on 17/3/1.
* https://github.com/Skykai521/InstagramPhotoPicker
*/
public class CoordinatorRecyclerView extends RecyclerView {
...
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
// 我自己加的,原因是onTouchEvent的down這個(gè)event
// 在RecyclerView的item是clickable的時(shí)候很容易失效,
// 導(dǎo)致downPositionY不更新,會(huì)有bug,折疊上去之后拽不下來
// 所以把down的處理也放在這里
if (e.getAction() == MotionEvent.ACTION_DOWN) {
downPositionY = e.getRawY();
}
return super.onInterceptTouchEvent(e);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (null == coordinatorListener) {
return super.onTouchEvent(ev);
}
final int action = ev.getAction();
final int y = (int) ev.getRawY();
final int x = (int) ev.getRawX();
switch (action) {
case MotionEvent.ACTION_DOWN:
downPositionY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int deltaY = (int) (downPositionY - y);
boolean deal;
if (isScrollTop(ev)) {
// 折疊著且recycler拉到頭,大圖被拽下來
deal = coordinatorListener.onCoordinateScroll(x, y, 0, deltaY + Math.abs(dragDistanceY), true);
} else {
// 大圖展開
deal = coordinatorListener.onCoordinateScroll(x, y, 0, deltaY, isScrollTop(ev));
}
if (deal) {
// 這里手動(dòng)調(diào)了下stopScroll,是因?yàn)槊看未髨D收起來之后
// item的點(diǎn)擊事件會(huì)有一次失效,推測是這次點(diǎn)擊被用來停止?jié)L動(dòng)了,所以手動(dòng)給他停下
stopScroll();
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// 這里即松手判斷大圖位置是不是變了,變了就自動(dòng)收起/折疊
scrollTop = false;
if (coordinatorListener.isBeingDragged()) {
coordinatorListener.onSwitch();
return true;
}
break;
}
return super.onTouchEvent(ev);
}
private boolean isScrollTop(MotionEvent ev) {
// 在折疊狀態(tài)下,RecyclerView依然是可以上下滾的,
// 只有RecyclerView下拉到頭馬上要把上面折疊的大圖拽下來了時(shí)是isScrollTop
LayoutManager layoutManager = getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
if (gridLayoutManager.findFirstVisibleItemPosition() == 0) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) gridLayoutManager.findViewByPosition(0).getLayoutParams();
// 這里代表RecyclerView下拉時(shí)被拉到頭了
// 一般情況下下面兩個(gè)條件必定有一個(gè)為true,所以這里用&&
// 這里的邏輯我也修改過,大致意思是第一個(gè)圖片toolbar底部的高度等于decoration或者margin
// 根據(jù)情況可以自己添加,因?yàn)檫@里出錯(cuò)會(huì)導(dǎo)致折疊的大圖拉不下來
if ((null != params && gridLayoutManager.findViewByPosition(0).getTop() != params.topMargin) &&
gridLayoutManager.findViewByPosition(0).getTop() != gridLayoutManager.getTopDecorationHeight(gridLayoutManager.findViewByPosition(0))) {
return false;
}
if (!scrollTop) {
// 這里的dragDistanceY即大圖折疊時(shí)RecyclerView被拽著滾動(dòng)的距離
dragDistanceY = (int) (downPositionY - ev.getRawY());
scrollTop = true;
}
return true;
}
}
return false;
}
public void setCoordinatorListener(CoordinatorListener listener) {
this.coordinatorListener = listener;
}
@Override
public void onScrolled(int dx, int dy) {
// 原本接口類里沒定義switchToTop和isWholeState這倆方法,
// 所以想用listener調(diào)用得自己加上,作用是滾過一段距離之后自動(dòng)展開
super.onScrolled(dx, dy);
totalY += dy;
if ((totalY > onSwitchDistance || totalY < -onSwitchDistance) && coordinatorListener.isWholeState()) {
coordinatorListener.switchToTop();
totalY = 0;
}
}
public void onItemClick(int position) {
// 自己寫的,搞這個(gè)是為了點(diǎn)擊item時(shí)大圖能展開,且item移動(dòng)到大圖正下面
if (null == coordinatorListener) {
return;
}
GridLayoutManager manager = (GridLayoutManager) getLayoutManager();
int firstPosition = manager.findFirstVisibleItemPosition();
int availablePosition = position - firstPosition;
// 如果position大于屏幕中顯示的child數(shù)量就會(huì)為空,所以這里要減去
View child = getLayoutManager().getChildAt(availablePosition);
if (null != child) {
scrollBy(0, child.getTop());
}
if (!coordinatorListener.isWholeState()) {
coordinatorListener.switchToWhole();
}
totalY = 0;
}
}
/**
* Created by sky on 17/3/1.
* https://github.com/Skykai521/InstagramPhotoPicker
*/
public class CoordinatorLinearLayout extends LinearLayout implements CoordinatorListener {
public static int DEFAULT_DURATION = 500;
private int state = WHOLE_STATE;
private int topBarHeight; // toolbar
private int topViewHeight; // toolbar + 正方形大照片的底部高度
private int minScrollToTop; // toolbar
private int minScrollToWhole; // 大照片高度 - toolbar,和上面的minScrollToTop一起,用于判斷松手后展開還是收起
private int maxScrollDistance; // 大照片高度,最大滑動(dòng)距離
private float lastPositionY; // 手指按下的位置
private boolean beingDragged;
private Context context;
private OverScroller scroller; // 用于松手后展開/收起
...
public CoordinatorLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private void init() {
scroller = new OverScroller(context);
}
public void setTopViewParam(int topViewHeight, int topBarHeight) {
// 初始化這些值,這些定義錯(cuò)了這個(gè)類是沒法實(shí)現(xiàn)效果的
this.topViewHeight = topViewHeight;
this.topBarHeight = topBarHeight;
this.maxScrollDistance = this.topViewHeight - this.topBarHeight;
this.minScrollToTop = this.topBarHeight;
this.minScrollToWhole = maxScrollDistance - this.topBarHeight;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
int y = (int) ev.getY();
int rawY = (int) ev.getRawY();
lastPositionY = y;
// 收起且點(diǎn)在最頂上,在這里處理,這里用getY和getRawY是會(huì)有區(qū)別的,看情況用吧
if (state == COLLAPSE_STATE && rawY < topBarHeight) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 應(yīng)該是只有碰到最頂上了才會(huì)走到這里
final int action = ev.getAction();
final int y = (int) ev.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastPositionY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaY = (int) (lastPositionY - y);
if (state == COLLAPSE_STATE && deltaY < 0) {
beingDragged = true;
setScrollY(maxScrollDistance + deltaY);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (beingDragged) {
onSwitch();
return true;
}
break;
}
return true;
}
@Override
public boolean onCoordinateScroll(int x, int y, int deltaX, int deltaY, boolean isScrollToTop) {
// deltaY 是按下位置 - 手指拖動(dòng)后的位置
if (y < topViewHeight && state == WHOLE_STATE && getScrollY() < getScrollRange()) {
// 展開,手指在滑動(dòng)區(qū)間(toolbar + 正方形)且在范圍內(nèi)(正方形高度)
beingDragged = true;
// 手指當(dāng)前位置和開始滑動(dòng)的位置的距離
setScrollY(topViewHeight - y);
return true;
} else if (isScrollToTop && state == COLLAPSE_STATE && deltaY < 0) {
// 在頂上,收起且向下滑
beingDragged = true;
setScrollY(maxScrollDistance + deltaY);
return true;
} else {
return false;
}
}
@Override
public void onSwitch() {
if (state == WHOLE_STATE) {
if (getScrollY() >= minScrollToTop) {
switchToTop();
} else {
switchToWhole();
}
} else if (state == COLLAPSE_STATE) {
if (getScrollY() <= minScrollToWhole) {
switchToWhole();
} else {
switchToTop();
}
}
}
@Override
public boolean isBeingDragged() {
return beingDragged;
}
public void switchToWhole() {
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
// 滾到原來的位置
scroller.startScroll(0, getScrollY(), 0, -getScrollY(), DEFAULT_DURATION);
postInvalidate();
state = WHOLE_STATE;
beingDragged = false;
}
public void switchToTop() {
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
scroller.startScroll(0, getScrollY(), 0, getScrollRange() - getScrollY(), DEFAULT_DURATION);
postInvalidate();
state = COLLAPSE_STATE;
beingDragged = false;
}
@Override
public void computeScroll() {
// 重寫這個(gè)來讓LinearLayout可以滾動(dòng)
if (scroller.computeScrollOffset()) {
setScrollY(scroller.getCurrY());
postInvalidate();
}
}
private int getScrollRange() {
return maxScrollDistance;
}
@Override
public boolean isWholeState() {
return state == WHOLE_STATE;
}
}
使用方法:
分別find出對(duì)象,然后將CoordinatorLinearLayout調(diào)用setCoordinatorListener給CoordinatorRecyclerView就好了
然后調(diào)用CoordinatorLinearLayout的setTopViewParam設(shè)置高度
至于RecyclerView的設(shè)置manager和adapter啥的就不說了
注意事項(xiàng):
設(shè)置高度不要出錯(cuò),一個(gè)toolbar+大照片高度,一個(gè)toolbar高度
記得也給RecyclerView重設(shè)下高度,不然它劃上去也只有被啃剩下那點(diǎn)高度,(一些別的情況下高度設(shè)置可能會(huì)失效,這個(gè)我就不管了……Google吧)
設(shè)置RV的高度時(shí)注意設(shè)置成它最大能展示在屏幕里的高度,設(shè)多了最后滾到最下面會(huì)顯示不全
可能的問題
- 折疊上去以后第一次點(diǎn)擊失效:可能是RecyclerView的ScrollState沒更新導(dǎo)致的
- 折疊上去之后拽不下來:downPositionY位置沒更新導(dǎo)致的
- 別的我沒發(fā)現(xiàn)的問題
前兩個(gè)在注釋里有提到原因和解決辦法