1. 程式人生 > >使用RecyclerView打造QQ條目側滑效果

使用RecyclerView打造QQ條目側滑效果

說起這個功能,先吐槽一下,剛來不久的一個產品經理,自己雖然使用的是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 @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中有相對應的監聽回撥,這樣就可以進行我們下一步操作了!