环境说明
操作系统:Win10
IDE:Android Studio
实现效果
实现代码
一、布局文件
(一)item_content.xml【ListView的Item的内容】
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="Item1"
android:textSize="25sp"
android:gravity="center"
android:textColor="@android:color/black">
</TextView>
显示效果
(二)item_menu.xml【Item的侧滑删除按钮】
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="70dp"
android:layout_height="60dp"
android:gravity="center"
android:background="@android:color/holo_red_light"
android:text="删除"
android:textSize="25sp"
android:textColor="#FFFFFFFF">
</TextView>
显示效果
(三)item_main.xml【一个完整Item】
SlideLayout为自定义Layout
<?xml version="1.0" encoding="utf-8"?>
<com.example.slidelayout.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.slidelayout.SlideLayout>
显示效果
(三)activity_main.xml【包含一个ListView】
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
二、Java代码
(一)、SlideLayout.java
package com.example.slidelayout;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Scroller;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class SlideLayout extends FrameLayout {
private static final String TAG=SlideLayout.class.getSimpleName();
private View contentView; // 内容视图
private View menuView; // 菜单视图
private int contentWidth; // 内容视图的高度
private int contentHeight; // 内容视图的宽度
private int menuWidth; // 菜单视图的宽度
private int menuHeight; // 菜单视图的宽度
private Scroller scroller; // 专门用于处理滚动效果的工具类
private float downX; // 按下的x坐标
private float downY;
private float endX;
private float endY; // 当前的Y坐标,中间变化
private float startX; // 开始的x坐标,中间变化
/**
* xml布局方式加载使用
* @param context
* @param attrs
*/
public SlideLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
scroller =new Scroller(context);
}
/**
* XML布局被加载完后,就会回调onFinshInfalte这个方法,在这个方法中我们可以初始化控件和数据。
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
contentView = findViewById(R.id.item_content);
menuView = findViewById(R.id.item_menu);
}
/**
* 设置子视图的位置
* @param changed
* @param left
* @param top
* @param right
* @param bottom
*/
@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,menuHeight);
}
/**
* 测量子视图的宽度和高度
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
contentHeight = contentView.getMeasuredHeight();
contentWidth = contentView.getMeasuredWidth();
menuHeight = menuView.getMeasuredHeight();
menuWidth = menuView.getMeasuredWidth();
}
/**
* 触摸事件
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 记录最初的x坐标
startX = event.getX();
downX = event.getX();
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
endX = event.getX();
endY = event.getY();
// dx为横向发生的完整距离
float dx = Math.abs(endX - downX);
if (dx > 0 ) { // 只要发生横向滑动,纵向禁止滑动
getParent().requestDisallowInterceptTouchEvent(true);
}
// 滑动距离
float dis = endX-startX;
Log.e("onTouchEvent:", String.valueOf(dis));
// 向左滑为负值,通过下面一行代码转换至滑至的x坐标
dis = getScrollX()-dis;
if (dis < 0){ // 坐标最小为0
dis = 0;
}else if (dis > menuWidth){ // 最多向左滑menuWidth距离
dis = menuWidth;
}
scrollTo((int) dis,getScrollY());
// 更新开始x坐标
startX = event.getX();
break;
case MotionEvent.ACTION_UP: // 如果滑出距离小于菜单宽度一半,菜单自动关闭
int totalX = getScrollX(); // 视图显示部分的左边缘
if (totalX < menuWidth/2){
closeMenu();
}else {
openMenu();
}
break;
}
return true;
}
/**
* 拦截触摸事件
* @param event
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean isIntercept = false; // 最初为不拦截
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = startX = event.getX();
try {
if (onStateChangeListener == null){
throw new Exception("状态改变监听者未设置");
}
onStateChangeListener.onDown(SlideLayout.this); // 关闭其他item
}catch (Exception e){
e.printStackTrace();
}
break;
case MotionEvent.ACTION_MOVE:
endX = event.getX();
float distance = Math.abs(endX - startX);
if (distance > 0){ // 横向发生滑动,拦截事件
isIntercept = true;
}
break;
}
return isIntercept;
}
/**
* 打开菜单
*/
public void openMenu(){
int distance = menuWidth - getScrollX(); // 剩余未滑动距离
Log.e("openMenu:", String.valueOf(getScrollX()));
scroller.startScroll(getScrollX(),getScrollY(),distance,0); // 滑动处理
invalidate(); // 重绘
try {
if (onStateChangeListener == null){
throw new Exception("状态改变监听者未设置");
}
onStateChangeListener.onOpen(SlideLayout.this); // 打开item为当前item
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 关闭菜单
*/
public void closeMenu(){
int distance = 0 - getScrollX();
Log.e("closeMenu:", String.valueOf(getScrollX()));
scroller.startScroll(getScrollX(),getScrollY(),distance,0);
invalidate();
try {
if (onStateChangeListener == null){
throw new Exception("状态改变监听者未设置");
}
onStateChangeListener.onClose(SlideLayout.this);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void computeScroll() {
super.computeScroll();
if (scroller.computeScrollOffset()){ // 滑动未结束
scrollTo(scroller.getCurrX(),scroller.getCurrY()); // 继续滑动到当前位置
invalidate();
}
}
/**
* 监听菜单打开关闭
*/
public interface OnStateChangeListener{
void onClose(SlideLayout layout);
void onDown(SlideLayout layout);
void onOpen(SlideLayout layout);
}
private OnStateChangeListener onStateChangeListener;
public void setOnStateChangeListener(OnStateChangeListener onStateChangeListener) {
this.onStateChangeListener = onStateChangeListener;
}
}
(二)、MyContent.java
package com.example.slidelayout;
public class MyContent {
private String name;
public MyContent(String name){ this.name=name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
(三)、MainActivity.java
package com.example.slidelayout;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ListView listView;
private ArrayList<MyContent> myContents;
private SlideLayout slideLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setMyContents();
initView();
}
/**
* 设置数据
*/
private void setMyContents(){
myContents = new ArrayList<>();
for (int i=1;i<100;++i){
MyContent myContent = new MyContent("Item"+i);
myContents.add(myContent);
}
}
/**
* 初始化视图
*/
private void initView(){
listView = findViewById(R.id.lv_main);
listView.setAdapter(new MyAdapter()); // 设置适配器
}
/**
* 适配器
*/
private class MyAdapter extends BaseAdapter {
@Override
public int getCount() {
return myContents.size();
}
@Override
public Object getItem(int position) {
return myContents.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder = null;
if (convertView == null){ // 若不存在,则创建一个新的视图
convertView = View.inflate(MainActivity.this,R.layout.item_main,null);
viewHolder = new ViewHolder();
viewHolder.item_content = convertView.findViewById(R.id.item_content);
viewHolder.item_menu = convertView.findViewById(R.id.item_menu);
convertView.setTag(viewHolder);
}else { // 直接复用
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.item_content.setText(myContents.get(position).getName());
viewHolder.item_content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String info = myContents.get(position).getName();
Toast.makeText(MainActivity.this,info,Toast.LENGTH_SHORT).show();
}
});
viewHolder.item_menu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SlideLayout slideLayout = (SlideLayout) v.getParent(); // 获取父亲视图
slideLayout.closeMenu(); // 关闭当前菜单
myContents.remove(position); // 移除
notifyDataSetChanged(); // 通知更变
}
});
SlideLayout slideLayout = (SlideLayout) convertView;
slideLayout.setOnStateChangeListener(new MyOnStateChangeListener()); // 设置监听者
return convertView;
}
}
static class ViewHolder {
TextView item_content;
TextView item_menu;
}
private class MyOnStateChangeListener implements SlideLayout.OnStateChangeListener {
@Override
public void onClose(SlideLayout layout) { // 菜单关闭后,选中item为空
if (slideLayout == layout){
slideLayout = null;
}
}
@Override
public void onDown(SlideLayout layout) {
if (slideLayout != null && slideLayout != layout) { // 当打开其他item的菜单时,关闭已打开的菜单
slideLayout.closeMenu();
}
}
@Override
public void onOpen(SlideLayout layout) {
if (slideLayout == null){
slideLayout = layout;
}
}
}
}