QQ聊天界面侧滑删除
2016-08-19 20:33
239 查看
侧滑删除
效果图:实现步骤:
1. 创建SwipeLayout
public class SwipeLayout extends FrameLayout { public SwipeLayout(Context context) { super(context); } public SwipeLayout(Context context, AttributeSet attrs) { super(context, attrs); } }
2. 创建SwipeLayout的布局
<com.itcast.jq.swipelayout.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="55dp" > <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="right" android:orientation="horizontal" > <TextView android:layout_width="55dp" android:layout_height="match_parent" android:background="#44ff0000" android:gravity="center" android:text="删除" /> <TextView android:layout_width="55dp" android:layout_height="match_parent" android:background="#4400ff00" android:gravity="center" android:text="取消" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="#33ff0000" > <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:paddingLeft="10dp" android:text="帅的人" /> </LinearLayout> </com.itcast.jq.swipelayout.view.SwipeLayout>
3. 重写回调方法获取控件尺寸及初始化位置
@Override protected void onFinishInflate() { super.onFinishInflate(); // System.out.println("---------onFinishInflate"); if (getChildCount() < 2) { throw new IllegalStateException("SwipeLayout must has at least 2 children"); } mBackView = getChildAt(0); mFrontView = getChildAt(1); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // System.out.println("---------onMeasure"); // 注意不能在onFinishInflate方法中获取宽高 mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); mBackWidth = mBackView.getMeasuredWidth(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // System.out.println("---------onMeasure"); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); // System.out.println("---------onMeasure"); // 设置两个子控件的初始化位置 mFrontView.layout(0, 0, mWidth, mHeight); mBackView.layout(mWidth, 0, mWidth + mBackWidth, mHeight); }
4. 使用ViewDragHelper实现拖动效果
public SwipeLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { // 1. 创建ViewDragHelper对象 mDragHelper = ViewDragHelper.create(this, mCallback); } // 4. 处理回调方法 Callback mCallback = new Callback() { // 根据返回决定是否可以拖动 public boolean tryCaptureView(View child, int pointerId) { return true; }; // 根据返回值决定被拖动的视图将要显示的位置 public int clampViewPositionHorizontal(View child, int left, int dx) { return left; }; }; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 2. 让ViewDragHelper决定是否拦截事件 return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // 3. 传触摸事件传给ViewDragHelper处理 mDragHelper.processTouchEvent(event); // 按下时需要返回true以便能持续地接收到触摸事件 if (event.getAction() == MotionEvent.ACTION_DOWN) { return true; } return super.onTouchEvent(event); }
5. 限制滑动范围
在clampViewPositionHorizontal方法中进行两个View的滑动范围限制// 根据返回值决定被拖动的视图将要显示的位置 public int clampViewPositionHorizontal(View child, int left, int dx) { // 限制frontView拖动的范围, [-mBackWidth, 0] if (child == mFrontView) { if (left < -mBackWidth) { left = -mBackWidth; } else if (left > 0) { left = 0; } } if (child == mBackView) { // 限制backView拖动的范围: [mWidth-mBackWidth, mWidth] if (left < mWidth-mBackWidth) { left = mWidth-mBackWidth; } else if (left > mWidth) { left = mWidth; } } return left; }
6. 关联滑动效果
在onViewPositionChanged方法通过offsetLeftAndRight方法关联滑动// 当子控件位置发生改变后调用 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { // 关联两个子控件的滑动 if (changedView == mFrontView) { // 滑动mFrontView时,同时滑动mBackView mBackView.offsetLeftAndRight(dx); } else if (changedView == mBackView) {// 滑动mBackView时,同时滑动mFrontView mFrontView.offsetLeftAndRight(dx); } // 解决往左滑时mBackView空白的问题,同时兼容低版本2.3 invalidate(); };
7. 打开或关闭SwipeLayout
// 松开手释放子控件时回调 // xvel: 往右甩速度大于0 public void onViewReleased(View releasedChild, float xvel, float yvel) { int left = mFrontView.getLeft(); if (xvel == 0 && left < -mBackWidth /2) { open(); } else if (xvel < 0) { // 往左甩 open(); } else { close(); } }; /** 关闭SwipeLayout*/ public void close() { // 平滑滚动到某一位置: 第一步 mDragHelper.smoothSlideViewTo(mFrontView, 0, 0); ViewCompat.postInvalidateOnAnimation(this); } /** 打开SwipeLayout */ public void open() { mDragHelper.smoothSlideViewTo(mFrontView, -mBackWidth, 0); ViewCompat.postInvalidateOnAnimation(this); } // 平滑滚动到某一位置: 第二步 @Override public void computeScroll() { super.computeScroll(); if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } }
8. 监听滑动状态(打开、关闭、准备打开、准备关闭)
// 当子控件位置发生改变后调用 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { ... // 监听滑动状态(打开,关闭,准备打开,准备关闭) listenDragStatus(); }; // ===========监听拖动状态(begin)============ public enum DragStatus { OPEN, CLOSE, DRAGGING } private DragStatus mCurrentStatus = DragStatus.CLOSE; public DragStatus getDragStatus() { return mCurrentStatus; } public interface OnDragListener { public void open(SwipeLayout swipeLayout); public void close(SwipeLayout swipeLayout); /** 准备打开 */ public void onStartOpen(SwipeLayout swipeLayout); /** 准备关闭 */ public void onStartClose(SwipeLayout swipeLayout); } private OnDragListener mOnDragListener; public void setOnDragListener(OnDragListener onDragListener) { this.mOnDragListener = onDragListener; } /** 监听拖动状态 */ protected void listenDragStatus() { int left = mFrontView.getLeft(); DragStatus status; if (left == 0) { status = DragStatus.CLOSE; } else if (left == -mBackWidth) { status = DragStatus.OPEN; } else { status = DragStatus.DRAGGING; } if (mOnDragListener != null) { if (status == DragStatus.OPEN) { mOnDragListener.open(this); } else if (status == DragStatus.CLOSE) { mOnDragListener.close(this); } else { // 当前状态为拖动状态 // 上一次状态为关闭,当前状态为拖动,则表示准备打开 if (mCurrentStatus == DragStatus.CLOSE) { mOnDragListener.onStartOpen(this); } else if (mCurrentStatus == DragStatus.OPEN) { // 上一次状态为打开,当前状态为拖动,则表示准备关闭 mOnDragListener.onStartClose(this); } } } mCurrentStatus = status; } // ===========监听拖动状态(end)============
9. 集成到ListView(显示列表)
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.list_view); mAdapter = new ContactAdapter(this, Arrays.asList(Constant.LIST_DATAS2)); mListView.setAdapter(mAdapter); } public class ContactAdapter extends MyBaseAdapter<String> { public ContactAdapter(Context context, List<String> listDatas) { super(context, listDatas); } @Override public BaseHolder<String> createViewHolder(Context context, ViewGroup parent, String bean, int position) { return new ContactHolder(context, parent, this, position, bean); } } public class ContactHolder extends BaseHolder<String> { private TextView tvName; public ContactHolder(Context context, ViewGroup parent, MyBaseAdapter<String> adapter, int position, String bean) { super(context, parent, adapter, position, bean); } @Override public View createView(Context context, ViewGroup parent, int position, String bean) { return super.inflater.inflate(R.layout.item_list, parent, false); } @Override protected void initView() { tvName = (TextView) super.itemView.findViewById(R.id.tv_name); } @Override protected void onRefreshViews(String bean, int position) { tvName.setText(bean); } }
10. 只打开一个SwipeLayout
public class SwipeLayoutManager { // 单例 private static SwipeLayoutManager instance = new SwipeLayoutManager(); private SwipeLayoutManager() { } public static SwipeLayoutManager getInstance() { return instance; } /** 用来记录打开的SwipeLayout */ private SwipeLayout mOpenSwipeLayout; public SwipeLayout getOpenSwipeLayout() { return mOpenSwipeLayout; } /** 记录列表中打开的SwipeLayout */ public void setOpenSwipeLayout(SwipeLayout swipeLayout) { this.mOpenSwipeLayout = swipeLayout; } /** 关闭之前打开的SwipeLayout */ public void closeSwipeLayout() { if (mOpenSwipeLayout != null) { mOpenSwipeLayout.close(); mOpenSwipeLayout = null; // 关闭后需要置为空 } } } // 列表的Holder类的查找item子控件的方法 protected void initView() { ... SwipeLayout layout = (SwipeLayout) super.itemView; layout.setOnDragListener(new OnDragListener() { @Override public void onOpen(SwipeLayout swipeLayout) { } // 准备打开某一个列表项的SwipeLayout时,先关闭之前打开的,再保存当前打开的 @Override public void onStartOpen(SwipeLayout swipeLayout) { if (SwipeLayoutManager.getInstance().getOpenSwipeLayout() != swipeLayout) { SwipeLayoutManager.getInstance().closeSwipeLayout(); } SwipeLayoutManager.getInstance().setOpenSwipeLayout(swipeLayout); } @Override public void onStartClose(SwipeLayout swipeLayout) { } @Override public void onClose(SwipeLayout swipeLayout) { } }); }
11. 优化体验:如果左右滑动请求父控件不要拦截事件
当左右滑动时,请求列表不要拦截事件 // 当子控件位置发生改变后调用 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { ... // 如果是左右滑动SwipeLayout,请求父控件ListView不要拦截事件,则 // ListView不会上下滚动,并且会把触摸事件传递下来,让SwipeLayout处理 if (Math.abs(dx) > Math.abs(dy)) { requestDisallowInterceptTouchEvent(true); } }
12. 滚动列表时关闭打开的SwipeLayout
// 滚动列表时,关闭打开的SwipeLayout mListView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { SwipeLayoutManager.getInstance().closeSwipeLayout(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } });
13. 列表项中的操作菜单点击事件
// 在列表的Holder类中添加 protected void initView() { tvName = (TextView) super.itemView.findViewById(R.id.tv_name); tv_delete = (TextView) super.itemView.findViewById(R.id.tv_delete); tv_cancel = (TextView) super.itemView.findViewById(R.id.tv_cancel); OnClickListener onClickListener = new OnClickListener() { @Override public void onClick(View v) { SwipeLayoutManager.getInstance().closeSwipeLayout(); switch (v.getId()) { case R.id.tv_delete: showToast("取消:" + bean); break; case R.id.tv_cancel: showToast("删除:" + bean); break; } } }; tv_delete.setOnClickListener(onClickListener); tv_cancel.setOnClickListener(onClickListener); ... }
完整源码:点击下载
相关文章推荐
- 类似于QQ的聊天记录的侧滑删除功能
- 仿QQ聊天界面侧滑
- Android 仿QQ界面的 RecyclerView 侧滑删除效果
- android开发之仿QQ拖拽界面效果(侧滑面板)
- [绍棠] APP跳转到手机QQ聊天界面如何跳转
- 在WEB项目中调用QQ通讯组件打开QQ聊天界面
- 仿微信 QQ聊天界面,弹出软键盘,listview上移,标题不动
- IOS第九天(1:QQ聊天界面frame模型)
- Android,iOS打开手机QQ与指定用户聊天界面
- Android学习笔记(一)——仿qq聊天的welcome界面登陆的实现
- QQ界面的MSN聊天软件
- SwipeMenuListView实现类似于QQ侧滑删除效果
- 0831Android基础自定义Notification+仿QQ聊天界面的小Demo(上)
- 安卓开发——根据QQ号跳转到QQ聊天界面
- Qt+html+JavaScript实现类似QQ聊天界面的气泡效果
- 利用swipelistview完成qq聊天列表右滑删除功能
- 纯代码实现QQ聊天界面---TableView使用详解
- Android,iOS打开手机QQ与指定用户聊天界面
- iOS开发中应用内跳转到QQ聊天界面和指定的联系人聊天