说起这个功能,先吐槽一下,刚来不久的一个产品经理,自己虽然使用的是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;
/**
* =========================================
* 版权所有 违法必究
* 作者: huangxioaguo.
* =========================================
*/
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
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 交给 mViewDragHelper 决定是否拦截
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public 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;
}
//测量 会调用很多次
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// 测量完成后 值改变后才会调用
@Override
protected 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
*/
@Override
protected 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放置 mFrontLayout
mFrontLayout.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);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
//添加健壮性 判断
//1 比如有 两个 或者 两个以上子view
int childCount = getChildCount();
if (childCount < 2) {
throw new IllegalStateException("You must have 2 children at least!! 你得有 至少两个子view!!");
}
//2 校验都是viewGroup
if (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事件 ----》CallBack
ViewDragHelper.Callback callBack = new ViewDragHelper.Callback() {
/**
*返回值决定是否 可以拖动
* @param child 拖拽的view对象 子view
* @param pointerId 多指 手指的id
* @return
*/
@Override
public boolean tryCaptureView(View child, int pointerId) {
// return child == mFrontLayout;
return true;
}
/**
* 当 view 被捕获的时候调用
* @param capturedChild
* @param activePointerId
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
/**
* 获取横向 拖拽范围 不决定 能否拖动
* 做伴随动画 计算执行时长 ,计算敏感度 >0
* @param child
* @return
*/
@Override
public int getViewHorizontalDragRange(View child) {
//返回实际的拖动范围
return mRange;
}
/**
* 1. 修正 位置 left 2. 没有 开始真正的移动
* @param child
* @param left
* @param dx
* @return
*/
@Override
public 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
*/
@Override
public 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 转交 给mFrontLayout
mFrontLayout.offsetLeftAndRight(dx);
}
dispatchEvent();
//手动 刷新 重新绘制
invalidate();
}
// @Override
// public int clampViewPositionVertical(View child, int top, int dy) {
// return top;
// }
// 当 拖动的view 释放的时候调用
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
//releasedChild 释放的view
//xvel 释放时横向的速度 向左 - + 停止后释放 0
//yvel 释放时纵向的速度
// System.out.println(" releasedChild = " + releasedChild + "::xvel = " + xvel);
// 释放时 位置小于 -mRange 丙炔速度为0
if (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 会调用很多次
@Override
public 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">
<LinearLayout
android:id="@+id/layout_back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal">
<TextView
android: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" />
<TextView
android: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" />
<TextView
android: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>
<RelativeLayout
android:id="@+id/layout_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:padding="8dp">
<ImageView
android: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" />
<TextView
android: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;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_swipe, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
holder.textview.setText(mNameList.get(position));
holder.swipelayout.setOnSwipeChangeListener(new SwipeLayout.OnSwipeChangeListener() {
@Override
public void onOpen(SwipeLayout layout) {
preLayout = layout;
if (onItemClickListener != null) {
onItemClickListener.onOpen(layout);
}
}
@Override
public void onClose(SwipeLayout layout) {
if (onItemClickListener != null) {
onItemClickListener.onClose(layout);
}
}
@Override
public void onSwiping(SwipeLayout layout) {
if (onItemClickListener != null) {
onItemClickListener.onSwiping(layout);
}
}
@Override
public void onStartOpen(SwipeLayout layout) {
if (preLayout != null) {
preLayout.close();
}
if (onItemClickListener != null) {
onItemClickListener.onStartOpen(layout);
}
}
@Override
public void onStartClose(SwipeLayout layout) {
if (onItemClickListener != null) {
onItemClickListener.onStartClose(layout);
}
}
});
holder.layoutFront.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
holder.placedTop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickListener != null) {
onItemClickListener.onpLacedTop(position);
}
}
});
holder.noRead.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickListener != null) {
onItemClickListener.onNoRead(position);
}
}
});
holder.delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickListener != null) {
onItemClickListener.onDelete(position);
}
}
});
holder.layoutFront.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(position);
}
}
});
}
@Override
public 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<>();
@Override
protected 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() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
SwipeLayout preLayout = mAdapter.getPreLayout();
if (preLayout != null) {
preLayout.close();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
private void initListener() {
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onOpen(SwipeLayout layout) {
ToastUtil.showToast(mContext, "打开");
}
@Override
public void onClose(SwipeLayout layout) {
ToastUtil.showToast(mContext, "关闭");
}
@Override
public void onSwiping(SwipeLayout layout) {
ToastUtil.showToast(mContext, "正在移动");
}
@Override
public void onStartOpen(SwipeLayout layout) {
ToastUtil.showToast(mContext, "开始打开");
}
@Override
public void onStartClose(SwipeLayout layout) {
ToastUtil.showToast(mContext, "开始关闭");
}
@Override
public void onpLacedTop(int position) {
ToastUtil.showToast(mContext, "置顶"+NameList.get(position));
}
@Override
public void onNoRead(int position) {
ToastUtil.showToast(mContext, "标记未读"+NameList.get(position));
}
@Override
public void onDelete(int position) {
ToastUtil.showToast(mContext, "删除"+NameList.get(position));
}
@Override
public void onItemClick(int position) {
ToastUtil.showToast(mContext, NameList.get(position));
}
});
}
}
Activity中有相对应的监听回调,这样就可以进行我们下一步操作了!
作者:huangxiaoguo1 发表于2017/1/7 18:44:09 原文链接
阅读:248 评论:0 查看评论