自定义控件那些事儿 ----- 左滑删除控件
来源:互联网 发布:java 动态合成图片 编辑:程序博客网 时间:2024/05/17 08:11
左滑删除使用比较多一点,在网上也有不少资料实现,不过坑总是不少。
整合资料做出来一个效果,大家看看依据效果选择是否需要继续研究。
一、自定义控件的实现
1,自定义控件的实现
实现思路主要是:将整体内容分为主体内容和左滑显示内容,布局文件中必须有两个子类。在依据ViewDragHelper的帮助计算主体内容和左滑内容的显示以及显示多少。
/** * 左滑删除 * 2017/12/11. */public class SwipeLayout extends FrameLayout { private ViewDragHelper dragHelper; private OnSwipeChangeListener swipeChangeListener; private View backView;//侧滑菜单 private View frontView;//内容区域 private int height;//自定义控件布局高 private int width;//自定义控件布局宽 private int range;//侧滑菜单可滑动范围 public boolean isOpen() { return isOpen; } private boolean isOpen = false;//是否是打开状态 public void setOpen(boolean open) { isOpen = open; } //重写三个构造方法 public SwipeLayout(Context context) { this(context, null); } public SwipeLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwipeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); dragHelper = ViewDragHelper.create(this, callback); } //获取两个View protected void onFinishInflate() { super.onFinishInflate(); int childCount = getChildCount(); if (childCount < 2) { throw new IllegalStateException("you need 2 children view"); } if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) { throw new IllegalArgumentException("your children must be instance of ViewGroup"); } backView = getChildAt(0);//侧滑菜单 frontView = getChildAt(1);//内容区域 } //初始化布局的高height宽width以及可滑动的范围range protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); height = frontView.getMeasuredHeight(); width = frontView.getMeasuredWidth(); range = backView.getMeasuredWidth(); } //布局子View protected void onLayout(boolean changed, int left, int top, int right, int bottom) {// super.onLayout(changed, left, top, right, bottom);// layoutContent(false); layoutContent(isOpen); } /** * @param isOpen 侧滑菜单是否打开 */ private void layoutContent(boolean isOpen) { Rect frontRect = computeFrontViewRect(isOpen); frontView.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom); Rect backRect = computeBackViewRect(frontRect); backView.layout(backRect.left, backRect.top, backRect.right, backRect.bottom); //调整顺序// bringChildToFront(backView); } /** * 通过内容区域所占矩形坐标计算侧滑菜单的矩形位置区域 * * @param frontRect 内容区域所占矩形 * @return */ private Rect computeBackViewRect(Rect frontRect) { int left = frontRect.right; return new Rect(left, 0, left + range, height); } /** * 通过菜单打开与否isOpen计算内容区域的矩形区 * * @param isOpen * @return */ private Rect computeFrontViewRect(boolean isOpen) { int left = 0; if (isOpen) { left = -range; } return new Rect(left, 0, left + width, height); } private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { //所有子View都可拖拽 public boolean tryCaptureView(View child, int pointerId) { return true; } //水平拖拽后处理 public int clampViewPositionHorizontal(View child, int left, int dx) { if (child == frontView) { if (left > 0) { return 0; } else if (left < -range) { return -range; } } else if (child == backView) { if (left > width) { return width; } else if (left < width - range) { return width - range; } } return left; } public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { if (changedView == frontView) { backView.offsetLeftAndRight(dx); } else if (changedView == backView) { frontView.offsetLeftAndRight(dx); } //事件派发 dispatchSwipeEvent(); //兼容低版本 invalidate(); } //松手后根据侧滑位移确定菜单打开与否 public void onViewReleased(View releasedChild, float xvel, float yvel) { if (xvel == 0 && frontView.getLeft() < -range * 0.3f) {//-range * 0.5f 修改灵敏度 open(); } else if (xvel < 0) { open(); } else { close(); } } //子View如果是clickable,必须重写的方法 public int getViewHorizontalDragRange(View child) { return 1; } public int getViewVerticalDragRange(View child) { return 1; } }; public boolean onInterceptTouchEvent(MotionEvent ev) { return dragHelper.shouldInterceptTouchEvent(ev); } public boolean onTouchEvent(MotionEvent event) { dragHelper.processTouchEvent(event); return true; } // 持续平滑动画 高频调用 public void computeScroll() { // 如果返回true,动画还需要继续 if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } public void open() { open(true); } public void open(boolean isSmooth) { int finalLeft = -range; if (isSmooth) { if (dragHelper.smoothSlideViewTo(frontView, finalLeft, 0)) { ViewCompat.postInvalidateOnAnimation(this); } //布局方式调整 /* else { layoutContent(true);//添加部分,执行OnLayout中方法,效果一样 }*/ } else { layoutContent(true); } } public void close() { close(true); } public void close(boolean isSmooth) { int finalLeft = 0; if (isSmooth) { if (dragHelper.smoothSlideViewTo(frontView, finalLeft, 0)) { ViewCompat.postInvalidateOnAnimation(this); } } else { layoutContent(false); } } private Status status = Status.CLOSE;//拖拽状态 默认关闭 public static enum Status { OPEN, CLOSE, DRAGING } //拖拽事件监听器 public static interface OnSwipeChangeListener { void onDraging(SwipeLayout mSwipeLayout); void onOpen(SwipeLayout mSwipeLayout); void onClose(SwipeLayout mSwipeLayout); void onStartOpen(SwipeLayout mSwipeLayout); void onStartClose(SwipeLayout mSwipeLayout); } //更改状态 private Status updateStatus() { int left = frontView.getLeft(); if (left == 0) { return Status.CLOSE; } else if (left == -range) { return Status.OPEN; } return Status.DRAGING; } //根据当前状态判断回调事件 protected void dispatchSwipeEvent() { Status preStatus = status; status = updateStatus(); if (swipeChangeListener != null) { swipeChangeListener.onDraging(this); } if (preStatus != status && swipeChangeListener != null) { if (status == Status.CLOSE) { swipeChangeListener.onClose(this); } else if (status == Status.OPEN) { swipeChangeListener.onOpen(this); } else if (status == Status.DRAGING) { if (preStatus == Status.CLOSE) { swipeChangeListener.onStartOpen(this); } else if (preStatus == Status.OPEN) { swipeChangeListener.onStartClose(this); } } } } public void setSwipeChangeListener(OnSwipeChangeListener swipeChangeListener) { this.swipeChangeListener = swipeChangeListener; }}
2,注意点
(1)onLayout()中修改重新计算布局的控制方式 --- 解决整体管理时,显示出来的部分能够显示左滑内容,而新滑动出来的内容不能正常显示。
(2)在滑动最后状态结束的地方调整比率,可以适当提高反应灵敏度;
(3)该控件放置在可以左滑的控件(ViewPager)中,滑动事件会冲突,效果使用不好。
二、自定义控件的使用
1,布局中使用 ListView 的Item布局中
<?xml version="1.0" encoding="utf-8"?><com.future.leftdragdeletedemo.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/swipeLayout" android:layout_width="match_parent" android:layout_height="60dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent"> <TextView android:layout_width="70dp" android:layout_height="match_parent" android:background="#ccc" android:gravity="center" android:text="置顶" android:textColor="#fff" /> <TextView android:id="@+id/tv_del" android:layout_width="70dp" android:layout_height="match_parent" android:background="#f00" android:clickable="true" android:gravity="center" android:text="刪除" android:textColor="#fff" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical"> <ImageView android:id="@+id/imageView" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:src="@mipmap/kxg" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:textColor="#444" android:textSize="18sp" /> </LinearLayout></com.future.leftdragdeletedemo.view.SwipeLayout>
2,主布局中使用
<?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="com.future.leftdragdeletedemo.MainActivity"> <RelativeLayout android:id="@+id/head_rl" android:layout_width="match_parent" android:layout_height="50dp"> <ImageView android:id="@+id/head_back_iv" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:src="@mipmap/head_backpic" /> <TextView android:id="@+id/title_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="控制我自己" android:textSize="18sp" /> <TextView android:id="@+id/whole_control_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="12dp" android:text="管理" android:textSize="18sp" /> </RelativeLayout> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/head_rl" android:choiceMode="singleChoice" /></RelativeLayout>
3,适配器实现
public class ListViewAdapter extends BaseAdapter { private Context context; private List<ItemBean> dataList; /** * 是否是打开状态 */ private boolean isOpen; public ListViewAdapter(Context context, List<ItemBean> dataList) { this.context = context; this.dataList = dataList; } /** * 设置全部打开状态 * * @param open */ public void setOpenStatus(boolean open) { isOpen = open; if (dataList != null && dataList.size() > 0) { for (int i = 0; i < dataList.size(); i++) { dataList.get(i).setOpen(open); } } notifyDataSetChanged(); } //存放所有已经打开的菜单 private List<SwipeLayout> openList = new ArrayList<SwipeLayout>(); @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { final ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = LayoutInflater.from(context).inflate(R.layout.layout_item, parent, false); holder.textView = (TextView) convertView.findViewById(R.id.textView); holder.imageView = (ImageView) convertView.findViewById(R.id.imageView); holder.tv_del = (TextView) convertView.findViewById(R.id.tv_del); holder.swipeLayout = (SwipeLayout) convertView.findViewById(R.id.swipeLayout); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } final ItemBean bean = dataList.get(position); holder.textView.setText(bean.getDescContent()); holder.imageView.setImageResource(bean.getPicIndex()); holder.tv_del.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { holder.swipeLayout.close(); Toast.makeText(context, bean.getDescContent(), Toast.LENGTH_SHORT).show(); } }); if (bean.isOpen()) { holder.swipeLayout.setOpen(true); holder.swipeLayout.open(); } else { holder.swipeLayout.setOpen(false); holder.swipeLayout.close(); } holder.swipeLayout.setSwipeChangeListener(new SwipeLayout.OnSwipeChangeListener() { @Override public void onStartOpen(SwipeLayout mSwipeLayout) { if (!isOpen) { for (SwipeLayout layout : openList) { layout.close(); } openList.clear(); } } @Override public void onStartClose(SwipeLayout mSwipeLayout) { } @Override public void onOpen(SwipeLayout mSwipeLayout) { openList.add(mSwipeLayout); bean.setOpen(true); } @Override public void onDraging(SwipeLayout mSwipeLayout) { } @Override public void onClose(SwipeLayout mSwipeLayout) { openList.remove(mSwipeLayout); bean.setOpen(false); } }); return convertView; } private class ViewHolder { SwipeLayout swipeLayout; TextView textView; TextView tv_del; ImageView imageView; }}
4,主类实现
public class MainActivity extends AppCompatActivity implements View.OnClickListener { /** * 内容控件 */ private ListView listView; /** * 返回 */ private ImageView headBack; /** * 控制按钮 */ private TextView controlTV; private ListViewAdapter adapter; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); initData(); initListener(); } /** * 初始化控件 */ private void initView() { headBack = findViewById(R.id.head_back_iv); controlTV = findViewById(R.id.whole_control_tv); listView = (ListView) findViewById(R.id.listView); } /** * 初始化数据 */ private void initData() { List<ItemBean> list = new ArrayList<>(); ItemBean item = new ItemBean(); for (int i = 0; i < strs.length; i++) { item.setDescContent(strs[i]); item.setPicIndex(ids[i]); list.add(item); item = new ItemBean();//不添加是同一个?? } adapter = new ListViewAdapter(MainActivity.this, list); listView.setAdapter(adapter); } /** * 初始化监听器 */ private void initListener() { headBack.setOnClickListener(this); controlTV.setOnClickListener(this); } private String strs[] = {"得劲", "加油", "坚持", "慎独", "控制情绪", "好好对待自己", "更美好的未来", "下一站", "懵懂", "秋高气爽", "嘟噜噜", "就这样吧", "还挺不错的", "普通的小孩"}; private int ids[] = {R.mipmap.kxg, R.mipmap.mmi, R.mipmap.geu, R.mipmap.feb, R.mipmap.fdy, R.mipmap.ewo, R.mipmap.mdt, R.mipmap.kxg, R.mipmap.mmi, R.mipmap.geu, R.mipmap.feb, R.mipmap.fdy, R.mipmap.ewo, R.mipmap.mdt}; @Override public void onClick(View view) { switch (view.getId()) { //返回 case R.id.head_back_iv: finish(); break; //管理 case R.id.whole_control_tv: String desc = controlTV.getText().toString().trim(); if ("管理".equals(desc)) { controlTV.setText("取消"); adapter.setOpenStatus(true); } else { controlTV.setText("管理"); adapter.setOpenStatus(false); } break; } } /** * 问题备注: * 1,list压入数据时,需要新创建bean对象;-----数据处理 * 2,上下滑动,左滑状态不能保持; ------ bean类中添加控制变量,适配器中做修改 * 3,管理添加后,状态不能统一管理; * 4,管理打开状态下,左滑之后右滑会状态混乱; * 5,同时向左滑动两个Item; * 6,管理点击后显示的页面正确,未展示出来的部分展示不对; * 7,多个条目同时滑动; */}
如此,按照以上的步骤实现,就能够实现开篇实现的效果。
这个过程出现以下问题,逐一分解解决。
三、出现相关问题修复优化
1,滑动状态不能保持
在Bean类中添加控制变量,赋予默认值。
public class ItemBean implements Serializable { /** * 图片索引 */ private int picIndex; /** * 描述 */ private String descContent; public int getPicIndex() { return picIndex; } public void setPicIndex(int picIndex) { this.picIndex = picIndex; } public String getDescContent() { return descContent; } public void setDescContent(String descContent) { this.descContent = descContent; } /** * 是否打开状态 */ private boolean isOpen = false; public boolean isOpen() { return isOpen; } public void setOpen(boolean open) { isOpen = open; }}
private boolean isOpen = false;
当实现滑动时,修改该变量的值,就能够保持状态值。
模拟器在录屏时刷新不及时,有点延迟。
2,添加全局的控制管理
顶部添加“管理”按钮,实现整体状态的管理。
注意在实现的时候,修改adpter中的状态时,要同时修改每一个bean类中的状态值。
/** * 设置全部打开状态 * * @param open */ public void setOpenStatus(boolean open) { isOpen = open; if (dataList != null && dataList.size() > 0) { for (int i = 0; i < dataList.size(); i++) { dataList.get(i).setOpen(open); } } notifyDataSetChanged(); }
3,注意在监听左滑时,我们加入了清除其他的已经左滑的状态。
在管理状态下,先右滑其中一个再次左滑时,按照以上的逻辑,会将其他的左滑状态全部改回正常状态。添加状态控制,正好在setOpenStatus()中保存了当前正处于的状态。
@Override public void onStartOpen(SwipeLayout mSwipeLayout) { if (!isOpen) { for (SwipeLayout layout : openList) { layout.close(); } openList.clear(); } }
4,ListView的多item同时左滑
就出来下面这个么情形:
Activity会引用theme,在theme中添加:
<!--添加到Application中,所有的Activity都不能多点点击使用--> <item name="android:windowEnableSplitTouch">false</item> <item name="android:splitMotionEvents">false</item>
则可以控制,一次只能滑动一个item。将上面的部分添加到Application中,会让全部的Activity都不能多点触碰。
源码传送门
存在感从来不是争取来的!你需要做的,是去锤炼自己的能力,同时找回那个被丢在半路上的自己。
因为你不爱自己。你让自己受了太多太多委屈。你逞强,你倔强,你尽量的照顾别人的感受,却频频的让自己受伤……
你要先爱自己,给她吃,给她喝,给她情书。
你若盛开,蝴蝶自来;你若精彩,天自安排!
你若精彩,老天自有安排!
——做有意义的事,去经营更好的自己
阅读全文
0 0
- 自定义控件那些事儿 ----- 左滑删除控件
- 自定义控件那些事儿 ----- 一
- 自定义控件那些事儿 ----- 二
- 3.4自定义控件的那些事儿~
- 自定义控件那些事儿 ----- 三【量测】
- 自定义控件那些事儿 ----- 四【布局】
- 自定义控件那些事儿 ----- 五【绘制文字】
- 自定义控件那些事儿 ------ 六【绘制路径】
- 自定义控件那些事儿 ----- 七【绘制Bitmap】
- 自定义控件—ViewDragHelper实现左滑删除
- 安卓自定义控件之左滑删除
- 控件那些事儿
- 自定义控件那些事儿 ----- 八【着色器使用】
- Android自定义组合控件---教你如何自定义下拉刷新和左滑删除
- Android自定义组合控件---教你如何自定义下拉刷新和左滑删除
- Android自定义组合控件---教你如何自定义下拉刷新和左滑删除
- 自定义控件--滑动删除
- Android自定义控件:左滑删除itemRecyclerView,ListView,GrdiView通配,教你如何最快最轻松定制,而不是复制粘贴!
- ExtJs grid的所有操作
- C#垃圾代码生成器
- 我的世界开发日志1——什么是Mesh
- Python 列表生成式(List Comprehensions)
- Java 经典算法之冒泡排序(Bubble Sort)
- 自定义控件那些事儿 ----- 左滑删除控件
- 如何跟各种人解释什么是产品经理
- webview上传图片一直提示"未选择任何文件"
- android两个子程程的通信
- Redis服务支持5000万的QPS,有什么好的思路?
- 利用「接口」做产品时我们该如何思考?
- 12.13培训日记
- 消息的接收和处理
- centos7中安装jdk