這次UI又腦洞大開,嫌棄我們項(xiàng)目中的 SwipeRefreshLayout 的下拉效果沒特色,整了一個(gè)帶主題色彩的下拉刷新,效果圖如下:

想到又要手?jǐn)]一個(gè)下拉刷新,心里頓時(shí)上萬條草泥馬奔騰。做為一只傲嬌程序猿,我們怎么能干出這種吃力不討好的事呢(邏輯判斷、Touch事件分發(fā)、狀態(tài)變化寫起來很麻煩,還特么可能有一大堆八阿哥)。吃口屎冷靜了一下,先分析吧~
要實(shí)現(xiàn)這個(gè)下拉刷新,主要的難點(diǎn)有:
1、下拉刷新功能的實(shí)現(xiàn)
2、刷新過程中的 UI 動(dòng)效
3、 Demo 寫出來了,要是一個(gè)一個(gè)頁面去移植這個(gè)下拉刷新,那又得花不少時(shí)間,怎樣更快捷的方式替換掉項(xiàng)目中幾十上百個(gè)頁面。
解決思路:
難點(diǎn)一:說的下拉刷新的功能實(shí)現(xiàn),很多小伙伴都會(huì)說PullToRefreshListview、SwipeRefreshLayout呀,度娘一搜一大把。對,沒錯(cuò),我也是直接在 SwipeRefreshLayout 這個(gè)類上修改了刷新 Ui 效果。至于為什么不用PullToRefreshListview,因?yàn)槲覀冺?xiàng)目中還有很多頁面并不是列表頁,但是也需要刷新。
難點(diǎn)二:刷新過程中的 UI 動(dòng)效,這個(gè)后文我會(huì)帶著大家分析代碼實(shí)現(xiàn)。
難點(diǎn)三:全局搜索替換,把“android.support.v4.widget.SwipeRefreshLayout”替換成你的控件名就好,注意接口回調(diào)以及方法名和SwipeRefreshLayout保持一致即可。
好了,問題都已經(jīng)分析完了,接下來就擼代碼吧~
難點(diǎn)一解決:我的解決方案是直接修改了SwipeRefreshLayout,但是由于公司的源碼不方便拿出來講解,這里我在 Github 上隨便搜了一個(gè)和SwipeRefreshLayout 功能一樣的?Github Demo 來作講解,最終實(shí)現(xiàn)效果不好有任何影響,希望大家不要介懷。
使用起來很簡單
在 需要刷新的 View 父節(jié)點(diǎn)用 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 是否滑動(dòng)到頂部來判斷是否要顯示刷新頭,就是一個(gè)類似于RefreshLayout的容器。具體實(shí)現(xiàn)這里就不做重點(diǎn)講解了,有興趣的小伙伴自己自己下載下來讀一遍,源碼我會(huì)在文章結(jié)尾處貼出來。
ShopView:自定義的請求頭,難點(diǎn)二中會(huì)詳細(xì)講解ShopView的實(shí)現(xiàn)。
難點(diǎn)二解決:?
第一步:新建一個(gè) ShopView 類,繼承RelativeLayout(繼承 ViewGroup 也行,我嫌棄麻煩),然后在構(gòu)造方面里面 View.inflate一個(gè)布局到當(dāng)前 View。
第二步:讓布局里面的元素在合適的時(shí)候動(dòng)起來
首先我們來分析刷新頭。動(dòng)畫中的基本元素有:1.購物袋上的手提繩、2.“直擊全國專柜特賣現(xiàn)場”文字圖片、3.購物袋中噴出的商品、4.購物袋、
觀察 UI 效果圖我們發(fā)現(xiàn),購物袋需要在刷新的時(shí)候抖動(dòng)、購物袋上的手提繩需要在下拉的過程中改變效果、刷新的時(shí)候購物袋中會(huì)噴出商品、刷新頭完整出現(xiàn)之后繼續(xù)下拉會(huì)出現(xiàn)“直擊全國專柜特賣現(xiàn)場”。
由于這些元素都包含在刷新頭里面,但是上面的4種動(dòng)畫元素是需要根據(jù)不同的狀態(tài)來展示的,因此,我們需要一個(gè)接口來監(jiān)聽RefreshLayout中的狀態(tài)變化,這里原作者已經(jīng)提供了狀態(tài)回調(diào),我們直接用就行了。
public interface RefreshHeader{
/**
* 松手,頭部隱藏后會(huì)回調(diào)這個(gè)方法
*/
voidreset();
/**
* 下拉出頭部的一瞬間調(diào)用
*/
voidpull();
/**
* 正在刷新的時(shí)候調(diào)用
*/
voidrefreshing();
/**
* 頭部滾動(dòng)的時(shí)候持續(xù)調(diào)用
*
* @paramcurrentPostarget當(dāng)前偏移高度
* @paramlastPostarget上一次的偏移高度
* @paramrefreshPos可以松手刷新的高度
* @paramisTouch手指是否按下狀態(tài)(通過scroll自動(dòng)滾動(dòng)時(shí)需要判斷)
* @paramstate當(dāng)前狀態(tài)
*/
voidonPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate);
/**
* 刷新成功的時(shí)候調(diào)用
*/
voidcomplete();
}
直接讓我們的 ShopView 實(shí)現(xiàn)這個(gè)接口,在相應(yīng)的狀態(tài)回調(diào)里面展示相應(yīng)的動(dòng)畫即可。
接下來就只需要把這四個(gè)動(dòng)畫擼出來就完成我們的定制下拉刷新了~
動(dòng)畫一:手提繩的變化
首先,在onPositionChange()條目下拉的時(shí)候改變手提繩的效果。經(jīng)過觀察我們發(fā)現(xiàn)手提袋就是一個(gè)上下翻滾的效果,這里我們直接用一個(gè)二階貝塞爾曲線,起始點(diǎn)不動(dòng),控制點(diǎn) x 軸在起始點(diǎn)正中間,y 軸根據(jù)onPositionChange()變化而變化即可。實(shí)現(xiàn)核心代碼如下:
@Override
public void onPositionChange(floatcurrentPos,floatlastPos,floatrefreshPos,booleanisTouch,RefreshLayout.Statestate){
mBezierLine.setControlY(currentPos);
}
這里直接把手提袋封裝了一個(gè) View,setControlY方法實(shí)際上就是改變了控制點(diǎn)的 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);
}
動(dòng)畫二:當(dāng)條目下拉過程中超過 刷新頭的高度時(shí),改變“直擊全國專柜特賣現(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));
}
}
動(dòng)畫三:在刷新的回調(diào)中開啟噴出商品的動(dòng)畫,在刷新結(jié)束的時(shí)候關(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);
}
說簡單點(diǎn)就是通過 handle 重復(fù)發(fā)送延時(shí)消息添加一個(gè) shop,然后再展示一段動(dòng)畫,最后在動(dòng)畫結(jié)束的時(shí)候 remove 掉就好。addHeart()方面里面代碼量較多,具體實(shí)現(xiàn)代碼就不貼出來了。
動(dòng)畫四:在刷新的回調(diào)中開啟購物袋抖動(dòng)效果,在刷新結(jié)束的時(shí)候關(guān)閉即可。這里只是做了一個(gè)簡單的 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);
難點(diǎn)三解決:
此時(shí),我們的下拉刷新 Demo 已經(jīng)完成了,測試沒問題之后就可以移植到項(xiàng)目中去了,傲嬌的程序員肯定不會(huì)選擇一個(gè)一個(gè)頁面去修改控件,那是代碼搬運(yùn)工干的蠢事,我們直接全局搜索替換android.support.v4.widget.SwipeRefreshLayout就完工了,不會(huì)全局替換代碼的小伙伴自己去問一下度娘吧~
到這里,本次UI 提出的更換下拉刷新效果的需求已經(jīng)完成,再給大家回顧一次整個(gè)流程。首先拿到一個(gè)需求先不要慌,冷靜下來好好分析,分析完了之后也不要急著敲代碼,自己把整個(gè)思路再捋一遍,畫個(gè)草圖。最后,實(shí)際開發(fā)的過程中,能有現(xiàn)成的輪子可以用,就盡量利用,比如說這次的“難點(diǎn)一”,就直接用了現(xiàn)有的輪子。當(dāng)然,時(shí)間充沛的前提下,自己造輪子也是可以的,這樣對自身的提高會(huì)比較有幫助。
我的夢想是“沒有bug”,感謝閱讀。