1. 程式人生 > >Android仿蘋果版QQ下拉重新整理實現(一) ——打造簡單平滑的通用下拉重新整理控制元件

Android仿蘋果版QQ下拉重新整理實現(一) ——打造簡單平滑的通用下拉重新整理控制元件

前言:

因為公司人員變動原因,導致了博主四個月沒有動安卓,一直在做IOS開發,如今接近年前,終於可以花一定的時間放在安卓上了.好了,廢話不多說,今天我們要帶來的效果是蘋果版本的QQ下拉重新整理.首先看一下目標效果以及demo效果:

    

因為此效果實現的步驟較多,所以今天博主要實現以上效果的第一步——打造一個通用的下拉重新整理控制元件,具體效果如下:

GIF圖片比較大,還希望讀者能耐心等待一下下可憐從效果圖中可以看出,我們的下拉重新整理的滑動還是很流暢的,可能大多數開發者用的是XListview或者PullToRefresh控制元件,在此博主本著能造輪子就造輪子的原則,打算自己打造一個自己喜歡的通用下拉重新整理控制元件;下面就由博主來說明一下此控制元件是如何完成的:

一、自定義LinearLayout,手動加入下拉重新整理佈局

首先我們得準備好我們的重新整理頭部的佈局,佈局稍微複雜一點,分兩層.一個是正在重新整理時候的佈局,一個是重新整理完成的佈局,在這裡我用的RelativeLayout來佈局,當然啦,除了RelativeLayout之外,FrameLayout和Linearlayout都可以直接或者間接的實現佈局,下面上佈局檔案程式碼:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <LinearLayout
        android:id="@+id/ll_ok"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center"
        android:orientation="horizontal"
        android:visibility="gone">

        <ImageView
            android:id="@+id/iv_ok"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/pull_ok" />

        <TextView
            android:id="@+id/tv_ok"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="10dp"
            android:gravity="center"
            android:text="重新整理成功"
            android:textSize="14sp"
            android:textAppearance="?android:attr/textAppearance"
            android:textColor="#999999"
            android:textStyle="bold" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_refresh"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center">

        <ProgressBar
            android:id="@+id/pb_refresh"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="15dp"
            android:layout_height="15dp"
            android:layout_centerVertical="true"
            android:layout_gravity="center"
            android:indeterminate="true"
            android:indeterminateDrawable="@drawable/pulling"
            android:visibility="gone" />

        <ImageView
            android:id="@+id/iv_refresh"
            android:layout_width="15dp"
            android:layout_height="15dp"
            android:layout_centerVertical="true"
            android:src="@mipmap/pull_down"
            android:layout_toRightOf="@+id/pb_refresh"
            android:visibility="visible"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_toRightOf="@+id/iv_refresh"
            android:gravity="center"
            android:layout_marginLeft="10dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="下拉重新整理"
                android:singleLine="true"
                android:textColor="#9D9D9B"
                android:textSize="14sp"
                android:textStyle="bold" />

            <TextView
                android:id="@+id/tv_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:layout_marginTop="5dp"
                android:text="上次重新整理:"
                android:singleLine="true"
                android:textColor="#AEAEAC"
                android:textSize="12sp" />
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>

高度我們限制死60dp的高度,這個很重要,因為我們的重新整理控制元件都是基於這個高度來計算的;

接下來,我們開始編寫自定義View,首先我們繼承LinearLayout然後在初始化中加入下拉重新整理佈局,並定義好一些重要引數:

/**
 * 下拉重新整理狀態
 */
public static final int REFRESH_BY_PULLDOWN=0;

/**
 * 鬆開重新整理狀態
 */
public static final int REFRESH_BY_RELEASE=1;
/**
 * 正在重新整理狀態
 */
public static final int REFRESHING=2;
/**
 * 重新整理成功狀態
 */
public static final int REFRESHING_SUCCESS=3;
/**
 * 重新整理失敗狀態
 */
public static final int REFRESHING_FAILD=4;

private View refreshView;
private int refreshTargetTop;
ObjectAnimator anim;

//下拉重新整理相關佈局
LinearLayout ll_ok;
RelativeLayout ll_refresh;
ImageView iv_refresh, iv_ok;
TextView tv_tip, tv_time, tv_ok;
ProgressBar pb_refresh;

private RefreshListener refreshListener;
private int lastY;
// 是否可重新整理標記
private boolean isRefreshEnabled = true;
/**
 * 重新整理時間
 */
Calendar LastRefreshTime;

int refreshState=REFRESH_BY_PULLDOWN;

private Context mContext;

public YPXRefreshView(Context context) {
	this(context,null);

}

public YPXRefreshView(Context context, AttributeSet attrs) {
	super(context, attrs);
	mContext = context;
	init();
}

private void init() {
	LastRefreshTime = Calendar.getInstance();
	//重新整理檢視頂端的的view
	refreshView = LayoutInflater.from(mContext).inflate(R.layout.layout_refresh_header, null);
	initRefreshView();
	refreshTargetTop =-ScreenUtils.dpToPx(getResources(),60);
	LayoutParams lp = new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, -refreshTargetTop);
	lp.topMargin = refreshTargetTop;
	lp.gravity = Gravity.CENTER;
	addView(refreshView, lp);
	anim = ObjectAnimator.ofFloat(refreshView, "ypx", 0.0f, 1.0f);

}

private void initRefreshView() {
	ll_ok = (LinearLayout) refreshView.findViewById(R.id.ll_ok);
	ll_refresh = (RelativeLayout) refreshView.findViewById(R.id.ll_refresh);
	iv_refresh = (ImageView) refreshView.findViewById(R.id.iv_refresh);
	iv_ok = (ImageView) refreshView.findViewById(R.id.iv_ok);
	tv_tip = (TextView) refreshView.findViewById(R.id.tv_tip);
	tv_time = (TextView) refreshView.findViewById(R.id.tv_time);
	tv_ok = (TextView) refreshView.findViewById(R.id.tv_ok);
	pb_refresh = (ProgressBar) refreshView.findViewById(R.id.pb_refresh);
}

變數註釋很詳細,首先我們定義好下拉重新整理的五種狀態,分別代表了:下拉重新整理、鬆開重新整理、正在重新整理、重新整理成功、重新整理失敗五種樣式.定義好我們的屬性動畫用作滑動動畫,最後就是手動塞入我們的佈局,程式碼很簡單,下面上一下五中重新整理狀態對應的顯示程式碼:

	/**
	 * 下拉重新整理狀態
	 */
	public void pullDownToRefresh() {
		setRefreshState(REFRESH_BY_PULLDOWN);
		ll_refresh.setVisibility(View.VISIBLE);
		ll_ok.setVisibility(View.GONE);
		tv_tip.setText("下拉重新整理");
		getRefreshTime();
		RotateAnimation anim1 = new RotateAnimation(0, 180,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		anim1.setDuration(300);
		anim1.setFillAfter(true);
		iv_refresh.clearAnimation();
		iv_refresh.startAnimation(anim1);
		pb_refresh.setVisibility(View.GONE);
		iv_refresh.setVisibility(View.VISIBLE);
		Log.i("下拉重新整理","下拉重新整理");
	}

	/**
	 * 鬆開重新整理狀態
	 */
	public void pullUpToRefresh() {
		setRefreshState(REFRESH_BY_RELEASE);
		ll_refresh.setVisibility(View.VISIBLE);
		ll_ok.setVisibility(View.GONE);
		tv_tip.setText("鬆開重新整理");
		getRefreshTime();
		iv_refresh.setImageDrawable(mContext.getResources().getDrawable(R.mipmap.pull_up));
		RotateAnimation anim1 = new RotateAnimation(180, 0,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		anim1.setDuration(300);
		anim1.setFillAfter(true);
		iv_refresh.clearAnimation();
		iv_refresh.startAnimation(anim1);
		pb_refresh.setVisibility(View.GONE);
		iv_refresh.setVisibility(View.VISIBLE);
		Log.i("鬆開重新整理", "鬆開重新整理");
	}

	/**
	 * 正在重新整理狀態
	 */
	public void refreshing() {
		setRefreshState(REFRESHING);
		ll_refresh.setVisibility(View.VISIBLE);
		ll_ok.setVisibility(View.GONE);
		tv_tip.setText("正在重新整理......");
		getRefreshTime();
		SPUtil.getInstance(mContext).setRefreshTime("MyMobile", "" +
				DateUtils.getDate(DateUtils.MM_DD_HH_MM, System.currentTimeMillis()));
		iv_refresh.clearAnimation();
		iv_refresh.setVisibility(View.GONE);
		pb_refresh.setVisibility(View.VISIBLE);
	}

	/**
	 * 重新整理成功狀態
	 */
	public void refreshOK() {
		setRefreshState(REFRESHING_SUCCESS);
		ll_refresh.setVisibility(View.GONE);
		ll_ok.setVisibility(View.VISIBLE);
		tv_ok.setText("重新整理成功");
		iv_ok.setImageDrawable(getResources().getDrawable(R.mipmap.pull_ok));
	}

	/**
	 * 重新整理失敗狀態
	 */
	public void refreshFailed() {
		setRefreshState(REFRESHING_FAILD);
		ll_refresh.setVisibility(View.GONE);
		ll_ok.setVisibility(View.VISIBLE);
		tv_ok.setText("重新整理失敗");
		iv_ok.setImageDrawable(getResources().getDrawable(R.mipmap.pull_failure));
	}


	public void getRefreshTime(){
		String time = SPUtil.getInstance(mContext).getRefreshTime("MyMobile");
		if (time == null || "".equals(time)) {
			tv_time.setVisibility(View.GONE);
			tv_tip.setGravity(Gravity.CENTER | Gravity.LEFT);
			ll_refresh.setGravity(Gravity.CENTER);
		} else {
			tv_time.setVisibility(View.VISIBLE);
			ll_refresh.setGravity(Gravity.CENTER | Gravity.LEFT);
			tv_time.setText("上次重新整理:" + time);
			tv_tip.setGravity(Gravity.BOTTOM|Gravity.LEFT);
		}
	}

五種重新整理狀態對應五種不同的佈局,簡單明瞭!

二、下拉重新整理的原理以及邏輯實現

首先我們介紹一下我們的重新整理控制元件的原理:

  1. 監聽滑動手勢,使用LayoutParams的topMargin屬性,動態改變topMargin的值達到滑動效果
  2. 通過滑動的高度判斷當前的狀態為哪種重新整理狀態
  3. 滑動結束,通過屬性動畫收回,回到初始樣式

原理很簡單,難點在於滑動手勢的判斷,廢話不多說,先上一下滑動手勢的程式碼:

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int y = (int) event.getRawY();
		switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				//記錄下y座標
				lastY = y;
				break;

			case MotionEvent.ACTION_MOVE:
				//y移動座標
				int m = y - lastY;
				doMovement(m);
				//記錄下此刻y座標
				this.lastY = y;
				break;

			case MotionEvent.ACTION_UP:
				fling();
				break;
		}
		return true;
	}



	/**
	 * 下拉move事件處理
	 *
	 * @param moveY
	 */
	private void doMovement(int moveY) {
		LayoutParams lp = (LayoutParams) refreshView.getLayoutParams();
		float f1 = lp.topMargin;
		int i = (int) (f1 + moveY * 0.4F);
		if (i >= refreshTargetTop) {//如果下拉大於-60dp的高度,動態重新整理子檢視
			lp.topMargin = i;
			refreshView.setLayoutParams(lp);
			refreshView.invalidate();
			invalidate();
		}

		if (lp.topMargin > 0) {//鬆開重新整理狀態
			if(refreshState!=REFRESH_BY_RELEASE) {
				pullUpToRefresh();
				setRefreshState(REFRESH_BY_RELEASE);
			}
		} else {//下拉重新整理狀態
			if(refreshState!=REFRESH_BY_PULLDOWN) {
				setRefreshState(REFRESH_BY_PULLDOWN);
				pullDownToRefresh();
			}

		}

	}

在這裡我們設定了一個0.4的滑動阻力值,在手勢滑動的時候,我們通過累加topMargin的值從而達到下拉的目的,如果下拉大於-60dp(即我們一開始設定的下拉重新整理頭部高度),則進入重新整理狀態,動態改變頁面.同樣原理我們可以判斷滑動的高度是向下還是向上,從而進行下拉重新整理和鬆開重新整理的判斷,在這裡,博主在改變狀態之前先加入判斷,這樣可以過濾掉很多不必要的點,從而使我們的重新整理頭部箭頭動畫流暢,不會導致狀態混亂問題.

到目前為止,我們的重新整理控制元件基本上已經可以下拉和上拉了,怎麼樣,原理是不是很簡單,最後,我們來看一下核心程式碼,就是手指離開後的處理:

	/**
	 * up事件處理
	 */
	private void fling() {
		LayoutParams lp = (LayoutParams) refreshView.getLayoutParams();
		if (lp.topMargin > 0) {//拉到了觸發可重新整理事件
			refresh();
		} else {//收回
			animRefreshView(lp.topMargin,refreshTargetTop,300);
		}
	}


	private void refresh() {
		LayoutParams lp = (LayoutParams) this.refreshView.getLayoutParams();
		int i = lp.topMargin;
		animRefreshView(i,0,200);
		refreshing();
		if (refreshListener != null) {
			refreshListener.onRefresh();
			setRefreshState(REFRESHING);

		}
	}

	/**
	 * 從開始位置滑動到結束位置
	 *
	 * @param startHeight
	 * @param endHeight
	 */
	public void animRefreshView(final int startHeight,final int endHeight,int duration){
		anim.start();
		anim.setDuration(duration);
		anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
		{
			@Override
			public void onAnimationUpdate(ValueAnimator animation){
				float cVal = (Float) animation.getAnimatedValue();
				LayoutParams lp = (LayoutParams)refreshView.getLayoutParams();
				int k =startHeight+(int)(cVal*(endHeight-startHeight));
				lp.topMargin = k;
				refreshView.setLayoutParams(lp);
				refreshView.invalidate();
				invalidate();
			}
		});

	}/**
	 * up事件處理
	 */
	private void fling() {
		LayoutParams lp = (LayoutParams) refreshView.getLayoutParams();
		if (lp.topMargin > 0) {//拉到了觸發可重新整理事件
			refresh();
		} else {//收回
			animRefreshView(lp.topMargin,refreshTargetTop,300);
		}
	}


	private void refresh() {
		LayoutParams lp = (LayoutParams) this.refreshView.getLayoutParams();
		int i = lp.topMargin;
		animRefreshView(i,0,200);
		refreshing();
		if (refreshListener != null) {
			refreshListener.onRefresh();
			setRefreshState(REFRESHING);

		}
	}

	/**
	 * 從開始位置滑動到結束位置
	 *
	 * @param startHeight
	 * @param endHeight
	 */
	public void animRefreshView(final int startHeight,final int endHeight,int duration){
		anim.start();
		anim.setDuration(duration);
		anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
		{
			@Override
			public void onAnimationUpdate(ValueAnimator animation){
				float cVal = (Float) animation.getAnimatedValue();
				LayoutParams lp = (LayoutParams)refreshView.getLayoutParams();
				int k =startHeight+(int)(cVal*(endHeight-startHeight));
				lp.topMargin = k;
				refreshView.setLayoutParams(lp);
				refreshView.invalidate();
				invalidate();
			}
		});

	}

首先我們判斷是否拉到了可觸發重新整理的高度,如果觸發到了,即顯示重新整理狀態,開啟收回動畫,因為當前使用者很可能滑動到了超過重新整理頭的高度,這時候我們需要先收回到重新整理的高度,即螢幕中顯示正在重新整理時候的樣式.重點在於屬性動畫,其實這裡博主之前沒有使用屬性動畫,而是使用了Scroller滑動器來實現收回,雖然效果大差不差,但是滑動和收回的感覺總感覺不是那麼的平滑,所以我首先想到的是用屬性動畫來收回,如果有不熟悉屬性動畫的朋友們,可以自行百度一下,使用很簡單,有許多的動畫監聽和回撥,它可以返回當前每一幀的offset,然後通過改變某個狀態值來重新整理整個頁面,上面的addUpdateListener就是它的一個動畫監聽方法,如何得到我們的offset呢,很簡單,只需要:

(Float) animation.getAnimatedValue()

即可,我們得到了偏移量之後就可以通過當前的高度和要回到的高度來動態設定topMargin,從而達到平滑的收回

到此,我們的重新整理控制元件完成了四分之三,是不是覺得很簡單呢!

三、重新整理結束回撥以及使用

當然了,因為我們的控制元件是下拉重新整理,當然少不了重新整理時候的回撥,當重新整理完成的時候,我們還要收回我們的重新整理控制元件,程式碼很簡單, 在這裡,博主就直接貼程式碼了:

	/**
	 * 重新整理監聽介面
	 *
	 * @author Nono
	 */
	public interface RefreshListener {
		 void onRefresh();
	}

	/**
	 * 設定重新整理回撥
	 * @param listener
	 */
	public void setRefreshListener(RefreshListener listener) {
		this.refreshListener = listener;
	}

	/**
	 * 結束重新整理事件
	 */
	public void finishRefresh(boolean isOK) {
		LayoutParams lp = (LayoutParams) this.refreshView.getLayoutParams();
		final int i = lp.topMargin;
		if (isOK) {
			refreshOK();
		} else {
			refreshFailed();
		}
		if(!anim.isRunning()&&refreshState!=REFRESHING){
			new Handler().postDelayed(new Runnable(){
				public void run() {
					animRefreshView(i,refreshTargetTop,500);
				}
			}, 300);
		}
	}/**
	 * 重新整理監聽介面
	 *
	 * @author Nono
	 */
	public interface RefreshListener {
		 void onRefresh();
	}

	/**
	 * 設定重新整理回撥
	 * @param listener
	 */
	public void setRefreshListener(RefreshListener listener) {
		this.refreshListener = listener;
	}

	/**
	 * 結束重新整理事件
	 */
	public void finishRefresh(boolean isOK) {
		LayoutParams lp = (LayoutParams) this.refreshView.getLayoutParams();
		final int i = lp.topMargin;
		if (isOK) {
			refreshOK();
		} else {
			refreshFailed();
		}
		if(!anim.isRunning()&&refreshState!=REFRESHING){
			new Handler().postDelayed(new Runnable(){
				public void run() {
					animRefreshView(i,refreshTargetTop,500);
				}
			}, 300);
		}
	}

其中結束重新整理事件中我們添加了延時,因為重新整理成功或者失敗要給使用者一個反饋,所以我們需要延時0.5秒給使用者.當我們的重新整理完成後,只需要呼叫一下finishRefresh的方法,告訴控制元件滑動完成了,可以收回了.

使用很簡單,因為我們是繼承LinearLayout的,所以我們可以直接在佈局中套在ScrollView上,使用的時候直接findViewByID繫結實現重新整理方法即可下面貼上佈局程式碼:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.ypx.jiehunle.ypx_bezierqqrefreshdemo.YPXRefreshView
        android:id="@+id/refreshableView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:visibility="visible">

        <ScrollView
            android:id="@+id/scrollView1"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:id="@+id/ll_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
            </LinearLayout>
        </ScrollView>
    </com.ypx.jiehunle.ypx_bezierqqrefreshdemo.YPXRefreshView>
</RelativeLayout>

使用時的Activity程式碼:

public class MainActivity extends Activity {
	YPXRefreshView refreshableView;
	LinearLayout layout;
	final int SUCCESS = 1;
	final int FAILED = 0;
	@SuppressLint("HandlerLeak")
	Handler handler = new Handler() {
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case SUCCESS:
				refreshableView.finishRefresh(true);
				TextView textView = new TextView(MainActivity.this);
				textView.setTextColor(Color.BLACK);
				textView.setTextSize(20);
				textView.setText("這是重新整理的文字");
				layout.addView(textView,0);
				break;
			case FAILED:
				refreshableView.finishRefresh(false);
				break;
			default:
				break;
			}
		};
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		initView();
		initData();
	}

	private void initData() {
		layout.removeAllViews();
		for (int i = 0; i < 50; i++) {
			final TextView textView = new TextView(MainActivity.this);
			textView.setTextColor(Color.BLACK);
			textView.setTextSize(20);
			textView.setText("這是第" + i + "個文字");
			textView.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View view) {
					Toast.makeText(MainActivity.this,textView.getText(),0).show();
				}
			});
			layout.addView(textView);
		}
		refreshableView.setRefreshListener(new YPXRefreshView.RefreshListener() {

			@Override
			public void onRefresh() {
				handler.postDelayed(new Runnable() {
					
					@Override
					public void run() {
						handler.sendEmptyMessage(SUCCESS);
						
					} 
				}, 500);
			}
		});
	}

	private void initView() {
		refreshableView = (YPXRefreshView) findViewById(R.id.refreshableView1);
		layout = (LinearLayout) findViewById(R.id.ll_layout);
		refreshableView.setRefreshEnabled(true);
	}

}

到這裡,我們的重新整理控制元件差不多完成了四分之三點五了,什麼?還沒有結束嗎?當然,因為題目是打造通用的重新整理控制元件,所以我們還有最後的環節!

四、事件攔截處理,達到通用效果

什麼是通用,因為我們的控制元件是下拉重新整理,所以應該支援所有的可滑動佈局才對,這就涉及到了事件分發機制,還不瞭解的小夥伴們,可以自行去補習一下,這裡博主就不贅述了.言歸正傳,既然我們要實現通用的重新整理,必然要進行事件攔截,首先想到的就是重寫ViewGroup的onInterceptTouchEvent方法了,那麼我們研究一下什麼時候需要攔截,什麼時候不需要攔截呢?

其實很簡單,當我們內部的滑動控制元件(ListView或ScrollView等)滑動到最頂部的時候,這時候我們需要觸發下拉重新整理,反之則不攔截,給子View自己處理,當然,在進行這一切的時候,我們要先判斷是否存在子View以及判斷子View是繼承哪一種滑動佈局,在這裡博主只是簡單的給個例子.所以如果要實現更多的滑動佈局重新整理,要新增判斷,比如WebView、GridView、RecyclerView等,判斷它們是否滑動到頂部即可,下面上博主的程式碼:

	@Override
	public boolean onInterceptTouchEvent(MotionEvent e) {
		if(!isRefreshEnabled){
			return false;
		}
		int action = e.getAction();
		int y = (int) e.getRawY();
		switch (action) {
			case MotionEvent.ACTION_DOWN:
				lastY = y;
				break;

			case MotionEvent.ACTION_MOVE:
				if (y > lastY && canScroll()) {
					return true;
				}
				//記錄下此刻y座標
				this.lastY = y;
				break;
		}
		return false;
	}

	private boolean canScroll() {
		View childView;
		if (getChildCount() > 1) {
			childView = this.getChildAt(1);
			if (childView instanceof ListView) {
				int top = ((ListView) childView).getChildAt(0).getTop();
				int pad = ((ListView) childView).getListPaddingTop();
				if ((Math.abs(top - pad)) < 3 &&
						((ListView) childView).getFirstVisiblePosition() == 0) {
					return true;
				} else {
					return false;
				}
			} else if (childView instanceof ScrollView) {
				if (((ScrollView) childView).getScrollY() == 0) {
					return true;
				} else {
					return false;
				}
			}else if (childView instanceof WebView) {
				if (((WebView) childView).getScrollY() == 0) {
					return true;
				} else {
					return false;
				}
			}else if (childView instanceof GridView) {
				int top = ((GridView) childView).getChildAt(0).getTop();
				int pad = ((GridView) childView).getListPaddingTop();
				if ((Math.abs(top - pad)) < 3 &&
						((GridView) childView).getFirstVisiblePosition() == 0) {
					return true;
				} else {
					return false;
				}
			}else if (childView instanceof RecyclerView) {
				RecyclerView.LayoutManager manager=((RecyclerView)childView).getLayoutManager();
				int top=0;
				if(manager instanceof LinearLayoutManager){
					top = ((LinearLayoutManager)manager).findFirstVisibleItemPosition();
				}else  if(manager instanceof StaggeredGridLayoutManager){
					top = ((StaggeredGridLayoutManager)manager).findFirstVisibleItemPositions(null)[0];
				}

				if(((RecyclerView)childView).getChildAt(0).getY()==0 &&top==0){
					return true;
				} else {
					return false;
				}

			}

		}
		return false;
	}

可以看到在canScroll函式中博主添加了很多判斷, 這裡只要判斷了字View型別是否是滑動佈局型別,其中包括,ScrollView、ListView、WebView、GridView、RecyclerView等,其中判斷很簡單,就是當前使用者如果滑動到頂部,則交給外部下拉重新整理處理,其餘則放給字View處理.如果使用者有自己自定義的滑動佈局的話,可以在此基礎上手動新增即可.到這裡,總算完成了我們的重新整理控制元件.

五、總結

總的來說,博主實現的下拉重新整理還是非常簡單易懂的,滑動流暢,使用簡單,當然,這不是博主的目的,正如前言所說,博主的目的是為了實現仿IOS的QQ下拉重新整理,本篇下拉重新整理只是實現的第一步,下一步將會在下一篇部落格(安卓仿IOS版QQ下拉重新整理(二) ——二維貝塞爾遠沒有你想的那麼複雜)中給大家帶來一點關於貝塞爾曲線的實現,期待的朋友們歡迎支援一下博主哦~

感謝大家的支援,謝謝!

作者:yangpeixing

QQ:313930500

轉載請註明出處~謝謝~