您的位置:首页 > 其它

自定义SwipeLayout控件实现ListView条目侧滑出现删除按钮,点击实现删除ListView条目

2016-04-08 22:03 525 查看
今天,我们来实现类似于QQ最近联系人的聊天记录向右滑动出现删除按钮的功能。效果图如下:





布局文件如下:



这里这需要自定义一个SwipeLayout实现滑动出现删除按钮的效果,SwipeLayout.java代码如下:

package com.example.myoperation;
import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

/**
* 侧拉删除控件
*/
public class SwipeLayout extends FrameLayout {
private Status status = Status.Close;
private OnSwipeLayoutListener swipeLayoutListener;

public Status getStatus() {
return status;
}

public void setStatus(Status status) {
this.status = status;
}

public OnSwipeLayoutListener getSwipeLayoutListener() {
return swipeLayoutListener;
}

public void setSwipeLayoutListener(OnSw
4000
ipeLayoutListener swipeLayoutListener) {
this.swipeLayoutListener = swipeLayoutListener;
}

/**
* 三种状态,关闭开启和正在拖拽
*/
public static enum Status{
Close, Open, Draging
}
public static interface OnSwipeLayoutListener {

void onClose(SwipeLayout mSwipeLayout);
void onOpen(SwipeLayout mSwipeLayout);
void onDraging(SwipeLayout mSwipeLayout);
// 要去关闭
void onStartClose(SwipeLayout mSwipeLayout);
// 要去开启
void onStartOpen(SwipeLayout mSwipeLayout);
}
/**
* 第一步实现父类方法,初始化ViewDragHelper
*/
public SwipeLayout(Context context) {
this(context, null);
}
public SwipeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1.0f, mCallback);
}
/**
* 第三步重写触摸事件方法
*/
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
/**
* 根据返回结果决定当前child是否可以被拖拽
*/
@Override
public boolean tryCaptureView(View view, int id) {
return true;//表示子View都可以被拖拽
}

/**
* 根据建议值修正将要移动到的(横向)位置
* @param child:当前拖拽的view
* @param left:建议值
* return: 真正值
*/
public int clampViewPositionHorizontal(View child, int left, int dx) {
if(child == mFrontView){
if(left > 0){
return 0;
}else if(left < -mRange){
return -mRange;
}
}else if (child == mBackView) {
if(left > mWidth){
return mWidth;
}else if (left < mWidth - mRange) {
return mWidth - mRange;
}
}
return left;
}
/**
* 当View位置改变的时候。处理要做的事(更新状态,伴随动画,重绘界面)
* @param changedView:改变位置的View
* @param left:新的左边值
* @param dx:水平方向变化量
*/
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
// 传递事件,是两个布局同时移动
if(changedView == mFrontView){
mBackView.offsetLeftAndRight(dx);
}else if (changedView == mBackView) {
mFrontView.offsetLeftAndRight(dx);
}
dispatchSwipeEvent();
// 兼容老版本
invalidate();
}
/**
* 当View被释放的时候,处理的事情(执行动画)
* @param releasedChild:被释放的子View
* @param xvel:释放时水平方向的速度
* @param yvel:释放时竖直方向的速度
*/
public void onViewReleased(View releasedChild, float xvel, float yvel) {

if (xvel == 0 && mFrontView.getLeft() < -mRange / 2.0f) {
//当水平方向无速度并且向左滑动的距离大于范围的一半则打开
open();
}else if (xvel < 0) {
//当有水平方向向左的速度则打开
open();
}else {
//其余情况关闭
close();
}
}
};
/**
* 第二步将触摸事件传递给ViewDragHelper,计算子View宽高和移动范围
*/
private ViewDragHelper mDragHelper;
private View mBackView;
private View mFrontView;
private int mHeight;
private int mWidth;
private int mRange;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}

protected void dispatchSwipeEvent() {

if(swipeLayoutListener != null){
swipeLayoutListener.onDraging(this);
}
// 记录上一次的状态
Status preStatus = status;
// 更新当前状态
status = updateStatus();
if (preStatus != status && swipeLayoutListener != null) {
if (status == Status.Close) {
swipeLayoutListener.onClose(this);
} else if (status == Status.Open) {
swipeLayoutListener.onOpen(this);
} else if (status == Status.Draging) {
if(preStatus == Status.Close){
swipeLayoutListener.onStartOpen(this);
}else if (preStatus == Status.Open) {
swipeLayoutListener.onStartClose(this);
}
}
}
}

/**
* 更新状态
*/
private Status updateStatus() {

int left = mFrontView.getLeft();
if(left == 0){
return Status.Close;
}else if (left == -mRange) {
return Status.Open;
}
return Status.Draging;
}

/**
* 关闭条目
*/
public void close() {
close(true);
}
public void close(boolean isSmooth){
int finalLeft = 0;
if(isSmooth){
//开始动画
if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
layoutContent(false);
}
}

public void open() {
open(true);
}
public void open(boolean isSmooth){
int finalLeft = -mRange;
if(isSmooth){
//开始动画
if(mDragHelper.smoothSlideViewTo(mFrontView, finalLeft, 0)){
ViewCompat.postInvalidateOnAnimation(this);
}
}else {
layoutContent(true);
}
}

@Override
public void computeScroll() {
super.computeScroll();
//持续平滑动画 (高频率调用)
if(mDragHelper.continueSettling(true)){
//如果返回true, 动画还需要继续执行
ViewCompat.postInvalidateOnAnimation(this);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
try {
mDragHelper.processTouchEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
/**
* 获取前后两个布局
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// 当xml被填充完毕时调用
mBackView = getChildAt(0);
mFrontView = getChildAt(1);
}

/**
* 计算前后两个布局的宽高和移动的范围
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

mHeight = mFrontView.getMeasuredHeight();
mWidth = mFrontView.getMeasuredWidth();
mRange = mBackView.getMeasuredWidth();

}
/**
* 对前后两个布局进行摆放
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
// 摆放位置
layoutContent(false);
}
private void layoutContent(boolean isOpen) {
// 摆放前View
Rect frontRect = computeFrontViewRect(isOpen);
mFrontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);
// 摆放后View
Rect backRect = computeBackViewViaFront(frontRect);
mBackView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);
// 调整顺序, 把mFrontView前置
bringChildToFront(mFrontView);
}

/**
* 计算前布局摆放的位置
*/
private Rect computeFrontViewRect(boolean isOpen) {
int left = 0;
if(isOpen){
left = -mRange;
}
return new Rect(left, 0, left + mWidth, 0 + mHeight);
}
/**
* 根据前布局计算后布局的位置
*/
private Rect computeBackViewViaFront(Rect frontRect) {
int left = frontRect.right;
return new Rect(left, 0, left + mRange, 0 + mHeight);
}
}

activity_main.xml布局文件代码如下:

<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>

listview的条目item_list布局文件如下,这里注意要使用自己定义的SwipeLayout控件作为根布局:

<?xml version="1.0" encoding="utf-8"?>
<com.example.myoperation.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sl"
android:layout_width="match_parent"
android:layout_height="60dp"
android:minHeight="60dp"
android:background="#44000000" >
<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="#fff" />
<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.example.myoperation.SwipeLayout>

MainActivity.java布局文件如下:

package com.example.myoperation;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.example.myoperation.SwipeLayout.OnSwipeLayoutListener;
import java.util.ArrayList;

public class MainActivity extends Activity {

MyAdapter myAdapter;
private ArrayList<SwipeLayout> opendItems = new ArrayList<>();
private ArrayList<String> NAMES = new ArrayList<>();
private ListView mList;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mList = (ListView) findViewById(R.id.lv);
initData();
myAdapter = new MyAdapter();
mList.setAdapter(myAdapter);
}

private void initData() {
for (int i = 0; i < 20; i++){
NAMES.add("name"+i);
}
}

public class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
return NAMES.size();
}

@Override
public String getItem(int position) {
return NAMES.get(position);
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = View.inflate(MainActivity.this, R.layout.item_list, null);
holder = new ViewHolder();
holder.tv_name = (TextView)convertView.findViewById(R.id.tv_name);
holder.tv_del = (TextView)convertView.findViewById(R.id.tv_del);
convertView.setTag(holder);
}else {
holder = (ViewHolder)convertView.getTag();
}
holder.tv_name.setText(getItem(position));
holder.tv_del.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NAMES.remove(position);
myAdapter.notifyDataSetChanged();
}
});
/**
* 设置listview滑动监听,当listview滑动时关闭已经开启的的Item
*/
mList.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {

}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
for (SwipeLayout layout : opendItems) {
layout.close();
}
opendItems.clear();
}
});
/**
* 设置SwipeLayout滑动监听,当新的Item开启时,关闭之前已经开启的Item
*/
SwipeLayout sl = (SwipeLayout)convertView;
sl.setSwipeLayoutListener(new OnSwipeLayoutListener() {

@Override
public void onStartOpen(SwipeLayout mSwipeLayout) {
for (SwipeLayout layout : opendItems) {
layout.close();
}
opendItems.clear();
}
@Override
public void onStartClose(SwipeLayout mSwipeLayout) {

}
@Override
public void onOpen(SwipeLayout mSwipeLayout) {
opendItems.add(mSwipeLayout);
}
@Override
public void onDraging(SwipeLayout mSwipeLayout) {
}

@Override
public void onClose(SwipeLayout mSwipeLayout) {
opendItems.remove(mSwipeLayout);
}
});
return convertView;
}
}
static class ViewHolder {
TextView tv_name;
TextView tv_del;
}
}

将上述布局文件和java代码布局好,编译运行就可以实现listview向右滑动点击删除按钮达到删除条目的效果了。代码中的一些注意点我都注释了,如果有不明白的地方可以留言哦,我尽量帮大家解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息