效果图:
思路
1先自定义一个滑动布局
写一个类MySlideLayout继承RelativeLayout
重写onFinishInflate(布局加载完成后调用),在其中拿到子控件的对象
重写onMeasure,在其中拿到子控件的宽和本控件的高
重写onLayout,设置删除按钮的位置
重写onTouchEvent在其中实现控件的滑动
2把滑动布局作为ListView的条目,展示
出现bug1
listView上下滑动
滑动布局(MySlideLayout)套在ListView中,当左右滑动的,一部分,再
上下滑动时,会出现左右滑动停止
原因是ListView滑动事件默认把子控件(MySlideLayout)的事件给拦截了,
只要子控件在左右滑动时反拦截,让ListView把事件传递给子控件(MySlideLayout)
就可以了。
主要代码:在onTouchEvent的MotionEvent.ACTION_MOVE中
if(DX>8) {
// 反拦截
// 请求 驳回 拦截 触摸事件
getParent().requestDisallowInterceptTouchEvent(true);
}
出现Bug2
点击MySlideLayout的子控件
当滑动布局(MySlideLayout)中的子控件TextView设置点击事件后,滑动布局(MySlideLayout)
就无法左右滑动
原因是滑动布局(MySlideLayout)把事件传给了子控件,所以不能左右滑动了,可以判断左右
滑动时不把事件传给子控件(拦截),当点击时,把事件传给子控件。
主要代码:在onInterceptTouchEvent中的MotionEvent.ACTION_MOVE中
if(DX>8) {
// 水平滑动,执行onTouchEvent方法,否则执行的子类的事件
intercept=true;
}
出现Bug3
点击别的条目,关闭已打开的条目
用接口回调调适配器中处理
主要 setOnStateChangeListenter 方法
完美,下面贴出所有代码:
MySlideLayout中
package com.zhh.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import com.orhanobut.logger.Logger;
/**
* Created by 16838 on 2018/6/26.
*/
public class MySlideLayout extends RelativeLayout{
// 内容TextView
View contentView;
// 删除TextView
View delView;
// 内容的宽
int contentViewWidth;
// 删除按钮的宽
int delViewWidth;
// 布局的高
int layoutHeight;
private float startX;
private float startY;
private float downX;//只赋值一次
private float downY;
// 滑动对象
private Scroller scroller;
/**
* 构造方法
* @param context
* @param attrs
*/
public MySlideLayout(Context context, AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(context);
}
/**
* 布局加载完成后调用
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
contentView=getChildAt(0);
delView=getChildAt(1);
}
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
contentViewWidth = contentView.getMeasuredWidth();
delViewWidth = delView.getMeasuredWidth();
layoutHeight = getMeasuredHeight();
}
/**
* 设置子字控件的位置
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
delView.layout(contentViewWidth,0,contentViewWidth+delViewWidth,layoutHeight);
}
/**
* 判断事件是否传给子控件
* true:拦截孩子的事件,但会执行当前控件的onTouchEvent()方法
* false:不拦截孩子的事件,事件继续传递
* @param event
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//1.按下记录坐标
downX = startX = event.getX();
if(onStateChangeListenter!=null) {
onStateChangeListenter.onDown(this);
}
break;
case MotionEvent.ACTION_MOVE:
Log.e("111","SlideLayout-onTouchEvent-ACTION_MOVE");
//2.记录结束值
float endX = event.getX();
//在X轴和Y轴滑动的距离
float DX = Math.abs(endX-downX);
if(DX>8) {
// 水平滑动,执行onTouchEvent方法,否则执行的子类的事件
intercept=true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return intercept;
}
/**
* 触摸事件中实现滑动
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//1.按下记录坐标
// event.getX()指的是当前控件(MySlideLayout)左上方为原点坐标,X轴上的坐标,向右越大
// 坐标和按下的位置有关系(比如这个控件宽度铺满屏幕,在左边按下开始坐标startX很小,在中间按下坐标startX就较大)
downX = startX = event.getX();
downY = startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
//2.记录结束值
float endX = event.getX();
float endY = event.getY();
Logger.t("111").d("startX>"+startX+">>>"+"endX>"+endX);
//3.计算偏移量
// distanceX = endX - startX 这个是公式
float distanceX = endX - startX;
// getScrollX()就是当前视图(MySlideLayout)相对于自己左上角为原点坐标,的偏移量
// child.getScrollX()当前视图(MySlideLayout)子控件相对于自己左上角为原点坐标,的偏移量
// 当滑动的是当前的控件(MySlideLayout)时,getScrollX()有值,(本例滑动的是自己),child.getScrollX()是0;
// 当滑动控件是子控件时child.getScrollX()有值,getScrollX()是0
// 简单的说是滑动谁,谁的getScrollX()有值
// 从左滑到右是负值,从右到左是正值
Logger.t("222").d(">>>contentView.getScrollX()"+contentView.getScrollX());
int dX = (int) (getScrollX() - distanceX);
if (dX < 0) {
dX = 0;
} else if (dX > delViewWidth) {
dX = delViewWidth;
}
// scrollTo中的dX,从左滑到右是负值,从右到左是正值
// scrollTo中的dX,中的x就是(getScrollX() - distanceX),这是公式
// (distanceX = endX - startX;)
// 代表的意思就是:将父视图左上角定为(0,0),在移动这个屏幕的左上角到父视图的点(x,y)处,
// (注意:此处的x,y是根据父视图的坐标系来定的)
scrollTo(dX, getScrollY());
startX = event.getX();
startY = event.getY();
//在X轴和Y轴滑动的距离
float DX = Math.abs(endX-downX);
float DY = Math.abs(endY-downY);
// 水平滑动的时候,让listView把事件传递过来,MySlideLayout处理,竖直滑动listView自己处理,不用传递过来
// 简单的说水平滑动,禁止竖直滑动,竖直滑动,禁止水平滑动
if(DX>8) {
// 反拦截
// 请求 驳回 拦截 触摸事件
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
Log.e("111", "SlideLayout-onTouchEvent-ACTION_UP");
int totalScrollX = getScrollX();//偏移量
if(totalScrollX < delViewWidth/2){
//关闭Menu
closeMenu();
}else{
//打开Menu
openMenu();
}
break;
}
return true;
}
/**
* 打开menu
*/
public void openMenu() {
//--->menuWidth
int distanceX = delViewWidth - getScrollX();
// scroller.startScroll中的坐标
// 中第一个参数startX,参照父视图为原点坐标的坐标系,滑屏时经常使用getScrollX()(代表品目左边缘)
// 中第三个参数dx,表示正值向左滑,负值向右滑。
// 公式:目标 - getScrollX();
// 这里要移动的目标是控件的宽,所以就是int distanceX = delViewWidth - getScrollX();
scroller.startScroll(getScrollX(), getScrollY(), distanceX, getScrollY());
invalidate();//强制刷新
if(onStateChangeListenter!=null) {
onStateChangeListenter.onOpen(this);
}
}
/**
* 关闭menu
*/
public void closeMenu() {
//--->0
int distanceX = 0 - getScrollX();
scroller.startScroll(getScrollX(), getScrollY(), distanceX, getScrollY());
invalidate();//强制刷新
if(onStateChangeListenter!=null) {
onStateChangeListenter.onClose(this);
}
}
/**
* scroller对象滑动平滑的方法
*/
@Override
public void computeScroll() {
super.computeScroll();
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
invalidate();
}
}
/**
* 设置SlideLayout状态的监听
* @param onStateChangeListenter
*
*/
private OnStateChangeListenter onStateChangeListenter;
public void setOnStateChangeListenter(OnStateChangeListenter onStateChangeListenter) {
this.onStateChangeListenter = onStateChangeListenter;
}
/**
* 监听SlideLayout状态的改变
*/
public interface OnStateChangeListenter{
void onClose(MySlideLayout layout);
void onDown(MySlideLayout layout);
void onOpen(MySlideLayout layout);
}
}
MainActivity中
package com.zhh.myapplication;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
private ListView lvMain;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lvMain = findViewById(R.id.lvMain);
// 拿到数据
List<String> list = getData();
LvAdapter lvAdapter = new LvAdapter(list, MainActivity.this);
lvMain.setAdapter(lvAdapter);
}
private List<String> getData() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 50; i++) {
list.add("内容数据" + i);
}
return list;
}
}
LvAdapter中
package com.zhh.myapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
/**
* Created by 16838 on 2018/6/26.
*/
public class LvAdapter extends BaseAdapter {
List<String> list;
Context context;
public LvAdapter(List<String> list, Context context) {
this.list = list;
this.context = context;
}
@Override
public int getCount() {
return list.size();
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = LayoutInflater.from(context).inflate(R.layout.item_main, null);
viewHolder.tvContent = convertView.findViewById(R.id.tvContent);
viewHolder.tvDel = convertView.findViewById(R.id.tvDel);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.tvContent.setText(list.get(position));
// 内容控件的点击事件
viewHolder.tvContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "内容点击事件", Toast.LENGTH_SHORT).show();
}
});
// 删除控件的点击事件
viewHolder.tvDel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "删除按钮", Toast.LENGTH_LONG).show();
// 先关闭再删除
MySlideLayout slideLayout = (MySlideLayout) v.getParent();
slideLayout.closeMenu();
list.remove(position);
notifyDataSetChanged();
}
});
MySlideLayout mySlideLayout= (com.zhh.myapplication.MySlideLayout) convertView;
mySlideLayout.setOnStateChangeListenter(new MyOnStateChangeListenter());
return convertView;
}
/**
* 把MySlideLayout中的事件回调出来
*/
private MySlideLayout slideLayout;
class MyOnStateChangeListenter implements MySlideLayout.OnStateChangeListenter {
@Override
public void onClose(MySlideLayout layout) {
if (slideLayout == layout) {
slideLayout = null;
}
}
@Override
public void onDown(MySlideLayout layout) {
// 判断点击的不是同一个,关掉上一个
if (slideLayout != null && slideLayout != layout) {
slideLayout.closeMenu();
}
}
@Override
public void onOpen(MySlideLayout layout) {
slideLayout = layout;
}
}
class ViewHolder {
TextView tvContent;
TextView tvDel;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
}
activity_main.xml中
<LinearLayout 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"
android:orientation="vertical"
>
<ListView
android:id="@+id/lvMain"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
item_main.xml中
<?xml version="1.0" encoding="utf-8"?>
<com.zhh.myapplication.MySlideLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#44000000"
android:gravity="center"
android:text="Content"
android:textColor="#000000"
android:textSize="25sp" />
<TextView
android:id="@+id/tvDel"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:background="#22000000"
android:gravity="center"
android:text="Delete"
android:textColor="#ff0000"
android:textSize="25sp" />
</com.zhh.myapplication.MySlideLayout>
源码下载:
TestLayout----slidetest3
https://download.csdn.net/download/zhaihaohao1/10500804
Myself ---- cehua1
http://download.csdn.net/download/zhaihaohao1/10111351
参考视频: