最近寫了個下拉控制項,和幾個下拉的頭部樣式,下拉控制項可以連續添加疊加幾個頭部視圖 下麵是沒有添加任何頭部尾部的視圖下拉效果 一步一步來介紹,先介紹這個下拉效果,在介紹自定義的頭部 首先在使用上,和普通的控制項沒有兩樣,拿recyclerview來做例子,因為recyclerview使用比較多,而且可以替 ...
最近寫了個下拉控制項,和幾個下拉的頭部樣式,下拉控制項可以連續添加疊加幾個頭部視圖
下麵是沒有添加任何頭部尾部的視圖下拉效果
一步一步來介紹,先介紹這個下拉效果,在介紹自定義的頭部
首先在使用上,和普通的控制項沒有兩樣,拿recyclerview來做例子,因為recyclerview使用比較多,而且可以替代很多的列表控制項
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"> <com.fragmentapp.view.refresh.RefreshLayout android:layout_width="match_parent" android:layout_height="match_parent" app:_height="@dimen/dp_60" android:id="@+id/refreshLayout" > <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:divider="@color/color_e1e1e1" android:dividerHeight="10dp" android:background="#ffffff" android:layout_width="match_parent" android:layout_height="match_parent" /> </com.fragmentapp.view.refresh.RefreshLayout> </LinearLayout>
protected void init() { for (int i = 0; i < 20; i++) { list.add("" + i); } recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(new HomeAdapter(getActivity(), R.layout.item_home, list)); refreshLayout .setCallBack(new RefreshLayout.CallBack() { @Override public void refreshHeaderView(int state, String stateVal) { switch (state) { case RefreshLayout.DOWN_REFRESH: // 下拉刷新狀態 break; case RefreshLayout.RELEASE_REFRESH: // 鬆開刷新狀態 break; case RefreshLayout.LOADING: // 正在刷新中狀態 break; } } @Override public void pullListener(int y) { } }); }
1 package com.fragmentapp.view.refresh; 2 3 import android.content.Context; 4 import android.content.res.TypedArray; 5 import android.graphics.Color; 6 import android.graphics.Rect; 7 import android.support.v4.view.ViewCompat; 8 import android.support.v7.widget.LinearLayoutManager; 9 import android.support.v7.widget.RecyclerView; 10 import android.support.v7.widget.StaggeredGridLayoutManager; 11 import android.util.AttributeSet; 12 import android.util.Log; 13 import android.view.Gravity; 14 import android.view.MotionEvent; 15 import android.view.View; 16 import android.view.ViewConfiguration; 17 import android.widget.AbsListView; 18 import android.widget.FrameLayout; 19 20 import com.fragmentapp.R; 21 22 import java.lang.reflect.Field; 23 import java.util.ArrayList; 24 import java.util.List; 25 26 /** 27 * Created by LiuZhen on 2017/3/24. 28 */ 29 public class RefreshLayout extends FrameLayout { 30 31 private String TAG = "tag"; 32 private int downY;// 按下時y軸的偏移量 33 private final static float RATIO = 3f; 34 //頭部的高度 35 protected int mHeadHeight = 120; 36 //頭部layout 37 protected FrameLayout mHeadLayout,mFootLayout;//頭部容器 38 private List<IHeadView> heads = new ArrayList<>();//支持添加多個頭部 39 private List<IFootView> foots = new ArrayList<>(); 40 41 public static final int DOWN_REFRESH = 0;// 下拉刷新狀態 42 public static final int RELEASE_REFRESH = 1;// 鬆開刷新 43 public static final int LOADING = 2;// 正在刷新中 44 private int currentState = DOWN_REFRESH;// 頭佈局的狀態: 預設為下拉刷新狀態 45 46 private View list;//子節點中的 recyclerview 視圖 47 private LayoutParams listParam,footParam;//用於控制下拉動畫展示 48 private boolean isLoadingMore = false;// 是否進入載入狀態,防止多次重覆的啟動 49 private boolean isStart = false;//表示正在載入刷新中,還沒停止 50 private boolean isTop = false,isBottom = false; 51 private int mTouchSlop; 52 private CallBack callBack; 53 54 public RefreshLayout(Context context) { 55 this(context, null, 0); 56 } 57 58 public RefreshLayout(Context context, AttributeSet attrs) { 59 this(context, attrs, 0); 60 } 61 62 public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { 63 super(context, attrs, defStyleAttr); 64 65 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RefreshLayout, defStyleAttr, 0); 66 try { 67 mHeadHeight = a.getDimensionPixelSize(R.styleable.RefreshLayout__height, 120); 68 } finally { 69 a.recycle(); 70 } 71 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 72 73 init(); 74 } 75 76 private void initHeaderContainer() { 77 mHeadLayout = new FrameLayout(getContext()); 78 LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight); 79 this.addView(mHeadLayout,layoutParams); 80 } 81 82 public void initFootContainer() { 83 footParam = new LayoutParams(LayoutParams.MATCH_PARENT,mHeadHeight); 84 mFootLayout = new FrameLayout(getContext());//底部佈局 85 mFootLayout.setBackgroundColor(Color.BLACK); 86 footParam.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; 87 footParam.setMargins(0,0,0,-mHeadHeight); 88 this.addView(mFootLayout,footParam); 89 } 90 91 private void init(){ 92 initHeaderContainer(); 93 } 94 95 @Override 96 protected void onFinishInflate() {//佈局載入成xml時觸發 97 super.onFinishInflate(); 98 99 initFootContainer(); 100 if (list == null) { 101 list = getChildAt(1); 102 listParam = (LayoutParams) list.getLayoutParams(); 103 list.setOnTouchListener(new OnTouchListener() { 104 @Override 105 public boolean onTouch(View v, MotionEvent event) { 106 return onFingerTouch(event); 107 } 108 }); 109 } 110 } 111 112 /** 113 * 設置頭部View 114 */ 115 public RefreshLayout setHeaderView(final IHeadView headView) { 116 if (headView != null) { 117 mHeadLayout.addView(headView.getView()); 118 heads.add(headView); 119 } 120 return this; 121 } 122 123 /** 124 * 設置尾部View 125 */ 126 public RefreshLayout setFootView(final IFootView footView) { 127 if (footView != null) { 128 mFootLayout.addView(footView.getView()); 129 foots.add(footView); 130 } 131 return this; 132 } 133 134 public boolean onFingerTouch(MotionEvent ev) { 135 isTop = isViewToTop(list,mTouchSlop); 136 isBottom = isViewToBottom(list,mTouchSlop); 137 // Log.e(TAG,"isTop "+isTop+" isBottom "+isBottom); 138 switch (ev.getAction()) { 139 case MotionEvent.ACTION_DOWN : 140 currentState = LOADING; 141 downY = (int) ev.getY(); 142 break; 143 case MotionEvent.ACTION_MOVE : 144 if (!isTop && !isBottom)//沒有到頂,無需計算操作 145 break; 146 int moveY = (int) ev.getY(); 147 int diff = (int) (((float)moveY - (float)downY) / RATIO); 148 // int paddingTop = -mHeadLayout.getHeight() + diff; 149 int paddingTop = diff; 150 if (paddingTop>0 && isTop) { 151 //向下滑動多少後開始啟動刷新,Margin判斷是為了限制快速用力滑動的時候導致頭部侵入的高度不夠就開始載入了 152 if (paddingTop >= mHeadHeight && (listParam.topMargin >= mHeadHeight) && currentState == DOWN_REFRESH) { // 完全顯示了. 153 // Log.i(TAG, "鬆開刷新 RELEASE_REFRESH"); 154 currentState = RELEASE_REFRESH; 155 refreshHeaderView(); 156 start(); 157 } else if (currentState == LOADING) { // 沒有顯示完全 158 // Log.i(TAG, "下拉刷新 DOWN_PULL_REFRESH"); 159 currentState = DOWN_REFRESH; 160 refreshHeaderView(); 161 } 162 if (paddingTop <= (mHeadHeight+10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置 163 listParam.setMargins(0, paddingTop, 0, 0); 164 list.setLayoutParams(listParam); 165 if (callBack != null) 166 callBack.pullListener(paddingTop); 167 } 168 169 }else if (isBottom){ 170 //限制上滑時不能超過底部的寬度,不然會超出邊界 171 //mHeadHeight+20 上滑設置的margin要超過headheight,不然下麵判斷的大於headheight不成立,下麵的margin基礎上面設置後的參數 172 if (Math.abs(paddingTop) <= (mHeadHeight+10) && !isStart) {//已經處於運行刷新狀態的時候禁止設置 173 listParam.setMargins(0, 0, 0, -paddingTop); 174 footParam.setMargins(0,0,0,-paddingTop-mHeadHeight); 175 list.setLayoutParams(listParam); 176 } 177 //如果滑動的距離大於頭部或者底部的高度,並且設置的margin也大於headheight 178 //listParam用來限制recyclerview列表迅速滑動,footParam用來限制bottom foothead迅速滑動導致沒有達到head的高度就開始載入了 179 if (Math.abs(paddingTop) >= mHeadHeight && (listParam.bottomMargin >= mHeadHeight || footParam.bottomMargin >= 0)) 180 isLoadingMore = true;//頭部是否拉取到位,然後執行載入動畫 181 182 } 183 // Log.e(TAG,"paddingTop "+paddingTop +" mHeadHeight "+mHeadHeight+ " topMargin "+listParam.topMargin+" bottomMargin "+listParam.bottomMargin 184 // +" footParam bottom "+footParam.bottomMargin); 185 // Log.i(TAG,"paddingTop "+paddingTop); 186 break; 187 case MotionEvent.ACTION_UP : 188 currentState = LOADING; 189 refreshHeaderView(); 190 if (isLoadingMore){ 191 isLoadingMore = false; 192 isStart = true;//是否開始載入 193 postDelayed(new Runnable() { 194 @Override 195 public void run() { 196 // Log.i(TAG, "停止 END"); 197 // currentState = END; 198 refreshHeaderView(); 199 listParam.setMargins(0, 0, 0, 0); 200 footParam.setMargins(0,0,0,-mHeadHeight); 201 list.setLayoutParams(listParam); 202 stop(); 203 } 204 },2000); 205 }else{ 206 if (!isStart){ 207 // 隱藏頭佈局 208 listParam.setMargins(0, 0,0,0); 209 footParam.setMargins(0,0,0,-mHeadHeight); 210 list.setLayoutParams(listParam); 211 } 212 } 213 // Log.i(TAG, "鬆開 REFRESHING"); 214 break; 215 default : 216 break; 217 } 218 return super.onTouchEvent(ev); 219 } 220 221 /** 222 * 根據currentState刷新頭佈局的狀態 223 */ 224 private void refreshHeaderView() { 225 if (callBack == null || isStart) 226 return; 227 String val = "準備刷新"; 228 switch (currentState) { 229 case DOWN_REFRESH : // 下拉刷新狀態 230 val = "下拉刷新"; 231 break; 232 case RELEASE_REFRESH : // 鬆開刷新狀態 233 val = "開始刷新..."; 234 break; 235 case LOADING : // 正在刷新中狀態 236 val = "正在刷新中..."; 237 break; 238 } 239 callBack.refreshHeaderView(currentState,val); 240 } 241 242 public static boolean isViewToTop(View view, int mTouchSlop){ 243 if (view instanceof AbsListView) return isAbsListViewToTop((AbsListView) view); 244 if (view instanceof RecyclerView) return isRecyclerViewToTop((RecyclerView) view); 245 return (view != null && Math.abs(view.getScrollY()) <= 2 * mTouchSlop); 246 } 247 248 public static boolean isViewToBottom(View view, int mTouchSlop){ 249 if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view); 250 if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view); 251 // if (view instanceof WebView) return isWebViewToBottom((WebView) view,mTouchSlop); 252 // if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view); 253 return false; 254 } 255 256 public static boolean isAbsListViewToTop(AbsListView absListView) { 257 if (absListView != null) { 258 int firstChildTop = 0; 259 if (absListView.getChildCount() > 0) { 260 // 如果AdapterView的子控制項數量不為0,獲取第一個子控制項的top 261 firstChildTop = absListView.getChildAt(0).getTop() - absListView.getPaddingTop(); 262 } 263 if (absListView.getFirstVisiblePosition() == 0 && firstChildTop == 0) { 264 return true; 265 } 266 } 267 return false; 268 } 269 270 public static boolean isRecyclerViewToTop(RecyclerView recyclerView) { 271 if (recyclerView != null) { 272 RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); 273 if (manager == null) { 274 return true; 275 } 276 if (manager.getItemCount() == 0) { 277 return true; 278 } 279 280 if (manager instanceof LinearLayoutManager) { 281 LinearLayoutManager layoutManager = (LinearLayoutManager) manager; 282 283 int firstChildTop = 0; 284 if (recyclerView.getChildCount() > 0) { 285 // 處理item高度超過一屏幕時的情況 286 View firstVisibleChild = recyclerView.getChildAt(0); 287 if (firstVisibleChild != null && firstVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) { 288 if (android.os.Build.VERSION.SDK_INT < 14) { 289 return !(ViewCompat.canScrollVertically(recyclerView, -1) || recyclerView.getScrollY() > 0); 290 } else { 291 return !ViewCompat.canScrollVertically(recyclerView, -1); 292 } 293 } 294 295 // 如果RecyclerView的子控制項數量不為0,獲取第一個子控制項的top 296 297 // 解決item的topMargin不為0時不能觸發下拉刷新 298 View firstChild = recyclerView.getChildAt(0); 299 RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) firstChild.getLayoutParams(); 300 firstChildTop = firstChild.getTop() - layoutParams.topMargin - getRecyclerViewItemTopInset(layoutParams) - recyclerView.getPaddingTop(); 301 } 302 303 if (layoutManager.findFirstCompletelyVisibleItemPosition() < 1 && firstChildTop == 0) { 304 return true; 305 } 306 } else if (manager instanceof StaggeredGridLayoutManager) { 307 StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager; 308 309 int[] out = layoutManager.findFirstCompletelyVisibleItemPositions(null); 310 if (out[0] < 1) { 311 return true; 312 } 313 } 314 } 315 return false; 316 } 317 318 /** 319 * 通過反射獲取RecyclerView的item的topInset 320 */ 321 private static int getRecyclerViewItemTopInset(RecyclerView.LayoutParams layoutParams) { 322 try { 323 Field field = RecyclerView.LayoutParams.class.getDeclaredField("mDecorInsets"); 324 field.setAccessible(true); 325 // 開發者自定義的滾動監聽器 326 Rect decorInsets = (Rect) field.get(layoutParams); 327 return decorInsets.top; 328 } catch (Exception e) { 329 e.printStackTrace(); 330 } 331 return 0; 332 } 333 334 public static boolean isAbsListViewToBottom(AbsListView absListView) { 335 if (absListView != null && absListView.getAdapter() != null && absListView.getChildCount() > 0 && absListView.getLastVisiblePosition() == absListView.getAdapter().getCount() - 1) { 336 View lastChild = absListView.getChildAt(absListView.getChildCount() - 1); 337 338 return lastChild.getBottom() <= absListView.getMeasuredHeight(); 339 } 340 return false; 341 } 342 343 public static boolean isRecyclerViewToBottom(RecyclerView recyclerView) { 344 if (recyclerView != null) { 345 RecyclerView.LayoutManager manager = recyclerView.getLayoutManager(); 346 if (manager == null || manager.getItemCount() == 0) { 347 return false; 348 } 349 350 if (manager instanceof LinearLayoutManager) { 351 // 處理item高度超過一屏幕時的情況 352 View lastVisibleChild = recyclerView.getChildAt(recyclerView.getChildCount() - 1); 353 if (lastVisibleChild != null && lastVisibleChild.getMeasuredHeight() >= recyclerView.getMeasuredHeight()) { 354 if (android.os.Build.VERSION.SDK_INT < 14) { 355 return !(ViewCompat.canScrollVertically(recyclerView, 1) || recyclerView.getScrollY() < 0); 356 } else { 357 return !ViewCompat.canScrollVertically(recyclerView, 1); 358 } 359 } 360 361 LinearLayoutManager layoutManager = (LinearLayoutManager) manager; 362 if (layoutManager.findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) { 363 return true; 364 } 365 } else if (manager instanceof StaggeredGridLayoutManager) { 366 StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) manager; 367 368 int[] out = layoutManager.findLastCompletelyVisibleItemPositions(null); 369 int lastPosition = layoutManager.getItemCount() - 1; 370 for (int position : out) { 371 if (position == lastPosition) { 372 return true; 373 } 374 } 375 } 376 } 377 return false; 378 } 379 380 public void start(){ 381 isLoadingMore = true; 382 for (IHeadView head : heads) { 383 head.startAnim(); 384 } 385 } 386 387 public void stop(){ 388 isLoadingMore = false; 389 isStart = false; 390 for (IHeadView head : heads) { 391 head.stopAnim(); 392 } 393 } 394 395 public RefreshLayout setCallBack(CallBack callBack) { 396 this.callBack = callBack; 397 return this; 398 } 399 400 public interface CallBack{ 401 /**監聽下拉時的狀態*/ 402 void refreshHeaderView(int state,String stateVal); 403 /**監聽下拉時的距離*/ 404 void pullListener(int y); 405