自定义下拉刷新之仿AcFun下拉刷新
俗話說好記性不如爛筆頭,決定以后將研究過的東西寫到博客里,方便自己以后查找,也方便技術分享。第一篇從基礎的自定義下拉刷新開始。這里說下,我是在大神的肩膀上進行自定義的,因為自己重寫下拉刷新的話會有很多邊界,狀態和動畫等問題要處理,以前寫過一次,效果和功能可以實現,但是有不少bug,而且封裝也不好。因此直接使用android-Ultra-Pull-To-Refresh
效果圖
本文是基于github上的一個開源項目:android-Ultra-Pull-To-Refresh(下面簡稱UltraPtr) ,有興趣的同學可以去看看。廢話不多說,開車。
1.準備headview頭部布局
<?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="wrap_content"android:layout_centerHorizontal="true"android:gravity="center_vertical"android:orientation="horizontal"android:paddingTop="0dp"><ImageViewandroid:id="@+id/iv_ptr"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="100dp"android:src="@drawable/ptr_dra" /><TextViewandroid:id="@+id/tv_ptr"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="下拉刷新..." /> </LinearLayout> 布局很簡單,就是線性布局套一個imageview和textview。
2. 實現PtrUIHandler,處理下拉刷新回調
這一步是最重要的,幾乎所有的刷新樣式變化的邏輯都是在這里進行處理的,看代碼。
public class RefreshHeadView extends FrameLayout implements PtrUIHandler {private Context context;private ImageView iv;private TextView tv;private AnimationDrawable animationDrawable;public RefreshHeadView(Context context) {super(context);this.context = context;initView();}private void initView() {View.inflate(context, R.layout.ptrheadview, this);iv = (ImageView) findViewById(R.id.iv_ptr);tv = (TextView) findViewById(R.id.tv_ptr);animationDrawable = (AnimationDrawable) iv.getDrawable();}public RefreshHeadView(Context context, AttributeSet attrs) {super(context, attrs);}public RefreshHeadView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic void onUIReset(PtrFrameLayout frame) {//onUIRefreshComplete之后調用,用于復位setImageAndText(R.mipmap.ptr_loading_9, "刷新完成...");}@Overridepublic void onUIRefreshPrepare(PtrFrameLayout frame) {//開始下拉的時候調用一次setImageAndText(R.mipmap.ptr_loading_1, "下拉刷新...");}@Overridepublic void onUIRefreshBegin(PtrFrameLayout frame) {//正在刷新的時候調用,開始幀動畫iv.setImageDrawable(animationDrawable);animationDrawable.start();tv.setText("F5的能量女朋友出現了");}@Overridepublic void onUIRefreshComplete(PtrFrameLayout frame) {//刷新完成的時候調用animationDrawable.stop();setImageAndText(R.mipmap.ptr_loading_9, "刷新完成...");}@Overridepublic void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {//觸發刷新的高度 默認是頭部高度的1.2倍final int offsetToRefresh = ptrIndicator.getOffsetToRefresh();//當前下拉的高度final int currentPos = ptrIndicator.getCurrentPosY();//計算下拉的百分比int persent = (int) (((double) currentPos / offsetToRefresh) * 100);//只有在prepare狀態的時候才執行下面的替換圖片if (status!=PtrFrameLayout.PTR_STATUS_PREPARE) {return;}//根據不同的比例替換不同的圖片和文字if (persent < 50) {setImageAndText(R.mipmap.ptr_loading_1, "下拉刷新");} else if (persent < 60) {setImageAndText(R.mipmap.ptr_loading_2, "下拉刷新");} else if (persent < 70) {setImageAndText(R.mipmap.ptr_loading_3, "下拉刷新");} else if (persent < 80) {setImageAndText(R.mipmap.ptr_loading_4, "下拉刷新");} else if (persent < 100) {setImageAndText(R.mipmap.ptr_loading_5, "下拉刷新");} else {setImageAndText(R.mipmap.ptr_loading_6, "松開松開~");}}private void setImageAndText(int res, String text) {iv.setImageResource(res);tv.setText(text);} } 代碼里基本上注釋都寫很清楚了,只需要注意幾個地方。?
在構造方法中initView().將剛才定義的headview填充進來,并找到里面的imageview和textview
實現PtrUIHandler,需要重寫幾個方法。
onUIRefreshPrepare 下拉準備,剛開始下拉的時候就會調用一次。這里我也沒什么好準備的,就設置了第一張圖片。
onUIRefreshBegin 這是刷新的時候調用一次,按照acfun的下拉刷新,會顯示一個萌萌的妹紙動畫。所以對圖片設置幀動畫,并開啟幀動畫,這樣刷新的時候就是小人動的效果
onUIRefreshComplete 刷新完成,停止幀動畫
onUIRefreshReset 復位,最后調用,設置了一張圖片。
onUIPositionChange 這是比較復雜一些的方法。這個回調會傳給我們下拉的距離和手指是否在下拉等參數,用于下拉的時候變換圖片。這里介紹一下參數,frame不用說了,父類。isUnderTouch,表示手指是否按在屏幕上。status:表示當前的刷新狀態,有init ?prepare loading comlete四種。最后ptrIndicator是手勢類。首先獲取到觸發刷新的高度,這個是可以自己設置的,默認頭部高度1.2倍。然后獲取當前下拉的高度,根據兩個高度計算下拉的百分比。最后根據百分比來替換不同的圖片就可以了。其中有一點要注意,只有status在prepare的時候才執行替換圖片,否則高度每次變化都會調用此方法不停替換圖片。
//只有在prepare狀態的時候才執行下面的替換圖片if (status!=PtrFrameLayout.PTR_STATUS_PREPARE) {return;} 至此,整個headview和uihandler都處理完成。
3,自定義PullToRefreshLayout
public class PullToRefreshLayout extends PtrFrameLayout {private Context context;private float startY;private float startX;// 記錄viewPager是否拖拽的標記private boolean mIsHorizontalMove;// 記錄事件是否已被分發private boolean isDeal;// viewpager觸發滑動的距離private int mTouchSlop;private ArrayList<ViewPager> mViewPagers = new ArrayList<>();public PullToRefreshLayout(Context context) {super(context,null);}public PullToRefreshLayout(Context context, AttributeSet attrs) {super(context, attrs);this.context=context;initView();}private void initView() {RefreshHeadView headview = new RefreshHeadView(context);//設置headviewsetHeaderView(headview);//設置uihandler回調 因為headview實現了PtrUIHandler接口,所以這里還是headviewaddPtrUIHandler(headview);//阻尼系數 默認1.7f 越大下拉越吃力setResistance(1.7f);//觸發刷新時移動的位置比例 默認,1.2f,移動達到頭部高度1.2倍時可觸發刷新操作。setRatioOfHeaderHeightToRefresh(1.2f);//回彈延時 默認 200ms,回彈到刷新高度所用時間setDurationToClose(500);//頭部回彈時間 默認1000mssetDurationToCloseHeader(1500);//下拉刷新還是釋放刷新 默認下拉刷新default is falsesetPullToRefresh(false);// default is true 刷新時是否保持頭部setKeepHeaderWhenRefresh(true);//viewpager處理final ViewConfiguration configuration = ViewConfiguration.get(getContext());mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);}//處理下拉刷新和viewpager滑動沖突@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mViewPagers.size() ==0) {return super.dispatchTouchEvent(ev);}int action = ev.getAction();switch (action) {case MotionEvent.ACTION_DOWN:// 記錄手指按下的位置startY = ev.getY();startX = ev.getX();// 初始化標記mIsHorizontalMove = false;isDeal = false;break;case MotionEvent.ACTION_MOVE:// 如果已經判斷出是否由橫向還是縱向處理,則跳出if (isDeal) {break;}/**攔截禁止交給Ptr的 dispatchTouchEvent處理**/mIsHorizontalMove = true;// 獲取當前手指位置float endY = ev.getY();float endX = ev.getX();float distanceX = Math.abs(endX - startX);float distanceY = Math.abs(endY - startY);if (distanceX != distanceY) {// 如果X軸位移大于Y軸位移,那么將事件交給viewPager處理。//橫向滑動的距離大于觸發viewpager滑動事件的距離并且 橫向大于豎向if (distanceX > mTouchSlop && distanceX > distanceY) {mIsHorizontalMove = true;isDeal = true;} else if (distanceY > mTouchSlop) {mIsHorizontalMove = false;isDeal = true;}}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL://下拉刷新狀態時如果滾動了viewpager 此時mIsHorizontalMove為true 會導致PtrFrameLayout無法恢復原位// 初始化標記,mIsHorizontalMove = false;isDeal = false;break;}if (mIsHorizontalMove) {//相當于攔截事件,不交給ptr處理,直接向下分發return dispatchTouchEventSupper(ev);}//交給ptr,不做攔截,正常下拉return super.dispatchTouchEvent(ev);}@Overrideprotected void onLayout(boolean flag, int i, int j, int k, int l) {super.onLayout(flag, i, j, k, l);if (flag) {getAlLViewPager(mViewPagers, this);}}/*** 獲取SwipeBackLayout里面的ViewPager的集合*/private void getAlLViewPager(List<ViewPager> mViewPagers, ViewGroup parent) {int childCount = parent.getChildCount();for (int i = 0; i < childCount; i++) {View child = parent.getChildAt(i);if (child instanceof ViewPager) {mViewPagers.add((ViewPager) child);} else if (child instanceof ViewGroup) {getAlLViewPager(mViewPagers, (ViewGroup) child);}}} } 這個類就是我們最終要使用到布局里面的類。比較簡單,分為兩個部分
1.initPara() ?用于設置各種參數,具體看代碼都有注釋
2,處理下拉刷新和viewpager的滑動沖突。雖然UIPtr的作者也更新了處理沖突的方法,但是處理的并不太好,在viewpager上的滑動還是太敏感。所以這里重寫dispatchTouchEvent方法,判斷子view里是否有viewpager,如果沒有,直接交給父類處理。如果有,判斷滑動方向和距離。如果是橫向滑動,那么就不要交給父類處理,直接跳過父類,交給父類的父類處理。否則就是正常下拉刷新,還是交給父類。
至此,整個自定義下拉刷新完成。
源碼地址?https://github.com/itwangyu/MyPullRefreshDemo
總結
以上是生活随笔為你收集整理的自定义下拉刷新之仿AcFun下拉刷新的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021年1~11月语音合成和语音识别论
- 下一篇: 【jzoj 4727】【NOIP2015