這次UI又腦洞大開,嫌棄我們項目中的 SwipeRefreshLayout 的下拉效果沒特色,整了一個帶主題色彩的下拉刷新,效果圖如下:
想到又要手擼一個下拉刷新,心里頓時上萬條草泥馬奔騰。做為一只傲嬌程序猿,我們怎么能干出這種吃力不討好的事呢(邏輯判斷、Touch事件分發(fā)、狀態(tài)變化寫起來很麻煩,還特么可能有一大堆八阿哥)。吃口屎冷靜了一下,先分析吧~
要實現(xiàn)這個下拉刷新,主要的難點有:
1、下拉刷新功能的實現(xiàn)
2、刷新過程中的 UI 動效
3、 Demo 寫出來了,要是一個一個頁面去移植這個下拉刷新,那又得花不少時間,怎樣更快捷的方式替換掉項目中幾十上百個頁面。
解決思路:
難點一:說的下拉刷新的功能實現(xiàn),很多小伙伴都會說PullToRefreshListview、SwipeRefreshLayout呀,度娘一搜一大把。對,沒錯,我也是直接在 SwipeRefreshLayout 這個類上修改了刷新 Ui 效果。至于為什么不用PullToRefreshListview,因為我們項目中還有很多頁面并不是列表頁,但是也需要刷新。
難點二:刷新過程中的 UI 動效,這個后文我會帶著大家分析代碼實現(xiàn)。
難點三:全局搜索替換,把“android.support.v4.widget.SwipeRefreshLayout”替換成你的控件名就好,注意接口回調(diào)以及方法名和SwipeRefreshLayout保持一致即可。
好了,問題都已經(jīng)分析完了,接下來就擼代碼吧~
難點一解決:我的解決方案是直接修改了SwipeRefreshLayout,但是由于公司的源碼不方便拿出來講解,這里我在 Github 上隨便搜了一個和SwipeRefreshLayout 功能一樣的?Github Demo 來作講解,最終實現(xiàn)效果不好有任何影響,希望大家不要介懷。
使用起來很簡單
在 需要刷新的 View 父節(jié)點用 RefreshLayout 包裹
<com.rongyi.diamond.pulltorefresh.RefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
? ? android:layout_width="match_parent"
? ? android:layout_height="50dp"?
? ? android:background="#FFDAB9"
? ? android:gravity="center"
? ? android:text="Target"
? ? android:textSize="30sp"/>
</com.rongyi.diamond.pulltorefresh.RefreshLayout>
然后在 Java代碼中設(shè)置自定義的下拉刷新頭即可。
RefreshLayout refreshLayout=(RefreshLayout)findViewById(R.id.refreshLayout);
ShopView shopView=new ShopView(this)
refreshLayout.setRefreshHeader(shopView);
其中RefreshLayout就是GitHub 上面搜索的和SwipeRefreshLayout相似的類,ShopView 就是我自己寫的自定義下拉刷新樣式。
RefreshLayout 功能:攔截Touch 事件根據(jù)判斷子 View 是否滑動到頂部來判斷是否要顯示刷新頭,就是一個類似于RefreshLayout的容器。具體實現(xiàn)這里就不做重點講解了,有興趣的小伙伴自己自己下載下來讀一遍,源碼我會在文章結(jié)尾處貼出來。
ShopView:自定義的請求頭,難點二中會詳細講解ShopView的實現(xiàn)。
難點二解決:?
第一步:新建一個 ShopView 類,繼承RelativeLayout(繼承 ViewGroup 也行,我嫌棄麻煩),然后在構(gòu)造方面里面 View.inflate一個布局到當前 View。
第二步:讓布局里面的元素在合適的時候動起來
首先我們來分析刷新頭。動畫中的基本元素有:1.購物袋上的手提繩、2.“直擊全國專柜特賣現(xiàn)場”文字圖片、3.購物袋中噴出的商品、4.購物袋、
觀察 UI 效果圖我們發(fā)現(xiàn),購物袋需要在刷新的時候抖動、購物袋上的手提繩需要在下拉的過程中改變效果、刷新的時候購物袋中會噴出商品、刷新頭完整出現(xiàn)之后繼續(xù)下拉會出現(xiàn)“直擊全國專柜特賣現(xiàn)場”。
由于這些元素都包含在刷新頭里面,但是上面的4種動畫元素是需要根據(jù)不同的狀態(tài)來展示的,因此,我們需要一個接口來監(jiān)聽RefreshLayout中的狀態(tài)變化,這里原作者已經(jīng)提供了狀態(tài)回調(diào),我們直接用就行了。
public interface RefreshHeader{
/**
* 松手,頭部隱藏后會回調(diào)這個方法
*/
voidreset();
/**
* 下拉出頭部的一瞬間調(diào)用
*/
voidpull();
/**
* 正在刷新的時候調(diào)用
*/
voidrefreshing();
/**
* 頭部滾動的時候持續(xù)調(diào)用
*
* @paramcurrentPostarget當前偏移高度
* @paramlastPostarget上一次的偏移高度
* @paramrefreshPos可以松手刷新的高度
* @paramisTouch手指是否按下狀態(tài)(通過scroll自動滾動時需要判斷)
* @paramstate當前狀態(tài)
*/
voidonPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate);
/**
* 刷新成功的時候調(diào)用
*/
voidcomplete();
}
直接讓我們的 ShopView 實現(xiàn)這個接口,在相應(yīng)的狀態(tài)回調(diào)里面展示相應(yīng)的動畫即可。
接下來就只需要把這四個動畫擼出來就完成我們的定制下拉刷新了~
動畫一:手提繩的變化
首先,在onPositionChange()條目下拉的時候改變手提繩的效果。經(jīng)過觀察我們發(fā)現(xiàn)手提袋就是一個上下翻滾的效果,這里我們直接用一個二階貝塞爾曲線,起始點不動,控制點 x 軸在起始點正中間,y 軸根據(jù)onPositionChange()變化而變化即可。實現(xiàn)核心代碼如下:
@Override
public void onPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate){
mBezierLine.setControlY(currentPos);
}
這里直接把手提袋封裝了一個 View,setControlY方法實際上就是改變了控制點的 Y 軸,重新繪制了 View
public void setControlY(floaty){
if(isRefresh){
return;
}
if(y+min
control.y=y+min;
}else if(y+min>max&&y+min<2*(max-min)){
control.y=2*max-min-y;
}else{
control.y=min;
}
invalidate();
}
繪制代碼如下:
protected voidonDraw(Canvascanvas){
super.onDraw(canvas);
// 繪制貝塞爾曲線
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(2);
Pathpath=newPath();
path.moveTo(start.x,start.y);
path.quadTo(control.x,control.y,end.x,end.y);
canvas.drawPath(path,mPaint);
}
動畫二:當條目下拉過程中超過 刷新頭的高度時,改變“直擊全國專柜特賣現(xiàn)場”的setTranslationY即可
@Override
public void onPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate){
if(currentPos>mHeight){
inttranslationY=(int) (currentPos-mHeight);
intdp20=Utils.dp2px(getContext(),20);
if(translationY>dp20){
translationY=dp20;
}
mIvTrans.setTranslationY(dp20-translationY);
}else{
mIvTrans.setTranslationY(Utils.dp2px(getContext(),20));
}
}
動畫三:在刷新的回調(diào)中開啟噴出商品的動畫,在刷新結(jié)束的時候關(guān)閉即可
@Override
public voidrefreshing(){
star();
}
Handlerhandler=newHandler(){
@Override
public void handleMessage(Messagemsg){
super.handleMessage(msg);
addHeart();
handler.sendEmptyMessageDelayed(0,250);
}
};
public void star(){
handler.sendEmptyMessage(0);
}
說簡單點就是通過 handle 重復發(fā)送延時消息添加一個 shop,然后再展示一段動畫,最后在動畫結(jié)束的時候 remove 掉就好。addHeart()方面里面代碼量較多,具體實現(xiàn)代碼就不貼出來了。
動畫四:在刷新的回調(diào)中開啟購物袋抖動效果,在刷新結(jié)束的時候關(guān)閉即可。這里只是做了一個簡單的 Y軸縮放0.95的過程
ScaleAnimation scaleAnimation=new ScaleAnimation(1,1.0f,0.95f,1.0f,
Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(800);
scaleAnimation.setRepeatCount(100);//設(shè)置縮放次數(shù)
rongYi.startAnimation(scaleAnimation);
難點三解決:
此時,我們的下拉刷新 Demo 已經(jīng)完成了,測試沒問題之后就可以移植到項目中去了,傲嬌的程序員肯定不會選擇一個一個頁面去修改控件,那是代碼搬運工干的蠢事,我們直接全局搜索替換android.support.v4.widget.SwipeRefreshLayout就完工了,不會全局替換代碼的小伙伴自己去問一下度娘吧~
到這里,本次UI 提出的更換下拉刷新效果的需求已經(jīng)完成,再給大家回顧一次整個流程。首先拿到一個需求先不要慌,冷靜下來好好分析,分析完了之后也不要急著敲代碼,自己把整個思路再捋一遍,畫個草圖。最后,實際開發(fā)的過程中,能有現(xiàn)成的輪子可以用,就盡量利用,比如說這次的“難點一”,就直接用了現(xiàn)有的輪子。當然,時間充沛的前提下,自己造輪子也是可以的,這樣對自身的提高會比較有幫助。
我的夢想是“沒有bug”,感謝閱讀。