一、概述:

1、目前一般的手机下拉信息栏时,会显示很多自己不想看到的内能,但我们又不想去触发它,这些厂商就实现了平拉动的时候可以把这些信息删除,就不会显示在顶部的信息里了!

2、但我们今天要实现的效果与这个差不多,但必须手动点击删除,如图:

Android 实现新闻端平拉动删除,拉下条新闻,上条新闻弹回特效_移动

3、那我们如何实现呢?仔细观看图片,其实是使用了两个图层,上面是个拖拽图层,下面是个按钮图层,把上面一层移动到某个位置时,显示下面的按钮,然后点击按钮,隐藏上面的图层
那既然讲到了侧拉,当然会想到现在最流行的工具类ViewDragHelper
那么我们先实现基本的功能。。。。。。。。。。。

二、实现基本功能:

1、创建自定义的View

public class SwipeLayout extends FrameLayout {
public SwipeLayout(Context context) {
super(context, null);
}

public SwipeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

}

2、创建布局

<?xml version="1.0" encoding="utf-8"?>
<com.android.imooc.swipelayout.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >


</com.android.imooc.swipelayout.SwipeLayout>

3、创建主页

public class SwipeActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_swipe);
}
}

这些基础框架搭建好后,我们得开始写ViewDragHelper。。。。。。。。。。。

三、ViewDragHelper基本写法

1、第一步:在构造方法里初始化

mHelper = ViewDragHelper.create(this, 1.0f, mCallback);

private Callback mCallback = new Callback() {
@Override
public boolean tryCaptureView(View arg0, int arg1) {
// TODO Auto-generated method stub
return false;
}
};

2、第二步:复写onInterceptTouchEvent

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mHelper.shouldInterceptTouchEvent(ev);
}

3、第三步:复写onTouchEvent

@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}

四、实现基本的拖动效果

1、我们现在写个有两层的布局文件,上层是内容,下面一层是按钮

<?xml version="1.0" encoding="utf-8"?>
<com.android.imooc.swipelayout.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sl"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#44000000"
android:minHeight="60dp" >

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal" >

<TextView
android:id="@+id/tv_call"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="#666666"
android:gravity="center"
android:text="Call"
android:textColor="#ffffff" />

<TextView
android:id="@+id/tv_del"
android:layout_width="60dp"
android:layout_height="match_parent"
android:background="#ff0000"
android:gravity="center"
android:text="Delete"
android:textColor="#ffffff" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#44ffffff"
android:gravity="center_vertical"
android:orientation="horizontal" >

<ImageView
android:id="@+id/iv_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="15dp"
android:src="@drawable/head_1" />

<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:text="Name" />
</LinearLayout>

</com.android.imooc.swipelayout.SwipeLayout>

2、在SwipeLayout里的回调函数里复写clampViewPositionHorizontal方法

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}

3)在onFinishInflate方法里获得子控件

private View mBackView;
private View mFrontView;
/**
* 当xml被填充完成后调用
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBackView = getChildAt(0);
mFrontView = getChildAt(1);
}

4、在onLayout里来设置子控件的位置

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//布局子控件
layoutChild(false);
}

/**
* 根据控件是否关闭来布
* @param isOpen
*/
private void layoutChild(boolean isOpen) {
Rect frontRect = computeFrontViewRect(isOpen);
mFrontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);
Rect backRect = computeBackViewRect(frontRect);
mBackView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);

//绘制完后置于前面
bringChildToFront(mFrontView);
}

private Rect computeBackViewRect(Rect frontRect) {
int left = frontRect.right;
return new Rect(left, frontRect.top, left + mRange, frontRect.bottom);
}

private Rect computeFrontViewRect(boolean isOpen) {
int left = 0;
int top = 0;
int right = left + mWidth;
int bottom = top + mHeight;
if (isOpen) {
left = -mRange;
}
return new Rect(left, top, right, bottom);
}


@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mFrontView.getMeasuredWidth();
mHeight = mFrontView.getMeasuredHeight();

mRange = mBackView.getMeasuredWidth();
}

5.如何实现上层控件拖动下层也跟着拖动,在回调函数里复写onViewPositionChanged方法
@Override

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
if (changedView == mFrontView) {
//让后面的view跟着前面的view相同的距离
mBackView.offsetLeftAndRight(dx);
}else if (changedView == mBackView) {
mFrontView.offsetLeftAndRight(dx);
}

if (dx > mRange) {
dx = mRange;
}else if (dx < 0) {
dx = 0;
}

invalidate();
}

6、限制移动的范围

/**
* 在这里修正
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
if (child == mFrontView) {
if (left < -mRange) {
left = -mRange;
} else if (left > 0) {
left = 0;
}
} else if (child == mBackView) {
if (left < mWidth - mRange) {
left = mWidth - mRange;
} else if (left > mWidth) {
left = mWidth;
}
}
return left;
}

7、在松手的时候,实现如果>mrang/2时就open,否则就close

@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel == 0 && mFrontView.getLeft() < -mRange / 2.0f) {
open();
} else if (xvel < 0) {
open();
} else {
close();
}
}

8、但这样是不是太单调了,没有动画效果,如果你的boss发现,岂不要被叼,那我们就加上一个平滑的效果

public void open(boolean isSmooth){
int finalLeft = -mRange;
if (isSmooth) {
//开始动画
if (mHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)) {
//没到达重绘
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
open();
}
}

protected void open() {
layoutChild(true);
}

protected void close() {
layoutChild(false);
}

public void close(boolean isSmooth){
int finalLeft = 0;
if (isSmooth) {
//开始动画
if (mHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)) {
//没到达重绘
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
close();
}
}

/**
* 实现持续动画
*/
@Override
public void computeScroll() {
super.computeScroll();
if (mHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}

五、添加状态与监听

1.添加状态

private Status msStatus = Status.CLOSE;
public static enum Status {
CLOSE, OPEN, DRAGING
}

2.添加监听

public interface onSwipeListener{
void onClose(SwipeLayout layout);
void onOpen(SwipeLayout layout);
void onDraging(SwipeLayout layout);
void onStartOpen(SwipeLayout layout);//表示准备打开时,其它的条目开始关闭
void onStartClose(SwipeLayout layout);
}
private onSwipeListener mListener;
public void setOnSwipeListener(onSwipeListener listener){
this.mListener = listener;
}

3.在什时候调用监听呢?当然是在位置变化的时候,所以我们在onViewPositionChanged方法里插入方法
dispatchSwipeEvent();

protected void dispatchSwipeEvent() {
mListener.onDraging(this);
// 记录上一次的状态
Status preStatus = mStatus;
// 更新当前状态
mStatus = updateStatus();

// 判断
if (mStatus != preStatus && mListener != null) {
if (mStatus == Status.CLOSE) {
mListener.onClose(this);
} else if (mStatus == Status.OPEN) {
mListener.onOpen(this);
} else if (mStatus == Status.DRAGING) {
//如果上次是关闭状态,就表示准备打开
if (preStatus == Status.CLOSE) {
mListener.onStartOpen(this);
}else if (preStatus == Status.OPEN) {
mListener.onStartClose(this);
}
}
}
}

/**
* 更新状态
*
* @return
*/
private Status updateStatus() {
int left = mFrontView.getLeft();
if (left == 0) {
return Status.CLOSE;
} else if (left == -mRange) {
return Status.OPEN;
}
return Status.DRAGING;
}

六、把上面的item添加到listview

1.创建布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/lv"
></ListView>

</RelativeLayout>

2、把主页布局设置成上面的布局

setContentView(R.layout.activity_swipe_list);

3、把原来的布局改成item_list布局
4、在主页里初始化数据

private void initDatas() {
mOpenLayouts = new HashMap<Integer, SwipeLayout>();
mDatas = new ArrayList<String>();
for (int i = 0; i < Cheeses.NAMES.length; i++) {
mDatas.add(Cheeses.NAMES[i]);
}

}

5、创建Adapter

private QuickAdapter<String> mAdapter = new QuickAdapter<String>(this, R.layout.item_list, mDatas) {
private SwipeLayout mSwipeLayout;

@Override
protected void convert(final BaseAdapterHelper helper, final String item) {
//设置数据
helper.setText(R.id.tv_name, item);
notifyDataSetChanged();

final int position = helper.getPosition();
Logger.i("tag", "position ==" + position);

View call = helper.getView(R.id.tv_call);

//删除
View del = helper.getView(R.id.tv_del);
del.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
remove(item);
}
});



View convertView = helper.getView();


//设置监听打开一个条目,关闭其它条目
if (convertView instanceof SwipeLayout) {
mSwipeLayout = (SwipeLayout) convertView;

mSwipeLayout.setOnSwipeListener(new onSwipeListener() {
@Override
public void onStartOpen(SwipeLayout layout) {
Logger.d(TAG, "onStartOpen");
for(Integer key : mOpenLayouts.keySet()){
SwipeLayout swipeLayout = mOpenLayouts.get(key);
swipeLayout.close(true);
}
mOpenLayouts.clear();
}

@Override
public void onStartClose(SwipeLayout layout) {
Logger.d(TAG, "onStartClose");

}

@Override
public void onOpen(SwipeLayout layout) {
Logger.d(TAG, "onOpen");
//添加到集合
mOpenLayouts.put(position, layout);
}

@Override
public void onDraging(SwipeLayout layout) {
Logger.d(TAG, "onDraging");
}

@Override
public void onClose(SwipeLayout layout) {
Logger.d(TAG, "onClose");
mOpenLayouts.remove(position);
}
});
}

}
};

6、把adapter设置到listview里,好了,这样就大功搞成了

七、源码下载:

链接:​​http://pan.baidu.com/s/1c1Krxpm​​ 密码:u5ys