仿QQ消息列表左滑彈出菜單,標(biāo)記已讀和刪除
話不多說,看到這樣的效果心動了么?
這里寫圖片描述
1.先上build .gradle,dependencies里面要這樣寫,其實(shí)就是導(dǎo)入v7里面的recyclerView。要用butterknife的話, 記得加到這里來:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:support-v4:23.4.0'
compile 'com.android.support:recyclerview-v7:23.2.0'
//butterknife
compile 'com.jakewharton:butterknife:6.1.0'
}
2.再來列表的item, item_msg_remind.xml:
消息主體,width記得match_parent,直接把后面兩個布局頂出去
<LinearLayout
//省略布局
android:id="@+id/ll_msg_remind_main">
<View
//省略布局
android:id="@+id/msg_remind_point" />
<LinearLayout
//省略布局
android:orientation="vertical">
<TextView
//省略布局
android:id="@+id/tv_remind_title"
android:layout_weight="1"
android:text="隔壁的二蛋" />
<TextView
//省略布局
android:id="@+id/tv_remind_content"
android:layout_weight="2"
android:ellipsize="end"
android:maxLines="2"
android:text="對方撤回了一條消息并砍了你的狗" />
</LinearLayout>
</LinearLayout>
<LinearLayout
//省略布局>
<TextView
//省略布局
android:id="@+id/tv_msg_remind_check"
android:text="標(biāo)記已讀" />
</LinearLayout>
<LinearLayout
//省略布局>
<TextView
//省略布局
android:id="@+id/tv_msg_remind_delete"
android:text="刪除"/>
</LinearLayout>
3.那什么,Adapter來了,MsgRemindAdapter:
消息adapter
-->特別注意extends后面Adapter<>里面要寫自己定義的ViewHolder
public class MsgRemindAdapter extends RecyclerView.Adapter<MsgRemindAdapter.RemindViewHolder>
implements ItemSlideHelper.Callback {
private Context context;
private List<MsgVo> mDatas = new ArrayList<MsgVo>();
private RecyclerView mRecyclerView;
public MsgRemindAdapter(Context context, List<MsgVo> mDatas) {
this.context = context;
this.mDatas = mDatas;
}
onCreateViewHolder
@Override
public RemindViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.item_msg_remind, parent, false);
return new RemindViewHolder(view);
}
//將recyclerView綁定Slide事件
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
mRecyclerView.addOnItemTouchListener(new ItemSlideHelper(mRecyclerView.getContext(), this));
}
onBindViewHolder(),綁定
@Override
public void onBindViewHolder(final RemindViewHolder holder, final int position) {
/**
* 消息狀態(tài)
*/
if (mDatas.get(position).isChecked()) {
holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_gray);
} else {
holder.msgRemindPoint.setBackgroundResource(R.drawable.shape_remind_point_theme);
}
//消息標(biāo)題
holder.tvRemindTitle.setText(mDatas.get(position).getTitle());
//消息內(nèi)容
holder.tvRemindContent.setText(mDatas.get(position).getContent());
-->特別注意,敲黑板了啊?。?!在執(zhí)行notify的時(shí)候,取position要取holder.getAdapterPosition(),消息被刪除之后,他原來的position是final的,所以取到的值不準(zhǔn)確,會報(bào)數(shù)組越界。
消息主體監(jiān)聽,這里我是讓他添加一條數(shù)據(jù),替換成你需要的操作即可
holder.llMsgRemindMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
addData(mDatas.size());
}
});
標(biāo)記已讀監(jiān)聽
holder.tvMsgRemindCheck.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mDatas.get(holder.getAdapterPosition()).setChecked(true);
notifyItemChanged(holder.getAdapterPosition());
}
});
刪除消息監(jiān)聽
holder.tvMsgRemindDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
removeData(holder.getAdapterPosition());
}
});
此方法用來計(jì)算水平方向移動的距離
@Override
public int getHorizontalRange(RecyclerView.ViewHolder holder) {
if (holder.itemView instanceof LinearLayout) {
ViewGroup viewGroup = (ViewGroup) holder.itemView;
//viewGroup包含3個控件,即消息主item、標(biāo)記已讀、刪除,返回為標(biāo)記已讀寬度+刪除寬度
return viewGroup.getChildAt(1).getLayoutParams().width
+ viewGroup.getChildAt(2).getLayoutParams().width;
}
return 0;
}
@Override
public RecyclerView.ViewHolder getChildViewHolder(View childView) {
return mRecyclerView.getChildViewHolder(childView);
}
@Override
public View findTargetView(float x, float y) {
return mRecyclerView.findChildViewUnder(x, y);
}
自定義的ViewHolder
public class RemindViewHolder extends RecyclerView.ViewHolder {
@InjectView(R.id.msg_remind_point)
View msgRemindPoint;
@InjectView(R.id.tv_remind_title)
TextView tvRemindTitle;
@InjectView(R.id.tv_remind_content)
TextView tvRemindContent;
@InjectView(R.id.ll_msg_remind_main)
LinearLayout llMsgRemindMain;
@InjectView(R.id.tv_msg_remind_check)
TextView tvMsgRemindCheck;
@InjectView(R.id.tv_msg_remind_delete)
TextView tvMsgRemindDelete;
public RemindViewHolder(View itemView) {
super(itemView);
ButterKnife.inject(this, itemView);
}
}
添加單條數(shù)據(jù)
public void addData(int position) {
MsgVo vo = new MsgVo();
if (position % 2 == 1) {
vo.setChecked(false);
vo.setTitle("隔壁的二蛋");
vo.setContent("對方撤回了一條消息并砍了你的狗,問你服不服。");
} else {
vo.setChecked(false);
vo.setTitle("對面的三娃");
vo.setContent("今天晚上開黑,4缺1,來不來?");
}
mDatas.add(position, vo);
notifyItemInserted(position);
}
刪除單條數(shù)據(jù)
public void removeData(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
}
4.要做這么高難度的滑動,是要一個ItemSlideHelper的:
/**
* 消息列表左滑菜單幫助類
*/
public class ItemSlideHelper implements RecyclerView.OnItemTouchListener, GestureDetector.OnGestureListener {
private final int DEFAULT_DURATION = 200;
private View mTargetView;
private int mActivePointerId;
private int mTouchSlop;
private int mMaxVelocity;
private int mMinVelocity;
private int mLastX;
private int mLastY;
private boolean mIsDragging;
private Animator mExpandAndCollapseAnim;
private GestureDetectorCompat mGestureDetector;
private Callback mCallback;
public ItemSlideHelper(Context context, Callback callback) {
this.mCallback = callback;
//手勢用于處理fling
mGestureDetector = new GestureDetectorCompat(context, this);
ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
mMinVelocity = configuration.getScaledMinimumFlingVelocity();
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
int action = MotionEventCompat.getActionMasked(e);
int x = (int) e.getX();
int y = (int) e.getY();
//如果RecyclerView滾動狀態(tài)不是空閑targetView不是空
if (rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
if (mTargetView != null) {
//隱藏已經(jīng)打開
smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
mTargetView = null;
}
return false;
}
//如果正在運(yùn)行動畫 ,直接攔截什么都不做
if (mExpandAndCollapseAnim != null && mExpandAndCollapseAnim.isRunning()) {
return true;
}
boolean needIntercept = false;
switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = MotionEventCompat.getPointerId(e, 0);
mLastX = (int) e.getX();
mLastY = (int) e.getY();
/*
* 如果之前有一個已經(jīng)打開的項(xiàng)目,當(dāng)此次點(diǎn)擊事件沒有發(fā)生在右側(cè)的菜單中則返回TRUE,
* 如果點(diǎn)擊的是右側(cè)菜單那么返回FALSE這樣做的原因是因?yàn)椴藛涡枰憫?yīng)Onclick
* */
if (mTargetView != null) {
return !inView(x, y);
}
//查找需要顯示菜單的view;
mTargetView = mCallback.findTargetView(x, y);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (x - mLastX);
int deltaY = (y - mLastY);
if (Math.abs(deltaY) > Math.abs(deltaX))
return false;
//如果移動距離達(dá)到要求,則攔截
needIntercept = mIsDragging = mTargetView != null && Math.abs(deltaX) >= mTouchSlop;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/*
* 走這是因?yàn)闆]有發(fā)生過攔截事件
* */
if (isExpanded()) {
if (inView(x, y)) {
// 如果走這那行這個ACTION_UP的事件會發(fā)生在右側(cè)的菜單中
} else {
//攔截事件,防止targetView執(zhí)行onClick事件
needIntercept = true;
}
//折疊菜單
smoothHorizontalExpandOrCollapse(DEFAULT_DURATION / 2);
}
mTargetView = null;
break;
}
return needIntercept;
}
private boolean isExpanded() {
return mTargetView != null && mTargetView.getScrollX() == getHorizontalRange();
}
private boolean isCollapsed() {
return mTargetView != null && mTargetView.getScrollX() == 0;
}
/*
* 根據(jù)targetView的scrollX計(jì)算出targetView的偏移,這樣能夠知道這個point
* 是在右側(cè)的菜單中
* */
private boolean inView(int x, int y) {
if (mTargetView == null)
return false;
int scrollX = mTargetView.getScrollX();
int left = mTargetView.getWidth() - scrollX;
int top = mTargetView.getTop();
int right = left + getHorizontalRange();
int bottom = mTargetView.getBottom();
Rect rect = new Rect(left, top, right, bottom);
return rect.contains(x, y);
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
if (mExpandAndCollapseAnim != null && mExpandAndCollapseAnim.isRunning() || mTargetView == null)
return;
//如果要響應(yīng)fling事件設(shè)置將mIsDragging設(shè)為false
if (mGestureDetector.onTouchEvent(e)) {
mIsDragging = false;
return;
}
int x = (int) e.getX();
int y = (int) e.getY();
int action = MotionEventCompat.getActionMasked(e);
switch (action) {
case MotionEvent.ACTION_DOWN:
//RecyclerView 不會轉(zhuǎn)發(fā)這個Down事件
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) (mLastX - e.getX());
if (mIsDragging) {
horizontalDrag(deltaX);
}
mLastX = x;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mIsDragging) {
if (!smoothHorizontalExpandOrCollapse(0) && isCollapsed())
mTargetView = null;
mIsDragging = false;
}
break;
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
/**
* 根據(jù)touch事件來滾動View的scrollX
*
* @param delta
*/
private void horizontalDrag(int delta) {
int scrollX = mTargetView.getScrollX();
int scrollY = mTargetView.getScrollY();
if ((scrollX + delta) <= 0) {
mTargetView.scrollTo(0, scrollY);
return;
}
int horRange = getHorizontalRange();
scrollX += delta;
if (Math.abs(scrollX) < horRange) {
mTargetView.scrollTo(scrollX, scrollY);
} else {
mTargetView.scrollTo(horRange, scrollY);
}
}
/**
* 根據(jù)當(dāng)前scrollX的位置判斷是展開還是折疊
*
* @param velocityX 如果不等于0那么這是一次fling事件,否則是一次ACTION_UP或者ACTION_CANCEL
*/
private boolean smoothHorizontalExpandOrCollapse(float velocityX) {
int scrollX = mTargetView.getScrollX();
int scrollRange = getHorizontalRange();
if (mExpandAndCollapseAnim != null)
return false;
int to = 0;
int duration = DEFAULT_DURATION;
if (velocityX == 0) {
//如果已經(jīng)展一半,平滑展開
if (scrollX > scrollRange / 2) {
to = scrollRange;
}
} else {
if (velocityX > 0)
to = 0;
else
to = scrollRange;
duration = (int) ((1.f - Math.abs(velocityX) / mMaxVelocity) * DEFAULT_DURATION);
}
if (to == scrollX)
return false;
mExpandAndCollapseAnim = ObjectAnimator.ofInt(mTargetView, "scrollX", to);
mExpandAndCollapseAnim.setDuration(duration);
mExpandAndCollapseAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mExpandAndCollapseAnim = null;
if (isCollapsed())
mTargetView = null;
}
@Override
public void onAnimationCancel(Animator animation) {
//onAnimationEnd(animation);
mExpandAndCollapseAnim = null;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
mExpandAndCollapseAnim.start();
return true;
}
public int getHorizontalRange() {
RecyclerView.ViewHolder viewHolder = mCallback.getChildViewHolder(mTargetView);
return mCallback.getHorizontalRange(viewHolder);
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(velocityX) > mMinVelocity && Math.abs(velocityX) < mMaxVelocity) {
if (!smoothHorizontalExpandOrCollapse(velocityX)) {
if (isCollapsed())
mTargetView = null;
return true;
}
}
return false;
}
/**
* 左滑菜單Callback
*/
public interface Callback {
int getHorizontalRange(RecyclerView.ViewHolder holder);
RecyclerView.ViewHolder getChildViewHolder(View childView);
View findTargetView(float x, float y);
}
}
5.最后來Activity,布局文件中添加:
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_msg_remind"
android:layout_width="match_parent"
android:layout_height="match_parent" />
MsgRemindActivity:
/**
* 消息主界面
*/
public class MsgRemindActivity extends Activity {
@InjectView(R.id.rv_msg_remind)
RecyclerView rvMsgRemind;
private MsgRemindAdapter msgRemindAdapter;
private List<MsgVo> mDatas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_msg_remind);
ButterKnife.inject(this);
initData();
//設(shè)置布局類型為listview的LinearLayoutManager
rvMsgRemind.setLayoutManager(new LinearLayoutManager(this));
rvMsgRemind.addItemDecoration(new RecyclerViewListDecoration(this,
RecyclerViewListDecoration.VERTICAL_LIST));
//固定recyclerview大小
rvMsgRemind.setHasFixedSize(true);
rvMsgRemind.setAdapter(msgRemindAdapter);
}
private void initData() {
mDatas = new ArrayList<MsgVo>();
for (int i = 'A'; i < 'G'; i++) {
MsgVo vo = new MsgVo();
if (i % 2 == 1) {
vo.setChecked(true);
vo.setTitle("原始消息,已讀狀態(tài)" + (char) i);
vo.setContent("烏啦啦啦啦啦啦啦啦啦");
} else {
vo.setChecked(false);
vo.setTitle("原始消息,未讀狀態(tài)");
vo.setContent("唔嚕嚕嚕嚕嚕嚕嚕嚕嚕嚕");
}
mDatas.add(vo);
}
msgRemindAdapter = new MsgRemindAdapter(this, mDatas);
}
}
6.以上代碼中的
rvMsgRemind.addItemDecoration(new RecyclerViewListDecoration(this,
RecyclerViewListDecoration.VERTICAL_LIST));
是設(shè)置recyclerView的分割線,跟ListView有點(diǎn)點(diǎn)不一樣哈。
好了,就這樣了吧。
特別感謝鴻洋老司機(jī)提供了學(xué)習(xí)RecyclerView 的車牌:
《Android RecyclerView 使用完全解析 體驗(yàn)藝術(shù)般的控件》
http://blog.csdn.net/lmj623565791/article/details/45059587
特別感謝Android小先森提供的思路:
http://blog.csdn.net/as_jon/article/details/51830504