仿QQ的侧滑菜单删除。
1.设置布局:两个TextView,一个为内容,一个为删除
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:text="Content"
android:textSize="25sp"
android:background="#44000000"
android:textColor="#000000"
android:gravity="center"
android:layout_height="60dp">
</TextView>
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:text="Delete"
android:textSize="25sp"
android:background="#88000000"
android:textColor="#ff0000"
android:gravity="center"
android:layout_height="60dp"
>
</TextView>
<?xml version="1.0" encoding="utf-8"?>
<com.example.slidemenu.SlideLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp">
<include android:id="@+id/item_content" layout="@layout/item_content"/>
<include android:id="@+id/item_menu" layout="@layout/item_menu"/>
</com.example.slidemenu.SlideLayout>
2.设置布局中各个控件的位置
指定孩子:
/**
* 当布局文件加载完成的时候回调这个方法
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
contentView = getChildAt(0);
menuView = getChildAt(1);
}
在测量方法里面得到各个控件的高和宽 :
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
contentWidth = contentView.getMeasuredWidth();
menuWidth = menuView.getMeasuredWidth();
viewHeight = getMeasuredHeight();
}
指定菜单(删除)的位置 :
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//指定菜单的位置
menuView.layout(contentWidth, 0,contentWidth+menuWidth,viewHeight);
}
3.打开或关闭菜单(删除)
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.w("www","ACTION_DOWN");
//1.按下记录坐标
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.w("www","ACTION_MOVE");
//2.记录结束值
float endX = event.getX();
float endY = event.getY();
//3.计算偏移值
float distanceX = endX - startX;
int toScrollX = (int) (getScrollX() - distanceX);
//4.屏蔽非法值
if(toScrollX < 0){
toScrollX = 0;
}else if(toScrollX > menuWidth){
toScrollX = menuWidth;
}
scrollTo(toScrollX,0);
//5.重新赋值
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
4.菜单回弹:
case MotionEvent.ACTION_UP:
Log.w("www","ACTION_UP");
int totalScrollX = getScrollX();//偏移量
if(totalScrollX < menuWidth / 2){
//关闭menu
closeMenu();
}else {
//打开menu
openMenu();
}
break;
关于int totalScrollX = getScrollX();//偏移量----打印日志观察:
03-03 10:24:11.889 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 0
03-03 10:24:11.889 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -12.612488
03-03 10:24:11.889 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 12
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 12
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -9.052307
03-03 10:24:11.908 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 21
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 21
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -8.729614
03-03 10:24:11.926 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 29
03-03 10:24:13.198 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.198 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -0.9217529
03-03 10:24:13.198 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === -0.56695557
03-03 10:24:13.216 25832-25832/com.example.slidemenu W/www: ACTION_MOVE === 165
03-03 10:24:13.269 25832-25832/com.example.slidemenu W/www: ACTION_UP
03-03 10:24:13.269 25832-25832/com.example.slidemenu W/www: ACTION_UP === 165 int totalScrollX = getScrollX();
每一次的toScrollX的值会是下一次getScrollX()的值。
所以最后up手指离开的时候getScrollX()是偏移量。
public void closeMenu() {
//--> 0 目标为0, 使用以下公式 : int distanceX = 目标 - getScrollX();
int distanceX = 0 - getScrollX();
Log.w("www",""+getScrollX());
Log.w("www",""+distanceX);
scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
Log.w("www",""+getScrollX());//当前
Log.w("www",""+getScrollY());
invalidate();//强制刷新
if(onstateCahngeListener != null){
onstateCahngeListener.onClose(this);
}
}
当小于1/2 的时候,执行closeMenu() ,负值,向右移动。
打印日志:
03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: 78
03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: -78
03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: 78
03-03 10:42:57.550 26991-26991/com.example.slidemenu W/www: 0
public void openMenu() {
//--> 0 使用以下公式 :int distanceX = 目标 - getScrollX();
int distanceX = menuWidth - getScrollX();
Log.w("www",""+getScrollX());
Log.w("www",""+distanceX);
scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
Log.w("www",""+getScrollX());
Log.w("www",""+getScrollY());
invalidate();//强制刷新
if(onstateCahngeListener != null){
onstateCahngeListener.onOpen(this);
}
}
当大于1/2 的时候,执行openMenu() ,正值,向左移动。
打印日志:
03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 158
03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 54
03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 158
03-03 11:47:36.588 26991-26991/com.example.slidemenu W/www: 0
5.解决item滑动后不能自动打开或关闭
问题:Menu被listview拦截。
解决:SlideLayout反拦截listview。孩子请求反拦截,如果水平方向的滑动大于8dp,就反拦截,响应侧滑菜单。
if(DX > DY && DX> 8){
//水平方向滑动
//响应侧滑
//反拦截 事件给孩子 SlideLayout
getParent().requestDisallowInterceptTouchEvent(true);
}
6.内容视图设置点击事件时不能滑动listview
问题:Content把事件消费掉了,SlideLayout不起作用。
解决:SlideLayout拦截ContentView。设置一个boolean 值,只要水平方向有滑动,就返回true。 true:拦截孩子的事件,但会执行当前控件的onTouchEvent()方法,menu可以实现侧滑。false:不拦截孩子的事件,事件继续传递。
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
super.onInterceptTouchEvent(event);
boolean intercpet = false;//默认传给孩子
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.w("www", "ACTION_DOWN");
//1.按下记录坐标
downX = startX = event.getX();
downY = startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.w("www", "ACTION_MOVE");
//2.记录结束值
float endX = event.getX();
//3.计算偏移值
float distanceX = endX - startX;
//5.重新赋值
startX = event.getX();
startY = event.getY();
//在X轴和Y轴滑动的距离
float DX = Math.abs(endX - downX);
if (DX > 8) {
intercpet = true;
}
break;
case MotionEvent.ACTION_UP:
Log.w("www", "ACTION_UP");
break;
}
return intercpet;
}
作为中间层,事件被上层拦截了,中间反拦截;下层把事件消费掉了,中间拦截事件。
7.设置删除功能
问题:删除后下一个打开了。
解决:
viewHolder.item_menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SlideLayout slideLayout = (SlideLayout) v.getParent();
slideLayout.closeMenu();
bean.remove(beans);
myadapter.notifyDataSetChanged();
}
});
8.限制只能打开一个item。
解决:使用接口
/**
* 监听slidelayout状态的改变
*/
public interface OnstateCahngeListener{
void onClose(SlideLayout layout);
void onDown(SlideLayout layout);
void onOpen(SlideLayout layout);
}
public OnstateCahngeListener onstateCahngeListener;
/**
* 设置slidelayout状态的监听
* @param onstateCahngeListener
*/
public void setOnstateCahngeListener(OnstateCahngeListener onstateCahngeListener){
this.onstateCahngeListener = onstateCahngeListener;
}
在 case MotionEvent.ACTION_DOWN: , openMenu() , closeMenu()中都写入以下代码
if(onstateCahngeListener != null){
onstateCahngeListener.onDown(this);
}
如果item是打开的,就赋值当前的;如果按下,并且item不等于空,也不等于当前的,就关闭。关闭时把slideLayout 赋值为null。
SlideLayout slideLayout;
class MyOnstateCahngeListener implements SlideLayout.OnstateCahngeListener{
@Override
public void onClose(SlideLayout layout) {
if(slideLayout == layout){
slideLayout = null;
}
}
@Override
public void onDown(SlideLayout layout) {
if(slideLayout != null && slideLayout != layout){
slideLayout.closeMenu();
}
}
@Override
public void onOpen(SlideLayout layout) {
slideLayout = layout;
}
}
源码:
SlideLayout
public class SlideLayout extends FrameLayout {
private View contentView;
private View menuView;
private int contentWidth;
private int menuWidth;
private int viewHeight;
private Scroller scroller;
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
scroller = new Scroller(context);
}
/**
* 当布局文件加载完成的时候回调这个方法
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
contentView = getChildAt(0);
menuView = getChildAt(1);
}
/**
* 在测量方法里面得到各个控件的高和宽
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
contentWidth = contentView.getMeasuredWidth();
menuWidth = menuView.getMeasuredWidth();
viewHeight = getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//制定菜单的位置
menuView.layout(contentWidth, 0,contentWidth+menuWidth,viewHeight);
}
private float startX;
private float startY;
private float downX;//只赋值一次
private float downY;
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.w("www","ACTION_DOWN");
//1.按下记录坐标
downX = startX = event.getX();
downY = startY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
Log.w("www","ACTION_MOVE");
//2.记录结束值
float endX = event.getX();
float endY = event.getY();
//3.计算偏移值
float distanceX = endX - startX;
int toScrollX = (int) (getScrollX() - distanceX);
Log.w("www","ACTION_MOVE === "+getScrollX());
Log.w("www","ACTION_MOVE === "+getScrollX());
Log.w("www","ACTION_MOVE === "+toScrollX);
//4.屏蔽非法值
if(toScrollX < 0){
toScrollX = 0;
}else if(toScrollX > menuWidth){
toScrollX = menuWidth;
}
scrollTo(toScrollX,0);
//5.重新赋值
startX = event.getX();
startY = event.getY();
//在X轴和Y轴滑动的距离
float DX = Math.abs(endX - downX);
float DY = Math.abs(endY - downY);
if(DX > DY && DX> 8){
//水平方向滑动
//响应侧滑
//反拦截 事件给孩子 SlideLayout
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
Log.w("www","ACTION_UP");
int totalScrollX = getScrollX();//偏移量
Log.w("www","ACTION_UP === "+totalScrollX);
if(totalScrollX < menuWidth / 2){
//关闭menu
closeMenu();
}else {
//打开menu
openMenu();
}
break;
}
return true;
}
public void closeMenu() {
//--> 0 目标为0, 使用以下公式 : int distanceX = 目标 - getScrollX();
int distanceX = 0 - getScrollX();
Log.w("www",""+getScrollX());
Log.w("www",""+distanceX);
scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
Log.w("www",""+getScrollX());//当前
Log.w("www",""+getScrollY());
invalidate();//强制刷新
if(onstateCahngeListener != null){
onstateCahngeListener.onClose(this);
}
}
@Override
public void computeScroll() {
super.computeScroll();
if(scroller.computeScrollOffset()){
scrollTo(scroller.getCurrX(),scroller.getCurrY());
}
}
public void openMenu() {
//--> 0 使用以下公式 :int distanceX = 目标 - getScrollX();
int distanceX = menuWidth - getScrollX();
Log.w("www",""+getScrollX());
Log.w("www",""+distanceX);
scroller.startScroll(getScrollX(),getScrollY(),distanceX,0);
Log.w("www",""+getScrollX());
Log.w("www",""+getScrollY());
invalidate();//强制刷新
if(onstateCahngeListener != null){
onstateCahngeListener.onOpen(this);
}
}
/**
* true:拦截孩子的事件,但会执行当前控件的onTouchEvent()方法
* false:不拦截孩子的事件,事件继续传递
* @param
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
super.onInterceptTouchEvent(event);
boolean intercpet = false;//默认传给孩子
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.w("www", "ACTION_DOWN");
//1.按下记录坐标
downX = startX = event.getX();
downY = startY = event.getY();
if(onstateCahngeListener != null){
onstateCahngeListener.onDown(this);
}
break;
case MotionEvent.ACTION_MOVE:
Log.w("www", "ACTION_MOVE");
//2.记录结束值
float endX = event.getX();
//3.计算偏移值
float distanceX = endX - startX;
//5.重新赋值
startX = event.getX();
startY = event.getY();
//在X轴和Y轴滑动的距离
float DX = Math.abs(endX - downX);
if (DX > 8) {
intercpet = true;
}
break;
case MotionEvent.ACTION_UP:
Log.w("www", "ACTION_UP");
break;
}
return intercpet;
}
/**
* 监听slidelayout状态的改变
*/
public interface OnstateCahngeListener{
void onClose(SlideLayout layout);
void onDown(SlideLayout layout);
void onOpen(SlideLayout layout);
}
public OnstateCahngeListener onstateCahngeListener;
/**
* 设置slidelayout状态的监听
* @param onstateCahngeListener
*/
public void setOnstateCahngeListener(OnstateCahngeListener onstateCahngeListener){
this.onstateCahngeListener = onstateCahngeListener;
}
}
MainAvtivity
public class MainActivity extends AppCompatActivity {
private ListView listview;
private ArrayList<Bean> bean;
private MyAdapter myadapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView)findViewById(R.id.listview);
//准备数据
//设置适配器
bean = new ArrayList<>();
for(int i = 0;i<100; i++){
bean.add(new Bean("Content"+i));
}
myadapter = new MyAdapter();
listview.setAdapter(myadapter);
}
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return bean.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView == null){
convertView = View.inflate(MainActivity.this,R.layout.item_main,null);
viewHolder = new ViewHolder();
viewHolder.item_content=(TextView)convertView.findViewById(R.id.item_content);
viewHolder.item_menu=(TextView)convertView.findViewById(R.id.item_menu);
convertView.setTag(viewHolder);
}else {
viewHolder = (ViewHolder)convertView.getTag();
}
//根据位置得到内容
final Bean beans = bean.get(position);
viewHolder.item_content.setText(beans.getName());
viewHolder.item_content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bean bean1 = bean.get(position);
Toast.makeText(MainActivity.this,"Content",Toast.LENGTH_SHORT).show();
}
});
viewHolder.item_menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SlideLayout slideLayout = (SlideLayout) v.getParent();
slideLayout.closeMenu();
bean.remove(beans);
myadapter.notifyDataSetChanged();
}
});
SlideLayout slideLayout = (SlideLayout) convertView;
slideLayout.setOnstateCahngeListener(new MyOnstateCahngeListener());
return convertView;
}
}
SlideLayout slideLayout;
class MyOnstateCahngeListener implements SlideLayout.OnstateCahngeListener{
@Override
public void onClose(SlideLayout layout) {
if(slideLayout == layout){
slideLayout = null;
}
}
@Override
public void onDown(SlideLayout layout) {
if(slideLayout != null && slideLayout != layout){
slideLayout.closeMenu();
}
}
@Override
public void onOpen(SlideLayout layout) {
slideLayout = layout;
}
}
static class ViewHolder{
TextView item_content;
TextView item_menu;
}
}
Bean
public class Bean {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
String name;
Bean(String name){
this.name = name;
}
}