Android自定義組合控件---教你如何自定義下拉刷新和左滑刪除

緒論

最近項(xiàng)目里面用到了下拉刷新和左滑刪除癞松,網(wǎng)上找了找并沒(méi)有可以用的浆竭,有比較好的左滑刪除鬼贱,但是并沒(méi)有和下拉刷新上拉加載結(jié)合到一起,要不就是一些比較水的結(jié)合始赎,并不能在項(xiàng)目里面使用和橙,小編一著急自己組合了一個(gè)仔燕,做完了和QQ的對(duì)比了一下,并沒(méi)有太大區(qū)別魔招,今天分享給大家晰搀,其實(shí)并不難,但是不知道為什么網(wǎng)上沒(méi)有比較好的Demo仆百,當(dāng)你的項(xiàng)目真的很急的時(shí)候厕隧,又沒(méi)有比較好的Demo,那么"那條友誼的小船兒真是說(shuō)翻就翻啊",好了俄周,下面先來(lái)具體看一下實(shí)現(xiàn)后的效果吧:

代碼已經(jīng)上傳到Github上了,小伙伴們記得star和follow哦
https://github.com/Hankkin/SwipeRefreshDemo

這里寫(xiě)圖片描述

還不錯(cuò)吧髓迎?比QQ多了個(gè)上拉加載峦朗,好了看看怎么實(shí)現(xiàn)的吧,小編在之前的游客評(píng)論中了解到排龄,代碼注釋很重要波势,所以盡量把注釋寫(xiě)的很清楚:

實(shí)現(xiàn)思路

由于時(shí)間有限,左滑菜單是在網(wǎng)上找的Demo

  1. 自定義下拉刷新頭橄维、尾
  2. 手勢(shì)判斷尺铣,根據(jù)滑動(dòng)距離顯示頭部下拉布局
  3. 判斷是否滑動(dòng)到底部顯示尾部上拉布局
  4. 創(chuàng)建左滑菜單,根據(jù)手勢(shì)滑動(dòng)事件彈出菜單

詳細(xì)的看一下實(shí)現(xiàn)過(guò)程
1.首先我們先自定義下拉頭布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="bottom" >

    <RelativeLayout
        android:id="@+id/xlistview_header_content"
        android:layout_width="fill_parent"
        android:background="#00000000"
        android:layout_height="60dp" >

        <LinearLayout
            android:id="@+id/xlistview_header_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/xlistview_header_hint_textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="#000"
                android:textSize="16sp"
                android:text="@string/xlistview_header_hint_normal" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="3dp" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#000"
                    android:text="@string/xlistview_header_last_time"
                    android:textSize="14sp" />

                <TextView
                    android:id="@+id/xlistview_header_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="#000"
                    android:textSize="14sp" />
            </LinearLayout>
        </LinearLayout>

        <ImageView
            android:id="@+id/xlistview_header_arrow"
            android:layout_width="wrap_content"
            android:layout_height="35dp"
            android:layout_alignLeft="@id/xlistview_header_text"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-50dp"
            android:src="@drawable/xlistview_arrow" />

        <ProgressBar
            android:id="@+id/xlistview_header_progressbar"
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:layout_alignLeft="@id/xlistview_header_text"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-55dp"
            android:visibility="gone"/>
    </RelativeLayout>

</LinearLayout>

2.接下來(lái)我們自定義HeaderView争舞,代碼很詳細(xì)了凛忿,不多介紹了

package com.hankkin.library;

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;



public class RefreshListHeader extends LinearLayout {
    //根布局
    private LinearLayout mContainer;
    //下拉箭頭圖片
    private ImageView mArrowImageView;
    //下拉進(jìn)度條
    private ProgressBar mProgressBar;
    //下拉文本
    private TextView mHintTextView;
    //狀態(tài)值 0-正常 1-準(zhǔn)備刷新  2-正在刷新
    private int mState = STATE_NORMAL;

    //抬起動(dòng)畫(huà)
    private Animation mRotateUpAnim;
    //下拉動(dòng)畫(huà)
    private Animation mRotateDownAnim;

    //下拉動(dòng)畫(huà)時(shí)間
    private final int ROTATE_ANIM_DURATION = 180;

    public final static int STATE_NORMAL = 0;
    public final static int STATE_READY = 1;
    public final static int STATE_REFRESHING = 2;

    public RefreshListHeader(Context context) {
        super(context);
        initView(context);
    }

    /**
     * @param context
     * @param attrs
     */
    public RefreshListHeader(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    /**
     * 初始化組件
     * @param context
     */
    private void initView(Context context) {
        LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, 0);
        mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_header, null);
        addView(mContainer, lp);
        setGravity(Gravity.BOTTOM);

        mArrowImageView = (ImageView) findViewById(R.id.xlistview_header_arrow);
        mHintTextView = (TextView) findViewById(R.id.xlistview_header_hint_textview);
        mProgressBar = (ProgressBar) findViewById(R.id.xlistview_header_progressbar);
        //設(shè)置抬起動(dòng)畫(huà)
        mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateUpAnim.setFillAfter(true);
        //設(shè)置下拉動(dòng)畫(huà)
        mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
        mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION);
        mRotateDownAnim.setFillAfter(true);
    }
    //設(shè)置狀態(tài)
    public void setState(int state) {
        if (state == mState)
            return;
        //正在刷新時(shí)-顯示進(jìn)度條,隱藏下拉箭頭
        if (state == STATE_REFRESHING) {
            mArrowImageView.clearAnimation();
            mArrowImageView.setVisibility(View.INVISIBLE);
            mProgressBar.setVisibility(View.VISIBLE);
        } else {
            //顯示下拉箭頭竞川,隱藏進(jìn)度條
            mArrowImageView.setVisibility(View.VISIBLE);
            mProgressBar.setVisibility(View.INVISIBLE);
        }

        switch (state) {
        case STATE_NORMAL:
            if (mState == STATE_READY) {//準(zhǔn)備刷新時(shí)開(kāi)啟動(dòng)畫(huà)
                mArrowImageView.startAnimation(mRotateDownAnim);
            }
            if (mState == STATE_REFRESHING) {//正在刷新時(shí)開(kāi)啟動(dòng)畫(huà)
                mArrowImageView.clearAnimation();
            }
            mHintTextView.setText(R.string.xlistview_header_hint_normal);
            break;
        case STATE_READY:
            if (mState != STATE_READY) {
                mArrowImageView.clearAnimation();
                mArrowImageView.startAnimation(mRotateUpAnim);
                mHintTextView.setText(R.string.xlistview_header_hint_ready);
            }
            break;
        case STATE_REFRESHING:
            mHintTextView.setText(R.string.xlistview_header_hint_loading);
            break;
        default:
        }

        mState = state;
    }

    /**
     * 設(shè)置頭部高度
     * @param height
     */
    public void setVisiableHeight(int height) {
        if (height < 0)
            height = 0;
        LayoutParams lp = (LayoutParams) mContainer.getLayoutParams();
        lp.height = height;
        mContainer.setLayoutParams(lp);
    }

    public int getVisiableHeight() {
        return mContainer.getHeight();
    }

}

3.HeaderView定義結(jié)束后店溢,我們需要自定義一個(gè)既支持下拉刷新又支持左滑刪除的ListView,看看我是怎么做的:(左滑菜單是引用網(wǎng)上的Demo委乌,代碼寫(xiě)的也比較易懂床牧,這里不詳細(xì)給大家介紹了)
然后我們?cè)谒幕A(chǔ)上添加下拉上拉事件:(重點(diǎn)看一下onTouchEvent事件)

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mLastY == -1) { //獲取上次y軸坐標(biāo)
            mLastY = ev.getRawY();
        }

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:   //手勢(shì)按下事件、獲取坐標(biāo)遭贸、設(shè)置上次下拉時(shí)間
                firstTouchY = ev.getRawY();
                mLastY = ev.getRawY();
                setRefreshTime(RefreshTime.getRefreshTime(getContext()));
                int oldPos = mTouchPosition;
                mDownX = ev.getX();
                mDownY = ev.getY();
                mTouchState = TOUCH_STATE_NONE;

                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());

                //彈出左滑菜單
                if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) {
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipe(ev);//左滑菜單手勢(shì)監(jiān)聽(tīng)事件戈咳,根據(jù)滑動(dòng)距離彈出菜單
                    return true;
                }


                //獲取item view,此方法是因?yàn)間etChildAt()傳入index值導(dǎo)致listview不可見(jiàn)的item會(huì)報(bào)空指針
                // 防止listview不可見(jiàn)的item獲取到的為空壕吹,使用下面方法
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                if (mTouchView != null && mTouchView.isOpen()) {//如果滑動(dòng)的item不為空并且已經(jīng)開(kāi)啟著蛙,則關(guān)閉該菜單
                    mTouchView.smoothCloseMenu();
                    mTouchView = null;
                    return super.onTouchEvent(ev);
                }

                if (mTouchView != null) {//否則打開(kāi)左滑菜單
                    mTouchView.onSwipe(ev);
                }
                if (view instanceof SwipeMenuLayout) {
                    mTouchView = (SwipeMenuLayout) view;
                }

                break;
            case MotionEvent.ACTION_MOVE://手勢(shì)滑動(dòng)事件
                final float deltaY = ev.getRawY() - mLastY;
                float dy = Math.abs((ev.getY() - mDownY));
                float dx = Math.abs((ev.getX() - mDownX));
                mLastY = ev.getRawY();
                //判斷左滑菜單是否未激活、或者x軸偏移平方小于y軸偏移平方3倍的時(shí)候
                if ((mTouchView == null || !mTouchView.isActive()) && Math.pow(dx, 2) / Math.pow(dy, 2) <= 3) {
                    //判斷第一個(gè)可見(jiàn)位置并且頭部布局可見(jiàn)高度大于0時(shí)或者y軸偏移量>0
                    if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
                        // 重新更新頭部高度
                        updateHeaderHeight(deltaY / OFFSET_RADIO);
                        invokeOnScrolling();
                    }
                }

                if (mTouchState == TOUCH_STATE_X) {//如果x軸偏移彈出左滑菜單
                    if (mTouchView != null) {
                        mTouchView.onSwipe(ev);
                    }
                    getSelector().setState(new int[] { 0 });
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                } else if (mTouchState == TOUCH_STATE_NONE) {
                    if (Math.abs(dy) > MAX_Y) { //如果y軸偏移量>指定y軸偏移量算利,設(shè)置y軸偏移狀態(tài)
                        mTouchState = TOUCH_STATE_Y;
                    } else if (dx > MAX_X) {//如果x軸偏移量>指定x軸偏移量册踩,設(shè)置x軸偏移狀態(tài),開(kāi)始彈出左滑菜單
                        mTouchState = TOUCH_STATE_X;
                        if (mOnSwipeListener != null) {
                            mOnSwipeListener.onSwipeStart(mTouchPosition);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP://手勢(shì)抬起事件
                mLastY = -1; // reset
                if (getFirstVisiblePosition() == 0) {
                    // 設(shè)置下拉刷新?tīng)顟B(tài)值效拭,開(kāi)啟下拉刷新?tīng)顟B(tài)
                    if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
                        mPullRefreshing = true;
                        mHeaderView.setState(RefreshListHeader.STATE_REFRESHING);
                        if (onRefreshListener != null) {
                            tag=REFRESH;
                            onRefreshListener.onRefresh();
                        }
                    }
                    resetHeaderHeight();
                }

                lastTouchY = ev.getRawY();//獲取上次y軸偏移量
                if (canLoadMore()) {//判斷是否滿(mǎn)足上拉
                    loadData();
                }

                if (mTouchState == TOUCH_STATE_X) {//如果為x軸偏移狀態(tài)暂吉,開(kāi)啟左滑
                    if (mTouchView != null) {
                        mTouchView.onSwipe(ev);
                        if (!mTouchView.isOpen()) {
                            mTouchPosition = -1;
                            mTouchView = null;
                        }
                    }
                    if (mOnSwipeListener != null) {
                        mOnSwipeListener.onSwipeEnd(mTouchPosition);
                    }
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

當(dāng)然這部分也是滑動(dòng)沖突解決的主要部分胖秒,看一下完整的代碼,應(yīng)該能夠看的懂了慕的,小編幾乎每一句都加上了注釋?zhuān)绻锌床幻靼椎牡胤娇梢粤粞匝指危【帟?huì)及時(shí)回復(fù)的:

package com.hankkin.library;

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.TextView;

import java.util.Date;

public class RefreshSwipeMenuListView extends ListView implements OnScrollListener {

    private static final int TOUCH_STATE_NONE = 0;
    private static final int TOUCH_STATE_X = 1; //x軸觸摸狀態(tài)值
    private static final int TOUCH_STATE_Y = 2; //y軸觸摸狀態(tài)值
    public static final int BOTH=2;//上拉和下拉
    public static final int HEADER=0;//下拉
    public static final int FOOTER=1;//上拉
    public static String tag;//ListView的動(dòng)作
    public static final String REFRESH="refresh";
    public static final String LOAD="load";
    private int MAX_Y = 5;  //Y軸最大偏移量
    private int MAX_X = 3;  //X軸最大偏移量
    private float mDownX;   //觸摸x
    private float mDownY;   //觸摸y
    private int mTouchState;    //觸摸狀態(tài)
    private int mTouchPosition; //觸摸位置
    private SwipeMenuLayout mTouchView; //滑動(dòng)彈出布局
    private OnSwipeListener mOnSwipeListener;   //彈出監(jiān)聽(tīng)器

    private float firstTouchY;  //第一次觸摸y坐標(biāo)
    private float lastTouchY;   //最后一次觸摸y坐標(biāo)
    //創(chuàng)建左滑菜單接口
    private SwipeMenuCreator mMenuCreator;
    //菜單點(diǎn)擊事件
    private OnMenuItemClickListener mOnMenuItemClickListener;
    //關(guān)閉菜單動(dòng)畫(huà)修飾Interpolator
    private Interpolator mCloseInterpolator;
    //開(kāi)啟菜單動(dòng)畫(huà)修飾Interpolator
    private Interpolator mOpenInterpolator;

    private float mLastY = -1;
    private Scroller mScroller;
    private OnScrollListener mScrollListener; // 滑動(dòng)監(jiān)聽(tīng)

    // 下拉上拉監(jiān)聽(tīng)器
    private OnRefreshListener onRefreshListener;

    //下拉頭
    private RefreshListHeader mHeaderView;

    //頭部視圖內(nèi)容,用來(lái)計(jì)算頭部高度肮街,不下拉時(shí)隱藏
    private RelativeLayout mHeaderViewContent;
    //下拉時(shí)間文本控件
    private TextView mHeaderTimeView;
    private int mHeaderViewHeight; // 頭部高度
    private boolean mEnablePullRefresh = true;//能否下拉刷新
    private boolean mPullRefreshing = false; // 是否正在刷新

    //上拉尾部視圖
    private LinearLayout mFooterView;
    private boolean mEnablePullLoad;//是否可以上拉加載
    private boolean mPullLoading;   //是否正在上拉
    private boolean mIsFooterReady = false;
    private int mTotalItemCount;
    private int mScrollBack;
    private final static int SCROLLBACK_HEADER = 0;
    private final static int SCROLLBACK_FOOTER = 1;

    private final static int SCROLL_DURATION = 400;
    private final static int PULL_LOAD_MORE_DELTA = 50;
    private final static float OFFSET_RADIO = 1.8f;
    private boolean isFooterVisible=false;

    public RefreshSwipeMenuListView(Context context) {
        super(context);
        init(context);
    }

    public RefreshSwipeMenuListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public RefreshSwipeMenuListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * 初始化組件
     * @param context
     */
    private void init(Context context) {
        mScroller = new Scroller(context, new DecelerateInterpolator());
        super.setOnScrollListener(this);
        // 初始化頭部視圖
        mHeaderView = new RefreshListHeader(context);
        mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.xlistview_header_content);
        mHeaderTimeView = (TextView) mHeaderView.findViewById(R.id.xlistview_header_time);
        addHeaderView(mHeaderView);

        // 初始化尾部視圖
        mFooterView = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.xlistview_footer, null, false);

        // 初始化頭部高度
        mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mHeaderViewHeight = mHeaderViewContent.getHeight();
                //向 ViewTreeObserver 注冊(cè)方法风题,以獲取控件尺寸
                getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }
        });
        MAX_X = dp2px(MAX_X);
        MAX_Y = dp2px(MAX_Y);
        mTouchState = TOUCH_STATE_NONE;
    }

    /**
     * 添加適配器
     * @param adapter
     */
    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mIsFooterReady == false) {  //添加尾部隱藏
            mIsFooterReady = true;
            addFooterView(mFooterView);
            mFooterView.setVisibility(GONE);
        }
        super.setAdapter(new SwipeMenuAdapter(getContext(), adapter) {
            @Override
            public void createMenu(SwipeMenu menu) {//創(chuàng)建左滑菜單
                if (mMenuCreator != null) {
                    mMenuCreator.create(menu);
                }
            }

            @Override
            public void onItemClick(SwipeMenuView view, SwipeMenu menu, int index) {
                if (mOnMenuItemClickListener != null) {//左滑菜單點(diǎn)擊事件
                    mOnMenuItemClickListener.onMenuItemClick(view.getPosition(), menu, index);
                }
                if (mTouchView != null) {
                    mTouchView.smoothCloseMenu();
                }
            }
        });
    }

    public void setCloseInterpolator(Interpolator interpolator) {
        mCloseInterpolator = interpolator;
    }

    public void setOpenInterpolator(Interpolator interpolator) {
        mOpenInterpolator = interpolator;
    }

    public Interpolator getOpenInterpolator() {
        return mOpenInterpolator;
    }

    public Interpolator getCloseInterpolator() {
        return mCloseInterpolator;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mLastY == -1) { //獲取上次y軸坐標(biāo)
            mLastY = ev.getRawY();
        }

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:   //手勢(shì)按下事件、獲取坐標(biāo)嫉父、設(shè)置上次下拉時(shí)間
                firstTouchY = ev.getRawY();
                mLastY = ev.getRawY();
                setRefreshTime(RefreshTime.getRefreshTime(getContext()));
                int oldPos = mTouchPosition;
                mDownX = ev.getX();
                mDownY = ev.getY();
                mTouchState = TOUCH_STATE_NONE;

                mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());

                //彈出左滑菜單
                if (mTouchPosition == oldPos && mTouchView != null && mTouchView.isOpen()) {
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipe(ev);//左滑菜單手勢(shì)監(jiān)聽(tīng)事件沛硅,根據(jù)滑動(dòng)距離彈出菜單
                    return true;
                }


                //獲取item view,此方法是因?yàn)間etChildAt()傳入index值導(dǎo)致listview不可見(jiàn)的item會(huì)報(bào)空指針
                // 防止listview不可見(jiàn)的item獲取到的為空绕辖,使用下面方法
                View view = getChildAt(mTouchPosition - getFirstVisiblePosition());

                if (mTouchView != null && mTouchView.isOpen()) {//如果滑動(dòng)的item不為空并且已經(jīng)開(kāi)啟摇肌,則關(guān)閉該菜單
                    mTouchView.smoothCloseMenu();
                    mTouchView = null;
                    return super.onTouchEvent(ev);
                }

                if (mTouchView != null) {//否則打開(kāi)左滑菜單
                    mTouchView.onSwipe(ev);
                }
                if (view instanceof SwipeMenuLayout) {
                    mTouchView = (SwipeMenuLayout) view;
                }

                break;
            case MotionEvent.ACTION_MOVE://手勢(shì)滑動(dòng)事件
                final float deltaY = ev.getRawY() - mLastY;
                float dy = Math.abs((ev.getY() - mDownY));
                float dx = Math.abs((ev.getX() - mDownX));
                mLastY = ev.getRawY();
                //判斷左滑菜單是否未激活、或者x軸偏移平方小于y軸偏移平方3倍的時(shí)候
                if ((mTouchView == null || !mTouchView.isActive()) && Math.pow(dx, 2) / Math.pow(dy, 2) <= 3) {
                    //判斷第一個(gè)可見(jiàn)位置并且頭部布局可見(jiàn)高度大于0時(shí)或者y軸偏移量>0
                    if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
                        // 重新更新頭部高度
                        updateHeaderHeight(deltaY / OFFSET_RADIO);
                        invokeOnScrolling();
                    }
                }

                if (mTouchState == TOUCH_STATE_X) {//如果x軸偏移彈出左滑菜單
                    if (mTouchView != null) {
                        mTouchView.onSwipe(ev);
                    }
                    getSelector().setState(new int[] { 0 });
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                } else if (mTouchState == TOUCH_STATE_NONE) {
                    if (Math.abs(dy) > MAX_Y) { //如果y軸偏移量>指定y軸偏移量仪际,設(shè)置y軸偏移狀態(tài)
                        mTouchState = TOUCH_STATE_Y;
                    } else if (dx > MAX_X) {//如果x軸偏移量>指定x軸偏移量围小,設(shè)置x軸偏移狀態(tài),開(kāi)始彈出左滑菜單
                        mTouchState = TOUCH_STATE_X;
                        if (mOnSwipeListener != null) {
                            mOnSwipeListener.onSwipeStart(mTouchPosition);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP://手勢(shì)抬起事件
                mLastY = -1; // reset
                if (getFirstVisiblePosition() == 0) {
                    // 設(shè)置下拉刷新?tīng)顟B(tài)值树碱,開(kāi)啟下拉刷新?tīng)顟B(tài)
                    if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
                        mPullRefreshing = true;
                        mHeaderView.setState(RefreshListHeader.STATE_REFRESHING);
                        if (onRefreshListener != null) {
                            tag=REFRESH;
                            onRefreshListener.onRefresh();
                        }
                    }
                    resetHeaderHeight();
                }

                lastTouchY = ev.getRawY();//獲取上次y軸偏移量
                if (canLoadMore()) {//判斷是否滿(mǎn)足上拉
                    loadData();
                }

                if (mTouchState == TOUCH_STATE_X) {//如果為x軸偏移狀態(tài)肯适,開(kāi)啟左滑
                    if (mTouchView != null) {
                        mTouchView.onSwipe(ev);
                        if (!mTouchView.isOpen()) {
                            mTouchPosition = -1;
                            mTouchView = null;
                        }
                    }
                    if (mOnSwipeListener != null) {
                        mOnSwipeListener.onSwipeEnd(mTouchPosition);
                    }
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    class ResetHeaderHeightTask extends AsyncTask<Void, Void, Void> {
        protected Void doInBackground(Void... params) {
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        protected void onPostExecute(Void result) {
            mPullRefreshing = false;
            mHeaderView.setState(RefreshListHeader.STATE_NORMAL);
            resetHeaderHeight();

        }
    }

    public void smoothOpenMenu(int position) {
        if (position >= getFirstVisiblePosition() && position <= getLastVisiblePosition()) {
            View view = getChildAt(position - getFirstVisiblePosition());
            if (view instanceof SwipeMenuLayout) {
                mTouchPosition = position;
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                }
                mTouchView = (SwipeMenuLayout) view;
                mTouchView.smoothOpenMenu();
            }
        }
    }

    private int dp2px(int dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
                getContext().getResources().getDisplayMetrics());
    }

    public void setMenuCreator(SwipeMenuCreator menuCreator) {
        this.mMenuCreator = menuCreator;
    }

    public void setOnMenuItemClickListener(OnMenuItemClickListener onMenuItemClickListener) {
        this.mOnMenuItemClickListener = onMenuItemClickListener;
    }

    public void setOnSwipeListener(OnSwipeListener onSwipeListener) {
        this.mOnSwipeListener = onSwipeListener;
    }

    public static interface OnMenuItemClickListener {
        void onMenuItemClick(int position, SwipeMenu menu, int index);
    }

    public static interface OnSwipeListener {
        void onSwipeStart(int position);

        void onSwipeEnd(int position);
    }

    /**
     * 設(shè)置刷新可用
     * @param enable
     */
    private void setPullRefreshEnable(boolean enable) {
        mEnablePullRefresh = enable;
        if (!mEnablePullRefresh) { // disable, hide the content
            mHeaderViewContent.setVisibility(View.INVISIBLE);
        } else {
            mHeaderViewContent.setVisibility(View.VISIBLE);
        }
    }

    /**
     * enable or disable pull up load more feature.
     * 設(shè)置加載可用
     * @param enable
     */
    private void setPullLoadEnable(boolean enable) {
        mEnablePullLoad = enable;
        if (!mEnablePullLoad) {
            mFooterView.setVisibility(GONE);
            mFooterView.setOnClickListener(null);
        } else {
            mPullLoading = false;
            mFooterView.setVisibility(VISIBLE);
            // both "pull up" and "click" will invoke load more.
            mFooterView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    startLoadMore();
                }
            });
        }
    }

    /**
     * stop refresh, reset header view.
     * 停止刷新,重置頭部控件
     */
    private void stopRefresh() {
        if (mPullRefreshing == true) {
            mPullRefreshing = false;
            resetHeaderHeight();
        }
    }

    /**
     * stop load more, reset footer view.
     * 停止加載更多,重置尾部控件
     */
    private void stopLoadMore() {
        if (mPullLoading == true) {
            mPullLoading = false;
            mFooterView.setVisibility(GONE);
        }
    }

    /**
     * set last refresh time
     *
     * @param time
     */
    public void setRefreshTime(String time) {
        mHeaderTimeView.setText(time);
    }

    private void invokeOnScrolling() {
        if (mScrollListener instanceof OnXScrollListener) {
            OnXScrollListener l = (OnXScrollListener) mScrollListener;
            l.onXScrolling(this);
        }
    }

    /**
     * 更新頭部高度,設(shè)置狀態(tài)值
     * @param delta
     */
    private void updateHeaderHeight(float delta) {
        mHeaderView.setVisiableHeight((int) delta + mHeaderView.getVisiableHeight());
        if (mEnablePullRefresh && !mPullRefreshing) {
            if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
                mHeaderView.setState(RefreshListHeader.STATE_READY);
            } else {
                mHeaderView.setState(RefreshListHeader.STATE_NORMAL);
            }
        }
        setSelection(0); // scroll to top each time
    }

    /**
     * 重置頭部視圖高度
     */
    private void resetHeaderHeight() {
        int height = mHeaderView.getVisiableHeight();
        if (height == 0) // 不可見(jiàn)
            return;
        // 如果正在刷新并且頭部高度沒(méi)有完全顯示不做操作
        if (mPullRefreshing && height <= mHeaderViewHeight) {
            return;
        }
        int finalHeight = 0; // 默認(rèn)
        //如果正在刷新并且滑動(dòng)高度大于頭部高度
        if (mPullRefreshing && height > mHeaderViewHeight) {
            finalHeight = mHeaderViewHeight;
        }
        mScrollBack = SCROLLBACK_HEADER;
        mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION);
        // 觸發(fā)computescroll
        invalidate();
    }


    /**
     * 開(kāi)啟上啦
     */
    private void startLoadMore() {
        mPullLoading = true;
        mFooterView.setVisibility(VISIBLE);
        if (onRefreshListener != null) {
            tag=LOAD;
            onRefreshListener.onLoadMore();
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            if (mScrollBack == SCROLLBACK_HEADER) {
                mHeaderView.setVisiableHeight(mScroller.getCurrY());
            } 
            postInvalidate();
            invokeOnScrolling();
        }
        super.computeScroll();
    }

    @Override
    public void setOnScrollListener(OnScrollListener l) {
        mScrollListener = l;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(view, scrollState);
        }
    }

    /**
     * 滑動(dòng)監(jiān)聽(tīng)
     * @param view
     * @param firstVisibleItem
     * @param visibleItemCount
     * @param totalItemCount
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mTotalItemCount = totalItemCount;
        if (mScrollListener != null) {
            mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }
        if(firstVisibleItem+visibleItemCount>=totalItemCount){
            isFooterVisible=true;
        }else{
            isFooterVisible=false;
        }
    }

    public void setOnRefreshListener(OnRefreshListener l) {
        onRefreshListener = l;
    }

    /**
     * you can listen ListView.OnScrollListener or this one. it will invoke
     * onXScrolling when header/footer scroll back.
     */
    public interface OnXScrollListener extends OnScrollListener {
        public void onXScrolling(View view);
    }

    /**
     * implements this interface to get refresh/load more event.
     */
    public interface OnRefreshListener {
        public void onRefresh();
        public void onLoadMore();
    }

    /**
     * 上拉加載和下拉刷新請(qǐng)求完畢
     */
    public void complete(){
        stopLoadMore();
        stopRefresh();
        if(REFRESH.equals(tag)){
            RefreshTime.setRefreshTime(getContext(),new Date());
        }
    }

    /**
     * 設(shè)置ListView的模式,上拉和下拉
     * @param mode
     */
    public void setListViewMode(int mode){
        if(mode==BOTH){
            setPullRefreshEnable(true);
            setPullLoadEnable(true);
        }else if(mode==FOOTER){
            setPullLoadEnable(true);
        }else if(mode==HEADER){
            setPullRefreshEnable(true);
        }
    }

    /**
     * 判斷是否可以上蠟加載
     * @return
     */
    private boolean canLoadMore() {
        return isBottom() && !mPullLoading && isPullingUp();
    }

    /**
     * 判斷是否到達(dá)底部
     * @return
     */
    private boolean isBottom() {
        if (getCount() > 0) {
            if (getLastVisiblePosition() == getAdapter().getCount() - 1 &&
                    getChildAt(getChildCount() - 1).getBottom() <= getHeight()) {
                return true;
            }
        }
        return false;
    }

    private boolean isPullingUp() {
        return (firstTouchY - lastTouchY) >= 200;
    }


    private void loadData() {
        if (onRefreshListener != null) {
            setLoading(true);
        }
    }

    /**
     * 設(shè)置是否上拉
     * @param loading
     */
    public void setLoading(boolean loading) {
        if (this == null) return;
        mPullLoading = loading;
        if (loading) {
            mFooterView.setVisibility(VISIBLE);

            setSelection(getAdapter().getCount() - 1);
            onRefreshListener.onLoadMore();
        } else {
            mFooterView.setVisibility(GONE);
            firstTouchY = 0;
            lastTouchY = 0;
        }
    }

}

定義完了之后我們是這樣用的:

rsmLv.setAdapter(adapter);
        rsmLv.setListViewMode(RefreshSwipeMenuListView.HEADER);
        rsmLv.setOnRefreshListener(this);

        SwipeMenuCreator creator = new SwipeMenuCreator() {
            @Override
            public void create(SwipeMenu menu) {
                // 創(chuàng)建滑動(dòng)選項(xiàng)
                SwipeMenuItem rejectItem = new SwipeMenuItem(
                        getApplicationContext());
                // 設(shè)置選項(xiàng)背景
                rejectItem.setBackground(new ColorDrawable(getResources().getColor(R.color.top)));
                // 設(shè)置選項(xiàng)寬度
                rejectItem.setWidth(dp2px(80,getApplicationContext()));
                // 設(shè)置選項(xiàng)標(biāo)題
                rejectItem.setTitle("置頂");
                // 設(shè)置選項(xiàng)標(biāo)題
                rejectItem.setTitleSize(16);
                // 設(shè)置選項(xiàng)標(biāo)題顏色
                rejectItem.setTitleColor(Color.WHITE);
                // 添加選項(xiàng)
                menu.addMenuItem(rejectItem);

                // 創(chuàng)建刪除選項(xiàng)
                SwipeMenuItem argeeItem = new SwipeMenuItem(getApplicationContext());
                argeeItem.setBackground(new ColorDrawable(getResources().getColor(R.color.del)));
                argeeItem.setWidth(dp2px(80, getApplicationContext()));
                argeeItem.setTitle("刪除");
                argeeItem.setTitleSize(16);
                argeeItem.setTitleColor(Color.WHITE);
                menu.addMenuItem(argeeItem);
            }
        };

        rsmLv.setMenuCreator(creator);

        rsmLv.setOnMenuItemClickListener(new RefreshSwipeMenuListView.OnMenuItemClickListener() {
            @Override
            public void onMenuItemClick(int position, SwipeMenu menu, int index) {
                switch (index) {
                    case 0: //第一個(gè)選項(xiàng)
                        Toast.makeText(MainActivity.this,"您點(diǎn)擊的是置頂",Toast.LENGTH_SHORT).show();
                        break;
                    case 1: //第二個(gè)選項(xiàng)
                        del(position,rsmLv.getChildAt(position+1-rsmLv.getFirstVisiblePosition()));
                        break;

                }
            }
        });

為了和QQ有著一樣的體驗(yàn)效果成榜,小編加了一個(gè)刪除item的動(dòng)畫(huà)
看起來(lái)就不會(huì)太生硬了框舔,用戶(hù)體驗(yàn)也比較好:

/**
     * 刪除item動(dòng)畫(huà)
     * @param index
     * @param v
     */
    private void del(final int index, View v){
        final Animation animation = (Animation) AnimationUtils.loadAnimation(v.getContext(), R.anim.list_anim);
        animation.setAnimationListener(new Animation.AnimationListener() {
            public void onAnimationStart(Animation animation) {}
            public void onAnimationRepeat(Animation animation) {}
            public void onAnimationEnd(Animation animation) {
                data.remove(index);
                adapter.notifyDataSetChanged();
                animation.cancel();
            }
        });

        v.startAnimation(animation);
    }

好了到這里了,我們的組合控件-上拉下拉+左滑刪除的組合ListView就定義好了伦连,分享出來(lái)小伙伴們以后遇到可以直接拿來(lái)用雨饺,"到時(shí)候小船兒也不會(huì)再翻了",不合理的地方希望大家提出來(lái)惑淳,共同交流额港,進(jìn)步。

代碼已經(jīng)上傳到Github上了歧焦,小伙伴們記得star和follow哦
https://github.com/Hankkin/SwipeRefreshDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末移斩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绢馍,更是在濱河造成了極大的恐慌向瓷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舰涌,死亡現(xiàn)場(chǎng)離奇詭異猖任,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瓷耙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)朱躺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刁赖,“玉大人,你說(shuō)我怎么就攤上這事长搀∮畛冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵源请,是天一觀(guān)的道長(zhǎng)枪芒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)谁尸,這世上最難降的妖魔是什么舅踪? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮良蛮,結(jié)果婚禮上硫朦,老公的妹妹穿的比我還像新娘。我一直安慰自己背镇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布泽裳。 她就那樣靜靜地躺著瞒斩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涮总。 梳的紋絲不亂的頭發(fā)上胸囱,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音瀑梗,去河邊找鬼烹笔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抛丽,可吹牛的內(nèi)容都是我干的谤职。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼亿鲜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼允蜈!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蒿柳,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饶套,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后垒探,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體妓蛮,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年圾叼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛤克。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捺癞。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咖耘,靈堂內(nèi)的尸體忽然破棺而出翘簇,到底是詐尸還是另有隱情,我是刑警寧澤儿倒,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布版保,位于F島的核電站,受9級(jí)特大地震影響夫否,放射性物質(zhì)發(fā)生泄漏彻犁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一凰慈、第九天 我趴在偏房一處隱蔽的房頂上張望汞幢。 院中可真熱鬧,春花似錦微谓、人聲如沸森篷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)仲智。三九已至,卻和暖如春姻氨,著一層夾襖步出監(jiān)牢的瞬間钓辆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工肴焊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留前联,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓娶眷,卻偏偏與公主長(zhǎng)得像似嗤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茂浮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)双谆、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評(píng)論 4 62
  • 不做卡奴O俊M绮觥!不做卡奴;闲摺4缑铡!不做卡奴J翳搿P艹铡他爸! 不要逾期!9啤诊笤!不要逾期!=砩隆讨跟!不要逾期!1擅骸晾匠! 下面是某論壇上的一些血淋淋...
    咔哧咔哧的分色閱讀 957評(píng)論 0 51
  • □文/從前慢 1 爸媽是“騙子” 他們說(shuō) 好好讀書(shū)才有將來(lái) 為此 我把自己埋藏在知識(shí)的海洋里 可是 這么多年 我還...
    從前慢者閱讀 235評(píng)論 6 10
  • 小時(shí)候家里在農(nóng)村凉馆,有一條從小陪我長(zhǎng)大的土狗,沒(méi)有名字亡资,就叫大白狗澜共,看著我長(zhǎng)大,看著我上學(xué)锥腻,在我快7歲的時(shí)候被送給大...
    大漂亮的媽媽閱讀 292評(píng)論 0 0