1.下拉重新整理肯定得用自己的 不用說 或者可以靈活運用别人的能滿足各種需求 也湊合
2.而且 設計思路肯定得是包裹式的 即自定義一個view group包裹recycler view 這樣解耦
這篇文章隻是提供制作方法,在這裡可以輕易的學會;但是想直接用恐怕不行,可以找别人開源的,這裡的下拉重新整理控件由于設計時間尚短 還很醜
http://blog.csdn.net/guolin_blog/article/details/9255575郭霖的這篇文章提供了一些必備的知識
效果還很初級,不過該有的都有了,是以直接用可能讀者還需要改動下(比如狀态恢複的時候,還有一個判斷,如果拉出來一點點,直接恢複;拉出來很多,就執行重新整理,還要有一個延時動畫,不過這些都不是什麼難事)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN0LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9MmaOJTTq1UMZpXTmZEWjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DM2AjMxcDNwITNwIDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
需要解決幾個問題
第一個問題
怎麼實作下拉重新整理,在于如何把header弄到螢幕之外
綠色的是手機螢幕,如何把header弄到外面就要:
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = -headerHeight;
pullView.setLayoutParams(params);
headerHeight是在onWindowsFocusChanged中擷取的控件高度,topMargin就是距離父控件的距離,負數自然就到了螢幕之外
當然這個難題可以通過ScrollView來實作,不過我猜ScrollView本質也是這樣實作的
第二個問題
邏輯問題,什麼時候事件給recyclerview,什麼時候給下拉頭(即recycler view的父view group ,我們要自定義的控件)
if (state == 下拉) {
攔截
執行下拉:修改lp
UP時,進入松開複原狀态
}
if (state == 松開複原中) {
全體阻塞
執行複原:屬性動畫複原
屬性動畫執行完畢,進入無狀态
}
if (無狀态) {
if (rv位移為0 && 下拉手勢) {
進入下拉狀态
} else {
不攔截
}
}
第三個問題
邏輯轉化為代碼,由于你是父容器,是以你需要再onIntercept中攔截事件,onTouchEvent中處理事件,這兩部分的事件各有耦合,是以算是個難點。不過代碼注釋的很清楚,可以輕松看懂
第四個問題
手松開後還有一個恢複過程,隻需要弄一個value animator即可,監聽topMargin的變化。
上代碼,注釋的一清二楚
public class PullToRefreshLayout extends LinearLayout {
private static final String TAG = "xbh";
private View pullView;
private RecyclerView rv;
private int headerHeight;
private int headerBarHeight;//有内容的部分
private static final float PULL_RATE = 0.4f;
private int recyclerViewOffset;
public PullToRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//擷取header view,并把它添加到recycler view之前
pullView = LayoutInflater.from(context).inflate(R.layout.pull_refresh_view, null, false);
addView(pullView, 0);
//設定LinearLayout為垂直
setOrientation(VERTICAL);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
//通過this.getChildAt來擷取recycler view
rv = (RecyclerView) getChildAt(1);
//擷取下拉view的高度(也包括上面的空白部分)
headerHeight = pullView.getHeight();
//擷取下拉view不含空白部分的高度
headerBarHeight = ((ViewGroup)pullView).getChildAt(1).getHeight();
//初始,設定header view反向偏移一段距離,為了讓他一開始處于隐藏狀态
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = -headerHeight;
pullView.setLayoutParams(params);
//通過recycler view的這個監聽,實時擷取recycler view的下滑位移距離
rv.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView rv, int dx, int dy) {
super.onScrolled(rv, dx, dy);
recyclerViewOffset = rv.computeVerticalScrollOffset();
}
});
}
int state = NONE;
private static final int PULL_DOWN = 1;
private static final int RECOVER = 2;
private static final int NONE = 3;
int downY = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
downY = y;//擷取按下時候的縱坐标
break;
case MotionEvent.ACTION_MOVE:
if (state == PULL_DOWN) {
intercepted = true;//如果是正在下拉狀态、恢複狀态,攔截!
}
if (state == RECOVER) {
intercepted = true;
}
if (state == NONE) {//當recycler view位移為0,且正是下拉手勢,進入正在下拉狀态
if (recyclerViewOffset == 0 && y - downY > 0) {
state = PULL_DOWN;
intercepted = true;
} else {
intercepted = false;
}
}
break;
}
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = y;
break;
case MotionEvent.ACTION_MOVE:
//這裡擷取的是滑鼠下滑的距離
int verticalOffset = y - downY;
//滑鼠下滑的距離 乘以 PULL_RATE,就是header view應該下滑的距離
//因為我發現如果滑鼠下滑多少,header view就下來多少,有點不太好看
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = -headerHeight + (int) (verticalOffset * PULL_RATE);
pullView.setLayoutParams(params);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//CANCEL情況是手機移出螢幕,導緻事件意外取消的情況,和UP事件一緻
if (state == PULL_DOWN) {//事件結束,從正在下拉狀态 進入 恢複狀态
state = RECOVER;
int verticalOffset2 = y - downY;
if (verticalOffset2 > headerBarHeight) {
//這裡就是改進的地方,不足距離,正常恢複;超過距離,延時操作
//還有一個改進的地方:我在恢複的時候,封鎖了事件;其實應該是在恢複之前可以繼續下拉的
} else {
}
//topMargin
//start:從你目前偏移的位置開始
//end:恢複到初始位置
final int start = -headerHeight + (int) (verticalOffset2 * PULL_RATE);
final int end = -headerHeight;
//執行一個value動畫
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.setDuration(500).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//i:這裡取得start到end動态在500ms中動态變化的值
int i = (int) animation.getAnimatedValue();
LinearLayout.LayoutParams params = (LayoutParams) pullView.getLayoutParams();
params.topMargin = i;
pullView.setLayoutParams(params);
//結束情況,修改标記位成:無狀态
if (i == end) {
state = NONE;
}
}
});
}
break;
}
return true;
}
}
pull fresh view
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="275dp"
android:background="@color/colorPrimary"
tools:layout_editor_absoluteY="81dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="38dp"
android:layout_height="36dp"
android:layout_marginStart="32dp"
android:layout_marginTop="220dp"
android:background="@mipmap/pull_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="387dp"
android:layout_height="69dp"
android:layout_marginTop="208dp"
android:gravity="center"
android:textColor="#FFFFFF"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="下拉重新整理" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="43dp"
android:layout_height="41dp"
android:layout_marginEnd="28dp"
android:layout_marginTop="216dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.test.MainActivity">
<com.example.test.PullToRefreshLayout
android:id="@+id/pt"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.test.PullToRefreshLayout>
</FrameLayout>