Android Listview item向左滑动 显示菜单(仿QQ滑动)
来源:互联网 发布:amazfit华米手表 知乎 编辑:程序博客网 时间:2024/04/27 23:25
1、说明
1.1 高仿QQ对话列表左滑删除的功能(作者不详,发现于公司年代久远的项目,拿来学习一下,看看思路),滑动item时显示操作菜单,只要划出来了,你想干嘛就好办了。。
2、无图无真相
3、思路分析
其实思路还是挺简单的,就是每个item多了几个操作菜单,这个ItemView其实就是两部分构成的,一个是本来的ContentView,一个是显示菜单的MenuView。我们要做的只是,找个Framelayout把两个view放在一起。然后通过手势去更新两个view的位置就OK了。
4、代码实现
我们从简单的搞起
第一步,先搞划出的菜单:
4.1
BaseMenuItem.java 这就是单个菜单的实体类。设置菜单icon和文字信息的。别忘了set和get方法。
public class BaseMenuItem { private int id; private Context mContext; private String title; private Drawable icon; private Drawable background; private int titleColor; private int titleSize; private int width; public BaseMenuItem(Context context) { mContext = context; }}
4.2
BaseMenuGroups.java 这个类就是BaseMenuItem的集合,这么没什么说的。同样,别忘了set和get方法。
public class BaseMenuGroups { private Context mContext; private List<BaseMenuItem> mItems; private int mViewType; public BaseMenuGroups(Context context) { mContext = context; mItems = new ArrayList<BaseMenuItem>(); }}
4.3
BaseMenuLayout.java 现在就来说这个装多个菜单的容器了。这部分就是划出可见的那部分,我们继承Linearlayout并实现OnClickListener接口,毕竟有菜单要能点击。从BaseMenuLayout的构造方法传入需要的菜单BaseMenuGroups。
a:先准备文字和图标的工具
//显示icon的Viewprivate ImageView createIcon(BaseMenuItem item) { ImageView iv = new ImageView(getContext()); iv.setImageDrawable(item.getIcon()); return iv;}//显示文字的viewprivate TextView createTitle(BaseMenuItem item) { TextView tv = new TextView(getContext()); tv.setText(item.getTitle()); tv.setGravity(Gravity.CENTER); tv.setTextSize(item.getTitleSize()); tv.setTextColor(item.getTitleColor()); return tv;}
b:创建我们单个菜单的view,这里用一个LinearLayout来装我们的图标和文字,至于是上下还是左右,就是看你喜好了。我们这里设置图标上,文字下。
这个参数id,当然是指BaseMenuLayout中的第几个菜单,不然我们怎么点啊。
private void addItem(BaseMenuItem item, int id) { LayoutParams params = new LayoutParams(item.getWidth(), LayoutParams.MATCH_PARENT); LinearLayout parent = new LinearLayout(getContext()); parent.setId(id); parent.setGravity(Gravity.CENTER); parent.setOrientation(LinearLayout.VERTICAL); parent.setLayoutParams(params); parent.setBackgroundDrawable(item.getBackground()); parent.setOnClickListener(this); //把菜单添加到我们的BaseMenuLayout中去。 addView(parent); //添加图标 if (item.getIcon() != null) { parent.addView(createIcon(item)); } //添加标题 if (!TextUtils.isEmpty(item.getTitle())) { parent.addView(createTitle(item)); } }
这里还需要一个位置信息。不然我怎么知道是点的Listview的哪一行的哪一个菜单啊,so,来自于Listview的item的position。
private int position;public int getPosition() { return position;}public void setPosition(int position) { this.position = position;}
光这样还不行,总得把单个小菜单的位置搞出去吧。
private OnSwipeItemClickListener onItemClickListener;public static interface OnSwipeItemClickListener { void onItemClick(BaseMenuLayout view, BaseMenuGroups menu, int index); }
其实这里还有其他的判断,就是要在我们的菜单处于显示的时候才能点击菜单,后面会上传demo。
再来说一说构造方法中的代码:把传入的BaseMenuGroups 中的全部菜单都添加到BaseMenuLayout中,按照顺序设置菜单的id
public BaseMenuLayout(BaseMenuGroups menu) { super(menu.getContext()); mMenu = menu; List<BaseMenuItem> items = menu.getMenuItems(); int id = 0; for (BaseMenuItem item : items) { addItem(item, id++); }}
4.4
接下来构建一个接口,BaseMenuCreator.java,只有一个实现方法,便于我们在外部设置菜单。怎么用这个,后面会讲到。
void create(BaseMenuGroups menu);
4.5
现在该做一个组装我们最先提到的ContentView和显示菜单的MenuView的BaseViewAdapter.java了,这个BaseViewAdapter并不是ListView的数据Adapter。在这个adapter里面做的事情有,组装MenuView,并且把MenuView和ContentView传入BaseItemViewLayout(这里就比较复杂了,接下来会提到),由BaseItemViewLayout来组装完整的Listview的item。
这里我们只说getView方法:
@Override public View getView(int position, View convertView, ViewGroup parent) { BaseItemViewLayout layout = null; if (convertView == null) { View contentView = mAdapter.getView(position, convertView, parent); BaseMenuGroups menu = new BaseMenuGroups(mContext); menu.setViewType(mAdapter.getItemViewType(position)); createMenu(menu); BaseMenuLayout menuView = new BaseMenuLayout(menu); menuView.setOnSwipeItemClickListener(this);//但这里都是在组装菜单的数据,接下来就是把contentView 和menuView 传到BaseItemViewLayout去了。剩下的就是处理view的复用了 DelListView listView = (DelListView) parent; layout = new BaseItemViewLayout(contentView, menuView, listView.getCloseInterpolator(), listView.getOpenInterpolator()); layout.setPosition(position); } else { layout = (BaseItemViewLayout) convertView; layout.closeMenu(); layout.setPosition(position); View view = mAdapter.getView(position, layout.getContentView(),parent); /** * 可能有一些小伙伴觉得view都没使用,就觉得这样代码没什么用,其实这里是为了复用,关键在于layout.getContentView()。 */ } return layout; }
4.6
下面一起来看看这个BaseItemViewLayout.java了。感觉这就是精华之一所在了。
首先说一下这里面的布局情况:
LayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT);mContentView.setLayoutParams(contentParams);mMenuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));addView(mContentView);addView(mMenuView);
很显然,感觉我们的menuView被顶到屏幕外面去了。但是,光感觉怎么行呢,看下面的代码就知道,肯定在屏幕外,至少我们看不到。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mMenuView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec( getMeasuredHeight(), MeasureSpec.EXACTLY)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mContentView.layout(0, 0, getMeasuredWidth(), mContentView.getMeasuredHeight()); mMenuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + mMenuView.getMeasuredWidth(), mContentView.getMeasuredHeight()); }
其实处理手势是最麻烦的了。因为有几种情况:
第一种,当快速滑动打开、快速滑动关闭、打开状态,点击一下关闭时,动作不跟手:
//关闭,动作不跟手public void smoothCloseMenu() { state = STATE_CLOSE; mBaseX = -mContentView.getLeft(); mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);//这里五个参数的意思是begin(x,y) end(x,y) duration; postInvalidate(); }//打开,动作不跟手 public void smoothOpenMenu() { state = STATE_OPEN;mOpenScroller.startScroll(-mContentView.getLeft(), 0, mMenuView.getWidth(), 0, 350); postInvalidate(); }
第二种:动作跟手时打开与关闭:这里的这个dis是根据手指滑动的距离算出来的
private void swipe(int dis) { if (dis > mMenuView.getWidth()) { dis = mMenuView.getWidth(); } if (dis < 0) { dis = 0; } mContentView.layout(-dis, mContentView.getTop(), mContentView.getWidth() - dis, getMeasuredHeight()); mMenuView.layout(mContentView.getWidth() - dis, mMenuView.getTop(), mContentView.getWidth() + mMenuView.getWidth() - dis, mMenuView.getBottom()); }
除开这两个处理滑动的方法之外,还有一个很重要的方法computeScroll(),这个方法会在Scroller.startScroll()的时候自动执行,这个方法的作用是计算滑动量的。没有这个方法,活动的时候,会显得卡顿。
@Override public void computeScroll() { if (state == STATE_OPEN) { if (mOpenScroller.computeScrollOffset()) { swipe(mOpenScroller.getCurrX()); postInvalidate(); } } else { if (mCloseScroller.computeScrollOffset()) { swipe(mBaseX - mCloseScroller.getCurrX()); postInvalidate(); } } }
然后就是对手势的监听了:这里的触摸事件肯定来自于最外层的ListView的触摸事件:所以:写一个方法等着MotionEvent传进来处理就好:这里就判断了是快速滑动打开,还是跟手滑动打开了,代码很简单。
public boolean onSwipe(MotionEvent event) { mGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = (int) event.getX(); isFling = false; break; case MotionEvent.ACTION_MOVE: int dis = (int) (mDownX - event.getX()); if (state == STATE_OPEN) { dis += mMenuView.getWidth(); } Log.d("DIS>>>>>>>>>>", dis+""); swipe(dis); break; case MotionEvent.ACTION_UP: if (isFling || (mDownX - event.getX()) > (mMenuView.getWidth() / 2)) { // open smoothOpenMenu(); } else { // close smoothCloseMenu(); return false; } break; } return true; }
4.7
现在就是精华之二所在了。我们的核心,DelListView.java了
这里面有两个重要的部分了:
第一个就是:我们的BaseMenuCreator终于排上用场了:
重写Listview的SetAdapter方法,把我们set进来的数据adapter传出我们的BaseViewAdapter,组建合成最终的itemView;并且重写BaseViewAdapter的createMenu方法,实现我们的BaseMenuCreator接口。这个接口实例从外部传进来;然后在重写BaseViewAdapter的onItemClick方法,把item的position菜单的position回调回来。
@Override public void setAdapter(ListAdapter adapter) { adapters = adapter; super.setAdapter(new BaseViewAdapter(getContext(), adapter) { @Override public void createMenu(BaseMenuGroups menu) { if (mMenuCreator != null) { mMenuCreator.create(menu); } } @Override public void onItemClick(BaseMenuLayout view, BaseMenuGroups menu, int index) { if (mOnMenuItemClickListener != null) { mOnMenuItemClickListener.onMenuItemClick( view.getPosition(), menu, index); } if (mTouchView != null) { mTouchView.smoothCloseMenu(); } } }); }
第二个就是:处理手势,处理完成后传入BaseItemViewLayout.onSwipe(MotionEvent event)方法:
这里要处理的就比BaseItemViewLayout多得多了。这里要判断点击的位置,菜单是否打开,滑动的方向。
@Override public boolean onTouchEvent(MotionEvent ev) { if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)//有父类处理事件 return super.onTouchEvent(ev); int action = MotionEventCompat.getActionMasked(ev); action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: int oldPos = mTouchPosition; mDownX = ev.getX(); mDownY = ev.getY(); mTouchState = TOUCH_STATE_NONE; //通过点击的位置来判断点在listview的那个位置上。 mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()); if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) { mTouchState = TOUCH_STATE_X; mTouchView.onSwipe(ev); return true; } View view = getChildAt(mTouchPosition - getFirstVisiblePosition()); ; if (mTouchView != null && mTouchView.isOpen()) { mTouchView.smoothCloseMenu(); mTouchView = null; return super.onTouchEvent(ev); } if (view instanceof BaseItemViewLayout) { mTouchView = (BaseItemViewLayout) view; /** * 可以通过item中某一个子view的显示还是隐藏来控制该item能不能别左滑出菜单 * 假如现在item中的tv_value_gone控件处于显示的状态,那么该item则不能被左滑出菜单 */ if(signViewId!=-1){ isshow = mTouchView.findViewById(signViewId).isShown(); } } if (!isshow) { if (mTouchView != null) { mTouchView.onSwipe(ev); } } break; case MotionEvent.ACTION_MOVE: float dy = Math.abs((ev.getY() - mDownY)); float dx = Math.abs((ev.getX() - mDownX)); if (mTouchState == TOUCH_STATE_X) { if (!isshow) { if (mTouchView != null) { mTouchView.onSwipe(ev); } } getSelector().setState(new int[] { 0 }); ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } else if (mTouchState == TOUCH_STATE_NONE) { if (Math.abs(dy) > MAX_Y) { mTouchState = TOUCH_STATE_Y; } else if (dx > MAX_X) { mTouchState = TOUCH_STATE_X; if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeStart(mTouchPosition); } } } break; case MotionEvent.ACTION_UP: if (mTouchState == TOUCH_STATE_X) { if (!isshow) { if (mTouchView != null) { mTouchView.onSwipe(ev); if (!mTouchView.isOpen()) { mTouchPosition = -1; mTouchView = null; } } } if (mOnSwipeListener != null) { mOnSwipeListener.onSwipeEnd(mTouchPosition); } ev.setAction(MotionEvent.ACTION_CANCEL); super.onTouchEvent(ev); return true; } break; } return super.onTouchEvent(ev); }
剩下的就是接口回调参数了。这部分代码请看demo吧。
5、最后
使用方法很简单。大家看demo吧
demo下载地址:http://download.csdn.net/detail/u010886975/9718243
估计有人可能对有些地方有疑问,请自行查看:
1、setViewType:https://www.pocketdigi.com/20130731/1155.html
2、computeScroll():http://www.linuxidc.com/Linux/2016-01/127276.htm
- Android Listview item向左滑动 显示菜单(仿QQ滑动)
- Android仿QQ、微信ListView滑动删除item
- 仿qq向左滑动列表
- Android开发仿QQ聊天滑动listview
- Android仿QQ实现ListView滑动删除
- Android仿QQ实现ListView滑动删除
- 仿qq向左滑动删除 案例 详解
- listView的item向左滑动实现删除
- Android开发之滑动选择菜单(仿QQ滑动删除)
- javascript仿QQ滑动菜单
- 仿QQ消息列表item横向滑动删除ListView中item侧滑删除
- Android仿QQ消息列表ListView滑动删除效果
- android滑动删除的listview,仿手机QQ的样子
- ANDROID 动态添加的listView,仿QQ滑动删除
- Android 仿腾讯QQ 的 ListView滑动删除
- Android仿qq下拉刷新及向左滑动列表----PullToRefresh, SwipeMenuListView开源项目整合
- Android仿qq下拉刷新及向左滑动列表----PullToRefresh, SwipeMenuListView开源项目整合
- Android仿qq下拉刷新及向左滑动列表----PullToRefresh, SwipeMenuListView开源项目整合
- 全球https时代已经到来
- 51Nod - 1024 对数+暴力
- 寻找链表的中间节点
- 主板芯片组与内存映射
- SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数式编程
- Android Listview item向左滑动 显示菜单(仿QQ滑动)
- 使用combobox进行模糊查询
- activity 获取 rootView 设置 backGroundColor
- 采集
- iframe自适应子页高度
- Shell - 20
- 百度echarts自定义主题使用
- php 读取txt文件中的内容,转换成数组
- Mysql随机取样——ORDER BY RAND()优化