说起这个功能,先吐槽一下,刚来不久的一个产品经理,自己虽然使用的是IOS手机,但也不能什么效果都是说人家IOS的效果交互设计的号,我们就按照它的效果做!
IOS自带的这个破侧滑功能,看着丑的要死,干嘛非要这个干,干,干……….啊…………啊………….
如果她不是个女的,我就……………………………….
好了!看看效果吧!
大致效果就是模仿QQ条目侧滑自己写了一个:
一、首先我们要自定义一个SwipeLayout继承自FrameLayout
来包装item的子view,子view分为两部分,一部分实在默认状态下(就是没侧滑)我们看的的内容,另一部分被隐藏在最右侧。
- 那好我们来看看SwipeLayout具体内容:
package cn.hnshangyu.swipelayout.view;import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;import cn.hnshangyu.swipelayout.R;/*** =========================================* 版权所有 违法必究* 作者: huangxiaoguo.* =========================================*/
public class SwipeLayout extends FrameLayout {/*** 滑动状态*/public enum SwipeState {OPEN, CLOSE, SWIPING}private SwipeState swipeState = SwipeState.CLOSE;//默认关闭状态private OnSwipeChangeListener onSwipeChangeListener;public OnSwipeChangeListener getOnSwipeChangeListener() {return onSwipeChangeListener;}public void setOnSwipeChangeListener(OnSwipeChangeListener onSwipeChangeListener) {this.onSwipeChangeListener = onSwipeChangeListener;}public interface OnSwipeChangeListener {void onOpen(SwipeLayout layout);void onClose(SwipeLayout layout);void onSwiping(SwipeLayout layout);// 将要打开 当前是 关闭状态 ----> 拖动void onStartOpen(SwipeLayout layout);//将要关闭 当前是 打开i状态--->拖动void onStartClose(SwipeLayout layout);}private ViewDragHelper mViewDragHelper;private ViewGroup mBackLayout;private ViewGroup mFrontLayout;private int mWidth;private int mHeight;private int mRange;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);//1 .初始化 ViewDragHelper对象mViewDragHelper = ViewDragHelper.create(this, 1.0f, callBack);}//2. 将touch 事件 转交给 mViewDragHelper@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// 交给 mViewDragHelper 决定是否拦截return mViewDragHelper.shouldInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_UP:Log.e("Log","onTouchEvent---ACTION_UP");if (swipeState == SwipeState.CLOSE) {Log.e("Log","onTouchEvent---ACTION_UP--swipeState");return false;}break;}// 让 mViewDragHelper 接收到 触摸事件try {mViewDragHelper.processTouchEvent(event);} catch (Exception e) {e.printStackTrace();}return true;}//测量 会调用很多次@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}// 测量完成后 值改变后才会调用@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mWidth = getMeasuredWidth();mHeight = getMeasuredHeight();//拖动范围mRange = mBackLayout.getMeasuredWidth();}/*** 放置 子view** @param changed* @param left* @param top* @param right* @param bottom*/@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);layoutInit(false);}private void layoutInit(boolean isOpen) {Rect frontRect = computeFrontRect(isOpen);//f放置 mFrontLayoutmFrontLayout.layout(frontRect.left, frontRect.top, frontRect.right, frontRect.bottom);Rect backRect = computeBackRect(frontRect);mBackLayout.layout(backRect.left, backRect.top, backRect.right, backRect.bottom);//将 控件前置bringChildToFront(mFrontLayout);}/*** 计算 mBackLayout 矩形位置** @param frontRect* @return*/private Rect computeBackRect(Rect frontRect) {int left = frontRect.right;return new Rect(left, frontRect.top, left + mRange, frontRect.bottom);}/*** 计算 mFrontLayout矩形位置** @param isOpen* @return*/private Rect computeFrontRect(boolean isOpen) {int left = 0;if (isOpen) {left = -mRange;} else {left = 0;}return new Rect(left, 0, left + mWidth, 0 + mHeight);}@Overrideprotected void onFinishInflate() {super.onFinishInflate();//添加健壮性 判断//1 比如有 两个 或者 两个以上子viewint childCount = getChildCount();if (childCount < 2) {throw new IllegalStateException("You must have 2 children at least!! 你得有 至少两个子view!!");}//2 校验都是viewGroupif (getChildAt(0) == null || !(getChildAt(0) instanceof ViewGroup) || getChildAt(1) == null && !(getChildAt(1) instanceof ViewGroup)) {throw new IllegalArgumentException("your child must be instance of ViewGroup! 你的view 必须是 viewgroup 的子类 ");}// 后边菜单mBackLayout = (ViewGroup) findViewById(R.id.layout_back);//前置条目mFrontLayout = (ViewGroup) findViewById(R.id.layout_front);}// 3 mViewDragHelper 解析完 touch事件 ----》CallBackViewDragHelper.Callback callBack = new ViewDragHelper.Callback() {/***返回值决定是否 可以拖动* @param child 拖拽的view对象 子view* @param pointerId 多指 手指的id* @return*/@Overridepublic boolean tryCaptureView(View child, int pointerId) {// return child == mFrontLayout;return true;}/*** 当 view 被捕获的时候调用* @param capturedChild* @param activePointerId*/@Overridepublic void onViewCaptured(View capturedChild, int activePointerId) {super.onViewCaptured(capturedChild, activePointerId);}/*** 获取横向 拖拽范围 不决定 能否拖动* 做伴随动画 计算执行时长 ,计算敏感度 >0* @param child* @return*/@Overridepublic int getViewHorizontalDragRange(View child) {//返回实际的拖动范围return mRange;}/*** 1. 修正 位置 left 2. 没有 开始真正的移动* @param child* @param left* @param dx* @return*/@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {//child 正在拖动的子view//left 建议达到的位置//dx deltaX 水平方向的瞬间变化量
// int currentLeft = mFrontLayout.getLeft();
// System.out.println( "currentLeft = "+currentLeft+"dx"+dx+" =? "+left);if (child == mFrontLayout) {left = fixedFrontLeft(left);} else if (child == mBackLayout) {left = fixedBackLeft(left);}return left;}private int fixedFrontLeft(int left) {if (left < -mRange) {left = -mRange;} else if (left > 0) {left = 0;}return left;}private int fixedBackLeft(int left) {if (left < (mWidth - mRange)) {left = mWidth - mRange;} else if (left > mWidth) {left = mWidth;}return left;}/*** 位置改变的时候调用 1. 伴随动画 2. 状态变化 3. 添加回调* @param changedView* @param left* @param top* @param dx* @param dy*/@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
// System.out.println("onViewPositionChanged>>>mBackLayout " + mBackLayout.getLeft());//changedView 当前正在拖动的子view//left clampViewPositionHorizontal 的返回值
// top
// dx 横向的瞬间变化量if (changedView == mFrontLayout) { //拖动mFrontLayout 让 mBackLayout跟着出来mBackLayout.offsetLeftAndRight(dx);} else if (changedView == mBackLayout) {//mBackLayout 转交 给mFrontLayoutmFrontLayout.offsetLeftAndRight(dx);}dispatchEvent();//手动 刷新 重新绘制invalidate();}// @Override
// public int clampViewPositionVertical(View child, int top, int dy) {
// return top;
// }// 当 拖动的view 释放的时候调用@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {//releasedChild 释放的view//xvel 释放时横向的速度 向左 - + 停止后释放 0//yvel 释放时纵向的速度
// System.out.println(" releasedChild = " + releasedChild + "::xvel = " + xvel);// 释放时 位置小于 -mRange 丙炔速度为0if (mFrontLayout.getLeft() < -mRange * 0.5f && xvel == 0) {open();} else if (xvel < 0) { // 向左快速滑动open();} else {close();}}};/*** 1. 更新状态 2.添加回调*/private void dispatchEvent() {SwipeState preState = swipeState;swipeState = updateState();if (onSwipeChangeListener != null) {onSwipeChangeListener.onSwiping(this);if (swipeState != preState) { //当前状态和上一个状态不一样if (swipeState == SwipeState.OPEN) {onSwipeChangeListener.onOpen(this);} else if (swipeState == SwipeState.CLOSE) {onSwipeChangeListener.onClose(this);} else if (preState == SwipeState.OPEN) {onSwipeChangeListener.onStartClose(this);} else if (preState == SwipeState.CLOSE) {onSwipeChangeListener.onStartOpen(this);}}}}/*** 获取当前 最新状态** @return*/private SwipeState updateState() {if (mFrontLayout.getLeft() == -mRange) { // 打开return SwipeState.OPEN;} else if (mFrontLayout.getLeft() == 0) {return SwipeState.CLOSE;}return SwipeState.SWIPING;}// scroller 执行会调用此方法 computeScroll 会调用很多次@Overridepublic void computeScroll() {super.computeScroll();// 是否继续触发动画if (mViewDragHelper.continueSettling(true)) {//执行动画ViewCompat.postInvalidateOnAnimation(this);}}public void open(boolean isSmooth) {if (isSmooth) {int finalLeft = -mRange;// 最终的位置// 返回值 决定是否触发动画boolean b = mViewDragHelper.smoothSlideViewTo(mFrontLayout, finalLeft, 0);if (b) {// 执行动画ViewCompat.postInvalidateOnAnimation(this);}} else {layoutInit(true);}}/*** 打开*/public void open() {open(true);//默认平滑状态}public void close(boolean isSmooth) {if (isSmooth) {int finalLeft = 0;// 最终的位置// 返回值 决定是否触发动画boolean b = mViewDragHelper.smoothSlideViewTo(mFrontLayout, finalLeft, 0);if (b) {// 执行动画ViewCompat.postInvalidateOnAnimation(this);}} else {layoutInit(false);}}/*** 关闭*/public void close() {close(true);// 默认平滑关闭}
}
代码里面解释的很详细,再让我解释,都不知道该说啥了!
- 我们再来看看item的布局
<cn.hnshangyu.swipelayout.view.SwipeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/swipelayout"android:layout_width="match_parent"android:layout_height="60dp"><LinearLayoutandroid:id="@+id/layout_back"android:layout_width="wrap_content"android:layout_height="match_parent"android:descendantFocusability="blocksDescendants"android:orientation="horizontal"><TextViewandroid:id="@+id/placed_top"android:layout_width="60dp"android:layout_height="match_parent"android:background="#bdbdbd"android:gravity="center"android:text="置顶"android:textColor="@android:color/white"android:textSize="16dp" /><TextViewandroid:id="@+id/no_read"android:layout_width="100dp"android:layout_height="match_parent"android:background="#e6be62"android:gravity="center"android:text="标为未读"android:textColor="@android:color/white"android:textSize="16dp" /><TextViewandroid:id="@+id/delete"android:layout_width="60dp"android:layout_height="match_parent"android:background="#e92f2f"android:gravity="center"android:text="删除"android:textColor="@android:color/white"android:textSize="16dp" /></LinearLayout><RelativeLayoutandroid:id="@+id/layout_front"android:layout_width="match_parent"android:layout_height="match_parent"android:descendantFocusability="blocksDescendants"android:padding="8dp"><ImageViewandroid:id="@+id/icon"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:gravity="center_vertical"android:src="@mipmap/icon_head" /><TextViewandroid:id="@+id/textview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginLeft="8dp"android:layout_toRightOf="@+id/icon"android:gravity="center_vertical"android:text="姓名"android:textSize="16dp" /></RelativeLayout>
</cn.hnshangyu.swipelayout.view.SwipeLayout>
可以看到item分为前后两部分,
- mainActivity的布局很简单只有一个RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="cn.hnshangyu.swipelayout.MainActivity"><android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent" /></RelativeLayout>
好了布局我们完成了,现在要看看我们的adapter了
package cn.hnshangyu.swipelayout.adapter;import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;import java.util.concurrent.CopyOnWriteArrayList;import butterknife.ButterKnife;
import butterknife.InjectView;
import cn.hnshangyu.swipelayout.R;
import cn.hnshangyu.swipelayout.view.SwipeLayout;public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {private Context mContext;private CopyOnWriteArrayList<String> mNameList;private SwipeLayout preLayout;//记录上一个打开public SwipeLayout getPreLayout() {return preLayout;}public MyAdapter(Context context, CopyOnWriteArrayList<String> nameList) {this.mContext = context;this.mNameList = nameList;}private OnItemClickListener onItemClickListener;public interface OnItemClickListener {void onOpen(SwipeLayout layout);void onClose(SwipeLayout layout);void onSwiping(SwipeLayout layout);void onStartOpen(SwipeLayout layout);void onStartClose(SwipeLayout layout);void onpLacedTop(int position);void onNoRead(int position);void onDelete(int position);void onItemClick(int position);}public void setOnItemClickListener(OnItemClickListener onItemClickListener) {this.onItemClickListener = onItemClickListener;}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(mContext).inflate(R.layout.item_swipe, parent, false);return new ViewHolder(view);}@Overridepublic void onBindViewHolder(ViewHolder holder, final int position) {holder.textview.setText(mNameList.get(position));holder.swipelayout.setOnSwipeChangeListener(new SwipeLayout.OnSwipeChangeListener() {@Overridepublic void onOpen(SwipeLayout layout) {preLayout = layout;if (onItemClickListener != null) {onItemClickListener.onOpen(layout);}}@Overridepublic void onClose(SwipeLayout layout) {if (onItemClickListener != null) {onItemClickListener.onClose(layout);}}@Overridepublic void onSwiping(SwipeLayout layout) {if (onItemClickListener != null) {onItemClickListener.onSwiping(layout);}}@Overridepublic void onStartOpen(SwipeLayout layout) {if (preLayout != null) {preLayout.close();}if (onItemClickListener != null) {onItemClickListener.onStartOpen(layout);}}@Overridepublic void onStartClose(SwipeLayout layout) {if (onItemClickListener != null) {onItemClickListener.onStartClose(layout);}}});holder.layoutFront.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {return false;}});holder.placedTop.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (onItemClickListener != null) {onItemClickListener.onpLacedTop(position);}}});holder.noRead.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (onItemClickListener != null) {onItemClickListener.onNoRead(position);}}});holder.delete.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (onItemClickListener != null) {onItemClickListener.onDelete(position);}}});holder.layoutFront.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (onItemClickListener != null) {onItemClickListener.onItemClick(position);}}});}@Overridepublic int getItemCount() {return mNameList.size();}class ViewHolder extends RecyclerView.ViewHolder {@InjectView(R.id.placed_top)TextView placedTop;@InjectView(R.id.no_read)TextView noRead;@InjectView(R.id.delete)TextView delete;@InjectView(R.id.layout_back)LinearLayout layoutBack;@InjectView(R.id.icon)ImageView icon;@InjectView(R.id.textview)TextView textview;@InjectView(R.id.layout_front)RelativeLayout layoutFront;@InjectView(R.id.swipelayout)SwipeLayout swipelayout;public ViewHolder(View itemView) {super(itemView);ButterKnife.inject(this, itemView);}}}
在adapter中自定义监听,便于与Activity的交互
- Activity的实现
package cn.hnshangyu.swipelayout;import android.content.Context;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.AbsListView;import java.util.concurrent.CopyOnWriteArrayList;import butterknife.ButterKnife;
import butterknife.InjectView;
import cn.hnshangyu.swipelayout.adapter.MyAdapter;
import cn.hnshangyu.swipelayout.utils.ToastUtil;
import cn.hnshangyu.swipelayout.view.RecycleViewDivider;
import cn.hnshangyu.swipelayout.view.SwipeLayout;public class MainActivity extends AppCompatActivity {@InjectView(R.id.recyclerView)RecyclerView mRecyclerView;private LinearLayoutManager manager;private Context mContext;private MyAdapter mAdapter;private CopyOnWriteArrayList<String> NameList = new CopyOnWriteArrayList<>();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mContext = this;ButterKnife.inject(this);initData();initView();initListener();}private void initData() {for (int i = 0; i < 108; i++) {NameList.add("huangxiaoguo" + i);}}private void initView() {manager = new LinearLayoutManager(this);manager.setOrientation(LinearLayoutManager.VERTICAL);mRecyclerView.setLayoutManager(manager);int mColor = ContextCompat.getColor(mContext, R.color.light_gray);mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.HORIZONTAL, 2, mColor));mAdapter = new MyAdapter(mContext, NameList);mRecyclerView.setAdapter(mAdapter);mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {SwipeLayout preLayout = mAdapter.getPreLayout();if (preLayout != null) {preLayout.close();}}}@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);}});}private void initListener() {mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {@Overridepublic void onOpen(SwipeLayout layout) {ToastUtil.showToast(mContext, "打开");}@Overridepublic void onClose(SwipeLayout layout) {ToastUtil.showToast(mContext, "关闭");}@Overridepublic void onSwiping(SwipeLayout layout) {ToastUtil.showToast(mContext, "正在移动");}@Overridepublic void onStartOpen(SwipeLayout layout) {ToastUtil.showToast(mContext, "开始打开");}@Overridepublic void onStartClose(SwipeLayout layout) {ToastUtil.showToast(mContext, "开始关闭");}@Overridepublic void onpLacedTop(int position) {ToastUtil.showToast(mContext, "置顶"+NameList.get(position));}@Overridepublic void onNoRead(int position) {ToastUtil.showToast(mContext, "标记未读"+NameList.get(position));}@Overridepublic void onDelete(int position) {ToastUtil.showToast(mContext, "删除"+NameList.get(position));}@Overridepublic void onItemClick(int position) {ToastUtil.showToast(mContext, NameList.get(position));}});}
}
Activity中有相对应的监听回调,这样就可以进行我们下一步操作了!
Demo下载地址:http://download.csdn.net/download/huangxiaoguo1/9731545