效果圖:
看網(wǎng)上的都是兩個(gè)view拼接癣防,默認(rèn)右側(cè)的不顯示割择,水平移動(dòng)的時(shí)候把右側(cè)的view顯示出來(lái)梧田。但是看最新版QQ上的效果不是這樣的,但給人的感覺(jué)卻很好绝淡,所以獻(xiàn)丑來(lái)一發(fā)比較高仿的铐伴。
知識(shí)點(diǎn):
1瘤睹、ViewDragHelper 的用法朱灿;
2、滑動(dòng)沖突的解決攻臀;
3焕数、自定義viewgroup。
ViewDragHelper 出來(lái)已經(jīng)比較久了 相信大家都比較熟悉茵烈,不熟悉的話google一大把 這里主要簡(jiǎn)單用一下它的幾個(gè)方法
1百匆、tryCaptureView(View child, int pointerId) :確定那個(gè)子view可以滑動(dòng)
2、 getViewHorizontalDragRange(View child):用我蹩腳的英語(yǔ)翻譯一下是‘返回的是子view在水平方向上可移動(dòng)的大小呜投,以像素為單位加匈,返回0的時(shí)候表示水平方向上不能拖動(dòng)’
3、clampViewPositionHorizontal(View child, int left, int dx):在這里可以對(duì)邊界進(jìn)行檢查仑荐,left和dx分別代表即將移動(dòng)到的位置
4雕拼、onViewPositionChanged(View changedView, int left, int top, int dx, int dy):當(dāng)要捕獲view,由于拖曳或者設(shè)定而發(fā)生位置變更時(shí)回調(diào)
它的基本用法是:
public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, callback);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = viewDragHelper.shouldInterceptTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
1)粘招、在構(gòu)造方法中創(chuàng)建
2)啥寇、在onInterceptTouchEvent 中判斷是否攔截
3 )、 在 onTouchEvent出來(lái)事件
好了 最不好理解的已經(jīng)搞定了。接下來(lái)看看具體實(shí)現(xiàn):
首先看布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<scrollviewgroup.lly.com.swiplayout.SwipeLayout
android:id="@+id/swipeLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- delete區(qū)域的布局 -->
<include layout="@layout/layout_delete" />
<!-- item內(nèi)容的布局 -->
<include layout="@layout/layout_content" />
</scrollviewgroup.lly.com.swiplayout.SwipeLayout>
</LinearLayout>
這個(gè)沒(méi)什么好說(shuō)的辑甜,一個(gè)自定義viewgroup包含兩個(gè)子控件衰絮。 接著看看SwipeLayout是怎么實(shí)現(xiàn)的:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
deleteView = getChildAt(0);
contentView = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
deleteHeight = deleteView.getMeasuredHeight();
deleteWidth = deleteView.getMeasuredWidth();
contentWidth = contentView.getMeasuredWidth();
screenWidth = getWidth();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// super.onLayout(changed, left, top, right, bottom);
deleteView.layout(screenWidth - deleteWidth, 0, (screenWidth - deleteWidth)
+ deleteWidth, deleteHeight);
contentView.layout(0, 0, contentWidth, deleteHeight);
}
上面代碼進(jìn)行了一些初始化的操作,重點(diǎn)看看onlayout里面的磷醋,我們繼承的是framelayout 這里先畫(huà)出來(lái) deleteView并讓他在右邊猫牡,然后在上面改了一層contentView,這樣顯示的時(shí)候只會(huì)顯示contentView邓线。
接下來(lái)看ontouch方法
public boolean onTouchEvent(MotionEvent event) {
//如果當(dāng)前有打開(kāi)的淌友,則下面的邏輯不能執(zhí)行
if(!SwipeLayoutManager.getInstance().isShouldSwipe(this)){
requestDisallowInterceptTouchEvent(true);
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//1.獲取x和y方向移動(dòng)的距離
float moveX = event.getX();
float moveY = event.getY();
float delatX = moveX - downX;//x方向移動(dòng)的距離
float delatY = moveY - downY;//y方向移動(dòng)的距離
if(Math.abs(delatX)>Math.abs(delatY)){
//表示移動(dòng)是偏向于水平方向,那么應(yīng)該SwipeLayout應(yīng)該處理骇陈,請(qǐng)求父view不要攔截
requestDisallowInterceptTouchEvent(true);
}
//更新downX震庭,downY
downX = moveX;
downY = moveY;
break;
case MotionEvent.ACTION_UP:
break;
}
viewDragHelper.processTouchEvent(event);
return true;
}
上面主要就是對(duì)事件沖突的處理,當(dāng)是水平移動(dòng)的時(shí)候就請(qǐng)求父視圖不要攔截你雌。
接下來(lái)來(lái)重點(diǎn)就來(lái)了
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child==contentView;
}
@Override
public int getViewHorizontalDragRange(View child) {
return deleteWidth;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if(child==contentView){
if(left>0)left = 0;
if(left<-deleteWidth)left = -deleteWidth;
}
return left;
}
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
//判斷開(kāi)和關(guān)閉的邏輯
if(contentView.getLeft()==0 && currentState!=SwipeState.Close){
//說(shuō)明應(yīng)該將state更改為關(guān)閉
currentState = SwipeState.Close;
//回調(diào)接口關(guān)閉的方法
if(listener!=null){
listener.onClose(getTag());
}
//說(shuō)明當(dāng)前的SwipeLayout已經(jīng)關(guān)閉器联,需要讓Manager清空一下
SwipeLayoutManager.getInstance().clearCurrentLayout();
}else if (contentView.getLeft()==-deleteWidth && currentState!=SwipeState.Open) {
//說(shuō)明應(yīng)該將state更改為開(kāi)
currentState = SwipeState.Open;
//回調(diào)接口打開(kāi)的方法
if(listener!=null){
listener.onOpen(getTag());
}
//當(dāng)前的Swipelayout已經(jīng)打開(kāi),需要讓Manager記錄一下下
SwipeLayoutManager.getInstance().setSwipeLayout(SwipeLayout.this);
}
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if(contentView.getLeft()<-deleteWidth/2){
//應(yīng)該打開(kāi)
open();
}else {
//應(yīng)該關(guān)閉
close();
}
}
};
上面這段代碼里面的方法一開(kāi)始我們都說(shuō)過(guò)了匪蝙,在來(lái)看下 在tryCaptureView中我們讓 contentView可以滑動(dòng)主籍,在getViewHorizontalDragRange中確定滑動(dòng)范圍是deleteWidth,在clampViewPositionHorizontal中對(duì)邊界進(jìn)行了下限制逛球,在onViewPositionChanged中進(jìn)行狀態(tài)的更新, 最后在手指抬起的時(shí)候讓view自動(dòng)回滾.
/**
* 打開(kāi)的方法
*/
public void open() {
viewDragHelper.smoothSlideViewTo(contentView,-deleteWidth,contentView.getTop());
ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
}
/**
* 關(guān)閉的方法
*/
public void close() {
viewDragHelper.smoothSlideViewTo(contentView,0,contentView.getTop());
ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
};
public void computeScroll() {
if(viewDragHelper.continueSettling(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
這里注意一定要重寫computeScroll方法苫昌,不然滑動(dòng)效果動(dòng)一下就不動(dòng)了颤绕。
至此這個(gè)自定義framelayout就完成了
但是發(fā)現(xiàn)一個(gè)問(wèn)題,我們?cè)谝呀?jīng)滑動(dòng)出來(lái)的view中上下滑動(dòng)時(shí)祟身,這個(gè)view的deleteView還是顯示狀態(tài)奥务,所以還要在activity中處理一下:
recyView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(dy>0 || dy<0){
SwipeLayoutManager.getInstance().closeCurrentLayout();
}
}
});
當(dāng)這個(gè)RecyclerView是上下滑動(dòng)時(shí),讓子view復(fù)位袜硫。 收工氯葬。 ps:本來(lái)是在eclipse中l(wèi)istview中實(shí)現(xiàn)的,但是想想google都已經(jīng)不支持eclipse了婉陷,而listview也快被RecyclerView代替了帚称,所以最后還是切換到Android studio,用RecyclerView實(shí)現(xiàn)了一套秽澳。
github